Skip to main content

Use the SDK to set and query stats - Stat tracking and display - (Unity module)

Last updated on June 23, 2025

Unwrap the wrapper

In this tutorial, you will learn how to implement AccelByte Gaming Services (AGS) Statistics using AGS Software Development Kit (SDK). In Byte Wars, there is a game instance wrapper defined in the StatsEssentialsWrapper class. This wrapper acts as a wrapper to manipulate the AGS statistics values.

In this tutorial, you will manipulate the statistics values using the StatsEssentialsWrapper_Starter wrapper, which is the starter version of the StatsEssentialsWrapper class.

What's in the Starter Pack

To follow this tutorial, you use the starter wrapper class called StatsEssentialsWrapper_Starter. This wrapper is defined in the file below:

  • C# file: Assets/Resources/Modules/StatsEssentials/Scripts/StatsEssentialsWrapper_Starter.cs

There is also a model class containing helper functions and variables defined in the file below:

  • C# file: Assets/Resources/Modules/StatsEssentials/Scripts/StatsEssentialsModels.cs

The StatsEssentialsWrapper_Starter class has several pre-defined components below.

  • Helper variables to reference the AGS SDK interfaces. These variables are assigned when the wrapper is initialized.

    private Statistic statistic;
    private ServerStatistic serverStatistic;

    void Start()
    {
    statistic = AccelByteSDK.GetClientRegistry().GetApi().GetStatistic();
    #if UNITY_SERVER
    serverStatistic = AccelByteSDK.GetServerRegistry().GetApi().GetStatistic();
    #endif
    }

The StatsEssentialsModels class has several helpers below.

  • A helper class to return statistics code for each game mode supported on Byte Wars. It helps to simplify the access so we don't need to hard code it when updating the statistic values. These statistic codes are the same as the ones you configured in Admin Portal in the previous section.

    public class GameStatsData
    {
    public struct GameStatsModel
    {
    public string StatCode;
    public string DisplayName;

    public GameStatsModel(string codeName, string displayName)
    {
    StatCode = codeName;
    DisplayName = displayName;
    }
    }

    public InGameMode GameMode { get; private set; }
    public GameStatsModel HighestScoreStats { get; private set; }
    public GameStatsModel TotalScoreStats { get; private set; }
    public GameStatsModel MatchesPlayedStats { get; private set; }
    public GameStatsModel MatchesWonStats { get; private set; }
    public GameStatsModel KillCountStats { get; private set; }
    public GameStatsModel DeathStats { get; private set; }
    public ReadOnlyCollection<string> StatsCodes { get; }
    public ReadOnlyCollection<GameStatsModel> StatsModels { get; }

    public GameStatsData(InGameMode gameMode)
    {
    GameMode = gameMode;

    string gameModeSuffx = "unknown";
    switch(GameMode)
    {
    case InGameMode.SinglePlayer:
    case InGameMode.LocalElimination:
    case InGameMode.LocalTeamDeathmatch:
    gameModeSuffx = "singleplayer";
    break;
    case InGameMode.MatchmakingElimination:
    case InGameMode.CreateMatchElimination:
    gameModeSuffx = "elimination";
    break;
    case InGameMode.MatchmakingTeamDeathmatch:
    case InGameMode.CreateMatchTeamDeathmatch:
    gameModeSuffx = "teamdeathmatch";
    break;
    }

    HighestScoreStats = new($"unity-highestscore-{gameModeSuffx}", "Highest Score");
    TotalScoreStats = new($"unity-totalscore-{gameModeSuffx}", "Total Score");
    MatchesPlayedStats = new($"unity-matchesplayed-{gameModeSuffx}", "Matches Played");
    MatchesWonStats = new($"unity-matcheswon-{gameModeSuffx}", "Matches Won");
    KillCountStats = new($"unity-killcount-{gameModeSuffx}", "Kill Count");
    DeathStats = new($"unity-deaths-{gameModeSuffx}", "Deaths");

    StatsModels = new ReadOnlyCollection<GameStatsModel>(new[]
    {
    HighestScoreStats,
    TotalScoreStats,
    MatchesPlayedStats,
    MatchesWonStats,
    KillCountStats,
    DeathStats
    });

    StatsCodes = new ReadOnlyCollection<string>(StatsModels.Select(model => model.StatCode).ToList());
    }
    }
  • Helper variables that stores statistics code for each game modes.

    public readonly static GameStatsData SinglePlayerStatsData = new(InGameMode.SinglePlayer);
    public readonly static GameStatsData EliminationStatsData = new(InGameMode.CreateMatchElimination);
    public readonly static GameStatsData TeamDeathmatchStatsData = new(InGameMode.CreateMatchTeamDeathmatch);
  • A helper function to return statistics code based on the game mode.

    public static GameStatsData GetGameStatsDataByGameMode(InGameMode gameMode)
    {
    switch (gameMode)
    {
    case InGameMode.SinglePlayer:
    case InGameMode.LocalElimination:
    case InGameMode.LocalTeamDeathmatch:
    return SinglePlayerStatsData;
    case InGameMode.MatchmakingElimination:
    case InGameMode.CreateMatchElimination:
    return EliminationStatsData;
    case InGameMode.MatchmakingTeamDeathmatch:
    case InGameMode.CreateMatchTeamDeathmatch:
    return TeamDeathmatchStatsData;
    default:
    return null;
    }
    }

