統計データサブシステムを実装する - 統計データを追跡し表示する - (Unreal Engine モジュール)
注釈:本資料はAI技術を用いて翻訳されています。
サブシステムの概要
このチュートリアルでは、AGS Online Subsystem (OSS) を使用して AccelByte Gaming Services (AGS) 統計データを実装する方法を学びます。Byte Wars には、StatsEssentialsSubsystem クラスで定義されたゲームインスタンスサブシステムがあります。このサブシステムは、AGS 統計データ値を操作するためのラッパーとして機能します。
このチュートリアルでは、StatsEssentialsSubsystem クラスのスターターバージョンである StatsEssentialsSubsystem_Starter サブシステムを使用して統計データ値を操作します。
スターターパックの内容
このチュートリアルを進めるために、StatsEssentialsSubsystem_Starter という名前のスターターサブシステムクラスが用意されています。このクラスはリソースセクションで入手でき、以下のファイルで構成されています。
- ヘッダーファイル:
/Source/AccelByteWars/TutorialModules/Storage/StatisticsEssentials/StatsEssentialsSubsystem_Starter.h - CPP ファイル:
/Source/AccelByteWars/TutorialModules/Storage/StatisticsEssentials/StatsEssentialsSubsystem_Starter.cpp
StatsEssentialsSubsystem_Starter クラスには、以下の機能が用意されています。
-
IdentityPtrとABStatsPtrという名前の AGS OSS インターフェース宣言。これらのインターフェースを使用して、後で統計データ関連の機能を実装します。private:
// ...
IOnlineIdentityPtr IdentityPtr;
FOnlineStatisticAccelBytePtr ABStatsPtr; -
内部使用のためのマルチキャストデリゲート。ウィジェットは各関数を呼び出す際にデリゲートを渡しますが、インターフェースのプログラム方法の制限により、このデリゲートはこれらのデリゲート変数にバインドされます。デリゲートがクラスメンバーとして保存されていない場合、呼び出し時に
nullptrとなり、ゲームがクラッシュします。これはOnlineStatisticInterfaceAccelByteの既存のデリゲートを使用するため、新しいデリゲートを宣言する必要はありません。private:
// ...
FOnlineStatsQueryUsersStatsComplete OnQueryUsersStatsComplete;
FOnlineStatsUpdateStatsComplete OnUpdateStatsComplete;
FOnUpdateMultipleUserStatItemsComplete OnServerUpdateStatsComplete; -
ゲーム終了時に呼び出される空の関数。この関数を使用して、後でプレイヤーの統計データを更新します。
private:
UFUNCTION()
void UpdateConnectedPlayersStatsOnGameEnds(const FString& Reason, bool bIsExpected);
統計データを取得する
このセクションでは、プレイヤーの統計データをクエリして取得する機能を実装します。
-
StatsEssentialsSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
bool QueryLocalUserStats(
const int32 LocalUserNum,
const TArray<FString>& StatNames,
const FOnlineStatsQueryUsersStatsComplete& OnComplete);
// ...
bool QueryUserStats(
const int32 LocalUserNum,
const TArray<FUniqueNetIdRef>& StatsUsers,
const TArray<FString>& StatNames,
const FOnlineStatsQueryUsersStatsComplete& OnComplete); -
次に、
StatsEssentialsSubsystem_Starterクラスの CPP ファイルを開き、QueryUserStats()を定義します。この関数は、指定されたプレイヤー ID に基づいて統計データをクエリします。クエリが完了すると、指定されたコールバックデリゲートが実行されます。bool UStatsEssentialsSubsystem_Starter::QueryUserStats(
const int32 LocalUserNum,
const TArray<FUniqueNetIdRef>& StatsUsers,
const TArray<FString>& StatNames,
const FOnlineStatsQueryUsersStatsComplete& OnComplete)
{
// AB OSS limitation: delegate must be a class member
if (OnQueryUsersStatsComplete.IsBound())
{
return false;
}
OnQueryUsersStatsComplete = FOnlineStatsQueryUsersStatsComplete::CreateWeakLambda(this, [OnComplete, this](const FOnlineError& ResultState, const TArray<TSharedRef<const FOnlineStatsUserStats>>& UsersStatsResult)
{
OnComplete.ExecuteIfBound(ResultState, UsersStatsResult);
OnQueryUsersStatsComplete.Unbind();
});
const FUniqueNetIdRef LocalUserId = IdentityPtr->GetUniquePlayerId(LocalUserNum).ToSharedRef();
ABStatsPtr->QueryStats(LocalUserId, StatsUsers, StatNames, OnQueryUsersStatsComplete);
return true;
} -
次に、
QueryLocalUserStats()関数を定義します。この関数は、ローカルプレイヤー ID をQueryUserStats()関数に渡すことで、ローカルプレイヤーをクエリします。bool UStatsEssentialsSubsystem_Starter::QueryLocalUserStats(
const int32 LocalUserNum,
const TArray<FString>& StatNames,
const FOnlineStatsQueryUsersStatsComplete& OnComplete)
{
const FUniqueNetIdRef LocalUserId = IdentityPtr->GetUniquePlayerId(LocalUserNum).ToSharedRef();
return QueryUserStats(LocalUserNum, {LocalUserId}, StatNames, OnComplete);
}
おめでとうございます!プレイヤーの統計データの取得を実装しました。
統計データを更新する
このセクションでは、プレイヤーの統計データ値を更新する関数を実装します。
-
StatsEssentialsSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
bool UpdateUsersStats(
const int32 LocalUserNum,
const TArray<FOnlineStatsUserUpdatedStats>& UpdatedUsersStats,
const FOnlineStatsUpdateStatsComplete& OnCompleteClient = {},
const FOnUpdateMultipleUserStatItemsComplete& OnCompleteServer = {});
// ...
bool UpdateConnectedPlayersStats(
const int32 LocalUserNum,
const bool bToReset = false,
const FOnlineStatsUpdateStatsComplete& OnCompleteClient = {},
const FOnUpdateMultipleUserStatItemsComplete& OnCompleteServer = {}); -
次に、
StatsEssentialsSubsystem_Starterクラスの CPP ファイルを開き、UpdateUsersStats()関数を定義します。この関数は、現在のインスタンスが専用サーバーかどうかに基づいてプレイヤーの統計データを更新し、対応するコールバックを使用します。この関数の戻り値は、非同期タスクが正常に開始されたかどうかを示します。bool UStatsEssentialsSubsystem_Starter::UpdateUsersStats(const int32 LocalUserNum,
const TArray<FOnlineStatsUserUpdatedStats>& UpdatedUsersStats,
const FOnlineStatsUpdateStatsComplete& OnCompleteClient,
const FOnUpdateMultipleUserStatItemsComplete& OnCompleteServer)
{
// AB OSS limitation: delegate must be a class member
if (OnUpdateStatsComplete.IsBound() || OnServerUpdateStatsComplete.IsBound())
{
return false;
}
if (IsRunningDedicatedServer())
{
OnServerUpdateStatsComplete = FOnUpdateMultipleUserStatItemsComplete::CreateWeakLambda(
this, [OnCompleteServer, this](const FOnlineError& ResultState, const TArray<FAccelByteModelsUpdateUserStatItemsResponse>& Result)
{
OnCompleteServer.ExecuteIfBound(ResultState, Result);
OnServerUpdateStatsComplete.Unbind();
});
ABStatsPtr->UpdateStats(LocalUserNum, UpdatedUsersStats, OnServerUpdateStatsComplete);
}
else
{
OnUpdateStatsComplete = FOnlineStatsUpdateStatsComplete::CreateWeakLambda(
this, [OnCompleteClient, this](const FOnlineError& ResultState)
{
OnCompleteClient.ExecuteIfBound(ResultState);
OnUpdateStatsComplete.Unbind();
});
const FUniqueNetIdRef LocalUserId = IdentityPtr->GetUniquePlayerId(LocalUserNum).ToSharedRef();
ABStatsPtr->UpdateStats(LocalUserId, UpdatedUsersStats, OnUpdateStatsComplete);
}
return true;
} -
次に、
UpdateConnectedPlayersStats()関数を定義します。この関数は、ゲーム内の接続されているすべてのプレイヤーの統計データを更新するラッパー関数です。まず、現在プレイされているゲームモードを判定します。bToResetがtrueの場合、関数はすべての統計データ値を 0 にリセットします。それ以外の場合は、接続されているプレイヤーをループして、そのデータを統計データオブジェクトに格納します。最後に、関数はすべての統計データオブジェクトを収集し、それらをUpdateUsersStats()に渡して、接続されているプレイヤーの統計データを更新します。備考以下の関数は、Byte Wars で利用可能なすべての統計データを変更する方法を示しています。ただし、このモジュールでは、プレイヤーの最高スコアの統計データのみを設定します。他の統計データ値は更新されません。
bool UStatsEssentialsSubsystem_Starter::UpdateConnectedPlayersStats(
const int32 LocalUserNum,
const bool bToReset,
const FOnlineStatsUpdateStatsComplete& OnCompleteClient,
const FOnUpdateMultipleUserStatItemsComplete& OnCompleteServer)
{
UE_LOG_STATSESSENTIALS(Log, TEXT("Updating connected player stats to %s"), bToReset ? TEXT("reset") : TEXT("new value"));
UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
if (!GameInstance)
{
UE_LOG_STATSESSENTIALS(Warning, TEXT("Failed to update players' statistics. Game instance is invalid."));
return false;
}
AAccelByteWarsGameState* GameState = Cast<AAccelByteWarsGameState>(GetWorld()->GetGameState());
if (!GameState)
{
UE_LOG_STATSESSENTIALS(Warning, TEXT("Failed to update players' statistics. Game state is invalid."));
return false;
}
// Get valid game stats data.
const bool bIsLocalGame = GameState->GameSetup.NetworkType == EGameModeNetworkType::LOCAL;
bool bIsGameStatsDataValid = false;
FGameStatsData GameStatsData{};
if (bIsLocalGame)
{
bIsGameStatsDataValid = GameInstance->GetGameStatsDataById(GAMESTATS_GameModeSinglePlayer, GameStatsData);
}
else if (GameState->GameSetup.GameModeType == EGameModeType::FFA)
{
bIsGameStatsDataValid = GameInstance->GetGameStatsDataById(GAMESTATS_GameModeElimination, GameStatsData);
}
else if (GameState->GameSetup.GameModeType == EGameModeType::TDM)
{
bIsGameStatsDataValid = GameInstance->GetGameStatsDataById(GAMESTATS_GameModeTeamDeathmatch, GameStatsData);
}
if (!bIsGameStatsDataValid)
{
UE_LOG_STATSESSENTIALS(Warning, TEXT("Failed to update players' statistics. No statistics data to update."));
return false;
}
// Update players' stats.
TArray<FOnlineStatsUserUpdatedStats> UpdatedUsersStats;
const int32 WinnerTeamId = GameState->GetWinnerTeamId();
for (const TObjectPtr<APlayerState>& PlayerState : GameState->PlayerArray)
{
AAccelByteWarsPlayerState* ABPlayerState = Cast<AAccelByteWarsPlayerState>(PlayerState);
if (!ABPlayerState)
{
UE_LOG_STATSESSENTIALS(Warning, TEXT("Failed to update player's statistics. Player state is invalid."));
continue;
}
const FUniqueNetIdRepl& PlayerUniqueId = PlayerState->GetUniqueId();
if (!PlayerUniqueId.IsValid())
{
UE_LOG_STATSESSENTIALS(Warning, TEXT("Failed to update player's statistics. User ID is invalid."));
continue;
}
FGameplayTeamData TeamData{};
float TeamScore = 0;
int32 TeamTotalLives = 0, TeamTotalKillCount = 0, TeamTotalDeaths = 0;
/* Local gameplay only has one valid account, which is the player who logged in to the game.
* Thus, set the stats based on the highest team data.*/
if (bIsLocalGame)
{
GameState->GetHighestTeamData(TeamScore, TeamTotalLives, TeamTotalKillCount, TeamTotalDeaths);
}
// Each connected player account in online gameplay is valid, so set the stats based on their respective teams.
else
{
GameState->GetTeamDataByTeamId(ABPlayerState->TeamId, TeamData, TeamScore, TeamTotalLives, TeamTotalKillCount, TeamTotalDeaths);
}
/* If the gameplay is local, set the winner status based on if the game ends in a draw or not.
* If the gameplay is online, set the winner status based on whether the team ID matches with the winning team ID.*/
const bool bIsWinner = bIsLocalGame ? WinnerTeamId != INDEX_NONE : TeamData.TeamId == WinnerTeamId;
FOnlineStatsUserUpdatedStats UpdatedUserStats(PlayerUniqueId->AsShared());
// Reset statistics values to zero.
if (bToReset)
{
for (const FString& Code : GameStatsData.GetStatsCodes())
{
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
Code,
FOnlineStatUpdate { 0, FOnlineStatUpdate::EOnlineStatModificationType::Set }
});
}
}
// Update statistics values.
else
{
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
GameStatsData.HighestScoreStats.CodeName,
FOnlineStatUpdate
{
TeamScore,
FOnlineStatUpdate::EOnlineStatModificationType::Largest
}
});
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
GameStatsData.TotalScoreStats.CodeName,
FOnlineStatUpdate
{
TeamScore,
FOnlineStatUpdate::EOnlineStatModificationType::Sum
}
});
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
GameStatsData.MatchesPlayedStats.CodeName,
FOnlineStatUpdate
{
1,
FOnlineStatUpdate::EOnlineStatModificationType::Sum
}
});
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
GameStatsData.MatchesWonStats.CodeName,
FOnlineStatUpdate
{
bIsWinner ? 1 : 0,
FOnlineStatUpdate::EOnlineStatModificationType::Sum
}
});
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
GameStatsData.KillCountStats.CodeName,
FOnlineStatUpdate
{
TeamTotalKillCount,
FOnlineStatUpdate::EOnlineStatModificationType::Sum
}
});
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
GameStatsData.DeathStats.CodeName,
FOnlineStatUpdate
{
TeamTotalDeaths,
FOnlineStatUpdate::EOnlineStatModificationType::Sum
}
});
}
UpdatedUsersStats.Add(UpdatedUserStats);
}
// Update stats
return UpdateUsersStats(LocalUserNum, UpdatedUsersStats, OnCompleteClient, OnCompleteServer);
} -
最後に、ゲーム終了時に接続されているプレイヤーの統計データを更新するために、事前定義された
UpdateConnectedPlayersStatsOnGameEnds()関数を以下のコードに置き換えて、UpdateConnectedPlayersStats()を呼び出します。void UStatsEssentialsSubsystem_Starter::UpdateConnectedPlayersStatsOnGameEnds(const FString& Reason, bool bIsExpected)
{
if (!bIsExpected)
{
UE_LOG_STATSESSENTIALS(Warning, TEXT("Game ends unexpectedly. Aborting update of connected player statistics."));
return;
}
const bool bStarted = UpdateConnectedPlayersStats(0, false);
if (bStarted)
{
UE_LOG_STATSESSENTIALS(Log, TEXT("Update connected player statistics on game ends is started"));
}
else
{
UE_LOG_STATSESSENTIALS(Warning, TEXT("Update connected player statistics on game ends is failed"));
}
}
おめでとうございます!プレイヤーの統計データの更新を実装しました。
リソース
- このチュートリアルセクションで使用されるファイルは、Byte Wars GitHub リポジトリで入手できます。