見出し画像

イベント発生のプレイヤーのチームを取得する

上記の動画を参考にさせていただきました!
Graeme Bull様、ありがとうございます🙏

# 必要なモジュールのインポート
using { /Fortnite.com/Devices }        # デバイス関連の機能
using { /Fortnite.com/Game }           # ゲーム関連の機能(elimination_result用)
using { /Fortnite.com/Characters }     # キャラクター関連の機能
using { /Verse.org/Simulation }        # シミュレーション機能
using { /UnrealEngine.com/Temporary/Diagnostics }  # デバッグ用
using { /UnrealEngine.com/Temporary/SpatialMath } # 空間計算用
using { /Verse.org/Random }            # 乱数生成用

# メインデバイスクラスの定義
Hello_world_device := class(creative_device):

    @editable
    CabbageSpawner : capture_item_spawner_device = capture_item_spawner_device{}

    @editable
    Team1CaptureArea: capture_area_device = capture_area_device{}

    @editable
    Team2CaptureArea: capture_area_device = capture_area_device{}
    
    @editable
    Team1GuardSpawner:guard_spawner_device = guard_spawner_device{}

    @editable
    Team2GuardSpawner:guard_spawner_device = guard_spawner_device{}

    # ゲーム開始時に自動的に呼び出される関数
    OnBegin<override>()<suspends>:void=
        CabbageSpawner.ItemPickedUpEvent.Subscribe(OnCabbagePickedUp)
        CabbageSpawner.ItemCapturedEvent.Subscribe(OnCabbageCaptured)

    OnCabbageCaptured(Agent: agent):void=
        Print("cabbage captured")
        Team2GuardSpawner.Despawn()
        Team1GuardSpawner.Despawn()

    OnCabbagePickedUp(Agent: agent):void=
        Print("cabbage picked up")
        TeamCollection := GetPlayspace().GetTeamCollection()
        if:
            AgentsTeam := TeamCollection.GetTeam[Agent]

        then:
            TeamsArray := TeamCollection.GetTeams()
            Print("we made it here 1")
            for(TeamNumber -> Team : TeamsArray):

                if(AgentsTeam = Team):
                    #if you get here the team's number will be TeamNumber
                    Print("somebody got the cabbage! from team {TeamNumber}")
                    if(TeamNumber = 0):
                        Print("spawn guard for team 2")
                        #spawn the guard for team 2
                        Team2GuardSpawner.Spawn()
                    else if(TeamNumber = 1):
                        Print("spawn guard for team 1")
                        #spawn the guard for team 1
                        Team1GuardSpawner.Spawn()
# Fortnite Creative関連の必要なモジュールをインポート
using { /Fortnite.com/Devices }        # デバイス関連の機能を使用可能にする
using { /Fortnite.com/Game }           # ゲーム関連の機能(elimination_result等)を使用可能にする
using { /Fortnite.com/Characters }     # キャラクター関連の機能を使用可能にする
using { /Verse.org/Simulation }        # シミュレーション機能を使用可能にする
using { /UnrealEngine.com/Temporary/Diagnostics }  # デバッグ用のPrint関数等を使用可能にする 
using { /UnrealEngine.com/Temporary/SpatialMath } # 位置や回転などの空間計算用の機能を使用可能にする
using { /Verse.org/Random }            # 乱数生成用の機能を使用可能にする

# メインのデバイスクラスを定義
# creative_deviceを継承して新しいデバイスを作成
キャベツゲーム := class(creative_device):

    # 編集可能なデバイス変数を定義
    # UEFNエディタで設定可能
    @editable 
    キャベツ生成機 : capture_item_spawner_device = capture_item_spawner_device{} # キャベツスポナーデバイス

    @editable
    第1チーム獲得エリア : capture_area_device = capture_area_device{} # チーム1のキャプチャーエリア

    @editable
    第2チーム獲得エリア : capture_area_device = capture_area_device{} # チーム2のキャプチャーエリア
    
    @editable
    第1チーム守護者生成機 : guard_spawner_device = guard_spawner_device{} # チーム1のガードスポナー

    @editable
    第2チーム守護者生成機 : guard_spawner_device = guard_spawner_device{} # チーム2のガードスポナー

    # ゲーム開始時に自動的に呼び出される関数
    OnBegin<override>()<suspends>:void=
        # イベントサブスクライブの設定
        キャベツ生成機.ItemPickedUpEvent.Subscribe(キャベツ拾得時)    # キャベツ拾得時のイベント登録
        キャベツ生成機.ItemCapturedEvent.Subscribe(キャベツ獲得時)    # キャベツキャプチャー時のイベント登録

    # キャベツがキャプチャーされた時の処理を定義
    キャベツ獲得時(プレイヤー: agent):void=
        Print("キャベツが獲得されました")           # デバッグメッセージ出力2チーム守護者生成機.Despawn()         # チーム2のガードを消去1チーム守護者生成機.Despawn()         # チーム1のガードを消去

    # キャベツが拾われた時の処理を定義
    キャベツ拾得時(プレイヤー: agent):void=
        Print("キャベツが拾われました")          # デバッグメッセージ出力
        全チーム情報 := GetPlayspace().GetTeamCollection() # チームコレクションの取得
        
        # プレイヤーのチーム取得を試みる
        if:
            プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
        then:
            チーム配列 := 全チーム情報.GetTeams()         # すべてのチーム情報を取得
            Print("チーム情報取得完了")                      # デバッグメッセージ出力

            # 各チームに対して処理
            for(チーム番号 -> チーム : チーム配列):
                if(プレイヤーチーム = チーム):
                    Print("チーム{チーム番号}のプレイヤーがキャベツを拾いました") # チーム番号付きデバッグメッセージ
                    
                    # チーム0(第1チーム)の場合の処理
                    if(チーム番号 = 0):
                        Print("第2チームの守護者を生成します")     # デバッグメッセージ出力2チーム守護者生成機.Spawn()           # チーム2のガードをスポーン
                    
                    # チーム1(第2チーム)の場合の処理
                    else if(チーム番号 = 1):
                        Print("第1チームの守護者を生成します")     # デバッグメッセージ出力1チーム守護者生成機.Spawn()           # チーム1のガードをスポーン
# ==================================
# モジュールのインポート
# ==================================
using { /Fortnite.com/Devices }        # デバイス関連の機能
using { /Fortnite.com/Game }           # ゲーム関連の機能
using { /Fortnite.com/Characters }     # キャラクター関連の機能
using { /Verse.org/Simulation }        # シミュレーション機能
using { /UnrealEngine.com/Temporary/Diagnostics }  # デバッグ機能
using { /UnrealEngine.com/Temporary/SpatialMath } # 空間計算機能
using { /Verse.org/Random }            # 乱数生成機能