Implement statistics for game client

This section will guide to implement statistics functionality for game client using AGS SDK.

  1. Open StatsEssentialsWrapper_Starter class. First, create a new function below to get user statistics to display on the UI later. This function require list of statistics code and when the request is completed, it call the callback function.

    public void GetUserStatsFromClient(string[] statCodes, string[] tags, ResultCallback<PagedStatItems> resultCallback)
    {
    statistic.GetUserStatItems(
    statCodes,
    tags,
    result => OnGetUserStatsFromClientCompleted(result, resultCallback)
    );
    }
  2. Next, create the callback function for the function above. Here, it simply log the request status whether it success to get statistics values or not.

    private void OnGetUserStatsFromClientCompleted(Result<PagedStatItems> result, ResultCallback<PagedStatItems> customCallback = null)
    {
    if (!result.IsError)
    {
    BytewarsLogger.Log("Get User's Stat Items from Client successful.");
    }
    else
    {
    BytewarsLogger.LogWarning($"Get User's Stat Items from Client failed. Message: {result.Error.Message}");
    }

    customCallback?.Invoke(result);
    }
  3. Now, create a new function below to update the statistics values. You will use this to update the statistics values that is intended to be updated by the game client, such as the highest score in single player mode you've set up on Admin Portal earlier. This function requires the list statistics code and values to update, and once the request is complete, it call the callback function.

    public void UpdateUserStatsFromClient(List<StatItemUpdate> statItems, ResultCallback<StatItemOperationResult[]> resultCallback = null)
    {
    statistic.UpdateUserStatItems(
    statItems.ToArray(),
    result => OnUpdateUserStatsFromClientCompleted(result, resultCallback)
    );
    }
  4. Next, create the callback function for the function above. Here, it simply log the request status whether it success to update statistics values or not.

    private void OnUpdateUserStatsFromClientCompleted(Result<StatItemOperationResult[]> result, ResultCallback<StatItemOperationResult[]> customCallback = null)
    {
    if (!result.IsError)
    {
    BytewarsLogger.Log("Update User's Stat Items from Client successful.");
    }
    else
    {
    BytewarsLogger.LogWarning($"Update User's Stat Items from Client failed. Message: {result.Error.Message}");
    }

    customCallback?.Invoke(result);
    }

Implement statistics for game server

This section will guide to implement statistics functionality for game server using AGS SDK.

  1. Open StatsEssentialsWrapper_Starter class. Create a new function below to update the statistics values that is intended to be updated by the game server, such as the highest score in elimination and team deathmatch mode you've set up on Admin Portal earlier. This function requires the list statistics code and values to update, and once the request is complete, it call the callback function.

    public void UpdateManyUserStatsFromServer(List<UserStatItemUpdate> statItems, ResultCallback<StatItemOperationResult[]> resultCallback)
    {
    serverStatistic.UpdateManyUsersStatItems(
    statItems.ToArray(),
    result => OnUpdateManyUserStatsFromServerCompleted(result, resultCallback));
    }
  2. Next, create the callback function for the function above. Here, it simply log the request status whether it success to update statistics values or not.

    private void OnUpdateManyUserStatsFromServerCompleted(Result<StatItemOperationResult[]> result, ResultCallback<StatItemOperationResult[]> customCallback = null)
    {
    if (!result.IsError)
    {
    BytewarsLogger.Log("Update User's Stat Items from Server successful.");
    }
    else
    {
    BytewarsLogger.LogWarning($"Update User's Stat Items from Server failed. Message: {result.Error.Message}");
    }

    customCallback?.Invoke(result);
    }

Implement to update statistics values

