Skip to main content

Put it all together - Stat tracking and display - (Unity module)

Last updated on December 5, 2024

What's in the Starter Pack

In this tutorial, you will update statistic (stat) values with gameplay data and display them in the Stats Profile Menu.

The StatsHelper_Starter class has been prepared for you as starter that subscribes to events related to changes in Options Menu. It is available in the Resources section and consists of the following file:

  • StatsHelper_Starter.cs: /Assets/Resources/Modules/StatsEssentials/Scripts/StatsHelper_Starter.cs

The helper class has some functionalities provided for you:

  • The AccelByte Gaming Services (AGS) Game SDK library declarations to allow the use of the Statistic and ServerStatistic classes.

    using AccelByte.Core;
    using AccelByte.Models;

  • The onGameOver event definition from GameManager to inform when the current round has finished.

    void Start()
    {
    GameManager.onGameOver += (gameMode, InGameMode, playerStates) => {};
    }

Query and update stats with gameplay data

In this section, you will learn how to query and update the player's highest score stat values. You will get the gameplay data from the .onGameOver event that is triggered when a game ends.

Query and update stats for single player highest score

  1. Open the /Assets/Resources/Modules/StatsEssentials/Scripts/StatsHelper_Starter.cs.

  2. Add a local variable to hold the reference of StatsEssentialsWrapper_Starter and initialize it by using the utility class called TutorialModuleManager to get the module class.

    private StatsEssentialsWrapper_Starter _statsWrapper;

    private void Start()
    {
    _statsWrapper = TutorialModuleManager.Instance.GetModuleClass<StatsEssentialsWrapper_Starter>();
    ...
    }
  3. Define a constant variables to hold the Stat Code of the Highest Score - Single Player statistic you configured in the Admin Portal previously. You will create local variables: currentUserId, to hold the current player's user ID, and currentStatCode, to hold the current target statistic's stat code.

    private const string SingleplayerStatCode = "unity-highestscore-singleplayer";
    private string currentUserId;
    private string currentStatCode;
  4. Create a new callback function to handle the updating of a stat value from the game client's result.

    private void OnUpdateStatsWithClientCompleted(Result<UpdateUserStatItemValueResponse> result)
    {
    if (!result.IsError)
    {
    Debug.Log("Successfully update statistics value with game client");
    }
    }
  5. Create another callback function to handle the result of a query of stat values from the game client. This function will check the player's highest score stored in the AccelByte Gaming Services (AGS) Statistics service. If there are no stored values or the new score is higher, it will call the UpdateUserStatsFromClient in the StatsEssentialsWrapper_Starter and you will use OnUpdateStatsWithClientCompleted you created earlier as the callback function.

    private void OnGetUserStatsFromClient(Result<PagedStatItems> result, PlayerState playerState)
    {
    // if query success
    if (!result.IsError)
    {
    // key: stat code, value: stat value
    Dictionary<string, float> userStatItems = result.Value.data.ToDictionary(stat => stat.statCode, stat => stat.value);
    if (userStatItems.ContainsKey(currentStatCode) && playerState.score > userStatItems[currentStatCode])
    {
    _statsWrapper.UpdateUserStatsFromClient(currentStatCode, playerState.score, "", OnUpdateStatsWithClientCompleted);
    }
    else if (!userStatItems.ContainsKey(currentStatCode))
    {
    _statsWrapper.UpdateUserStatsFromClient(currentStatCode, playerState.score, "", OnUpdateStatsWithClientCompleted);
    }
    }
    else
    {
    // Create a new stat item since the stat doesn't exist yet
    _statsWrapper.UpdateUserStatsFromClient(currentStatCode, playerState.score, "", OnUpdateStatsWithClientCompleted);
    }
    }
  6. Create another function that will call the GetUserStatsFromClient() function in the StatsEssentialsWrapper_Starter to get the current player's highest score stat based on the player's PlayerState.

    private void CheckHighestScoreStats(GameModeEnum gameMode, InGameMode inGameMode, List<PlayerState> playerStates)
    {
    var apiClient = AccelByteSDK.GetClientRegistry().GetApi();
    var currentUserId = apiClient.session.UserId;

    if (gameMode is GameModeEnum.SinglePlayer)
    {
    currentStatCode = SingleplayerStatCode;
    PlayerState playerState = null;
    foreach (PlayerState currentPlayerState in playerStates)
    {
    if (currentPlayerState.playerId == currentUserId)
    {
    playerState = currentPlayerState;
    break;
    }
    }
    _statsWrapper.GetUserStatsFromClient(new string[]{currentStatCode}, null, result => OnGetUserStatsFromClient(result, playerState));
    }
    }
  7. Modify the Start() function to bind the CheckHighestScoreStats() function to subscribe to the onGameOver event of GameManager, which will be triggered when the game ends to save the highest score stat.

    void Start()
    {
    ...
    GameManager.OnGameOver += CheckHighestScoreStats;
    }

