Put it all together - Stat tracking and display - (Unity module)
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
andServerStatistic
classes.using AccelByte.Core;
using AccelByte.Models;The
onGameOver
event definition fromGameManager
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
Open the
/Assets/Resources/Modules/StatsEssentials/Scripts/StatsHelper_Starter.cs
.Add a local variable to hold the reference of
StatsEssentialsWrapper_Starter
and initialize it by using the utility class calledTutorialModuleManager
to get the module class.private StatsEssentialsWrapper_Starter _statsWrapper;
private void Start()
{
_statsWrapper = TutorialModuleManager.Instance.GetModuleClass<StatsEssentialsWrapper_Starter>();
...
}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, andcurrentStatCode
, to hold the current target statistic's stat code.private const string SingleplayerStatCode = "unity-highestscore-singleplayer";
private string currentUserId;
private string currentStatCode;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");
}
}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 theStatsEssentialsWrapper_Starter
and you will useOnUpdateStatsWithClientCompleted
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);
}
}Create another function that will call the
GetUserStatsFromClient()
function in theStatsEssentialsWrapper_Starter
to get the current player's highest score stat based on the player'sPlayerState
.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));
}
}Modify the
Start()
function to bind theCheckHighestScoreStats()
function to subscribe to theonGameOver
event ofGameManager
, 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
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";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");
}
}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 theStatsEssentialsWrapper_Starter
, and add theOnUpdateStatsWithServerCompleted
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);
}
}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 theBulkGetUsersStatFromServer()
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.
Open
/Assets/Resources/Modules/StatsEssentials/Scripts/UI/StatsHandler_Starter.cs
.Add a local variable to hold the reference of
StatsEssentialsWrapper_Starter
and initialize it in theStart
function by using the utility class calledTutorialModuleManager
to get the module class.private StatsEssentialsWrapper_Starter _statsWrapper;
void Start(){
...
// get stats' wrapper
_statsWrapper = TutorialModuleManager.Instance.GetModuleClass<StatsEssentialsWrapper_Starter>();
}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";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;
}
}
}
}Create a new function called
DisplayStats()
and define a string arraystatCodes
that holds the stat values you want to display. Since the Stats Profile Menu UI is part of the game client, call theGetUserStatsFromClient()
function inStatsEssentialsWrapper_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);
}Now modify the
Start()
function to call theDisplayStats()
function on initializing the Stats Profile Menu.void Start(){
...
DisplayStats();
}Add the
OnEnable()
function and call theDisplayStats()
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
- GitHub link to the file in the Unity Byte Wars repository: