SDK を使用して統計データを設定しクエリを実行する - 統計データを追跡し表示する - (Unity モジュール)
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.
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)
);
}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);
}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)
);
}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.
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));
}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.
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 eitherUpdateUserStatsFromClient()
orUpdateManyUserStatsFromServer()
based on the game mode.備考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
}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
The files used in this tutorial are available in the Unity Byte Wars GitHub repository.