Query and update stats for online multiplayer highest score

  1. In StatsHelper_Starter.cs, define the constant variables to store the Stat Code of the Highest Score - Elimination and the Highest Score - Team Deathmatch statistic you configured in the Admin Portal previously.

    private const string EliminationStatCode = "unity-highestscore-elimination";
    private const string TeamDeathmatchStatCode = "unity-highestscore-teamdeathmatch";
  2. Create a new callback function to handle the result of updating the stat values from the game server.

    private void OnUpdateStatsWithServerCompleted(Result<StatItemOperationResult[]> result)
    {
    if (!result.IsError)
    {
    Debug.Log("Successfully update statistics value with game server");
    }
    }
  3. Create another callback function to handle the result of querying stat values from the game server. This function handles the checking of players' highest scores. If there are no stored values or the new score is higher, call the UpdateManyUserStatsFromServer function you created in the StatsEssentialsWrapper_Starter, and add the OnUpdateStatsWithServerCompleted as the callback function to handle the result.

    private void OnBulkGetUserStatFromServer(Result<FetchUserStatistic> result, Dictionary<string, float> userStats)
    {
    if (!result.IsError)
    {
    // key: userId, value: stat value
    Dictionary<string, float> bulkUserStats = result.Value.UserStatistic.ToDictionary(stat => stat.UserId, stat => stat.Value);
    List<string> userIdToRemove = new List<string>();
    foreach (string userId in userStats.Keys)
    {
    if (bulkUserStats.ContainsKey(userId) && userStats[userId] < bulkUserStats[userId])
    {
    userIdToRemove.Add(userId);
    }
    }
    foreach (var uId in userIdToRemove)
    {
    userStats.Remove(uId);
    }
    _statsWrapper.UpdateManyUserStatsFromServer(currentStatCode, userStats, OnUpdateStatsWithServerCompleted);
    }
    else
    {
    _statsWrapper.UpdateManyUserStatsFromServer(currentStatCode, userStats, OnUpdateStatsWithServerCompleted);
    }
    }
  4. Add the code below to the CheckHighestScoreStats() function to set the Stat Code you are going to use based on the current game mode. Make sure to add #if UNITY_SERVER and #endif to define this code as a game server only. Call the BulkGetUsersStatFromServer() function to query the stat value.

    private void CheckHighestScoreStats(GameModeEnum gameMode, InGameMode inGameMode, List<PlayerState> playerStates)
    {
    var currentUserId = apiClient.session.UserId;

    #if UNITY_SERVER
    if (inGameMode is InGameMode.OnlineEliminationGameMode or InGameMode.CreateMatchEliminationGameMode)
    {
    currentStatCode = EliminationStatCode;
    }
    else if (inGameMode is InGameMode.OnlineDeathMatchGameMode or InGameMode.CreateMatchDeathMatchGameMode)
    {
    currentStatCode = TeamDeathmatchStatCode;
    }

    Dictionary<string, float> userStats = playerStates.ToDictionary(state => state.playerId, state => state.score);
    _statsWrapper.BulkGetUsersStatFromServer(userStats.Keys.ToArray(), currentStatCode, result => OnBulkGetUserStatFromServer(result, userStats));
    #endif

    ...
    }

Connect the UI to display the user's stats

In this section, you will learn how to connect the Stats Profile Menu user interface (UI) with the implemented functions from the previous section.

  1. Open /Assets/Resources/Modules/StatsEssentials/Scripts/UI/StatsHandler_Starter.cs.

  2. Add a local variable to hold the reference of StatsEssentialsWrapper_Starter and initialize it in the Start function by using the utility class called TutorialModuleManager to get the module class.

    private StatsEssentialsWrapper_Starter _statsWrapper;

    void Start(){
    ...
    // get stats' wrapper
    _statsWrapper = TutorialModuleManager.Instance.GetModuleClass<StatsEssentialsWrapper_Starter>();
    }
  3. Define the constant variables to store the Stat Code of desired stats that you are going to display in the Stats Profile Menu UI.

    private const string SingleplayerStatCode = "unity-highestscore-singleplayer";
    private const string EliminationStatCode = "unity-highestscore-elimination";
    private const string TeamDeathmatchStatCode = "unity-highestscore-teamdeathmatch";
  4. Create a new callback function called OnGetUserStatsCompleted. Display the query stat values result on the Text UI you prepared previously.

    private void OnGetUserStatsCompleted(Result<PagedStatItems> result)
    {
    if (!result.IsError){
    foreach (StatItem statItem in result.Value.data)
    {
    switch (statItem.statCode)
    {
    case SingleplayerStatCode:
    singlePlayerStatValueText.text = statItem.value.ToString();
    break;
    case EliminationStatCode:
    eliminationStatValueText.text = statItem.value.ToString();
    break;
    case TeamDeathmatchStatCode:
    teamDeathmatchStatValueText.text = statItem.value.ToString();
    break;
    }
    }
    }
    }
  5. Create a new function called DisplayStats() and define a string array statCodes that holds the stat values you want to display. Since the Stats Profile Menu UI is part of the game client, call the GetUserStatsFromClient() function in StatsEssentialsWrapper_Starter to get the stat values with the AGS Game SDK as game client.

    private void DisplayStats()
    {
    string[] statCodes =
    {
    SingleplayerStatCode,
    EliminationStatCode,
    TeamDeathmatchStatCode
    };
    _statsWrapper.GetUserStatsFromClient(statCodes, null, OnGetUserStatsCompleted);
    }
  6. Now modify the Start() function to call the DisplayStats() function on initializing the Stats Profile Menu.

    void Start(){
    ...
    DisplayStats();
    }
  7. Add the OnEnable() function and call the DisplayStats() function on the Stats Profile Menu UI active status so it can keep the value up to date.

    void OnEnable()
    {
    if (gameObject.activeSelf && _statsWrapper != null)
    {
    DisplayStats();
    }
    }

Resources