On Byte Wars, it is designed to update the statistics values for players when the game ends. These values must be updated based on the game modes and statistic type (e.g. for game client, or game server). This section will guide you on how to update statistics values using the functions you created previously.

  1. Open StatsEssentialsWrapper_Starter class and create the function below. This function is a wrapper function to update statistics of all connected players in the game. First, it determines which game mode is currently being played. Then, it loops through the connected players to store their data into a statistics items. Finally, the function collects all the statistics items call either UpdateUserStatsFromClient() or UpdateManyUserStatsFromServer() based on the game mode.

    info

    The function below shows how to modify all statistics available in Byte Wars. However, in this module, you will only configure statistics for the player's highest score. Other statistics values won't be updated.

    private void UpdateConnectedPlayersStatsOnGameEnds(GameManager.GameOverReason reason)
    {
    if (reason != GameManager.GameOverReason.MatchEnded)
    {
    return;
    }

    GameModeEnum gameMode = GameManager.Instance.GameMode;
    InGameMode inGameMode = GameManager.Instance.InGameMode;
    StatsEssentialsModels.GameStatsData gameStatsData = StatsEssentialsModels.GetGameStatsDataByGameMode(inGameMode);
    List<PlayerState> playerStates = GameManager.Instance.ConnectedPlayerStates.Values.ToList();

    // Store statistics to update.
    List<UserStatItemUpdate> statItems = new();
    Dictionary<int, (bool isWinner, int score, int kills, int deaths)> teamStats = new();
    GameManager.Instance.GetWinner(out TeamState winnerTeam, out PlayerState winnerPlayer);
    foreach (PlayerState playerState in playerStates)
    {
    if (!teamStats.ContainsKey(playerState.TeamIndex))
    {
    List<PlayerState> teamPlayers = playerStates.Where(p => p.TeamIndex == playerState.TeamIndex).ToList();
    teamStats.Add(playerState.TeamIndex, new()
    {
    isWinner = winnerTeam != null ? playerState.TeamIndex == winnerTeam.teamIndex : false,
    score = (int)teamPlayers.Sum(p => p.Score),
    kills = teamPlayers.Sum(p => p.KillCount),
    deaths = (GameData.GameModeSo.PlayerStartLives * teamPlayers.Count) - teamPlayers.Sum(p => p.Lives)
    });
    }

    (bool isWinner, int score, int kills, int deaths) = teamStats[playerState.TeamIndex];

    // Highest score statistic
    statItems.Add(new()
    {
    updateStrategy = StatisticUpdateStrategy.MAX,
    statCode = gameStatsData.HighestScoreStats.StatCode,
    userId = playerState.PlayerId,
    value = score
    });

    // Total score statistic
    statItems.Add(new()
    {
    updateStrategy = StatisticUpdateStrategy.INCREMENT,
    statCode = gameStatsData.TotalScoreStats.StatCode,
    userId = playerState.PlayerId,
    value = score
    });

    // Matches played statistic
    statItems.Add(new()
    {
    updateStrategy = StatisticUpdateStrategy.INCREMENT,
    statCode = gameStatsData.MatchesPlayedStats.StatCode,
    userId = playerState.PlayerId,
    value = 1
    });

    // Matches won statistic
    statItems.Add(new()
    {
    updateStrategy = StatisticUpdateStrategy.INCREMENT,
    statCode = gameStatsData.MatchesWonStats.StatCode,
    userId = playerState.PlayerId,
    value = isWinner ? 1 : 0
    });

    // Kill count statistic
    statItems.Add(new()
    {
    updateStrategy = StatisticUpdateStrategy.INCREMENT,
    statCode = gameStatsData.KillCountStats.StatCode,
    userId = playerState.PlayerId,
    value = kills
    });

    // Death statistic
    statItems.Add(new()
    {
    updateStrategy = StatisticUpdateStrategy.INCREMENT,
    statCode = gameStatsData.DeathStats.StatCode,
    userId = playerState.PlayerId,
    value = deaths
    });
    }

    #if UNITY_SERVER
    BytewarsLogger.Log($"[Server] Update the stats of connected players when the game ended. Game mode: {gameMode}. In game mode: {inGameMode}");
    UpdateManyUserStatsFromServer(statItems, (Result<StatItemOperationResult[]> result) =>
    {
    if (!result.IsError)
    {
    BytewarsLogger.Log("[Server] Successfully updated the stats of connected players when the game ended.");
    }
    else
    {
    BytewarsLogger.LogWarning($"[Server] Failed to update the stats of connected players when the game ended. Error: {result.Error.Message}");
    }
    });
    #else
    BytewarsLogger.Log($"[Client] Update the stats of local connected players when the game ended. Game mode: {gameMode}. In game mode: {inGameMode}");

    /* Local gameplay only has one valid account, which is the player who logged in to the game.
    * Thus, we can only update the stats based on that player's user ID. */
    List<StatItemUpdate> localPlayerStatItems = statItems
    .Where(x => x.userId == GameData.CachedPlayerState.PlayerId)
    .Select(x => new StatItemUpdate
    {
    updateStrategy = x.updateStrategy,
    value = x.value,
    statCode = x.statCode,
    additionalData = x.additionalData
    }).ToList();

    UpdateUserStatsFromClient(localPlayerStatItems, (Result<StatItemOperationResult[]> result) =>
    {
    if (!result.IsError)
    {
    BytewarsLogger.Log("[Client] Successfully updated the stats of connected players when the game ended.");
    }
    else
    {
    BytewarsLogger.LogWarning($"[Client] Failed to update the stats of connected players when the game ended. Error: {result.Error.Message}");
    }
    });
    #endif
    }
  2. Finally, in the Start() function, bind the function above to the event below so it will be called when the game ends.

    void Start()
    {
    statistic = AccelByteSDK.GetClientRegistry().GetApi().GetStatistic();
    #if UNITY_SERVER
    serverStatistic = AccelByteSDK.GetServerRegistry().GetApi().GetStatistic();
    #endif

    GameManager.OnGameEnded += UpdateConnectedPlayersStatsOnGameEnds;
    }

Resources