メインコンテンツまでスキップ

統計データサブシステムを実装する - 統計データを追跡し表示する - (Unreal Engine モジュール)

Last updated on February 4, 2026

注釈:本資料は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 クラスには、以下の機能が用意されています。

  • IdentityPtrABStatsPtr という名前の 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);

統計データを取得する

このセクションでは、プレイヤーの統計データをクエリして取得する機能を実装します。

  1. 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);
  2. 次に、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;
    }
  3. 次に、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);
    }

おめでとうございます!プレイヤーの統計データの取得を実装しました。

統計データを更新する

このセクションでは、プレイヤーの統計データ値を更新する関数を実装します。

  1. 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 = {});
  2. 次に、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;
    }
  3. 次に、UpdateConnectedPlayersStats() 関数を定義します。この関数は、ゲーム内の接続されているすべてのプレイヤーの統計データを更新するラッパー関数です。まず、現在プレイされているゲームモードを判定します。bToResettrue の場合、関数はすべての統計データ値を 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);
    }
  4. 最後に、ゲーム終了時に接続されているプレイヤーの統計データを更新するために、事前定義された 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"));
    }
    }

おめでとうございます!プレイヤーの統計データの更新を実装しました。

リソース