レースゲームでは、前のラウンドでの成績に基づいてプレイヤーのスタート位置が異なるのが一般的です。 これは、他のプレイヤーよりも前でスタートするために、1 位ではなくてもプレイヤーがレースを早く終えることを促します。
そのためには、ゲームはプレイヤーの現在のラウンドを認識し、レーサーのゴール順はすべてのラウンドで持続する必要がありますが、すべてのゲーム セッションに渡って持続する必要はありません。 Verse でのセッションのウィーク マップ変数ではラウンドごとにそのデータがリセットされるため、プレイヤーのウィーク マップ変数を使用してラウンド情報をプレイヤーごとに格納し、ゲーム終了後にリセットする必要があります。
現在、プロジェクトでは 2 つまでプレイヤー ウィーク マップ変数を持つことができます。 プロジェクトにすでにプレイヤー ウィーク マップ変数が 1 つある場合、2 つ目は、ゲーム終了後またはプレイヤーがセッションを退出したあとでリセットするデータから常に持続する必要のあるデータを区別するためにラウンド情報を記録させることをお勧めします。
また、ラウンドに特化したロジックを適用し、最後のラウンドでラウンド情報をリセットするために、現在プレイヤーがいるラウンドを把握することも重要です。 現在のラウンドを取得する API は現時点ではありません。そのため、この情報も各プレイヤーの持続データに記録する必要があります。
まとめると、少なくとも次の情報があるプレイヤー ウィーク マップ変数が必要です。
ゴール順
前回完了したラウンド
次のセクションでは、このデータとラウンドロジックを設定する方法を説明します。 すべてのコードはページの最後にあります。
前回完了したラウンドを記録する
次の手順に従って、各プレイヤーの持続データを設定し、前回完了したラウンドを記録します。
「
player_circuit_info」という持続可能クラスを作成し、ラウンドをまたいでプレイヤー情報を格納します。 このクラスには、プレイヤーの前回のゴール順であるLastRoundFinishOrderフィールドと、前回完了したラウンドであるLastCompletedRoundフィールドが必要です。 これらのフィールドは、無効な値を表す-1で初期化されているため、役立つ情報がフィールドに実際に含まれているかどうかがわかります。Verse# Tracks the number of and in what order a player finished the previous round. player_circuit_info<public> := class<final><persistable>: Version:int = 0 LastRoundFinishOrder:int = -1 LastCompletedRound<public>:int = -1player_circuit_infoクラスを使用してプレイヤーのウィーク マップ変数を作成し、プレイヤーのラウンド情報を持続させます。Verse# A persistable map that maps each player to # what order they finished the previous round. var CircuitInfo<public>:weak_map(player, player_circuit_info) = map{}持続可能データを使用する際のベスト プラクティスとして、持続可能クラスで各プレイヤーの情報を容易に更新できるように、コンストラクタを作成します。 詳細については、「コンストラクタを使用して部分更新する」を参照してください。
Verse# Creates a new player_circuit_info from the given older player_circuit_info. MakePlayerCircuitInfo<constructor>(OldPlayerCircuitInfo:player_circuit_info)<transacts> := player_circuit_info: Version := OldPlayerCircuitInfo.Version LastRoundFinishOrder := OldPlayerCircuitInfo.LastRoundFinishOrder LastCompletedRound := OldPlayerCircuitInfo.LastCompletedRoundこれでこのデータの構造の定義ができたので、プレイヤーのゴール順を記録し、持続データを更新する関数を追加します。 この関数は前のステップで作成したコンストラクタを使用して、必要とする唯一の情報であるゴール順のためのデータを部分的に更新します。
Verse# Creates a new player_circuit_info for the given player with the order they finished the round in. RecordPlayerFinishOrder<public>(Agent:agent, FinishOrder:int)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo:player_circuit_info = if: Info := CircuitInfo[Player] then: Info else: player_circuit_info{}プレイヤーの前回完了したラウンドのみを更新する別の関数を作成します。
Verse# Updates a player's player_circuit_info with their last completed round. UpdateRound<public>(Agent:agent, CompletedRound:int)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] set CircuitInfo[Player] = player_circuit_info: MakePlayerCircuitInfo<constructor>(PlayerCircuitInfo) LastCompletedRound := CompletedRoundこれでプレイヤーの前回完了したラウンドを記録できるようになったので、どのプレイヤーが最新のラウンドを記録しているかを確認してゲームの前回完了したラウンドを計算する関数を作成します。 進行中のセッションに参加した可能性のあるプレイヤーを考慮し、すべてのプレイヤーを確認する必要があります。 前回完了したラウンドの変数は、無効なデータを表すように
-1で初期化されています。 この値が-1より大きいプレイヤーは、1 つのラウンドをすでに終了しています。Verse# Returns the highest last completed round among all players. GetLastCompletedRound<public>(Players:[]player, TotalRounds:int)<transacts>:int= var LastCompletedRound:int = -1 for: Player : Players Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] do: # Update LastCompletedRound if this player has the highest last completed round. else if:ラウンドとプレイヤーのゴール順が予想どおりに機能しているかテストするため、Verse の仕掛けを作成します。 島設定で Total Rounds プロパティを設定することによって、プロジェクトを複数ラウンド用に設定しておきます。
Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= Players := GetPlayspace().GetPlayers() CurrentRound := GetLastCompletedRound(Players) + 1 Print("Current round is {CurrentRound}") for:
プレイヤー退出時にラウンド情報をリセットする
プレイヤーがゲーム中に退出した場合、プレイヤーのラウンド情報の持続データをリセットする必要があります。 プレイ空間の PlayerRemovedEvent にサブスクライブして、プレイヤーがいつ退場したかを把握できます。
以下の手順に従って、プレイヤー退出時にラウンド情報をリセットします。
プレイヤーのラウンド情報の持続データをリセットする関数を作成します。 つまり、変数の値を
-1、またはフィールドのデータが無効であることを表すことにする任意の値に設定します。 この実装は、ここでリセットされるべきでない他のデータをあとで持続クラスに追加した場合、どのデータをリセットするべきかオプトインします。Verse# Resets a player's player_circuit_info. ResetCircuitInfo<public>(Agent:agent)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] set CircuitInfo[Player] = player_circuit_info: MakePlayerCircuitInfo<constructor>(PlayerCircuitInfo) LastRoundFinishOrder := -1 LastCompletedRound := -1プレイヤー退出時にラウンド情報をリセットするための「
OnPlayerRemoved」という名前の関数を作成します。Verse# When a player is removed from the race, reset their circuit to prevent their # stats from showing up on billboards and player references. OnPlayerRemoved(Player:player):void= # Reset circuit info when player leaves the game. if: ResetCircuitInfo[Player] else: Print("Unable to reset circuit info for player")イベント
GetPlayspace().PlayerRemovedEvent()をサブスクライブすることで、プレイヤーがゲームを退出したときにOnPlayerRemoved関数が呼び出されるように設定します。Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved) Players := GetPlayspace().GetPlayers() CurrentRound := GetLastCompletedRound(Players) + 1プレイヤーがゲームを退出すると情報がリセットされることを確認するテストを行います。
ゲーム終了時にラウンド情報をリセットする
Verse の仕掛けの OnBegin 関数は、ラウンドの開始ごとに実行されます。 これは、プレイヤーの前回完了したラウンドが合計ラウンド数と同じかどうかなど、プレイヤーが予期しない持続データを保持しているかどうかを判断する良い機会です。 保持している場合、プレイヤーのデータをリセットする必要があります。 現在、ゲームの合計ラウンド数を把握するための API はありません。 その代わりに、Verse コードで合計ラウンド数を表す編集可能なプロパティを Verse の仕掛けに追加し、その値が [Island Settings] での Total Rounds プロパティと一致するようにします。
以下の手順に従って、ゲーム終了時にラウンド情報をリセットします。
ゲーム内の合計ラウンド数の Verse の仕掛けに編集可能なプロパティを追加します。 TotalRounds は
0以下であってはならないため、このプロパティの値を1以上に制限します。Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)その値がゲームで想定されているラウンド数より多い場合は、
GetLastCompletedRoundで、前回完了したラウンドのプレイヤーの持続データをリセットするように、関数を更新します。Verse# Returns the highest last completed round among all players. GetLastCompletedRound<public>(Players:[]player, TotalRounds:int)<transacts>:int= var LastCompletedRound:int = -1 for: Player : Players Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] do: # If player's recorded info is greater than the total rounds for whatever reason, # then need to reset the player's circuit info because they shouldn't have more than what's allowed.合計ラウンド数を引数として渡し、想定外の状態である場合はプレイヤーのラウンド情報をリセットするように、
GetLastCompletedRoundの呼び出しを更新します。Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)すべてのラウンドをプレイし終えた後でもプレイヤーのラウンド情報がリセットされることを確認するテストを行います。
現在のラウンドに基づいてロジックを追加する
これで、この情報を使用してプレイヤーがどのラウンドにいるかによってカスタム ロジックを作成できるようになりました。 たとえば、ゲームの最初のラウンドで、ローカル ランキングを表示することもできます。
どのラウンドがプレイされているかを把握する必要があるたびに GetLastCompletedRound() を呼び出すのは理想的ではありません。 その代わり、1 ラウンドにつき 1 回だけ行い、ラウンド情報をセッションのウィーク マップ変数に記録することで、プロジェクト内のすべての Verse コードは、毎回再計算する必要なく、いつでもこの値にアクセスできます。
これは、コードでセッション ウィーク マップ変数とプレイヤー ウィーク マップ変数の違いと使い分ける理由を示す良い例です。
セッションのウィーク マップ変数は、シングルトンや、毎回再計算したくない現在のラウンドのデータを格納するのに便利です。
プレイヤー ウィーク マップ変数は、複数のラウンドやゲーム セッションをまたいで保持される必要があるものの、個々のプレイヤーに関連付けられている必要がある情報向けに設計されています。
以下の手順に従って、現在のラウンドを保存するためのセッション ウィーク マップ変数を設定します。
ラウンド情報を保存するクラスを作成します。 少なくとも
CurrentRoundのフィールドが必要ですが、プレイヤーのスタート位置や乗り物など、Verse コード全体で保存する他のラウンド情報を含めることもできます。CurrentRoundを-1に初期化し、無効データを表します。Verseround_info := class: CurrentRound:int = -1round_infoクラスを使用してセッション ウィーク マップ変数を作成し、セッションとラウンド情報を保存します。Verse# Maps the current session to its associated round info. var RoundInfo:weak_map(session, round_info) = map{}セッション ウィーク マップ変数から現在のラウンドを取得するため getter 関数を追加します。
VerseGetRound<public>()<decides><transacts>:int= RoundInfo[GetSession()].CurrentRound現在のラウンドを取得し、セッション ウィーク マップ変数に格納する関数を追加します。
VerseRecordCurrentRound<public>(Players:[]player, TotalRounds:int):void= var CurrentRoundInfo:round_info = if: Info := RoundInfo[GetSession()] then: Info else: LastCompletedRound := GetLastCompletedRound(Players, TotalRounds) round_info: CurrentRound := LastCompletedRound + 1新しい
RecordCurrentRound関数を使用し、どのラウンドであるのか把握する必要がある場合、GetRoundを呼び出すために Verse の仕掛けを更新します。Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
これで、この情報はセッション ウィーク マップ変数に保存されたため、ラウンドのカスタム ロジックを簡単に追加できるようになりました。 たとえば、最初のラウンドかどうかを確認し、プレイヤーのためにロビーとリーダーボード閲覧エリアを設定できます。
# Returns true if this is the first round of the game.
IsFirstRound<public>(RoundToCheck:int)<decides><transacts>:void=
RoundToCheck <= 0応用編
レースゲームでこのコードを使用してプレイヤーのスタート順を決める方法については、「Verse Persistence を使用したスピードウェイ レース」を確認してください。
テンプレートを確認後、次の手順を試してみてください。
どの乗り物がプレイヤーに割り当てられているかなどのラウンド情報を追加します。
各ラウンドの開始時にマップの別のエリアにプレイヤーをテレポートします。
他にはどのようなラウンド固有のロジックを使用するゲームが考えられますか?
完全なコード
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# A persistable map that maps each player to
# what order they finished the previous round.
var CircuitInfo<public>:weak_map(player, player_circuit_info) = map{}
# Maps the current session to its associated round info.
var RoundInfo:weak_map(session, round_info) = map{}