# ==================================
# メインゲームクラス
# ==================================
キャベツゲーム := class(creative_device):

    # ==================================
    # デバイス変数の定義(UEFNエディタで設定可能)
    # ==================================
    # キャベツ関連
    @editable 
    キャベツ生成機 : capture_item_spawner_device = capture_item_spawner_device{} 

    # チーム1関連
    @editable
    第1チーム獲得エリア : capture_area_device = capture_area_device{}
    @editable
    第1チーム守護者生成機 : guard_spawner_device = guard_spawner_device{}

    # チーム2関連
    @editable
    第2チーム獲得エリア : capture_area_device = capture_area_device{}
    @editable
    第2チーム守護者生成機 : guard_spawner_device = guard_spawner_device{}

    # ==================================
    # ゲーム初期化処理
    # ==================================
    OnBegin<override>()<suspends>:void=
        # イベントの登録
        キャベツ生成機.ItemPickedUpEvent.Subscribe(キャベツ拾得時)
        キャベツ生成機.ItemCapturedEvent.Subscribe(キャベツ獲得時)

    # ==================================
    # キャベツ獲得時の処理
    # ==================================
    キャベツ獲得時(プレイヤー: agent):void=
        Print("キャベツが獲得されました")
        # 両チームの守護者を消去2チーム守護者生成機.Despawn()
        第1チーム守護者生成機.Despawn()

    # ==================================
    # キャベツ拾得時の処理
    # ==================================
    キャベツ拾得時(プレイヤー: agent):void=
        Print("キャベツが拾われました")
        
        # チーム情報の取得
        全チーム情報 := GetPlayspace().GetTeamCollection()
        
        # キャベツ拾得のプレイヤーのチーム確認
        if:
            プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
        then:
            チーム配列 := 全チーム情報.GetTeams()
            Print("チーム情報取得完了")

            # チーム判定と守護者生成
            for(チーム番号 -> チーム : チーム配列):
                if(プレイヤーチーム = チーム):
                    Print("チーム{チーム番号}のプレイヤーがキャベツを拾いました")
                    
                    # チーム別の処理
                    if(チーム番号 = 0):
                        Print("第2チームの守護者を生成します")
                        第2チーム守護者生成機.Spawn()
                    else if(チーム番号 = 1):
                        Print("第1チームの守護者を生成します")
                        第1チーム守護者生成機.Spawn()

流れ

[ゲーム開始]
      ↓
[イベント登録]
      ↓
[プレイヤーの行動待ち]
      ↓
┌──────────────────┐
│キャベツを拾う    │キャベツを獲得エリアに持ち込む
│     ↓           │          ↓
│チーム情報取得    │    "キャベツが獲得されました"
│     ↓           │          ↓
│プレイヤーの      │    両チームの守護者を消去
│チーム判定        │          
│     ↓           │          
│第1チーム → 第2チームの守護者生成
│第2チーム → 第1チームの守護者生成
└──────────────────┘

このスクリプトについて、主要な部分ごとに解説します:

モジュールのインポート部分

using { /Fortnite.com/Devices }        
using { /Fortnite.com/Game }           
using { /Fortnite.com/Characters }     
using { /Verse.org/Simulation }        
using { /UnrealEngine.com/Temporary/Diagnostics }  
using { /UnrealEngine.com/Temporary/SpatialMath } 
using { /Verse.org/Random }            

これらは必要な機能を使用可能にするためのモジュールインポートです。各モジュールは特定の機能群を提供します。

メインクラスの定義

キャベツゲーム := class(creative_device):

`creative_device`を継承した新しいデバイスクラスを作成しています。これによりUEFNエディタでこのデバイスを配置できるようになります。

デバイス変数の定義

@editable
キャベツ生成機 : capture_item_spawner_device
第1チーム獲得エリア : capture_area_device
第2チーム獲得エリア : capture_area_device
第1チーム守護者生成機 : guard_spawner_device
第2チーム守護者生成機 : guard_spawner_device

`@editable`アノテーションにより、UEFNエディタでこれらの変数を設定できます。それぞれ特定の機能を持つデバイスを参照します。

ゲーム開始時の初期化

OnBegin<override>()<suspends>:void=
    キャベツ生成機.ItemPickedUpEvent.Subscribe(キャベツ拾得時)
    キャベツ生成機.ItemCapturedEvent.Subscribe(キャベツ獲得時)

ゲーム開始時に自動的に呼び出される関数で、イベントのサブスクライブ(監視)を設定します。

キャベツ獲得時の処理

キャベツ獲得時(プレイヤー: agent):void=
    Print("キャベツが獲得されました")
    第2チーム守護者生成機.Despawn()
    第1チーム守護者生成機.Despawn()

キャベツが獲得エリアに持ち込まれた時の処理を定義します。両チームの守護者を消去します。

キャベツ拾得時の処理

キャベツ拾得時(プレイヤー: agent):void=

この部分は最も複雑で、以下の処理を行います:

  1. プレイヤーのチーム情報を取得

  2. チーム配列を取得して各チームを確認

  3. キャベツを拾ったプレイヤーの所属チームに応じて、

    • チーム0の場合:第2チームの守護者を生成

    • チーム1の場合:第1チームの守護者を生成

このスクリプト全体で、2つのチーム間でキャベツの争奪戦を実現するゲームロジックを実装しています。プレイヤーがキャベツを拾うと相手チームの守護者が現れ、キャベツが獲得されると両チームの守護者が消えるという仕組みです。

デバイス変数の定義部分について詳しく解説します:

  1. @editableアノテーション

  • UEFNエディタ上で値を設定できるようにするための修飾子です

  • このアノテーションがついた変数は、デバイスをマップに配置した後に設定可能になります

  1. 各デバイスの説明:

キャベツ生成機 (capture_item_spawner_device)

  • アイテム(この場合はキャベツ)を生成・スポーンするためのデバイス

  • プレイヤーが拾って運べるアイテムを生成する機能を持つ

第1チーム獲得エリア/第2チーム獲得エリア (capture_area_device)

  • 特定のエリアを設定し、そこでのアイテムの獲得を検知するデバイス

  • 各チームの得点エリアとして機能する

  • キャベツが運ばれてきた時の検知に使用

第1チーム守護者生成機/第2チーム守護者生成機 (guard_spawner_device)

  • NPCガードを生成するためのデバイス

  • 各チームの防衛ユニットとして機能する

  • 相手チームがキャベツを持った時に、自動的にガードを生成する

  1. 変数の初期化

device = device_type{} 

この形式で各デバイスのインスタンスを作成します。空の波括弧{}は、デフォルト値での初期化を意味します。

  1. コードの設計意図

  • 2つのチームが対戦する形式のゲームを想定

  • キャベツの奪い合いと、守護者による防衛という要素を組み合わせた

  • チーム間でバランスの取れた競争を実現するための基本的なデバイス構成

この変数定義により、UEFNエディタ上で実際のデバイスをこのVerse scriptに関連付けることが可能になります。

キャベツ拾得時の処理

1. 関数の定義と引数

キャベツ拾得時(プレイヤー: agent):void=
  • この関数はキャベツが拾われた時に呼び出されます

  • `プレイヤー`という引数で、キャベツを拾ったプレイヤーの情報を受け取ります

2. デバッグメッセージの出力

Print("キャベツが拾われました")
  • デバッグ用のメッセージを画面に表示します

  • 開発時の動作確認に役立ちます

3. チーム情報の取得

チーム情報 := GetPlayspace().GetTeamCollection()
  • 現在のゲームプレイスペースからチーム情報を取得します

  • `GetPlayspace()`:現在のゲーム空間を取得

  • `GetTeamCollection()`:そのゲーム空間のチーム情報を取得

4. プレイヤーのチーム確認

if:
    プレイヤーチーム := チーム情報.GetTeam[プレイヤー]
then:
  • キャベツを拾ったプレイヤーが所属するチームを確認します

  • `GetTeam`でプレイヤーの所属チームを取得します

5. チーム配列の取得と処理

チーム配列 := チーム情報.GetTeams()
Print("チーム情報取得完了")
  • ゲーム内の全チーム情報を配列として取得します

  • 処理の進行確認のためのデバッグメッセージを出力します

6. チーム判定とガード生成

for(チーム番号 -> チーム : チーム配列):
    if(プレイヤーチーム = チーム):
  • 各チームに対して処理を実行

  • キャベツを拾ったプレイヤーのチームを特定します

