Track global statistics for players and games
Overview
This article walks you through how to set up statistic configurations to track global statistics for your game and your players.
Goals
- Provide an overview of the statistics configuration
- Explain how to configure stats for user statistics and global statistics
- Explain how to use statistics using the AccelByte SDK, including how to utilize the multiple update functionality
Prerequisites
You will need access to:
- An AccelByte Admin Portal
- The AccelByte Unreal or Unity SDK
- The AccelByte statistics API documentation for further reference
Create user and global statistics configuration
It is very important for a game to have persistent statistics tracking to consistently track user progress.
To create a statistics configuration in the Admin Portal, follow these steps:
On the Admin Portal sidebar, go to Progression & Inventory > Statistics > Configurations.
On the Statistic Configuration page, click the Add Configuration button. The Add New Configuration form appears.
Fill in the required information:
Fill in the Stat Code using the allowed formats, and also the statistic Name to make it easier for you to identify the stats in the future. You can also put an optional Description to provide extra information about the statistic you created.
Enter the optional Min. Value and Max. Value for extra server side validation, to make sure you can prevent users from exceeding the allowed numbers. You also need to add the mandatory Default Value to determine the starting number, or value, of a statistic for each user.
There are also some additional configurations that you can set up to make sure you utilize the statistics feature fully. Refer to the following forms.
You can choose to prevent a statistic value from decreasing by setting Increment to true. This feature provides you extra server side validation, to make sure users take advantage of a certain game mechanic for their own benefit.
To track a global accumulated value of the statistic from all of the contributing users, make sure Set as Global is set to true. This way, the system will not only track an individual user statistic, but also the accumulated statistics from all users that contribute to a global statistic.
You can also add validation to make sure that only the game server can update the statistics, by setting the Set By field to Server. This way, users will get rejected from updating the statistics from the game client directly. This is also a good way to prevent cheating or game client tampering.
You can optionally configure tags for the statistics, to make it easier for you to group and organize the statistics from the game.
Using Statistics with Game SDK or Extend SDK
Once the statistics are configured in the Admin Portal, you can use them using our SDKs, referring to the example explained in this section.
Note that you can also do server authoritative statistics management by following the guide in here.
Updating Statistics
This update functionality can be called directly from the game client, and is suitable for a non-competitive genre such as single player or peer to peer multiplayer that don't utilize any game server.
There are several update strategies supported, as listed in the table below:
Update Strategy | Usage | Example Implementation |
OVERRIDE | The update will replace the existing value of your statistic with the latest one | This is suitable if you have your own calculation, such as ELO or MMR. You can pass the latest value, for the service to store. |
INCREMENT | The update will add or subtract the new value to the existing value, depending if the new value is (+) or (-) | This is suitable to track experience points gained in the battle. This way, you can pass the points directly, and the service will handle the increment. |
MAX | This will only update the statistic if the value is higher than the existing one | This is suitable to track best/highest scores such as Kills or Damage dealt. The service will ignore the update if the score is lower than the existing one. |
MIN | This will only update the statistic if the value is lower than the existing one. | This is suitable to track the smallest score such as best racing lap or fastest round. The service will ignore the update if the score is higher than the existing one. |
Update Single User Stats
- Unreal
- Unity
- Go Extend SDK
- Python Extend SDK
- Java Extend SDK
- C# Extend SDK
FApiClientPtr ApiClient = FMultiRegistry::GetApiClient();
FString AdditionalKey = " Your additional key";
FString StatCode = " Your stat code ";
FAccelByteModelsPublicUpdateUserStatItem UserStatItem{};
UserStatItem.UpdateStrategy = EAccelByteStatisticUpdateStrategy::OVERRIDE;
UserStatItem.Value = 100.0f;
ApiClient->Statistic.UpdateUserStatItemsValue(StatCode
, AdditionalKey
, UserStatItem
, THandler<FAccelByteModelsUpdateUserStatItemValueResponse>::CreateLambda([](FAccelByteModelsUpdateUserStatItemValueResponse Result)
{
// Do something if UpdateUserStatItemsValue is successful
})
, FErrorHandler::CreateLambda([](int32 ErrorCode, FString ErrorMessage)
{
// Do something if UpdateUserStatItemsValue has an error
}));
Statistic statistic = AccelByteSDK.GetClientRegistry().GetApi().GetStatistic();
string additionalKey = " Your additional key";
string statCode = " Your stat code ";
PublicUpdateUserStatItem userStatItem = new PublicUpdateUserStatItem
{
updateStrategy = StatisticUpdateStrategy.OVERRIDE,
value = 100
};
statistic.UpdateUserStatItemsValue(statCode, additionalKey, userStatItem, result =>
{
if (result.IsError)
{
// Do something if UpdateUserStatItemsValue an error
Debug.Log($"Error UpdateUserStatItemsValue, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if UpdateUserStatItemsValue is successful
});
userStatisticService := &social.UserStatisticService{
Client: factory.NewSocialClient(&repository.ConfigRepositoryImpl{}),
TokenRepository: &repository.TokenRepositoryImpl{},
}
namespace := "mygame"
statCode := "mystatcode"
userId := "myuserid"
updateStrategy := socialclientmodels.StatItemUpdateUpdateStrategyOVERRIDE
value := float64(100)
body := socialclientmodels.StatItemUpdate {
UpdateStrategy: &updateStrategy,
Value: &value,
}
additionalKey := "myadditionalkey"
input := &user_statistic.UpdateUserStatItemValueParams{
Body: &body,
Namespace: namespace,
StatCode: statCode,
UserID: userId,
AdditionalKey: &additionalKey,
}
result, err := userStatisticService.UpdateUserStatItemValueShort(input)
import accelbyte_py_sdk.api.social as social_service
import accelbyte_py_sdk.api.social.models as social_models
result, error = social_service.update_user_stat_item_value_1(
body=social_models.StatItemUpdate()
.with_update_strategy("OVERRIDE")
.with_value(100.0)
stat_code="YourStatCode",
additional_key="YourAdditionalKey",
user_id="********************************",
)
if error:
exit(error)
UserStatistic userStatisticWrapper = new UserStatistic(sdk);
String additionalKey = "Your additional key";
String statCode = "Your stat code";
String userId = "<user-id>";
StatItemIncResult response;
try {
StatItemUpdate reqBody = StatItemUpdate.builder()
.updateStrategy(StatItemUpdate.UpdateStrategy.OVERRIDE.toString())
.value(100f)
.build();
response = userStatisticWrapper.updateUserStatItemValue(UpdateUserStatItemValue.builder()
.namespace("<namespace>")
.userId(userId)
.statCode(statCode)
.additionalKey(additionalKey)
.body(reqBody)
.build());
} catch (Exception e) {
// Do something when failed
return;
}
if (response == null) {
// Null response from server
} else {
//do something if success
}
string additionalKey = "Your additional key";
string statCode = "Your stat code";
string userId = "<user-id>";
var response = sdk.Social.UserStatistic.UpdateUserStatItemValueOp
.SetAdditionalKey(additionalKey)
.SetBody(new StatItemUpdate()
{
UpdateStrategy = StatItemUpdateUpdateStrategy.OVERRIDE,
Value = 100
})
.Execute(sdk.Namespace, statCode, userId);
if (response != null)
{
//do something if success
}
Update Multiple Stats for One User
- Unreal
- Unity
- Go Extend SDK
- Python Extend SDK
- Java Extend SDK
- C# Extend SDK
FApiClientPtr ApiClient = FMultiRegistry::GetApiClient();
FString AdditionalKey = " Your additional key";
FAccelByteModelsUpdateUserStatItemWithStatCode UserStatItem1{};
UserStatItem.StatCode = " Your 1st stat code ";
UserStatItem.UpdateStrategy = EAccelByteStatisticUpdateStrategy::OVERRIDE;
UserStatItem.Value = 100.0f;
FAccelByteModelsUpdateUserStatItemWithStatCode UserStatItem2{};
UserStatItem.StatCode = " Your 2nd stat code ";
UserStatItem.UpdateStrategy = EAccelByteStatisticUpdateStrategy::INCREMENT;
UserStatItem.Value = 50.0f;
TArray<FAccelByteModelsUpdateUserStatItemWithStatCode> BulkUpdateUserStatItems = { UserStatItem1, UserStatItem2 };
ApiClient->Statistic.BulkUpdateUserStatItemsValue(AdditionalKey
, BulkUpdateUserStatItems
, THandler<TArray<FAccelByteModelsUpdateUserStatItemsResponse>>::CreateLambda([](TArray<FAccelByteModelsUpdateUserStatItemsResponse> Result)
{
// Do something if BulkUpdateUserStatItemsValue is successful
})
, FErrorHandler::CreateLambda([](int32 ErrorCode, FString ErrorMessage)
{
// Do something if BulkUpdateUserStatItemsValue has an error
}));
Statistic statistic = AccelByteSDK.GetClientRegistry().GetApi().GetStatistic();
string additionalKey = " Your additional key";
StatItemUpdate userStatItem1 = new StatItemUpdate
{
statCode = " Your 1st stat code ",
updateStrategy = StatisticUpdateStrategy.OVERRIDE,
value = 100
};
StatItemUpdate userStatItem2 = new StatItemUpdate
{
statCode = " Your 2nd stat code ",
updateStrategy = StatisticUpdateStrategy.INCREMENT,
value = 50
};
StatItemUpdate[] bulkUpdateUserStatItems = { userStatItem1, userStatItem2 };
statistic.UpdateUserStatItems(additionalKey, bulkUpdateUserStatItems, result =>
{
if (result.IsError)
{
// Do something if UpdateUserStatItems an error
Debug.Log($"Error UpdateUserStatItems, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if UpdateUserStatItems is successful
});
userStatisticService := &social.UserStatisticService{
Client: factory.NewSocialClient(&repository.ConfigRepositoryImpl{}),
TokenRepository: &repository.TokenRepositoryImpl{},
}
namespace := "mygame"
userId := "myuserid"
statCode1 := "statcode1"
value1 := float64(100)
statCode2 := "statcode2"
value2 := float64(100)
updateStrategy := socialclientmodels.BulkStatItemUpdateUpdateStrategyOVERRIDE
item1 := socialclientmodels.BulkStatItemUpdate {
StatCode: &statCode1,
UpdateStrategy: &updateStrategy,
Value: &value1,
}
item2 := socialclientmodels.BulkStatItemUpdate {
StatCode: &statCode2,
UpdateStrategy: &updateStrategy,
Value: &value2,
}
body := []*socialclientmodels.BulkStatItemUpdate{
&item1,
&item2,
}
additionalKey, _ := cmd.Flags().GetString("additionalKey")
input := &user_statistic.BulkUpdateUserStatItemParams{
Body: body,
Namespace: namespace,
UserID: userId,
AdditionalKey: &additionalKey,
}
result, err := userStatisticService.BulkUpdateUserStatItemShort(input)
import accelbyte_py_sdk.api.social as social_service
import accelbyte_py_sdk.api.social.models as social_models
result, error = social_service.bulk_update_user_stat_item_2(
body=[
social_models.BulkStatItemUpdate()
.with_stat_code("YourStatCode1")
.with_update_strategy("OVERRIDE")
.with_value(100.0),
social_models.BulkStatItemUpdate()
.with_stat_code("YourStatCode2")
.with_update_strategy("INCREMENT")
.with_value(50.0),
],
user_id="********************************",
additional_key="YourAdditionalKey",
)
if error:
exit(error)
UserStatistic userStatisticWrapper = new UserStatistic(sdk);
String additionalKey = "Your additional key";
String userId = "<user-id>";
List<BulkStatOperationResult> response;
try {
List<BulkStatItemUpdate> reqBody = Arrays.asList(
BulkStatItemUpdate.builder().statCode("Your first stat code")
.updateStrategy(StatItemUpdate.UpdateStrategy.OVERRIDE.toString())
.value(100f)
.build(),
BulkStatItemUpdate.builder().statCode("Your second stat code")
.updateStrategy(StatItemUpdate.UpdateStrategy.OVERRIDE.toString())
.value(50f)
.build()
);
response = userStatisticWrapper.bulkUpdateUserStatItem(BulkUpdateUserStatItem.builder()
.namespace("<namespace>")
.userId(userId)
.additionalKey(additionalKey)
.body(reqBody)
.build());
} catch (Exception e) {
// Do something when failed
return;
}
if (response == null) {
// Null response from server
} else {
// Do something if success
}
string additionalKey = "Your additional key";
string userId = "<user-id>";
var response = sdk.Social.UserStatistic.BulkUpdateUserStatItemOp
.SetAdditionalKey(additionalKey)
.SetBody(new List<BulkStatItemUpdate>()
{
new BulkStatItemUpdate()
{
StatCode = "Your first stat code",
UpdateStrategy = BulkStatItemUpdateUpdateStrategy.OVERRIDE,
Value = 100
},
new BulkStatItemUpdate()
{
StatCode = "Your second stat code",
UpdateStrategy = BulkStatItemUpdateUpdateStrategy.OVERRIDE,
Value =50
}
})
.Execute(sdk.Namespace, userId);
if (response != null)
{
//do something if success
}
Retrieving User Statistics
Retrieving user statistics is straightforward, you can display user stats as part of the user profile or anywhere within the game UI. You can also choose to list all user statistics, display some statistics by statcodes or simply display statistics by the tags that you configured through the admin portal before.
To use the SDK functionality, refer to the snippets below
Get Current and Other User Statistics
- Unreal
- Unity
- Go Extend SDK
- Python Extend SDK
- Java Extend SDK
- C# Extend SDK
FApiClientPtr ApiClient = FMultiRegistry::GetApiClient();
FString UserId = "Player User Id";
TArray<FString> StatCodes = { "Stat Code 1", "Stat Code 2" };
TArray<FString> Tags = { "Tag 1", "Tag 2", "Tag 3" };
int32 Offset = 0;
int32 Limit = 20;
ApiClient->Statistic.GetUserStatItems(UserId
, StatCodes
, Tags
, THandler<FAccelByteModelsUserStatItemPagingSlicedResult>::CreateLambda([](FAccelByteModelsUserStatItemPagingSlicedResult Result)
{
// Do something if GetUserStatItems is successful
})
, FErrorHandler::CreateLambda([](int32 ErrorCode, FString ErrorMessage)
{
// Do something if GetUserStatItems has an error
})
, Offset
, Limit);
Statistic statistic = AccelByteSDK.GetClientRegistry().GetApi().GetStatistic();
string[] statCodes = { "Stat Code 1", "Stat Code 2" };
string[] tags = { "Tag 1", "Tag 2", "Tag 3" };
statistic.GetUserStatItems(statCodes, tags, result =>
{
if (result.IsError)
{
// Do something if GetUserStatItemshas an error
Debug.Log($"Error GetUserStatItems, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if GetUserStatItemshas is successful
});
userStatisticService := &social.UserStatisticService{
Client: factory.NewSocialClient(&repository.ConfigRepositoryImpl{}),
TokenRepository: &repository.TokenRepositoryImpl{},
}
namespace := "mygame"
userId := "myuserid"
statCodes := "statcode1,statcode2"
tags := "tag1,tag2,tag3"
input := &user_statistic.GetUserStatItemsParams{
Namespace: namespace,
UserID: userId,
StatCodes: &statCodes,
Tags: &tags,
}
result, err := userStatisticService.GetUserStatItemsShort(input)
import accelbyte_py_sdk.api.social as social_service
result, error = social_service.public_query_user_stat_items(
limit=20,
offset=0,
stat_codes=["StatCode1", "StatCode2"],
tags=["Tag1", "Tag2"],
user_id="********************************",
)
if error:
exit(error)
UserStatistic userStatisticWrapper = new UserStatistic(sdk);
List <String> statCodes = List.of("Stat Code 1", "Stat Code 2");
List <String> tags = List.of("Tag 1", "Tag 2", "Tag 3");
String userId = "<user-id>";
UserStatItemPagingSlicedResult response;
try {
response = userStatisticWrapper.getUserStatItems(GetUserStatItems.builder()
.namespace("<namespace>")
.userId(userId)
.offset(0)
.limit(100)
.statCodes(String.join(",", statCodes))
.tags(String.join(",", tags))
.build());
} catch (Exception e) {
// Do something when failed
return;
}
if (response == null) {
// Null response from server
} else {
// Do something if success
}
string[] statCodes = new[] { "Stat Code 1", "Stat Code 2" };
string[] tags = new[] { "Tag 1", "Tag 2", "Tag 3" };
string userId = "<user-id>";
var response = sdk.Social.UserStatistic.GetUserStatItemsOp
.SetOffset(0)
.SetLimit(100)
.SetStatCodes(String.Join(",", statCodes))
.SetTags(String.Join(",", tags))
.Execute(sdk.Namespace, userId);
if (response != null)
{
//do something if success
}
Retrieving Global Statistics
Similar to the user statistics, you can also display global statistics anywhere within the game UI easily. You can choose to display all of the global statistics that you have or choose a specific stat using the statcode.
To use the SDK functionality, refer to the snippets below.
Get Global Stats Using a Statcode
- Unreal
- Unity
- Go Extend SDK
- Python Extend SDK
- Java Extend SDK
- C# Extend SDK
FApiClientPtr ApiClient = FMultiRegistry::GetApiClient();
FString StatCode = "Your Global Statistic Code";
ApiClient->Statistic.GetGlobalStatItemsByStatCode(StatCode
, THandler<FAccelByteModelsGlobalStatItemValueResponse>::CreateLambda([](FAccelByteModelsGlobalStatItemValueResponse Result)
{
// Do something if GetGlobalStatItemsByStatCode is successful
})
, FErrorHandler::CreateLambda([](int32 ErrorCode, FString ErrorMessage)
{
// Do something if GetGlobalStatItemsByStatCode has an error
}));
Statistic statistic = AccelByteSDK.GetClientRegistry().GetApi().GetStatistic();
string statCode = "Your Global Statistic Code";
statistic.GetGlobalStatItemsByStatCode(statCode, result =>
{
if (result.IsError)
{
// Do something if GetGlobalStatItemsByStatCode has an error
Debug.Log($"Error GetGlobalStatItemsByStatCode, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if GetGlobalStatItemsByStatCode is successful
});
globalStatisticService := &social.GlobalStatisticService{
Client: factory.NewSocialClient(&repository.ConfigRepositoryImpl{}),
TokenRepository: &repository.TokenRepositoryImpl{},
}
namespace := "mygame"
statCode := "mystatcode"
input := &global_statistic.GetGlobalStatItemByStatCodeParams{
Namespace: namespace,
StatCode: statCode,
}
result, err := globalStatisticService.GetGlobalStatItemByStatCodeShort(input)
import accelbyte_py_sdk.api.social as social_service
result, error = social_service.get_global_stat_item_by_stat_code_1(
stat_code="YourGlobalStatisticCode",
)
if error:
exit(error)
GlobalStatistic globalStatisticWrapper = new GlobalStatistic(sdk);
String statCode = "GlobalStatCode";
GlobalStatItemInfo response;
try {
response = globalStatisticWrapper.getGlobalStatItemByStatCode(GetGlobalStatItemByStatCode.builder()
.namespace("<namespace>")
.statCode(statCode)
.build());
} catch (Exception e) {
// Do something when failed
return;
}
if (response == null) {
// Null response from server
} else {
// Do something if success
}
string statCode = "GlobalStatCode";
var response = sdk.Social.GlobalStatistic.GetGlobalStatItemByStatCodeOp
.Execute(sdk.Namespace, statCode);
if (response != null)
{
//do something if success
}