前提条件: persistent-player-statistics-in-unreal-editor-for-fortnite
このチュートリアルは 持続的なプレイヤー統計情報 のコンセプトに基づいています。まずはそちらをご確認ください。
ランキングは競争型ゲームに不可欠なものであり、プレイヤーは保有するスキルを示して有名になることができます。プレイヤーが成長を実感できるだけでなく、ランキングのトップを目指してもう一度プレイしたくなります。
Verse Persistence は、こうしたランキングを作成し、体験に競争力を付加するうえで役立つツールを提供します。持続的なプレイヤー統計情報のチュートリアルでは、プレイセッション間の持続データを追跡する方法と、さまざまなイベントに基づいてそのデータを変更、更新する方法を確認してきました。次は、こうした知識を応用して完全なローカルランキングを作成し、プレイヤー統計データをソートして、レースゲームで活用する方法を学習します。
使用する Verse 言語機能
-
クラス:この例では、プレイヤー単位で統計データのグループを追跡する、永続可能な Verse クラスを作成します。
-
コンストラクタ:コンストラクタは、関連付けられているクラスのインスタンスを作成する特別な関数です。
-
weak_map:weak_map は、反復処理できない単純なマップです。Verse の持続データは weak_map に格納する必要があります。
レベルを設定する
この例では、次の小道具と仕掛けを使用します。
-
3 x ビルボードの仕掛け:各プレイヤーのライフタイム統計データが表示されます。ライフタイム ポイントに基づいてソートし、ロビー内のベスト プレイヤーを表示することができます。
-
3 x プレイヤー参照の仕掛け:ビルボードと組み合わせて、プレイヤー参照がトップパフォーマーの名前に顔を付け、他のプレイヤーがゲーム中に、誰に味方すべきか把握できるようにします。
-
3 x チェックポイントの仕掛け:プレイヤーがレースを完走するために通過するチェックポイントです。
-
1 x レース マネージャーの仕掛け:プレイヤーがレースを開始および終了したときに追跡し、最終順位に基づいてポイントを付与します。
-
1 x ピックアップ トラック スポナーの仕掛け:レース中に使用する乗り物がスポーンされますが、体験に合わせて任意の乗り物に変更できます。
レベルを設定するには、次の手順を実行します。
ビルボードとプレイヤー参照
プレイヤー統計データを表示するには、ビルボードとプレイヤー参照を組み合わせて使用します。各ビルボードにはプレイヤーのライフタイム統計データが表示され、プレイヤー参照にはそのプレイヤーのビジュアル イメージが表示されます。これらの要素を追加するには、次の手順を実行します。
-
3 つの プレイヤー参照 の仕掛けをレベルに追加し、隣り合わせで配置します。
-
各プレイヤー参照について、アウトライナー で選択します。ユーザー オプション の下にある [Details] パネルで、ロビーで 1 番目、2 番目、3 番目に優れたプレイヤーを表す色に カスタム カラー を設定します。この例では、そうした色にゴールド、シルバー、ブロンズを使用しています。
-
3 つの ビルボード の仕掛けをレベルに追加し、1 つを各プレイヤー参照の前に置きます。ゲームが始まると、Verse で各プレイヤーの統計データを使用して情報を更新します。
チェックポイント、ピックアップ トラック、レース マネージャー
これはレースであるため、争うものが必要となります。また、通過するチェックポイントと、ゲーム中にレースを誘導するレース マネージャーも必要となります。これらの要素を追加するには、次の手順を実行します。
-
3 つの レース チェックポイント の仕掛けをレベルに追加します。プレイヤーが通過する順番に仕掛けを配置します。アウトライナー の各チェックポイントで、チェックポイント番号 とプレイヤーがチェックポイントを通過する順番が一致していることを確認します。
-
1 つの レース マネージャー の仕掛けをレベルに追加します。これによってレースの走行を管理し、プレイヤーをチェックポイントに誘導します。後でこの仕掛けから
RaceCompletedEvent()
をリッスンし、プレイヤーがレースを終えた時間を把握します。 -
1 つの ピックアップ トラック スポナー の仕掛けをレベルに追加します。乗り物はオプションですが、このガイドでは、Speedway Template に合わせてピックアップ トラックを使用しており、プレイヤーに何らかの乗り物を提供します。
統計データテーブルを変更する
この例では、持続的なプレイヤー統計データ の「player_stats_table
」ファイル修正版を使用しています。これは、その例のファイルとよく似たものに見えますが、実装の変更につながる重要な違いがあります。通常、player_stat
クラスは使用しません。player_stats_table
のデータはすでに持続可能であり、別の統計クラスに保存することなく、必要なデータをテーブル クラスで直接インスタンス化できます。統計データを更新するたびに MakeUpdatedPlayerStat()
を呼び出す必要がなくなり、コードと関数呼び出しの節約にもなります。
以下の手順に従って、プレイヤー統計データテーブルを作成します。
-
player_stats_table
クラス内:-
Losses
の統計データを削除します。 -
Score
の統計データをPoints
に変更します。 -
Points
とWins
の両方を、player_stat
から整数
値に変更します。
# プレイヤーごとに異なる持続可能な統計データを追跡します。 player_stats_table<public>:= class<final><persistable>: # 最新の統計データ テーブルのバージョン。 Version<public>:int = 0 # プレイヤーのポイント。 Points<public>:int = 0 # プレイヤーの勝利数。 勝利<public>:int = 0
-
-
ファイル内の
MakePlayerStatsTable()
コンストラクタ関数を修正し、更新済みの統計データを反映させます。# 前回の player_stats_table と同じ値を使用して、新しい player_stats_table を作成します。 MakePlayerStatsTable<constructor>(OldTable:player_stats_table)<transacts> := player_stats_table: Version := OldTable.Version Points := OldTable.Points Wins := OldTable.Wins
-
新しい構造体の
player_and_stats
を「player_stats_table.verse」ファイルに追加します。この構造体には、プレイヤー
とそのplayer_stats_table
クラスへの参照が含まれており、何度も取得することなく、関数で両方のデータを使用できます。完成したplayer_and_stats
構造体は次のようになります。# プレイヤーとその統計データを引数として渡すための構造体。 player_and_stats<public> := struct: Player<public>:player StatsTable<public>:player_stats_table
統計データを管理する
持続的なプレイヤー統計情報 の作業と同様に、マネージャー ファイルを使用して、プレイヤーの統計データ変更の管理と記録を処理します。この例では、一部の修正を使用してクラスを削除し、ヘルパー関数を使用してプレイヤー統計データの管理をよりスケーラブルなものにします。以下の手順に従って、修正済みの「player_stats_manager
」ファイルをビルドします。
クリーンアップする
「player_stats_manager
」ファイルで、stat_type
クラスと StatType
モジュールの両方、およびそれらに含まれるものをすべて削除します。player_stats_manager
クラスと RecordPlayerStat()
関数を削除し、GetPlayerStats()
、InitializeAllPlayers()
、および InitializePlayer()
をすべてモジュールレベルの関数にします。最初の「player_stats_manager
」ファイルは次のようになります。
using { /Fortnite.com/Devices}
using { /Verse.org/Simulation}
using { /UnrealEngine.com/Temporary/Diagnostics}
# 指定されたエージェントの player_stats_table を返します。
GetPlayerStats(Agent:agent)<decides><transacts>:player_stats_table=
var PlayerStats:player_stats_table = player_stats_table{}
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
set PlayerStats = MakePlayerStatsTable(PlayerStatsTable)
PlayerStats
# 現在の全プレイヤーの統計データを初期化します。
InitializeAllPlayers(Players:[]player):void =
for (Player : Players):
InitializePlayer(Player)
# 指定されたプレイヤーの統計データを初期化します。
InitializePlayer(Player:player):void=
if:
not PlayerStatsMap[Player]
set PlayerStatsMap[Player] = player_stats_table{}
統計データを初期化および更新する
プレイヤーを初期化するためのロジックは、引き続きほぼ同じです。唯一の違いは、2 つの初期化関数の関数シグネチャを更新することです。RecordPlayerStat()
を削除する場合、プレイヤーごとの統計データの記録を処理するために新しい関数も必要になります。
特異関数を使用して統計データを記録するのではなく、各統計データの追加を処理する個別の関数を作成します。以前の RecordPlayerStat()
関数では、それぞれの StatType
をチェックして更新が必要かを確認する長い if
else
チェーンを使用する必要がありましたが、それよりも柔軟な設計となっています。責任を複数の関数に分割することで単一障害点が減り、新しい統計データをランキングに簡単に追加できるようになります。また、1 つの関数の変更で設計全体が破綻することもありません。
以下の手順に従って、統計データの記録関数を追加します。
-
InitializeAllPlayers()
およびInitializePlayer()
の関数シグネチャをInitializeAllPlayerStats()
およびInitializePlayerStat()
に変更します。GetPlayerStat()
関数との関係をより的確に反映した名前となります。更新済みの関数は次のようになります。# 現在の全プレイヤーの統計データを初期化します。 InitializeAllPlayerStats<public>(Players:[]player):void = for (Player : Players): InitializePlayerStat(Player) # 指定されたプレイヤーの統計データを初期化します。 InitializePlayerStat<public>(Player:player):void= if: not PlayerStatsMap[Player] set PlayerStatsMap[Player] = player_stats_table{} else: Print("Unable to initialize player stats")
-
「
player_stats_manager
」ファイルに「AddPoints()
」という名前の新しい関数を追加します。この関数は、ポイントを付与するエージェントと、付与する整
数のポイントを受け取ります。# 指定されたエージェントの StatToAdd に加算し、以下に含まれる両方の統計データ テーブルを更新します。 # PlayerStatsManager とレベル内のビルボード。 AddPoints<public>(Agent:agent, NewPoints:int):void=
-
AddPoints()
で、if
式でエージェントからプレイヤーを取得し、player_stats_map
からその統計データ テーブルを取得します。次に、「CurrentPoints
」という名前の変数で現在のポイント数を取得します。AddPoints<public>(Agent:agent, NewPoints:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentPoints := PlayerStatsTable.Points
-
新しい
player_stats_table
を作成してPlayerStatsMap
を更新し、古いテーブルとCurrentPoints + NewPoints
に等しい新しいPoints
値を渡します。この例では、前のチュートリアルのplayer_stat
クラスを使用しないため、MakeUpdatedPlayerStat()
関数を使用せずにPoints
値を直接初期化できます。完成したAddPoints()
関数は次のようになります。# 指定されたエージェントの StatToAdd に加算し、以下に含まれる両方の統計データ テーブルを更新します。 # PlayerStatsManager とレベル内のビルボード。 AddPoints<public>(Agent:agent, NewPoints:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentPoints := PlayerStatsTable.Points set PlayerStatsMap[Player] = player_stats_table: MakePlayerStatsTable<constructor>(PlayerStatsTable) Points := CurrentPoints + NewPoints else: Print("Unable to record player points")
-
player_stats_manager
ファイルに別の関数「AddWin()
」を追加します。この関数は、Points
統計データではなくプレイヤーのWins
統計データを取得して更新する点を除いて、AddPoints()
に類似したものになります。AddWin()
関数、および完成したplayer_stats_manager
ファイルは次のようになります。# このファイルは、各プレイヤーの player_stats_tables を初期化、更新、返すためのコードを # 処理します。また、統計データの更新に使用する抽象 stat_type クラス、 # 統計データの表示に使用する StatType モジュールを定義します。 using { /Fortnite.com/Devices} using { /Verse.org/Simulation} using { /UnrealEngine.com/Temporary/Diagnostics} # 指定されたエージェントの player_stats_table を返します。 GetPlayerStats<public>(Agent:agent)<decides><transacts>:player_stats_table= var PlayerStats:player_stats_table = player_stats_table{} if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] set PlayerStats = MakePlayerStatsTable(PlayerStatsTable) PlayerStats # 現在の全プレイヤーの統計データを初期化します。 InitializeAllPlayerStats<public>(Players:[]player):void = for (Player : Players): InitializePlayerStat(Player) # 指定されたプレイヤーの統計データを初期化します。 InitializePlayerStat<public>(Player:player):void= if: not PlayerStatsMap[Player] set PlayerStatsMap[Player] = player_stats_table{} else: Print("Unable to initialize player stats") # 指定されたエージェントの StatToAdd に加算し、以下に含まれる両方の統計データ テーブルを更新します。 # PlayerStatsManager とレベル内のビルボード。 AddPoints<public>(Agent:agent, NewPoints:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentPoints := PlayerStatsTable.Points set PlayerStatsMap[Player] = player_stats_table: MakePlayerStatsTable<constructor>(PlayerStatsTable) Points := CurrentPoints + NewPoints else: Print("Unable to record player points") # 指定されたエージェントの StatToAdd に加算し、以下に含まれる両方の統計データ テーブルを更新します。 # PlayerStatsManager とレベル内のビルボード。 AddWin<public>(Agent:agent, NewWins:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentWins := PlayerStatsTable.Wins set PlayerStatsMap[Player] = player_stats_table: MakePlayerStatsTable<constructor>(PlayerStatsTable) Wins := CurrentWins + NewWins else: Print("Unable to record player Wins")
プレイヤー ランキングを作成する
ランキングにプレイヤー データを表示するには、いくつかの処理が必要となります。ビルボードのテキストと、プレイヤー参照の仕掛けのプレイヤーを更新する手段が必要です。また、トップ プレイヤーをランキングで目立たせるために、これらの仕掛けをソートする方法も必要となります。これらの関数には、レベル内の仕掛けを修正するという同様の目的があるため、関数を共通ファイルでグループ化することをお勧めします。
以下の手順に従って、レベル内の仕掛けを更新する関数を作成します。
-
「player_leaderboards.verse」という名前の新しい Verse ファイルを作成します。このファイルには、レベル内ランキングの更新に共通する関数が格納されます。
-
ビルボードのテキストについては、引数を渡すことができるメッセージを使用します。
CurrentPlayer
、Points
、Wins
、すべての型のメッセージ
を受け取り、組み合わせたテキストをメッセージ
として返す、StatsMessage
という名前の新しいメッセージを作成します。# 統計データのビルボードに表示されるメッセージ。 StatsMessage<localizes>(CurrentPlayer:message, Points:message, Wins:message):message= "{CurrentPlayer}:\n{Points}\n{Wins}"
-
さらに 3 つの
メッセージ
変数を追加します (StatsMessage
への各インプットに 1 つ)。PlayerText
メッセージはAgent
、PointsText
メッセージはそのエージェントのポイント、WinsText
メッセージはそのエージェントの勝利を受け取ります。StatsMessage
はこれらすべてからのメッセージを作成し、レベル内のデータを明確に表示します。# 統計データのビルボードに表示されるメッセージ。 StatsMessage<localizes>(CurrentPlayer:message, Points:message, Wins:message):message= "{CurrentPlayer}:\n{Points}\n{Wins}" PlayerText<localizes>(CurrentPlayer:agent):message = "Player {CurrentPlayer}" PointsText<localizes>(Points:int):message = "Total Points {Points}" WinsText<localizes>(Wins:int):message = "{Wins} Total Wins"
-
ビルボードを更新するには、プレイヤーの持続的な統計データのチュートリアルから
UpdateStatsBillboard()
関数を呼び出します。この関数は、Verse の仕掛けとは異なるファイルで定義されているため、追加の引数としてStatsBillboard
を追加し、更新するビルボードを指定する必要があります。# 指定されたビルボードの仕掛けを更新して、指定されたプレイヤーの統計データを表示します。 UpdateStatsBillboard<public>(Player:agent, StatsBillboard:billboard_device):void=
-
最初に、
GetPlayerStats[]
を使用して引数として渡されるプレイヤーの統計データを取得します。別のクラスではなくなるため、player_stats_manager
への参照は不要です。次に、プレイヤーと、そのCurrentPlayerStats
のPoints
とWins
を使用して、新しいStatsMessage
を作成します。最後に、StatsBillboard
のSetText()
を呼び出し、レベル内のビルボード テキストを更新します。完成したUpdateStatsBillboard()
関数は次のようになります。# 指定されたビルボードの仕掛けを更新して、指定されたプレイヤーの統計データを表示します。 UpdateStatsBillboard<public>(Player:agent, StatsBillboard:billboard_device):void= if: CurrentPlayerStats := GetPlayerStats[Player] then: PlayerStatsText := StatsMessage( PlayerText(Player), PointsText(CurrentPlayerStats.Points), WinsText(CurrentPlayerStats.Wins)) StatsBillboard.SetText(PlayerStatsText)
ベスト プレイヤーをソートして表示する
次のステップに進む前に、これらのビルボードをどのようにソートするか検討しておくことが重要です。最も多くのポイントを獲得したプレイヤーと、最も勝利を収めたプレイヤーのどちらをトップにしますか。異なる統計データでソートする場合はどうしますか。こうした問題に対処する方法が必要となりますが、その解決策が ソート アルゴリズム です。ソート アルゴリズムと比較関数を使用することで、ソート条件を指定できます。次に、ビルボードとプレイヤー参照をソートして、体験内にトップ プレイヤーを表示できます。この例では マージ ソート アルゴリズムを使用していますが、独自のアルゴリズムを自由に実装できます。
以下の手順に従って比較とソートをビルボードに追加し、レベル内の仕掛けの更新を完了します。
-
player_stats_table
ファイルに戻り、各統計データの比較関数を定義します。各関数はLeft
およびRight
のplayer_and_stats
構造体を受け取り、特定の統計データに基づいてそれらを比較します。これらの関数には<decides><transacts>
モディファイヤが含まれるため、比較が失敗すると関数も失敗します。 たとえば、Left
がRight
よりも少ない場合です。MorePointsComparison()
という名前の新しい関数を player_stats_table.verseファイルに追加します。 この関数は、
Left.Pointsが
Right.Pointsよりも多いかどうかを確認し、少ない場合は失敗します。 成功した場合は、
Left` を返します。# Left のポイント数が Right よりも多い場合、Left を返します。 MorePointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:Left= Left.StatsTable.Points > Right.StatsTable.Points 左
-
この関数を 3 回コピーします (1 つは少ないポイント数の比較用、2 つは勝利の比較用)。比較関数は次のようになります。
# Left のポイント数が Right よりも多い場合、Left を返します。 MorePointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points > Right.StatsTable.Points 左 # Left のポイント数が Right よりも少ない場合、Left を返します。 LessPointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points < Right.StatsTable.Points 左 # Left のポディウム数が Right よりも多い場合、Left を返します。 MorePodiumsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points > Right.StatsTable.Points 左 # Left のポディウム数が Right よりも少ない場合、Left を返します。 LessPodiumsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points < Right.StatsTable.Points 左
-
マージ ソート アルゴリズムを追加します。これを別のファイルまたはモジュールに配置し、指定されたテスト ファイルでアルゴリズムをテストできます。
-
player_leaderboards
に戻り、「UpdateStatsBillboards()
」という新しい関数を追加します。この関数は、さまざまなエージェントとさまざまなビルボードを受け取り、それらをソートして、UpdateStatsBillboard()
を呼び出し、レベル内の各ビルボードを更新します。# 各プレイヤーが保有する # ライフタイム ポイント数に基づいてソートし、統計データのビルボードを更新します。 UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void=
-
UpdateStatsBillboards()
で、PlayerAndStatsArray
と呼ばれる、新しい配列変数のplayer_and_stats
を初期化します。for
式の結果と等しくなるように設定します。そのfor
式では、エージェント
ごとに、そのエージェント
のプレイヤー
を受け取り、GetPlayerStats[]
を使用してそのplayer_stats_table
を取得します。 次に、プレイヤー
とその統計データ テーブルから作成されたplayer_and_stats
構造体を返します。UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void= var PlayerAndStatsArray:[]player_and_stats = for: Agent:Players Player := player[Agent] PlayerStats := GetPlayerStats[Player] do: player_and_stats: Player := Player StatsTable := PlayerStats
-
PlayerAndStatsArray
をソートするには、MergeSort()
を呼び出した結果への新しい変数「SortedPlayersAndStats
」を初期化し、配列とMorePointsComparison
を渡します。for
式でソートした後、SortedPlayerAndStats
で各要素をイテレートし、変数「PlayerIndex
」に要素インデックスを格納します。PlayerIndex
を使用してStatsBillboards
配列にインデックスを作成します。次に、UpdateStatsBillboard
を呼び出して、更新するプレイヤーとビルボードを渡します。完成したUpdateStatsBillboard()
関数は次のようになります。# 各プレイヤーが保有する # ライフタイム ポイント数に基づいてソートし、統計データのビルボードを更新します。 UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void= var PlayerAndStatsArray:[]player_and_stats = for: Agent:Players Player := player[Agent] PlayerStats := GetPlayerStats[Player] do: player_and_stats: Player := Player StatsTable := PlayerStats # 合計ポイント数に基づいてプレイヤーを比較およびソートします。 # これがロビー内の「ベスト」プレイヤーの総合的な評価基準です。体験のニーズに合わせて # 比較関数を変更できます。 SortedPlayersAndStats := SortingAlgorithms.MergeSort( PlayerAndStatsArray, MorePointsComparison) for: PlayerIndex -> PlayerAndStats : SortedPlayersAndStats StatsBillboard := StatsBillboards[PlayerIndex] do: UpdateStatsBillboard(PlayerAndStats.Player, StatsBillboard)
-
プレイヤー参照を更新するには、
UpdatePlayerReferences()
という名前の非常に類似した関数を使用します。この関数は、ビルボードの代わりにplayer_reference_device
の配列を受け取ります。また、最後にUpdateStatsBillboard()
を呼び出すのではなく、各プレイヤーのプレイヤー参照の仕掛けでRegister()
を呼び出します。UpdateStatsBillboard()
のコードを、上記の変更点を含む新しい関数「UpdatePlayerReferences()
」にコピーします。完成したUpdatePlayerReferences()
関数は次のようになります。# 各プレイヤーが保有する # ライフタイム ポイント数に基づいてソートし、プレイヤー参照の仕掛けを更新します。 UpdatePlayerReferences<public>(Players:[]player, PlayerReferences:[]player_reference_device):void= var PlayerAndStatsArray:[]player_and_stats = for: Agent:Players Player := player[Agent] PlayerStats := GetPlayerStats[Player] do: player_and_stats: Player := Player StatsTable := PlayerStats # 合計ポイント数に基づいてプレイヤーを比較およびソートします。 # これがロビー内の「ベスト」プレイヤーの総合的な評価基準です。体験のニーズに合わせて # 比較関数を変更できます。 SortedPlayersAndStats := SortingAlgorithms.MergeSort( PlayerAndStatsArray, MorePointsComparison) for: PlayerIndex -> PlayerAndStats : SortedPlayersAndStats PlayerReference := PlayerReferences[PlayerIndex] do: PlayerReference.Register(PlayerAndStats.Player)
レベル内のプレイヤー ランキング
すべての設定が完了したら、プレイヤーを表示しましょう。プレイヤーがボタンを操作したときにポイントを付与する仕掛けを作成し、ベスト プレイヤーが前に出るようにプレイヤー参照とビルボードをソートします。以下の手順に従って、レベル内のランキングをテストする Verse の仕掛けを作成します。
-
player_leaderboards_example という名前の新しい Verse の仕掛けを作成します。手順については、「Verse を使用して独自の仕掛けを作成する」を参照してください。
-
player_leaderboards_example class
定義の先頭に、以下のフィールドを追加します。-
PlayerReferences
という名前のプレイヤー参照の仕掛けの編集可能な配列。これらは、レースにおける各プレイヤーのビジュアル イメージとなります。# 各プレイヤーのビジュアル イメージ。 @editable PlayerReferences:[]player_reference_device = array{}
-
Leaderboards
という名前のビルボードの仕掛けの編集可能な配列。レベル内のビルボードに各プレイヤーの統計データを表示します。# 各プレイヤーの統計データを表示するビルボード。 @editable Leaderboards:[]billboard_device = array{}
-
RaceManager
という名前の編集可能なレース マネージャーの仕掛け。プレイヤーがレースを終えた時間を把握するため、レース マネージャーのイベントをサブスクライブします。# プレイヤーがレースを終えた時間、および優勝となるプレイヤーを追跡します。 @editable RaceManager:race_manager_device = race_manager_device{}
-
PlacementRequiredForWin
という名前の編集可能な整数。これは、プレイヤーが勝利するために獲得しなければならない順位です。# 勝利するには、プレイヤーの順位がこれ以下になる必要があります。 @editable PlacementRequiredForWin:int = 1
-
PointsPerPlace
という名前の編集可能な整数の配列。各プレイヤーが順位に基づいて獲得したポイント数です。# 各順位のプレイヤーが獲得するポイント数。 # これを調整し、順位に基づいてプレイヤーに必要な点数を # 付与します。 @editable PointsPerPlace:[]int = array{5, 3, 1}
-
CurrentFinishOrder
という名前の整数変数。これは、最近レースを終えたプレイヤーの順位です。# レースを終えたばかりのプレイヤーの順位。 # 最初にレースを終えた 3 人のプレイヤーに勝利が与えられます。 var CurrentFinishOrder:int = 0
-
順位に基づいた統計データを付与する
プレイヤーがレースを終えたとき、その順位に基づいてプレイヤーの統計データを更新することができます。良い順位のプレイヤーにより多くのポイントを付与し、最も良い順位のプレイヤーに勝利を付与します。
次の手順に従って、プレイヤーがレースを終えたときに統計データを付与します。
-
これを処理するため、新しい関数「
RecordPlayerFinish()
」をplayer_leaderboards_example
クラス定義に追加します。この関数は、プレイヤーをパラメータとして受け取り、統計データを付与します。# プレイヤーがレースを終えたら、順位に基づいてポイントを付与し、 # その順位が PlacementRequiredForWin を上回る場合に勝利を付与します。 RecordPlayerFinish(Player:agent):void=
-
RecordPlayerFinish()
では、PlayerFinishOrder
と呼ばれる新しい整数
でCurrentFinishOrder
の最新値を取得することで、このプレイヤーの順位を受け取ります。次にレースを終えたプレイヤーが同じ順位とならないよう、CurrentFinishOrder
を増やしていきます。RecordPlayerFinish(Player:agent):void= PlayerFinishOrder:int = CurrentFinishOrder set CurrentFinishOrder += 1
-
次はいよいよ、統計データの付与です。
if
式で、このプレイヤーに付与するポイント数を確認するには、PlayerFinishOrder
を使用してPointsPerPlace
配列にインデックスを作成します。次に、AddPoints()
を呼び出し、そのプレイヤーにそのポイント数を付与します。set CurrentFinishOrder += 1 if: PointsToAward := PointsPerPlace[PlayerFinishOrder] then: AddPoints(Player, PointsToAward)
-
プレイヤーの順位が勝利の条件を満たしていれば、その統計データ テーブルに勝利を記録する必要があります。別の
if
式で、PlayerFinishOrder
がPlacementRequiredToWin
よりも良いことを確認します。その場合、AddWin()
を呼び出し、プレイヤーに勝利を付与します。完成したRecordPlayerFinish()
関数は次のようになります。# プレイヤーがレースを終えたら、順位に基づいてポイントを付与し、 # その順位が PlacementRequiredForWin を上回る場合に勝利を付与します。 RecordPlayerFinish(Player:agent):void= PlayerFinishOrder:int = CurrentFinishOrder set CurrentFinishOrder += 1 if: PointsToAward := PointsPerPlace[PlayerFinishOrder] then: AddPoints(Player, PointsToAward) # プレイヤーの最終順位が PlacementRequiredToWin と同じかそれよりも良い場合、 # プレイヤーに勝利を付与し、その player_stats_table に勝利を記録します。 if: PlayerFinishOrder < PlacementRequiredForWin then: AddWin(Player, 1)
プレイヤーがレースを終えるまで待つ
これで統計データを記録する準備が整いました。次は、プレイヤーがレースを終えた時間を把握し、統計データを更新する必要があります。そのために、レース マネージャーの RaceCompletedEvent()
をリッスンします。このイベントは、プレイヤーがレースを終えるたびに起動するため、非同期関数で継続的にリッスンする必要があります。
-
新しい関数「
WaitForPlayerToFinishRace()
」をplayer_leaderboards_example
クラス定義に追加します。この関数はプレイヤーを受け取り、そのプレイヤーがレースを終えるまで待ちます。# プレイヤーがレースを終えたら、統計データ テーブルに完了を記録します。 WaitForPlayerToFinishRace(Player:agent)<suspends>:void=
-
WaitForPlayerToFinishRace()
では、レース
式で、2 つのループを開始します。最初のループは、プレイヤーがレースを終えるまで待ちます。次のループは、プレイヤーがレースを終える前にセッションから離脱した場合に発生した状況に対処します。プレイヤーが離脱した場合、ループを永久に稼働させることを避けるため、そうした状況から抜け出す手段が必要となります。# プレイヤーがレースを終えたら、統計データ テーブルに完了を記録します。 WaitForPlayerToFinishRace(Player:agent)<suspends>:void= race: # このプレイヤーがレースを終えるまで待ち、完了を記録します。 loop: # このプレイヤーがゲームから退出するまで待ちます。 loop:
-
最初のループでは、
RaceManager.RaceCompletedEvent
を待ち、FinishingPlayer
という名前の変数にその結果を格納します。このイベントは、プレイヤーがレースを終えるたびに起動するため、格納したプレイヤーが監視していたプレイヤーであることを確認する必要があります。FinishingPlayer
と、このループが監視しているプレイヤーを比較します。両者が同一であれば、そのプレイヤーをRecordPlayerFinish()
に渡して、ループから離脱します。# このプレイヤーがレースを終えるまで待ち、完了を記録します。 loop: FinishingPlayer := RaceManager.RaceCompletedEvent.Await() if: FinishingPlayer = Player then: RecordPlayerFinish(Player) break
-
次のループでは、プレイ空間イベント「
PlayerRemovedEvent()
」を待ちます。先ほどと同じように、離脱直後のプレイヤーを受け取り、変数「LeavingPlayer
」に格納します。離脱したプレイヤーが、このループが待っているプレイヤーである場合は、ループから離脱します。完成したWaitForPlayerToFinishRace()
関数は次のようになります。# プレイヤーがレースを終えたら、統計データ テーブルに完了を記録します。 WaitForPlayerToFinishRace(Player:agent)<suspends>:void= race: # このプレイヤーがレースを終えるまで待ち、完了を記録します。 loop: FinishingPlayer := RaceManager.RaceCompletedEvent.Await() if: FinishingPlayer = Player then: RecordPlayerFinish(Player) break # このプレイヤーがゲームから退出するまで待ちます。 loop: LeavingPlayer := GetPlayspace().PlayerRemovedEvent().Await() if: LeavingPlayer = Player then: break
すべてをリンクする
関数の設定が完了したら、仕掛けに接続して、レースを始めましょう。
次の手順に従って、ロジックを仕掛けに接続します。
-
OnBegin()
では、GetPlayers()
を使用して、プレイ空間内のすべてのプレイヤーを取得します。この配列をInitializeAllPlayerStats()
に渡して、各プレイヤーのplayer_stats_tables
を設定します。# 実行中のゲームで仕掛けが開始されたときに実行します OnBegin<override>()<suspends>:void= # 現在のレースに参加しているプレイヤーを取得し、各プレイヤーの player_stat_table # を作成します。 Players := GetPlayspace().GetPlayers() InitializeAllPlayerStats(Players)
-
UpdateStatsBillboards()
を呼び出し、プレイヤー
およびランキング
配列を渡して、各プレイヤーの最新データでレベル内ビルボードを更新します。次に、UpdatePlayerReferences()
を呼び出し、プレイヤーに合わせてレベル内参照を更新します。最後に、for
式で、各プレイヤーのWaitForPlayerToFinishRace()
関数をスポーンします。完成したOnBegin()
関数は次のようになります。# 実行中のゲームで仕掛けが開始されたときに実行します OnBegin<override>()<suspends>:void= # 現在のレースに参加しているプレイヤーを取得し、各プレイヤーの player_stat_table # を作成します。 Players := GetPlayspace().GetPlayers() InitializeAllPlayerStats(Players) UpdateStatsBillboards(Players, Leaderboards) UpdatePlayerReferences(Players, PlayerReferences) # すべてのプレイヤーがレースを終えるまで待ちます。 for: Player:Players do: spawn{WaitForPlayerToFinishRace(Player)}
-
コードを保存してコンパイルします。
player_leaderboards_example の仕掛けをレベル内にドラッグします。PlayerReferences 配列にプレイヤー参照を割り当て、順位を記録します。第 1 インデックスの仕掛けは 1 位のプレイヤーのプレイヤー参照に対応し、第 2 インデックスは 2 位のプレイヤーに対応している必要があります (以下同じ)。ランキングについても同様に対応し、プレイヤー参照の仕掛けとの整合性を確保します。レース マネージャーの仕掛けにも忘れずに割り当てます。
永続可能ランキングをテストする
持続データは編集セッションでテストできますが、セッションを終了して再起動すると、そのデータはリセットされます。セッションをまたいでデータを持続させるには、プレイテスト セッション を起動し、島設定 の特定の設定を変更する必要があります。 編集セッションとプレイテスト セッションの両方で持続データをテストできるように島を設定する方法については、「持続データでテストする」を参照のうえ、島設定の一部の設定を変更してください。編集セッションとプレイテスト セッションの両方で持続データをテストできるように島を設定する方法については、「持続データでテストする」を参照してください。
セッションを設定後、レベルをプレイテストする場合、レースを終了するプレイヤーはその順位に基づいてポイントが与えられる必要があります。上位のプレイヤーは勝利したものとされ、統計データはプレイ セッションをまたいで持続する必要があります。プレイヤーとその統計データは、最もポイントが多いプレイヤーが 1 位に表示されるようにソートされる必要があります。
応用編
このガイドを完了し、レベル内での持続型のプレイヤー統計データを表示するランキングを作成する方法を習得しました。また、ランキングをソートして更新し、ベストプレイヤーが誰なのか、誰もが分かるようにする方法も習得しました。このチュートリアルを独自の体験に対応させ、最高の体験を披露しましょう。
完全なコード
player_stats_table.verse
# このファイルは、持続可能なプレイヤー統計データのコレクションである、player_stats_table を定義します。
# また、プレイヤーを並び変えるために各統計データで統計データ テーブルを比較する
# 関数も含みます。
using { /Fortnite.com/Devices}
using { /Verse.org/Simulation}
using { /UnrealEngine.com/Temporary/Diagnostics}
# プレイヤーとその統計データを引数として渡すための構造体。
player_and_stats<public> := struct:
Player<public>:player
StatsTable<public>:player_stats_table
# プレイヤーごとに異なる持続可能な統計データを追跡します。
player_stats_table<public>:= class<final><persistable>:
# 最新の統計データ テーブルのバージョン。
Version<public>:int = 0
# プレイヤーのポイント。
Points<public>:int = 0
# プレイヤーの勝利数。
Wins<public>:int = 0
# Left のポイント数が Right よりも多い場合、Left を返します。
MorePointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points > Right.StatsTable.Points
左
# Left のポイント数が Right よりも少ない場合、Left を返します。
LessPointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points < Right.StatsTable.Points
左
# Left の勝利数が Right よりも多い場合、Left を返します。
MoreWinsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points > Right.StatsTable.Points
左
# Left の勝利数が Right よりも少ない場合、Left を返します。
LessWinsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points < Right.StatsTable.Points
左
# Left の BestLapTime が Right よりも遅い場合、Left を返します。
# ラップタイムは短い方が良いため、他の統計データとは逆になっていることに留意してください。
SlowerLapTimeComparison(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points > Right.StatsTable.Points
左
# Left の BestLapTime が Right よりも早い場合、Left を返します。
# ラップタイムは短い方が良いため、他の統計データとは逆になっていることに留意してください。
FasterLapTimeComparison(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points < Right.StatsTable.Points
左
# 前回の player_stats_table と同じ値を使用して、新しい player_stats_table を作成します。
MakePlayerStatsTable<constructor>(OldTable:player_stats_table)<transacts> := player_stats_table:
Version := OldTable.Version
Points := OldTable.Points
Wins := OldTable.Wins
# プレイヤーをそれぞれのプレイヤー統計データのテーブルにマッピングします。
var PlayerStatsMap:weak_map(player, player_stats_table) = map{}
player_leaderboards.verse
# このファイルには、島上のビルボード、プレイヤー参照、UI を更新し
# プレイヤー統計データのテーブルからプレイヤーの統計データを表示するためのコードが含まれます。また、プレイヤー統計データのテーブルへの勝利数とポイントの
# 追加を処理します。
using { /Fortnite.com/Devices}
using { /Verse.org/Simulation}
using { PlayerStatistics }
# 統計データのビルボードに表示されるメッセージ。
StatsMessage<localizes>(CurrentPlayer:message, Points:message, Wins:message):message=
"{CurrentPlayer}:\n{Points}\n{Wins}"
PlayerText<localizes>(CurrentPlayer:agent):message = "Player {CurrentPlayer}"
PointsText<localizes>(Points:int):message = "Total Points {Points}"
WinsText<localizes>(Wins:int):message = "{Wins} Total Wins"
# 指定されたビルボードの仕掛けを更新して、指定されたプレイヤーの統計データを表示します。
UpdateStatsBillboard<public>(Player:agent, StatsBillboard:billboard_device):void=
if:
CurrentPlayerStats := GetPlayerStats[Player]
then:
PlayerStatsText := StatsMessage(
PlayerText(Player),
PointsText(CurrentPlayerStats.Points),
WinsText(CurrentPlayerStats.Wins))
StatsBillboard.SetText(PlayerStatsText)
# 各プレイヤーが保有する
# ライフタイム ポイント数に基づいてソートし、統計データのビルボードを更新します。
UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void=
var PlayerAndStatsArray:[]player_and_stats =
for:
Agent:Players
Player := player[Agent]
PlayerStats := GetPlayerStats[Player]
do:
player_and_stats:
Player := Player
StatsTable := PlayerStats
# 合計ポイント数に基づいてプレイヤーを比較およびソートします。
# これがロビー内の「ベスト」プレイヤーの総合的な評価基準です。体験のニーズに合わせて
# 比較関数を変更できます。
SortedPlayersAndStats := SortingAlgorithms.MergeSort(
MorePointsComparison
PlayerAndStatsArray)
for:
PlayerIndex -> PlayerAndStats : SortedPlayersAndStats
StatsBillboard := StatsBillboards[PlayerIndex]
do:
UpdateStatsBillboard(PlayerAndStats.Player, StatsBillboard)
# 各プレイヤーが保有する
# ライフタイム ポイント数に基づいてソートし、プレイヤー参照の仕掛けを更新します。
UpdatePlayerReferences<public>(Players:[]player, PlayerReferences:[]player_reference_device):void=
var PlayerAndStatsArray:[]player_and_stats =
for:
Agent:Players
Player := player[Agent]
PlayerStats := GetPlayerStats[Player]
do:
player_and_stats:
Player := Player
StatsTable := PlayerStats
# 合計ポイント数に基づいてプレイヤーを比較およびソートします。
# これがロビー内の「ベスト」プレイヤーの総合的な評価基準です。体験のニーズに合わせて
# 比較関数を変更できます。
SortedPlayersAndStats := SortingAlgorithms.MergeSort(
MorePointsComparison
PlayerAndStatsArray)
for:
PlayerIndex -> PlayerAndStats : SortedPlayersAndStats
PlayerReference := PlayerReferences[PlayerIndex]
do:
PlayerReference.Register(PlayerAndStats.Player)
player_stats_manager.verse
# このファイルは、各プレイヤーの player_stats_tables を初期化、更新、返すためのコードを
# 処理します。また、統計データの更新に使用する抽象 stat_type クラス、
# 統計データの表示に使用する StatType モジュールを定義します。
using { /Fortnite.com/Devices}
using { /Verse.org/Simulation}
using { /UnrealEngine.com/Temporary/Diagnostics}
# 指定されたエージェントの player_stats_table を返します。
GetPlayerStats<public>(Agent:agent)<decides><transacts>:player_stats_table=
var PlayerStats:player_stats_table = player_stats_table{}
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
set PlayerStats = MakePlayerStatsTable(PlayerStatsTable)
PlayerStats
# 現在の全プレイヤーの統計データを初期化します。
InitializeAllPlayerStats<public>(Players:[]player):void =
for (Player : Players):
InitializePlayerStat(Player)
# 指定されたプレイヤーの統計データを初期化します。
InitializePlayerStat<public>(Player:player):void=
if:
not PlayerStatsMap[Player]
set PlayerStatsMap[Player] = player_stats_table{}
else:
Print("Unable to initialize player stats")
# 指定されたエージェントの StatToAdd に加算し、以下に含まれる両方の統計データ テーブルを更新します。
# PlayerStatsManager とレベル内のビルボード。
AddPoints<public>(Agent:agent, NewPoints:int):void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
CurrentPoints := PlayerStatsTable.Points
set PlayerStatsMap[Player] = player_stats_table:
MakePlayerStatsTable<constructor>(PlayerStatsTable)
Points := CurrentPoints + NewPoints
else:
Print("Unable to record player points")
# 指定されたエージェントの StatToAdd に加算し、以下に含まれる両方の統計データ テーブルを更新します。
# PlayerStatsManager とレベル内のビルボード。
AddWin<public>(Agent:agent, NewWins:int):void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
CurrentWins := PlayerStatsTable.Wins
set PlayerStatsMap[Player] = player_stats_table:
MakePlayerStatsTable<constructor>(PlayerStatsTable)
Wins := CurrentWins + NewWins
else:
Print("Unable to record player Wins")
player_leaderboards_example.verse
using { /Fortnite.com/Devices}
using { /Verse.org/Simulation}
using { /UnrealEngine.com/Temporary/Diagnostics}
using { PlayerStatistics }
using { PlayerLeaderboard }
# Verse の仕掛けの作成方法については、https://dev.epicgames.com/documentation/ja-jp/uefn/create-your-own-device-in-verse を参照してください。
# レベルに配置できる、Verse で作成したクリエイティブの仕掛け
player_leaderboards_example := class(creative_device):
# 各プレイヤーのビジュアル イメージ。
@editable
PlayerReferences:[]player_reference_device = array{}
# 各プレイヤーの統計データを表示するビルボード。
@editable
Leaderboards:[]billboard_device = array{}
# プレイヤーがレースを終えた時間、および優勝となるプレイヤーを追跡します。
@editable
RaceManager:race_manager_device = race_manager_device{}
# 勝利するには、プレイヤーの順位がこれ以下になる必要があります。
@editable
PlacementRequiredForWin:int = 1
# 各順位のプレイヤーが獲得するポイント数。
# これを調整し、順位に基づいてプレイヤーに必要な点数を
# 付与します。
@editable
PointsPerPlace:[]int = array{5, 3, 1}
# レースを終えたばかりのプレイヤーの順位。
# 最初にレースを終えた 3 人のプレイヤーに勝利が与えられます。
var CurrentFinishOrder:int = 0
# 実行中のゲームで仕掛けが開始されたときに実行します
OnBegin<override>()<suspends>:void=
# 現在のレースに参加しているプレイヤーを取得し、各プレイヤーの player_stat_table
# を作成します。
Players := GetPlayspace().GetPlayers()
InitializeAllPlayerStats(Players)
UpdateStatsBillboards(Players, Leaderboards)
UpdatePlayerReferences(Players, PlayerReferences)
# すべてのプレイヤーがレースを終えるまで待ちます。
for:
Player:Players
do:
spawn{WaitForPlayerToFinishRace(Player)}
# プレイヤーがレースを終えたら、統計データ テーブルに完了を記録します。
WaitForPlayerToFinishRace(Player:agent)<suspends>:void=
race:
# このプレイヤーがレースを終えるまで待ち、完了を記録します。
loop:
FinishingPlayer := RaceManager.RaceCompletedEvent.Await()
if:
FinishingPlayer = Player
then:
RecordPlayerFinish(Player)
break
# このプレイヤーがゲームから退出するまで待ちます。
loop:
LeavingPlayer := GetPlayspace().PlayerRemovedEvent().Await()
if:
LeavingPlayer = Player
then:
break
# プレイヤーがレースを終えたら、順位に基づいてポイントを付与し、
# その順位が PlacementRequiredForWin を上回る場合に勝利を付与します。
RecordPlayerFinish(Player:agent):void=
PlayerFinishOrder:int = CurrentFinishOrder
set CurrentFinishOrder += 1
if:
PointsToAward := PointsPerPlace[PlayerFinishOrder]
then:
AddPoints(Player, PointsToAward)
# プレイヤーの最終順位が PlacementRequiredToWin と同じかそれよりも良い場合、
# プレイヤーに勝利を付与し、その player_stats_table に勝利を記録します。
if:
PlayerFinishOrder < PlacementRequiredForWin
then:
AddWin(Player, 1)