7. チームに応じた処理分岐

if(チーム番号 = 0):
    Print("第2チームの守護者を生成します")
    第2チーム守護者生成機.Spawn()
else if(チーム番号 = 1):
    Print("第1チームの守護者を生成します")
    第1チーム守護者生成機.Spawn()
  • チーム0(第1チーム)のプレイヤーがキャベツを拾った場合:

    • 第2チームの守護者を生成

  • チーム1(第2チーム)のプレイヤーがキャベツを拾った場合:

    • 第1チームの守護者を生成

この処理により、キャベツを拾ったプレイヤーの相手チームに守護者が出現し、ゲームバランスを保つ仕組みが実装されています。

✅1. 関数定義とパラメータ

キャベツ拾得時(プレイヤー: agent):void=
  • キャベツを拾った時に呼び出される関数

  • `プレイヤー`パラメータはキャベツを拾ったプレイヤーの情報を受け取る

  • `agent`型はゲーム内のプレイヤーや他のエージェントを表す

2. チーム情報の取得

全チーム情報 := GetPlayspace().GetTeamCollection()
  • 現在のプレイスペース(ゲーム空間)からチーム情報を取得

  • `GetPlayspace()`でゲーム空間を取得

  • `GetTeamCollection()`でチーム情報のコレクションを取得

3. プレイヤーのチーム確認

if:
    プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
then:
  • キャベツを拾ったプレイヤーの所属チームを取得

  • `GetTeam`メソッドでプレイヤーのチーム情報を取得

  • 取得に成功した場合のみ続行

4. チーム処理のループ

for(チーム番号 -> チーム : チーム配列):
    if(プレイヤーチーム = チーム):
  • 全チームに対してループ処理

  • プレイヤーのチームと一致するチームを探す

  • `チーム番号`は配列のインデックス(0または1)

5. チーム別の守護者生成処理

if(チーム番号 = 0):
    Print("第2チームの守護者を生成します")
    第2チーム守護者生成機.Spawn()
else if(チーム番号 = 1):
    Print("第1チームの守護者を生成します")
    第1チーム守護者生成機.Spawn()
  • チーム0(第1チーム)のプレイヤーがキャベツを拾った場合:

    • 第2チームの守護者を生成

  • チーム1(第2チーム)のプレイヤーがキャベツを拾った場合:

    • 第1チームの守護者を生成

このコードは、キャベツを拾ったプレイヤーの相手チームに守護者を出現させることで、ゲームバランスを保つ仕組みを実装しています。

ifを使用している理由

  1. GetTeamメソッドの特徴:

  • `GetTeam`は失敗する可能性のある操作(decides)です

  • プレイヤーがチームに所属していない場合に失敗する可能性があります

  • 無効なプレイヤー情報の場合にも失敗する可能性があります

  1. ifを使用する利点:

  • GetTeamの操作が成功した場合のみ、then以降のコードが実行されます

  • 失敗した場合は安全にスキップされます

  • これにより、無効なチーム情報による実行時エラーを防ぐことができます

  1. コードの安全性:

if:
    プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
then:

このパターンは、Verseにおける安全なチーム情報取得の標準的な書き方です。

このように、if文を使用することで、チーム情報の取得が失敗した場合でもプログラムが安全に動作することが保証されます。

チーム取得のロジック

if:
    プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
then:
  • `GetTeam`は失敗する可能性のある操作(decides)です

  • プレイヤーのチーム所属が確認できない場合に失敗します

  • チーム取得に成功した場合のみ、then以降の処理が実行されます

チーム情報の取得処理

チーム配列 := 全チーム情報.GetTeams()
  • `GetTeams`で現在のゲームに存在する全てのチーム情報を配列として取得します

  • この配列には、チーム1とチーム2の情報が含まれています

デバッグ確認

Print("チーム情報取得完了")
  • チーム情報の取得が完了したことを確認するためのデバッグメッセージを出力します

  • デバッグ用のメッセージなので、開発時の動作確認に役立ちます

このコードは以下のような安全性を確保しています:

  1. プレイヤーのチーム所属を安全に確認

  2. チーム情報の取得に成功した場合のみ処理を継続

  3. エラーが発生した場合は処理を中断

これにより、チーム関連の処理で起こりうる問題を事前に防ぐことができます。

1. チームの列挙処理

for(チーム番号 -> チーム : チーム配列):
  • `チーム配列`の各要素に対してループ処理を実行

  • `チーム番号`は配列のインデックス(0または1)

  • `チーム`は各チームの情報

2. プレイヤーのチーム判定

if(プレイヤーチーム = チーム):
  • キャベツを拾ったプレイヤーの所属チームと一致するかチェック

  • 一致した場合のみ、以降の処理を実行

3. デバッグメッセージ

Print("チーム{チーム番号}のプレイヤーがキャベツを拾いました")
  • どのチームのプレイヤーがキャベツを拾ったかを表示

  • `{チーム番号}`は実際の番号(0または1)に置き換えられる

4. チーム別の処理分岐

if(チーム番号 = 0):
    Print("第2チームの守護者を生成します")
    第2チーム守護者生成機.Spawn()
else if(チーム番号 = 1):
    Print("第1チームの守護者を生成します")
    第1チーム守護者生成機.Spawn()

チーム0(第1チーム)の場合:

  • 相手チーム(第2チーム)の守護者を生成

  • デバッグメッセージを出力して処理を確認

チーム1(第2チーム)の場合:

  • 相手チーム(第1チーム)の守護者を生成

  • デバッグメッセージを出力して処理を確認

このコードは、キャベツを拾ったプレイヤーの相手チームに守護者を出現させることで、ゲームバランスを保つ仕組みを実装しています。プレイヤーがキャベツを拾うと、相手チームの守護者が自動的に出現する仕組みです。

forループを使った理由

  1. チーム判定の目的:

  • `チーム配列`の中から、キャベツを拾ったプレイヤーの所属チーム(`プレイヤーチーム`)を見つけ出す

  • `if(プレイヤーチーム = チーム)` で一致するチームを特定する

  1. チーム番号の取得:

  • forループで`チーム番号`も同時に取得できる

  • この番号を使って、第1チーム(チーム番号 = 0)か第2チーム(チーム番号 = 1)かを判断する

  1. 適切な処理の実行:

  • チーム番号に基づいて、対抗チームの守護者を生成する

  • チーム0の場合は第2チームの守護者を生成

  • チーム1の場合は第1チームの守護者を生成

このように、forループはチーム判定とそれに応じた処理を効率的に行うために使用されています。

`else if`について

else ifの基本構造

if(条件1):
    # 条件1が真の時の処理
else if(条件2):
    # 条件1が偽で、条件2が真の時の処理

このコードでの使用方法

if(チーム番号 = 0):
    # チーム0の処理
else if(チーム番号 = 1):
    # チーム1の処理

特徴

  1. 条件の排他性

  • 前のif条件が偽の場合のみ評価される

  • この場合、チーム番号が0でない場合のみ、チーム番号 = 1の判定が行われる

  1. チェーンの仕組み

  • 最初のif条件が偽の場合、次のelse if条件をチェック

  • チーム番号が0でない場合に、チーム番号が1かどうかをチェック

  1. 処理の明確性

  • チーム0とチーム1で異なる処理を明確に分けて記述できる

  • コードの可読性が向上する

このコードでは、チーム番号に基づいて相手チームの守護者を生成する処理を、論理的に分岐させるために`else if`を使用しています。

elseは「前の条件が当てはまらない場合」に処理が実行

