このセクションでは、プレイヤーのチームおよびクラスを決定し、カスタマイズする方法について説明します。
使用されている仕掛けは以下のとおりです。
-
チーム設定 & インベントリ × 2
-
クラス デザイナー × 2
-
クラス セレクター × 2
チーム設定 & インベントリ
チーム設定 & インベントリの仕掛けを使用して、スコアボードに表示するチームの名前と色を設定します。
各チームに 1 つずつ、プレイヤーから見えないエリアに仕掛けを配置します。この小道具チームを設定するには、[User Options (ユーザー オプション)] を次のように設定します。
| オプション | Value | 説明 |
|---|---|---|
| Team Name (チーム名) | Props (小道具) | スコアボードと HUD 要素でチームを特定するのに使用するテキスト文字列の設定。 |
| Team Color (チームカラー) | Sky Blue (スカイブルー) | 選択したチームに、スコアボード、HUD、および特定の仕掛けで使用する色を割り当てます。 |
| Team (チーム) | Team Index (チームインデックス):1 | この仕掛けの設定内容がどのチームに適用されるかの設定。 |
ハンター チームを設定するには、他の仕掛けの [User Options] を次の表のように設定します。
| オプション | Value | 説明 |
|---|---|---|
| Team Name | Hunters (ハンター) | スコアボードと HUD 要素でチームを特定するのに使用するテキスト文字列の設定。 |
| Team Color | Orange (オレンジ) | 選択したチームに、スコアボード、HUD、および特定の仕掛けで使用する色を割り当てます。 |
| Team (チーム) | Team Index:2 | この仕掛けの設定内容がどのチームに適用されるかの設定。 |
クラスデザイナー
クラス デザイナーを使用すると、作成したチームを変更できます。
クラス デザイナーの仕掛けを 2 つ (各チームに 1 つずつ)、プレイヤーの見えないエリアに配置します。この小道具チームをカスタマイズするには、[User Options] を次の表のように設定します。
| オプション | Value | 説明 |
|---|---|---|
| Class Name (クラス名) | Prop | このクラスの名前を決定します。 |
| Class Description (クラスの説明) | Hide from hunters.Survive. (ハンターから隠れて、生き延びろ。) | このクラスの説明を設定します。 |
| Class Identifier (クラス識別子) | Class Slot (クラス スロット):1 | このクラスの一意の識別子を設定します。 |
| Max Health (最大ヘルス値) | 1 | ゲーム中にプレイヤーが到達できる最大体力値を決定する。小道具は一撃で撃破されます。 |
| Item List (アイテムリスト) | Prop-O-Matic (小道具プロセッサー) | このクラスが含むアイテムのリストを設定します。 |
| Equip Granted Item (付与されたアイテムの装備) | First Item (1 つ目のアイテム) | リストのどのアイテムを装備するかを決定します。 |
ハンター チームをカスタマイズするには、他の仕掛けの [User Options (ユーザー オプション)] を次の表のように設定します。
| オプション | Value | 説明 |
|---|---|---|
| ClassName | Hunter | このクラスの名前を決定します。 |
| Class Description | Find props.Eliminate them. (小道具を見つけて、敵を倒せ。) | このクラスの説明を設定します。 |
| Class Identifier | Class Slot:2 | このクラスの一意の識別子を設定します。 |
| Item List | Flashlight Pistol (フラッシュライトピストル) | このクラスが含むアイテムのリストを設定します。 |
| Equip Granted Item | First Item | リストのどのアイテムを装備するかを決定します。 |
クラスセレクター
クラス デザイナーとクラス セレクターを組み合わせると、作成したカスタマイズ クラスとチームを管理することができます。
Verse で、この仕掛けの設定を使用することで、クラス スロット 1 のプレイヤーは、リスポーン時にクラス スロット 2 に転送されます。
クラス セレクターを 2 つ (各チームに 1 つずつ)、プレイヤーの見えないエリアに配置します。小道具チームを管理するには、以下の表の設定を使用して、この仕掛けの [User Options] を設定します。
| オプション | Value | 説明 |
|---|---|---|
| Class to Switch to (切り替え先のクラス) | Class Slot:1 | プレイヤーがどのクラスに切り替わるかを決定します。 |
| Visible During Game (ゲーム中に表示) | オフ | この仕掛けは、インゲームでは表示されません。 |
| Zone Audio (ゾーンのオーディオ) | オフ | プレイヤーがゾーンに入った際にクラスセレクターがオーディオを再生するかの設定。 |
| Team to Switch to (切り替え先のチーム) | Team Index:1 | プレイヤーがどのチームに切り替わるかの設定。 |
| Clear Items on Switch (切り替え時にアイテムをクリア) | オン | 切り替えが適用された際にプレイヤーのインベントリからアイテムを削除するかどうかを決定します。 |
| Volume Visible in Game (ゲーム内でボリュームを表示) | オフ | ゲーム中に仕掛けのボリュームを表示するかどうかを決定します。 |
| Display VFX on Activation (起動時に VFX を表示) | オフ | プレイヤーのクラスまたはチームを変更した際、この仕掛けが VFX を生成するかの設定。 |
ハンター チームを管理するには、以下の表の設定を使用して、この仕掛けの [User Options] を設定します。
| オプション | Value | 説明 |
|---|---|---|
| Class to Switch to | Class Slot:2 | プレイヤーがどのクラスに切り替わるかを決定します。 |
| Team to Switch to | Team Index (チーム インデックス):2 | プレイヤーがどのチームに切り替わるかの設定。 |
Verse を使用してチーム機能を作成する
この小道具かくれんぼゲームには、ハンターと小道具の 2 つのチームがあります。 ゲームを機能させるためには、両方のチームで同じ機能を実行できるようにする必要があります。 次に例を示します。
-
チームにプレイヤーを追加する
-
チームからプレイヤーを削除する
-
自分のチームに関する情報をプレイヤーに表示する
コードを重複させることなく両方のチームにこの機能を作成するには、<abstract> 指定子を持つクラスを作成します。 abstract 指定子を持つクラスは、そのクラスのサブクラスが継承して、作成する部分的な機能を持ちます。 まず、base_team という抽象クラスを作成して、小道具チームとハンター チームの両方が共有する機能を提供します。
このドキュメントには、このゲームプレイで必要なゲームプレイ メカニクスの実行方法を示す Verse のスニペットが含まれています。以下の手順を実行してから、このチュートリアルの ステップ 6 にある完全なスクリプトをコピーしてください。
base_team.verse というプロジェクトで新しい Verse ファイルを作成します。これは Verse の仕掛けではないため、空の Verse ファイルとして作成できます。
using { /Fortnite.com/Characters}
using { /Fortnite.com/Devices}
using { /Fortnite.com/UI}
using { /UnrealEngine.com/Temporary/Diagnostics}
using { /UnrealEngine.com/Temporary/SpatialMath}
using { /UnrealEngine.com/Temporary/UI}
using { /Verse.org/Colors}
using { /Verse.org/Simulation}
log_team := class(log_channel){}
# このクラスは、体験中にさまざまなチームに必要な仕掛けを定義します。
# このクラスは抽象クラスであるため、単独で使用することはできません。他のクラスによって継承される必要があります。
base_team := class<abstract>:
Logger:log = log{Channel:=log_team}
@editable # プレイヤーをチームに設定するために使用します。
ClassSelector:class_and_team_selector_device = class_and_team_selector_device{}
@editable # チームのエージェントにスコアを付与するために使用します。
ScoreManager:score_manager_device = score_manager_device{}
@editable # チームの任務のタイトルを表示するために使用します。
TeamTitle:hud_message_device = hud_message_device{}
@editable # チームの任務の説明を表示するために使用します。
TeamDescription:hud_message_device = hud_message_device{}
@editable # Used to subscribe to team member (prop team) or enemy (hunter team) eliminated events.
TeamManager:team_settings_and_inventory_device = team_settings_and_inventory_device{}
# これはチームでのエージェントの配列です。
var TeamAgents<private>:[]agent = array{}
# このイベントは、TeamAgents 配列が空になったときに通知されます (ラウンドの終了を示す)。
TeamEmptyEvent:event() = event(){}
# 現在の TeamAgents 配列を返します。
# これは必須です。TeamAgents 配列はプライベートであり、他のクラスが直接アクセスできないためです。
GetAgents()<decides><transacts>:[]agent =
TeamAgents
# TeamAgents 配列のサイズを返します。
# これには、関数が必要です。TeamAgents 配列はプライベートであり、他のクラスが直接アクセスできないためです。
Count()<transacts>:int =
TeamAgents.Length
# エージェントの TeamAgents 配列内のインデックスを返します。それ以外の場合は失敗します。
FindOnTeam(Agent:agent)<decides><transacts>: int =
Index := TeamAgents.Find[Agent]
# エージェントをチームに設定し、プレイヤーに通知します。
InitializeAgent(Agent:agent):void =
AddAgentToTeam(Agent)
ClassSelector.ChangeTeamAndClass(Agent)
DisplayTeamInformation(Agent)
# エージェントを TeamAgents に追加します。
AddAgentToTeam(AgentToAdd:agent):void =
if (not FindOnTeam[AgentToAdd]):
Logger.Print("Adding agent to team.")
set TeamAgents += array{AgentToAdd}
#HUD メッセージの仕掛けをアクティブ化し、プレイヤーが所属するチームを表示します。
DisplayTeamInformation(Agent:agent):void =
TeamTitle.Show(Agent)
TeamDescription.Show(Agent)
# エージェントがマッチから退出するときに、TeamAgents 配列からエージェントを削除し、ラウンドが終了するかどうかを確認します。
EliminateAgent(Agent:agent)<suspends>:void =
Sleep(0.0) # 1 ゲーム ティック遅延させることで、進む前にプレイヤーが確実にリスポーンされるようにします。
RemoveAgentFromTeam(Agent)
# TeamAgents からエージェントを削除します。
# 削除されたエージェントが最後のエージェントだった場合、TeamEmptyEvent を通知します。
RemoveAgentFromTeam(AgentToRemove:agent):void =
set TeamAgents = TeamAgents.RemoveAllElements(AgentToRemove)
Logger.Print("{Count()} agent(s) on team remaining.")
if (Count() < 1):
Logger.Print("チームのエージェントが残っていません。ラウンドを終了します。")
TeamEmptyEvent.Signal()
このクラスを作成できたので、次に小道具チームとハンター チームのクラスを作成します。これらのクラスはそれぞれ base_team を継承するため、次のような利点があります。
-
base_teamで共通の関数とデータが定義済みであるため、各チームを実装するコードが大幅にコンパクトになる。 -
小道具チームとハンター チームに特化したコードは、共通のコードと混在することなく、それぞれのクラス内にあるため、わかりやすい。
-
ゲーム モードにチームを追加するのがはるかに容易になる。新しいチームはすべて
base_teamを継承し、新しいチームの異なる機能を実装するコードは独自のクラスに含まれる。
指定子 <abstract> を使用してクラスのインスタンスを作成することはできないことにご注意ください。 抽象クラスを継承するクラスを作成し、そのクラスをインスタンス化する必要があります。
ハンター チーム
まず、ハンター チームのクラスを作成します。hunter_team.verse というプロジェクトで新しい Verse ファイルを作成します。これは Verse の仕掛けではないため、空の Verse ファイルとして作成できます。
hunter_team というクラスを宣言します。これは、<concrete> である必要があり、やはり base_team を継承します。
hunter_team := class<concrete>(base_team):
クラス <concrete> を作成するということは、クラスのすべてのフィールドにデフォルト値が設定されている必要があるということです。 詳細は、「指定子および属性」を参照してください。
以下は、hunter_team.verse スクリプトの完全なコードです。
hunter_team クラスには、base_team クラスの関数と同じ名前の関数が 2 つありますこれは、どちらも <override> 指定子が指定されているために使用可能です。 つまり、これらの関数が hunter_team のインスタンスで呼び出されると、hunter_team クラスのバージョンが使用されます。
たとえば、以下のコードでは、hunter_team で定義されている InitializeAgent() のバージョンが使用されます。これは、base_team の同名の関数がオーバーライドされるためです。これを、base_team で定義されたバージョンを使用する Count() の呼び出しと比較します。なぜなら、この呼び出しにはオーバーライド関数がないためです。
HunterTeam:hunter_team = hunter_team{}
# hunter_team の関数を使用します
HunterTeam.InitializeAgent(StartingHunterAgent)
# base_team の関数を使用します
HunterTeam.Count()
オーバーライドされた 2 つの関数も (super:) を使用します。これにより、base_team は hunter_team のスーパークラスであるため、これらの関数は base_team で定義されたバージョンの関数を呼び出すことができます。InitializeAgent() および EliminateAgent() の場合、どちらも Logger.Print() を使用して、ログへの出力を行います。次に、base_team からそれぞれの関数を呼び出します。つまり、Logger.Print() への呼び出しを除き、これらの関数は base_team のバージョンとまったく同じように動作します。
「サブクラス」で、<override> と (super:) の詳細をご覧ください。
小道具チーム
今度は、小道具チームのクラスを作成します。prop_team.verse というプロジェクトで新しい Verse ファイルを作成します。これは Verse の仕掛けではないため、空の Verse ファイルとして作成できます。
小道具チームのメンバーについては、必要な管理が増えます。 小道具チームのメンバーにはハートビート エフェクトが設定されており、タイマーとメンバーの移動距離に基づいて開始と停止を実行する必要があります。 また、撃破されると、ハンター チームに移動させなければなりません。
小道具チームのメンバーを管理するには、RunPropGameLoop() メソッドを使用します。このメソッドは、小道具のゲームの全行程のマネージャーと考えることができます。スポーンされてから撃破されるかゲームから退出するまで、このメソッドはすべての小道具チームのメンバーに対して実行されます。
# 小道具エージェントが移動を停止した場合、小道具エージェントが MinimumMoveDistance を超えて移動するか、ハートビートタイマーが完了するか、小道具エージェントが撃破されるかの、いずれが先に起こるかを確認します。
RunPropGameLoop(PropAgent:agent)<suspends>:void =
Logger.Print("Starting prop agent game loop.")
# 小道具エージェントが撃破されるか、プレイヤーがセッションを退出するまで、小道具の動作を永続的にループします。
race:
PropAgent.AwaitNoLongerAProp()
loop:
# 小道具エージェントの移動距離が最小移動距離を下回るまで待機してから、先に進みます。
PropAgent.AwaitStopMoving(MinimumMoveDistance)
# 小道具エージェントの移動距離が最小移動距離を超えるまで、ハートビートまでのカウントダウンを実行してから、ハートビートを無期限に再生します。
race:
PropAgent.AwaitStartMoving(MinimumMoveDistance)
block:
CountdownTimer(PropAgent)
PropAgent.StartHeartbeat()
Sleep(0.0) # レースが完了したら (小道具エージェントが移動したら)、再びループを開始します。
RunPropGameLoop() には 1 つのパラメータ PropAgent があります。これは小道具チームのプレイヤーを表す定数です。 また、<suspends> 指定子も含んでいます。つまり、完了までに時間がかかります。 この場合、渡された PropAgent が小道具チームでなくなるまで終了しません。
このメソッドのすべての機能はレース式内に含まれます。 つまり、このレース内の式の 1 つが完了するまでメソッドが完了しません。 これらの式は、次のとおりです。
-
PropAgent.AwaitNoLongerAProp() -
loop
このレース内のループ式が終了することはありません。これは意図的に無限ループになっています。つまり、AwaitNoLongerAProp() メソッドは、常に レースに勝利し、メソッドが完了するということです。レースをこのように使用することは、何かが発生するまで、特定のコードのセットを何度も実行するようにプログラムに指示するのに似ています。この強力な式の詳細については、「レース」を参照してください。
このコードでは、AwaitNoLongerAProp() がレースに勝利します。
# 小道具エージェントが PropAgents 配列の一部でなくなるまでループします。小道具エージェントが撃破されてハンターになるか、プレイヤーがセッションから退出すると削除が実行されます。
(PropAgent:agent).AwaitNoLongerAProp()<suspends>:void =
loop:
if (not FindOnTeam[PropAgent]):
Logger.Print("Cancelling prop agent behavior.")
break
Sleep(0.0) # 次のゲーム ティックに進みます。
このメソッドは PropAgent が小道具チームにいるかどうかを常にチェックします。この処理は、not FindOnTeam[PropAgent] が成功するまでループを実行し、成功した時点でループを抜けてメソッドが完了します。詳細については、「Loop と Break」を参照してください。
FindOnTeam[] は base_team で宣言された失敗する可能性がある関数です。小道具チームで PropAgent が見つかれば成功します。ただし、PropAgent が小道具チームに見つからない場合に限り、ループから抜ける必要があるため、not 演算子を使用する必要があります。not の詳細は、「演算子 」をご覧ください。
最後に、ループの最後に Sleep(0.0) を追加する必要があります。これにより、ループが一度実行された後、次の シミュレーションの更新 に確実に進みます。このチェックを頻繁に実行する必要はないため、パフォーマンスをサポートする目的で、Sleep(0.0) が追加されます。詳細については、Sleep に関する「Verse API リファレンス」ページを参照してください。
AwaitNoLongerAProp() がどのように動作するかを理解できたので、次はこのメソッドと RunPropGameLoop() がレースする無限ループを記述することができます。