このコードの場合:

if(チーム番号 = 0):
    # チーム0の処理
else if(チーム番号 = 1):
    # チーム1の処理
  1. まず `チーム番号 = 0` をチェック

  • チーム番号が0の場合は最初のif文の処理を実行

  • チーム番号が0でない場合は次のelse ifの条件をチェック

  1. `else if(チーム番号 = 1)` をチェック

  • チーム番号が1の場合はelse ifの処理を実行

  • チーム番号が1でもない場合は、どの処理も実行されない

このように、else ifは「前の条件に当てはまらず、かつ自分の条件に当てはまる場合」に処理が実行される仕組みになっています。

  1. 関数の定義部分

キャベツ拾得時(プレイヤー: agent):void=
  • キャベツが拾われた時に実行される関数を定義

  • プレイヤー情報を`agent`型で受け取る

  1. デバッグ情報の出力

Print("キャベツが拾われました")
  • デバッグ用のメッセージを出力

  • キャベツが拾われたことを確認するため

  1. チーム情報の取得

全チーム情報 := GetPlayspace().GetTeamCollection()
  • 現在のゲーム空間から全てのチーム情報を取得

  • チームの判定に必要な情報を収集

  1. プレイヤーのチーム確認

if:
    プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
then:
  • キャベツを拾ったプレイヤーの所属チームを取得

  • 失敗する可能性があるため、if文で安全に処理

  1. チーム配列の取得

チーム配列 := 全チーム情報.GetTeams()
Print("チーム情報取得完了")
  • 全てのチーム情報を配列として取得

  • デバッグ用に処理の進行を確認

  1. チーム判定のループ処理

for(チーム番号 -> チーム : チーム配列):
    if(プレイヤーチーム = チーム):
  • 各チームを順番にチェック

  • キャベツを拾ったプレイヤーのチームを特定

  1. チーム別の処理分岐

if(チーム番号 = 0):
    Print("第2チームの守護者を生成します")
    第2チーム守護者生成機.Spawn()
else if(チーム番号 = 1):
    Print("第1チームの守護者を生成します")
    第1チーム守護者生成機.Spawn()
  • チーム0(第1チーム)の場合:第2チームの守護者を生成

  • チーム1(第2チーム)の場合:第1チームの守護者を生成

  • 相手チームの守護者を生成することでゲームバランスを保つ

このコードは、チーム対戦型のゲームにおいて、キャベツを拾ったプレイヤーの相手チームに守護者を出現させる仕組みを実装しています。

`全チーム情報 := GetPlayspace().GetTeamCollection()`は、ゲーム内の全てのチーム関連情報を取得するため

この行について詳しく説明します:

  1. GetPlayspace()の役割:

  • 現在のゲームプレイスペース(ゲーム空間)を取得します

  • ゲーム全体のコンテキストにアクセスするための入り口となります

  1. GetTeamCollection()の役割:

  • チーム関連の情報をまとめて管理するコレクションを取得します

  • チームの所属情報や、チーム数などの情報を含みます

  1. この情報を取得する理由:

  • キャベツを拾ったプレイヤーがどのチームに所属しているか確認するため

  • チーム情報に基づいて、適切な守護者を生成するため

  • チーム対戦型のゲームロジックを実装するために必要な情報を得るため

  1. 変数として保存する理由:

  • 後続の処理で複数回使用するため

  • チーム情報への効率的なアクセスを可能にするため

このコードは、チーム対戦型のゲームロジックを実装する上で必要不可欠な、チーム情報の取得と管理を行っています。

`プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]`を書く主な理由

  1. プレイヤーの所属チームを取得するため

  • GetTeamメソッドを使用してキャベツを拾ったプレイヤーがどのチームに所属しているかを特定します

  • これによって、適切な守護者の生成処理を行うことができます

  1. チーム情報の安全な取得

  • GetTeamは失敗する可能性のある操作(decides)なので、if文で囲んで安全に処理します

  • プレイヤーがチームに所属していない場合や無効なプレイヤー情報の場合に対応できます

  1. 後続の処理で使用するため

  • 取得したチーム情報は、その後のチーム判定やガード生成の処理で必要となります

  • 変数として保存することで、複数回の参照が可能になります

キャベツを拾ったプレイヤーのチーム所属を確認し、それに応じた適切な処理(相手チームの守護者生成など)を行うために、この処理が必要不可欠となっています。


# まずゲームを作るために必要なモジュールを考えよう
# キャベツを使った対戦ゲームだから、以下の機能が必要になるはずだ:
# - デバイスの操作(キャベツ生成機とか)
# - チーム管理(2チーム対戦だから)
# - プレイヤーの制御(キャベツを拾ったプレイヤーの判定とか)
# - 位置計算(スポーン位置とか)
# - デバッグ出力(動作確認用)
# ==================================  
# モジュールのインポート
# ==================================
using { /Fortnite.com/Devices }        # デバイス関連の機能が必要
using { /Fortnite.com/Game }           # チーム管理とかゲームルール用
using { /Fortnite.com/Characters }     # プレイヤー操作用
using { /Verse.org/Simulation }        # 基本的なシミュレーション機能
using { /UnrealEngine.com/Temporary/Diagnostics }  # デバッグ出力必須
using { /UnrealEngine.com/Temporary/SpatialMath } # 位置計算用
using { /Verse.org/Random }            # ランダム要素があれば使う

# メインのゲームクラスを設計しよう
# creative_deviceを継承して独自のデバイスを作る
# 名前は機能が分かりやすいようにキャベツゲームにしよう
# ==================================
# メインゲームクラス
# ==================================
キャベツゲーム := class(creative_device):

    # デバイス変数を考えよう
    # 必要なものは:
    # 1. キャベツを生成するデバイス
    # 2. チーム1用の獲得エリアと守護者生成機
    # 3. チーム2用の獲得エリアと守護者生成機
    # @editableをつけることでUEFNエディタから設定できるようになる
    # これは重要で、マップ制作時に位置とか設定を調整できる
    # ==================================
    # デバイス変数の定義
    # ==================================
    @editable 
    キャベツ生成機 : capture_item_spawner_device = capture_item_spawner_device{} 

    # チーム1とチーム2で同じような機能が必要
    # 獲得エリアはキャベツを持ち込む場所
    # 守護者生成機は相手の動きを妨害する守護者を出すところ
    @editable
    第1チーム獲得エリア : capture_area_device = capture_area_device{}
    @editable
    第1チーム守護者生成機 : guard_spawner_device = guard_spawner_device{}

    @editable
    第2チーム獲得エリア : capture_area_device = capture_area_device{}
    @editable
    第2チーム守護者生成機 : guard_spawner_device = guard_spawner_device{}

    # ゲーム開始時の処理を考えよう
    # キャベツに関する2つのイベントを監視する必要がある:
    # 1. キャベツが拾われた時
    # 2. キャベツが獲得エリアに持ち込まれた時
    # ==================================
    # ゲーム初期化処理
    # ==================================
    OnBegin<override>()<suspends>:void=
        キャベツ生成機.ItemPickedUpEvent.Subscribe(キャベツ拾得時)
        キャベツ生成機.ItemCapturedEvent.Subscribe(キャベツ獲得時)

    # キャベツ獲得時の処理はシンプルにしよう
    # キャベツが獲得エリアに持ち込まれたら、
    # 両チームの守護者を消去するだけでいい
    # ==================================
    # キャベツ獲得時の処理
    # ==================================
    キャベツ獲得時(プレイヤー: agent):void=
        Print("キャベツが獲得されました")
        第2チーム守護者生成機.Despawn()
        第1チーム守護者生成機.Despawn()

    # キャベツ拾得時の処理が一番複雑だ
    # やることは:
    # 1. どのプレイヤーが拾ったか確認
    # 2. そのプレイヤーのチームを特定
    # 3. 相手チームの守護者を生成
    # ==================================
    # キャベツ拾得時の処理
    # ==================================
    キャベツ拾得時(プレイヤー: agent):void=
        Print("キャベツが拾われました")
        
        # まずチーム情報を取得
        # GetPlayspaceでゲーム空間を取得して、
        # そこからチーム情報を取り出す
        全チーム情報 := GetPlayspace().GetTeamCollection()
        
        # プレイヤーのチーム取得は失敗する可能性がある
        # - プレイヤーがチームに所属していない
        # - プレイヤー情報が無効
        # などの場合があるので、ifで安全に処理する
        if:
            プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
        then:
            # 全チームの情報を配列で取得
            # これでどのチームがキャベツを拾ったか判定できる
            チーム配列 := 全チーム情報.GetTeams()
            Print("チーム情報取得完了")

            # チーム判定のループ
            # 配列の各要素をチェックして、
            # キャベツを拾ったプレイヤーのチームを見つける
            for(チーム番号 -> チーム : チーム配列):
                if(プレイヤーチーム = チーム):
                    Print("チーム{チーム番号}のプレイヤーがキャベツを拾いました")
                    
                    # チーム番号で分岐
                    # チーム0が拾ったら第2チームの守護者を、
                    # チーム1が拾ったら第1チームの守護者を生成
                    if(チーム番号 = 0):
                        Print("第2チームの守護者を生成します")
                        第2チーム守護者生成機.Spawn()
                    else if(チーム番号 = 1):
                        Print("第1チームの守護者を生成します")
                        第1チーム守護者生成機.Spawn()


`プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]`の部分で、キャベツを拾った`プレイヤー`(agent型)が所属しているチーム情報を取得

具体的な流れとしては:

  1. `全チーム情報.GetTeam[プレイヤー]`は引数として渡された`プレイヤー`が所属するチーム情報を取得

  2. その結果が`プレイヤーチーム`変数に格納される

  3. このプレイヤーチーム情報を使って、後続の処理で:

    • どのチームのプレイヤーがキャベツを拾ったのか判定

    • 相手チームの守護者を生成する判断に使用

この実装により、キャベツを拾ったプレイヤーのチーム所属を確認し、適切な対応(相手チームの守護者生成)を行うことができます。

なお、プレイヤーがチームに所属していない場合やプレイヤー情報が無効な場合に備えて、`if-then`で囲んで安全に処理しています。

forループの実行例

ケース1: チーム0のプレイヤーがキャベツを拾った場合

チーム配列 = [チーム0, チーム1]
プレイヤーチーム = チーム0

# 1周目
チーム番号 = 0
チーム = チーム0
if(プレイヤーチーム = チーム): # true
    Print("チーム0のプレイヤーがキャベツを拾いました")
    if(チーム番号 = 0): # true
        Print("第2チームの守護者を生成します")
        第2チーム守護者生成機.Spawn()

# 2周目
チーム番号 = 1
チーム = チーム1
if(プレイヤーチーム = チーム): # false
    # 実行されない

ケース2: チーム1のプレイヤーがキャベツを拾った場合

チーム配列 = [チーム0, チーム1]
プレイヤーチーム = チーム1

# 1周目
チーム番号 = 0
チーム = チーム0
if(プレイヤーチーム = チーム): # false
    # 実行されない

# 2周目
チーム番号 = 1
チーム = チーム1
if(プレイヤーチーム = チーム): # true
    Print("チーム1のプレイヤーがキャベツを拾いました")
    if(チーム番号 = 0): # false
        # 実行されない
    else if(チーム番号 = 1): # true
        Print("第1チームの守護者を生成します")
        第1チーム守護者生成機.Spawn()

このように、forループは配列の各要素を順番に処理し、プレイヤーのチームと一致した場合にのみ守護者生成の処理を実行します。チーム番号に基づいて、相手チームの守護者が生成されることになります。

`プレイヤーチーム`には`team`型のオブジェクトが格納

検索結果から、`team`は以下のように定義されています:

team<native><public> := class<unique><epic_internal>

この実装から分かることは:

  • `team`はユニークなクラス型である

  • Fortnite内部で管理されている型である(`epic_internal`)

  • プレイヤーのチーム所属情報を表現する

実際の処理の流れとしては:

  1. `全チーム情報.GetTeam[プレイヤー]`でプレイヤーのチーム情報を取得

  2. `プレイヤーチーム`変数に格納

  3. その後の`if(プレイヤーチーム = チーム)`で、チーム配列の各要素と比較して一致判定を行う

つまり、`プレイヤーチーム`には「そのプレイヤーが所属しているチームを表すオブジェクト」が格納されており、これを使ってチーム判定やチーム別の処理分岐を実現しています。

if(プレイヤーチーム = チーム): はインデックスで比較しないほうがよい

  1. 型の一致性:

  • `プレイヤーチーム`は`team`型(チームの情報を持つオブジェクト)

  • `チーム番号`は`int`型(数値)

  • これらを直接比較すると、型が異なるためエラーになるはずです

  1. 正しい比較対象:

  • `プレイヤーチーム = チーム`は、同じ`team`型のオブジェクト同士を比較

  • これにより、プレイヤーが所属するチームと、現在ループで処理中のチームが同じかどうかを正確に判定できます

  1. 意図した動作:

  • プレイヤーの所属チームと、配列内の各チームを比較して一致を見つける

  • チーム番号はあくまでもインデックスとして使用し、実際のチーム判定には`team`オブジェクトを使用する

そのため、コードは以下のように保持するべきです:

if(プレイヤーチーム = チーム):  # team型同士の比較

もしエラーが出なかったとしても、意図した動作と異なる可能性があり、バグの原因となる可能性があります。

なぜforのteamはteam型なのか。

`GetTeams()`メソッドの戻り値の型に関係しています。検索結果から、以下のことが分かります:

  1. `GetTeamCollection()`は、チーム情報を管理するコレクションを返します

  2. そのコレクションの`GetTeams()`メソッドは`[]team`型(team型の配列)を返します

  3. `team`は以下のように定義されています:

team<native><public> := class<unique><epic_internal>

したがって:

  • `チーム配列`には`[]team`型(team型の配列)が格納されています

  • `for(チーム番号 -> チーム : チーム配列)`の構文では:

    • `チーム番号`には配列のインデックス(0または1)が入ります

    • `チーム`には配列の各要素、つまり`team`型のオブジェクトが入ります

このため、`チーム`変数には自動的に`team`型のオブジェクトが代入されることになります。これにより、後のチーム判定で`プレイヤーチーム = チーム`という比較が可能になっています。

`GetTeams()`メソッドの実装

`GetTeams()`は以下の特徴を持ちます:

  1. 戻り値の型:

  • `[]team` (team型の配列)を返します

  • この配列には現在のゲームに存在する全てのチームの情報が含まれます

  1. メソッドの性質:

  • `transacts`キーワードが付いているため、トランザクション内で実行される必要があります

  • チーム情報のクエリは安全に行われます

  1. 用途:

  • 現在のゲーム内の全チーム情報を取得するために使用します

  • チーム判定やチーム別の処理を行う際の基礎となります

  1. 実際の使用例:

チーム配列 := 全チーム情報.GetTeams()

このように、全チームの情報を配列として取得し、その後のチーム判定などに使用できます。


プレイヤーが所属するチームを判別し、それに応じて異なる動作をさせる方法について解説しています。具体的には、「キャベツ争奪戦」という2チーム対戦ゲームを例に、以下の内容を説明しています。

ゲームの概要

  • 2チーム対戦で、相手チームのキャベツを奪い、自陣まで持ち帰るゲーム。

  • キャベツを拾うと、チーム1のプレイヤーに対してはチーム1用のガードが出現し、チーム2のプレイヤーに対してはチーム2用のガードが出現する。

  • キャベツが自陣に持ち帰られると、ガードは消滅する。

動画の要点

  1. キャプチャーアイテムスポナーとキャプチャーエリア:

    • キャベツは「キャプチャーアイテムスポナー」から出現します。

    • 「キャプチャーエリア」は、キャベツを持ち帰るべき自陣エリアを示します。

    • このゲームでは、キャプチャーエリアは必須ではありませんが、キャプチャーアイテムスポナーのイベントを購読するために使用しています。

  2. イベントの購読:

    • キャプチャーアイテムスポナーの OnItemPickedUp イベント(アイテムが拾われた時)と OnItemCaptured イベント(アイテムが自陣に持ち帰られた時)を購読します。

  3. チーム情報の取得:

    • OnItemPickedUp イベントと OnItemCaptured イベントは、イベントを発生させたエージェント(プレイヤー)の情報を提供します。

    • GetPlayspace().GetTeamCollection() を使用して、ゲーム内のチームコレクションを取得します。

    • TeamCollection.GetTeam[Agent] を使用して、エージェントが所属するチームを取得します。この関数は、失敗する可能性があるため(エージェントがチームに所属していない場合など)、if ステートメントでラップしてエラーを処理する必要があります。

  4. チームインデックスに基づく処理:

    • GetTeam から取得したチームを、チーム配列に格納します。

    • Verseでは配列のインデックスは0から始まるため、チームインデックス1は配列の0番目、チームインデックス2は配列の1番目に対応します。

    • プレイヤーのチームインデックスが1(配列の0番目)であれば、チーム2のガードを出現させます。

    • プレイヤーのチームインデックスが2(配列の1番目)であれば、チーム1のガードを出現させます。

  5. ガードの出現と消滅:

    • ガードスポナーには Spawn 関数と Despawn 関数があり、ガードを出現させたり消滅させたりすることができます。

    • キャベツが拾われたら、プレイヤーのチームに応じたガードを Spawn させます。

    • キャベツが自陣に持ち帰られたら、両方のガードを Despawn させます。Despawn は失敗する可能性があるため、エラー処理が必要です(例:ガードが既に消滅している場合)。

  6. ガードの設定:

    • ガードスポナーを配置し、武器、弾薬、体力、シールド、視界などのパラメータを設定します。

    • チームインデックスを適切に設定することで、ガードを特定のチームに所属させることができます。

  7. バグの修正:

    • ガードが正しく動作しないバグ(武器を持たない、動かないなど)が発生することがあります。

    • この場合、新しいガードスポナーを配置し、古いガードスポナーと置き換えることで問題を解決できる場合があります。

    • ただし、新しいガードスポナーを使用する場合は、ゲームマネージャーデバイス内の参照を更新する必要があります。そうしないと、コードが新しいガードスポナーと連携できません。

結論

プレイヤーのチームを判別し、それに基づいてゲーム内の動作を変更する方法を、具体的な例を通して学ぶことができました。GetPlayspace().GetTeamCollection()、TeamCollection.GetTeam[Agent]、Spawn、Despawn などの関数を理解し、適切に使用することで、チームベースのゲームに複雑なロジックを実装することができます。

Fortnite Creative内で「キャベツ」というアイテムを巡って2つのチームが競い合うゲームのロジックを定義しています。コード全体を通して、プレイヤーの行動に基づいたイベント処理、チーム情報の取得、そしてガード(敵NPC)の生成・消去を制御しています。

トピック1: モジュールのインポート

コードの該当部分:

      using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Random }
    

解説:

Verseでは、特定の機能を利用するために必要なモジュールを明示的にインポートする必要があります。これは、他のプログラミング言語でいうところのimportやinclude、usingに相当します。モジュールをインポートすることで、そのモジュールが提供するクラス、関数、定数などを利用できるようになります。

なぜ1: なぜモジュールをインポートするのか?

Verseの設計思想として、必要な機能のみを明示的にインポートすることで、コードの可読性と保守性を向上させるためです。

なぜ2: なぜFortnite.com/Devicesをインポートするのか?

Fortnite Creativeでゲームロジックを構築する上で、デバイス(仕掛け)の操作は必須です。このモジュールは各種デバイスを制御するための機能を提供しているためです。例えば、capture_item_spawner_device や capture_area_device はこのモジュールに含まれます。

なぜ3: なぜFortnite.com/Gameをインポートするのか?

ゲームの基本的な情報やイベント(例:elimination_result)にアクセスするために必要です。これにより、ゲームの進行状況を把握し、それに応じた処理を実装できます。

なぜ4: なぜFortnite.com/Charactersをインポートするのか?

プレイヤーやNPC(Non-Player Character:ゲーム側で操作されるキャラクター)などのキャラクターに関連する操作を行うために必要です。例えば、キャラクターの移動やアニメーションの制御などに利用されます。

なぜ5: なぜVerse.org/Simulationをインポートするのか?

Verseコードの実行環境であるシミュレーションに関する機能を利用するためです。時間管理やイベントのスケジューリングなどに利用されます。

なぜ6: なぜUnrealEngine.com/Temporary/Diagnosticsをインポートするのか?

デバッグ用の機能を利用するためです。Print関数を使ってログを出力したり、エラーを検知したりする際に役立ちます。開発中のコードの動作確認に必須のモジュールです。

なぜ7: なぜUnrealEngine.com/Temporary/SpatialMathをインポートするのか?

3D空間における位置や回転、距離計算など、数学的な計算を行うための機能を利用するためです。例えば、オブジェクト同士の距離を計算したり、特定の方向にオブジェクトを移動させたりする際に利用されます。

なぜ8: なぜVerse.org/Randomをインポートするのか?

ランダムな数値を生成するために必要です。ゲームにランダム要素を取り入れることで、予測不可能な展開を生み出し、プレイヤーに新鮮な体験を提供できます。

トピック2: デバイスクラスの定義

コードの該当部分:

      キャベツゲーム := class(creative_device):
    

これは、キャベツゲームという新しいデバイスクラスを定義しています。creative_deviceを継承することで、Fortnite Creativeで使用できるデバイスとしての基本的な機能を持つことになります。クラスは、変数(属性)と関数(メソッド)をまとめて、一つのオブジェクト(ここではデバイス)として扱うための仕組みです。

なぜ1: なぜクラスを定義するのか?

関連するデータ(変数)と処理(関数)をひとまとめにすることで、コードの構造を整理し、管理しやすくするためです。

なぜ2: なぜcreative_deviceを継承するのか?

Fortnite Creativeでデバイスとして認識されるためには、creative_deviceクラスが持つ基本的な機能を備えている必要があるからです。継承により、既存のクラスの機能を再利用しながら、新しい機能を追加できます。これは、オブジェクト指向プログラミングの重要な概念です。

なぜ3: なぜクラス名はキャベツゲームなのか?

このデバイスが「キャベツ」をテーマにしたゲームのロジックを担当することを明確に示すためです。クラス名は、その役割や機能を分かりやすく表現することが重要です。

なぜ4: なぜ:=を使ってクラスを定義するのか?

Verseでは、:=は定数や変数の定義に使う「定義演算子」です。 クラス定義では、クラスの名前も定数と見なされるため、:=を使うことになります。

なぜ5: なぜデバイスをクラスとして定義する必要があるのか?

デバイスは、Fortnite Creativeにおいて、特定の機能を持つ独立したオブジェクトです。クラスとして定義することで、デバイスの動作や特性をコード上で明確に表現し、管理しやすくしています。

トピック3: 編集可能なデバイス変数の定義

コードの該当部分:

      @editable 
    キャベツ生成機 : capture_item_spawner_device = capture_item_spawner_device{}
    第1チーム獲得エリア : capture_area_device = capture_area_device{}
    第2チーム獲得エリア : capture_area_device = capture_area_device{}
    第1チーム守護者生成機 : guard_spawner_device = guard_spawner_device{}
    第2チーム守護者生成機 : guard_spawner_device = guard_spawner_device{}
    

解説:

@editableは属性(アトリビュート)と呼ばれるもので、その後に続く変数や定数がUEFNエディタ上で編集可能であることを示します。これにより、Verseコードを直接変更することなく、エディタ上でデバイスの設定を調整できます。

  • capture_item_spawner_device: アイテムを生成するデバイス

  • capture_area_device: 特定のエリアを定義するデバイス

  • guard_spawner_device: ガード(敵NPC)を生成するデバイス

なぜ1: なぜ@editable属性を使うのか?

Verseコードを直接編集することなく、UEFNエディタ上でデバイスの設定を簡単に変更できるようにするためです。これにより、ゲームデザイナーやレベルデザイナーが、プログラミングの知識がなくてもゲームのバランス調整などを容易に行うことができます。

なぜ2: なぜ変数を定義するのか?

各デバイスへの参照を保持するためです。これらの変数を通じて、コードからデバイスの機能にアクセスしたり、イベントを処理したりできます。例えば、キャベツ生成機.ItemPickedUpEvent.Subscribe(キャベツ拾得時) は、キャベツ生成機 変数を使って capture_item_spawner_device の ItemPickedUpEvent にアクセスしています。

なぜ3: なぜcapture_item_spawner_deviceを使うのか?

「キャベツ」というアイテムをゲーム内に生成するためです。このデバイスは、指定されたアイテムを一定時間ごとに生成する機能を持っています。

なぜ4: なぜcapture_area_deviceを使うのか?

プレイヤーがアイテムを運ぶための目標地点(キャプチャーエリア)を定義するためです。このデバイスは、特定のエリアを定義し、プレイヤーがそのエリアに侵入したかどうかを検知する機能を持っています。

なぜ5: なぜguard_spawner_deviceを使うのか?

特定のチームのガード(敵NPC)を生成するためです。このデバイスは、指定された位置にガードを生成する機能を持っています。

なぜ6: なぜ各デバイスに名前を付けるのか?

コードの可読性を向上させ、各デバイスの役割を明確にするためです。名前を見るだけで、そのデバイスが何をするものなのかを理解しやすくなります。

なぜ7: なぜ {} を使ってデバイスを初期化するのか?

{} は空のブロック式で、デバイスのデフォルト設定を使って初期化することを意味します。エディタで設定が変更されていなければ、デバイスはデフォルトの状態で使用されます。

なぜ8: なぜこれらのデバイスがこのゲームに必要なのか?

「キャベツ」を奪い合うというゲームの基本的なルールを実現するためです。キャベツの生成、キャプチャーエリアの設定、そしてそれらを守るガードの生成という、ゲームの主要な要素をこれらのデバイスが担っています。

トピック4: ゲーム開始時のイベント処理

コードの該当部分:

      OnBegin<override>()<suspends>:void=
        キャベツ生成機.ItemPickedUpEvent.Subscribe(キャベツ拾得時)
        キャベツ生成機.ItemCapturedEvent.Subscribe(キャベツ獲得時)
    

解説:

OnBeginは、ゲーム開始時に自動的に呼び出される特別な関数です。<override>は、この関数がcreative_deviceクラスで定義されているOnBegin関数をオーバーライド(上書き)していることを示します。<suspends>は、この関数が非同期処理を含む可能性があることを示します。

  • ItemPickedUpEvent: アイテムが拾われた時に発生するイベント

  • ItemCapturedEvent: アイテムがキャプチャーエリアに運ばれた時に発生するイベント

  • Subscribe: イベントが発生した時に呼び出す関数を登録する

なぜ1: なぜOnBegin関数をオーバーライドするのか?

ゲーム開始時に特定の処理を実行したいからです。creative_deviceクラスのOnBegin関数は、デバイスの初期化処理などを行うために使用されますが、このゲームではさらに、イベントの登録処理を追加する必要があります。

なぜ2: なぜ<suspends>修飾子を付けるのか?

OnBegin関数内で非同期処理(他の処理の完了を待たずに実行される処理)を行う可能性があるからです。この場合、イベントの登録は非同期処理ではありませんが、将来的に非同期処理が追加される可能性があるため、<suspends>を付けておくことが推奨されます。

なぜ3: なぜItemPickedUpEventにSubscribeするのか?

キャベツが拾われた時に、特定の処理(この場合はキャベツ拾得時関数)を実行したいからです。イベント駆動型のプログラミングでは、イベントが発生した時に実行する関数を登録することで、ゲームのロジックを構築します。

なぜ4: なぜItemCapturedEventにSubscribeするのか?

キャベツがキャプチャーエリアに運ばれた時に、特定の処理(この場合はキャベツ獲得時関数)を実行したいからです。これにより、ゲームの目標達成を検知し、それに応じた処理を実行できます。

なぜ5: なぜイベントにSubscribeする必要があるのか?

イベントは、ゲーム内で何かが起こったことを知らせるための仕組みです。Subscribeすることで、特定のイベントが発生した時に、それに応じた処理を実行できます。例えば、キャベツが拾われたらガードを生成する、キャベツが獲得されたらゲームを終了するなど、イベントを起点に様々なゲームロジックを実現できます。

なぜ6: なぜ Subscribe の引数に キャベツ拾得時 や キャベツ獲得時 という関数を渡すのか?

これらの関数が、キャベツが拾われた時や獲得された時に実行したい処理を定義しているからです。Subscribe は、イベントが発生した時に呼び出す関数を引数として受け取ります。この仕組みにより、イベントと処理を関連付けることができます。

なぜ7: なぜ :void を関数の後ろにつけるのか?

void は、この関数が何も値を返さないことを示します。OnBegin 関数は、ゲーム開始時に呼び出されて処理を実行しますが、値を返す必要はありません。

トピック5: キャベツ獲得時の処理

コードの該当部分:

      キャベツ獲得時(プレイヤー: agent):void=
        Print("キャベツが獲得されました")
        第2チーム守護者生成機.Despawn()
        第1チーム守護者生成機.Despawn()
    

解説:

キャベツ獲得時関数は、キャベツがキャプチャーエリアに運ばれた時に呼び出されます。引数として、キャベツを獲得したプレイヤー(agent型)を受け取ります。

  • Print: デバッグ用のメッセージをログに出力する関数

  • Despawn: スポーンさせたものを消去する関数

なぜ1: なぜキャベツ獲得時関数を定義するのか?

キャベツが獲得された時に特定の処理を実行したいからです。この関数は、キャベツ生成機.ItemCapturedEvent.Subscribe(キャベツ獲得時)によって、キャベツが獲得された時に呼び出されるように登録されています。

なぜ2: なぜ引数としてプレイヤー: agentを受け取るのか?

キャベツを獲得したプレイヤーを特定し、そのプレイヤーに対して何らかの処理(例えば、スコアを加算する)を行いたい場合に、その情報を使用できるからです。

なぜ3: なぜPrint("キャベツが獲得されました")を実行するのか?

デバッグのためです。このメッセージをログに出力することで、キャベツが正常に獲得されたことを確認できます。

なぜ4: なぜ第2チーム守護者生成機.Despawn()を実行するのか?

キャベツが獲得されたら、第2チームのガードを消去するためです。これは、ゲームのルールや進行状況に応じて、ガードの状態をリセットするためと考えられます。

なぜ5: なぜ第1チーム守護者生成機.Despawn()を実行するのか?

キャベツが獲得されたら、第1チームのガードも消去するためです。ゲームの進行状況に応じて、すべてのガードの状態をリセットするためと考えられます。

なぜ6: なぜ Despawn() を使うのか?

生成したガードを消去するためです。Spawn()で生成したものは Despawn()で消去するのが一般的な対となる処理です。

なぜ7: なぜこの関数は値を返さないのか? (void)

この関数は、ガードを消去するという処理を実行するだけで、値を返す必要がないからです。

トピック6: キャベツ拾得時の処理とチーム情報の取得

コードの該当部分:

      キャベツ拾得時(プレイヤー: agent):void=
        Print("キャベツが拾われました")
        全チーム情報 := GetPlayspace().GetTeamCollection()
        if:
            プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]
        then:
            チーム配列 := 全チーム情報.GetTeams()
            Print("チーム情報取得完了")
            for(チーム番号 -> チーム : チーム配列):
                if(プレイヤーチーム = チーム):
                    Print("チーム{チーム番号}のプレイヤーがキャベツを拾いました")
                    if(チーム番号 = 0):
                        Print("第2チームの守護者を生成します")
                        第2チーム守護者生成機.Spawn()
                    else if(チーム番号 = 1):
                        Print("第1チームの守護者を生成します")
                        第1チーム守護者生成機.Spawn()
    

解説:

キャベツ拾得時関数は、キャベツが拾われた時に呼び出されます。この関数では、キャベツを拾ったプレイヤーのチームを特定し、それに応じて異なるチームのガードを生成しています。

  • GetPlayspace(): 現在のゲームのPlayspace(プレイ空間)を取得する関数

  • GetTeamCollection(): Playspaceからチーム情報を管理するオブジェクトを取得する関数

  • GetTeam[プレイヤー]: プレイヤーが所属するチームを取得する

  • GetTeams(): すべてのチームの配列を取得する

  • for: 配列の各要素に対して処理を繰り返すための構文

  • if: 条件分岐を行うための構文

なぜ1: なぜキャベツ拾得時関数を定義するのか?

キャベツが拾われた時に特定の処理を実行したいからです。この関数は、キャベツ生成機.ItemPickedUpEvent.Subscribe(キャベツ拾得時)によって、キャベツが拾われた時に呼び出されるように登録されています。

なぜ2: なぜPrint("キャベツが拾われました")を実行するのか?

デバッグのためです。このメッセージをログに出力することで、キャベツが正常に拾われたことを確認できます。

なぜ3: なぜ全チーム情報 := GetPlayspace().GetTeamCollection()を実行するのか?

プレイヤーが所属するチームを特定するために、まずゲーム内の全てのチーム情報を取得する必要があるからです。GetPlayspace()でゲームのプレイ空間を取得し、そこからGetTeamCollection()でチーム情報を管理しているオブジェクトを取得します。

なぜ4: なぜif: プレイヤーチーム := 全チーム情報.GetTeam[プレイヤー]を実行するのか?

GetTeam[プレイヤー]は、プレイヤーが所属するチームを取得する「オプション型を返す関数」です。プレイヤーがチームに所属していない場合、falseを返し、プレイヤーチーム には何も代入されず、then 以降は実行されません。プレイヤーがチームに所属している場合のみ、then 以降が実行され、プレイヤーチーム にチーム情報が代入されます。これは、プレイヤーが必ずしもチームに所属しているとは限らないため、安全にチーム情報を取得するための処理です。

なぜ5: なぜチーム配列 := 全チーム情報.GetTeams()を実行するのか?

後続の処理で、すべてのチームに対して反復処理を行うために、すべてのチームの配列を取得しています。

なぜ6: なぜPrint("チーム情報取得完了")を実行するのか?

デバッグのためです。このメッセージをログに出力することで、チーム情報が正常に取得できたことを確認できます。

なぜ7: なぜfor(チーム番号 -> チーム : チーム配列)を実行するのか?

すべてのチームに対して反復処理を行い、キャベツを拾ったプレイヤーが所属するチームを特定するためです。forループは、配列の各要素に対して処理を繰り返すために使用されます。ここでは、チーム配列の各要素をチーム変数に代入し、さらにインデックスをチーム番号変数に代入してループ処理を実行しています。

なぜ8: なぜif(プレイヤーチーム = チーム)を実行するのか?

現在のループで処理しているチームが、キャベツを拾ったプレイヤーが所属するプレイヤーチームと一致するかどうかを判定するためです。

なぜ9: なぜPrint("チーム{チーム番号}のプレイヤーがキャベツを拾いました")を実行するのか?

デバッグのためです。このメッセージをログに出力することで、どのチームのプレイヤーがキャベツを拾ったのかを確認できます。

なぜ10: なぜif(チーム番号 = 0)とelse if(チーム番号 = 1)を実行するのか?

キャベツを拾ったプレイヤーのチーム番号に応じて、異なるチームのガードを生成するためです。チーム番号が0の場合は第2チームのガードを生成し、チーム番号が1の場合は第1チームのガードを生成します。これは、ゲームのルールとして、キャベツを拾ったプレイヤーの敵対チームのガードを生成するという意図に基づいています。

なぜ11: なぜ第2チーム守護者生成機.Spawn()と第1チーム守護者生成機.Spawn()を実行するのか?

それぞれのチームのガードを生成するためです。Spawn()関数は、guard_spawner_deviceに設定されたガードを、指定された位置に生成します。

なぜ12: なぜこの関数は値を返さないのか? (void)

この関数は、ガードを生成するという処理を実行するだけで、値を返す必要がないからです。

なぜ13: なぜチーム番号を使って条件分岐するのか?

チーム番号は各チームを一意に識別するためのものです。チーム番号を使って条件分岐することで、各チーム固有の処理を実装できます。このコードでは、チーム番号を使って、どのチームのガードを生成するかを決定しています。

なぜ14: なぜガードをスポーンさせる必要があるのか?

ゲームの難易度を調整したり、プレイヤーに課題を与えたりするためです。ガードはプレイヤーの行動を妨害する敵として機能し、ゲームプレイに変化と挑戦をもたらします。このゲームでは、キャベツを拾ったプレイヤーの敵対チームのガードを生成することで、ゲームの展開に変化を与えています。

なぜ15: なぜ agent 型を使うのか?

agent 型は、プレイヤーや AI など、ゲーム内で行動する主体を表す型です。この関数では、キャベツを拾ったプレイヤーの情報を受け取るために agent 型を使用しています。

まとめ

このコードは、Fortnite Creativeで「キャベツ」を奪い合うゲームの基本的なロジックを実装しています。イベント駆動型のプログラミングモデルを採用し、プレイヤーの行動(キャベツを拾う、キャプチャーエリアに運ぶ)をトリガーとして、ガードの生成や消去といったゲームの状態変化を制御しています。