Typical Use Case
1. Case 1: Identify Players and Give Them Profile
Related to FAS, PlayerAuth, PlayerInfo.
Unicorn studio is planning to make a new online game. At present, the tricky thing is that they have not decided which account service to integrate, and integrating is also a very heavy task. So they want a simple account service temporarily which gives players identities and some basic profile (such as nickname, avatar, etc.) in the game, to help the game to go through the whole gameplay process. When the game reaches the publishing stage, a formal account service will be integrated into the game project, and there is no need to change too much existing code.
The studio uses PGOS to get through this.
1.1 Log in with a Fake Account Service (FAS)
Use a fake account service to obtain a fake open id and its token.
#include "PgosSDKCpp.h"
void SomeUObjectClass::LoginAccount()
{
auto FAS = IPgosSDKCpp::Get().GetClientFakeAccountAPI(); // fetch FAS service.
if (FAS)
{
// Log in account service.
FAS->AutoLogin([](const FPgosResult& Ret, const FClientFasLoginInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnLoginSuccess: open_id=%s"), *Data->open_id);
LoginPGOS(Data->open_id, Data->token); // Log in PGOS service.
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnLoginFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
1.2 Log in PGOS Service
Use fake open id and its token to log in PGOS and obtain player_id
& is_first_login
, you can save them for further use.
#include "PgosSDKCpp.h"
// After a successful account login
void SomeUObjectClass::LoginPGOS(const FString& OpenID, const FString& Token)
{
auto PAAPI = IPgosSDKCpp::Get().GetClientPlayerAuthAPI();
if (PAAPI)
{
FString TitleRegionID = TEXT("euff_xxx_666_dev"); // obtain your region id from pgos portal.
TMap<FString, FString> ExtraParam;
FClientLoginPGOSParams Params;
Params.account_open_id = LoginInfo->open_id;
Params.account_token = LoginInfo->token;
Params.title_region_id = TitleRegionID;
Params.extra_param = {};
Params.custom_data = "";
PAAPI->LoginPGOS(OpenID, Token, TitleRegionID, ExtraParam, [](const FPgosResult& Ret, const FClientAuthenticationInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnLoginPGOSSuccess: player_id=%s"), *Data->player_id);
MyPlayerID = Data->player_id; // save the currently logged in player ID for later possible use.
IsFirstLogin = Data->is_first_login; // save the flag whether the player login the region for the first time.
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnLoginPGOSFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
1.3 Query Player Info and Render In Game
If It's the first time the player login the region, give the player a default nickname and avatar URI.
#include "PgosSDKCpp.h"
void SomeUObjectClass::RenderPlayerPanelUI()
{
auto PPAPI = IPgosSDKCpp::Get().GetClientPlayerProfileAPI();
if (!PPAPI) return;
// If It's the first time the player login the region,
// Give the player a default nickname and avatar URI.
if(IsFirstLogin)
{
// Set a random nickname to my player.
FString DisplayName = TEXT("ConfusedGirl"); // you can generate a random nickname.
PPAPI->SetMyName(DisplayName, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SetMyName Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SetMyName Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
// Set a random avatar uri to my player.
FString AvatarURI = TEXT("Icon_001"); // you can generate a random URI.
PPAPI->SetMyAvatar(AvatarURI, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SetMyAvatar Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SetMyAvatar Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
RenderPlayerHUD(DisplayName, AvatarURI);
}
else
{
// Get the existing player information.
PPAPI->GetMyInfo([](const FPgosResult& Ret, const FPlayerDetail* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetMyInfo Success"));
RenderPlayerHUD(Data->display_name, Data->avatar_uri);
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetMyInfo Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
void RenderPlayerHUD(const FString& DisplayName, const FString& AvatarURI)
{
// perform the actual rendering work...
}
1.4 View Player Profile on the Portal
For how to view the player profile, you can check here.
2. Case Set Player Attributes
Related to PlayerData.
Unicorn studio feels that only basic player profile is not enough to realize the gameplay. As a shooting game, they need to give player more attributes: player MMR (used for matchmaking), player match count, player win count, character currently using, etc.
The studio uses PGOS to get through this.
2.1 Customize Data Template
Log in to the web portal, enter the console and go to Players > Data Template:
According to the current needs, the studio has added 4 items to the data template.
- mmr: Integer value used for matchmaking. It is only used on the server(like DS) and invisible to player clients, so
Server Only
is ✔. - matches: Integer value used to record player matches. It is updated by the server and visible to all player clients, so
Client Public
is ✔. - wins: Integer value used to record player wins. It is updated by the server and visible to all player clients, so
Client Public
is ✔. - character: String value used to record the id of the character that the player using. It allows to be updated by the owner player and is visible to all player clients, so
Client Writable
is ✔ andClient Public
is ✔.
After that, when a player is created, it will automatically have these 4 attributes (the values are the default values).
2.2 Access Player Attributes (Player Data)
In this game, the player can change his character before entering the lobby, and the game client can implement this feature by updating the value of the character
item in player data.
#include "PgosSDKCpp.h"
void SomeUObjectClass::ChangeMyCharacter(const FString& NewCharacterID)
{
auto PPAPI = IPgosSDKCpp::Get().GetClientPlayerProfileAPI();
if (!PPAPI) return;
PPAPI->SetOneOfMyKVData(TEXT("character"), NewCharacterID, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SetOneOfMyKVData Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SetOneOfMyKVData Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
The game has a lobby scene: the players in the lobby will appear as their current using characters, and there will be UI text showing their matches
and wins
. So when the player enters the lobby, the game client will query these data and render it to the game.
#include "PgosSDKCpp.h"
// query player data from PGOS backend.
void SomeUObjectClass::QueryPlayerAppearance(const TArray<FString>& PlayerIds)
{
auto PPAPI = IPgosSDKCpp::Get().GetClientPlayerProfileAPI();
if (!PPAPI) return;
TArray<FString> QueryKeys = {TEXT("character"), TEXT("matches"), TEXT("wins")};
PPAPI->BatchGetPlayerKVData(PlayerIds, QueryKeys, [](const FPgosResult& Ret, const FBatchGetPlayerKVDataRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("BatchGetPlayerKVData Success"));
ParsePlayerAppearance(Data);
}
else
{
UE_LOG(LogTemp, Log, TEXT("BatchGetPlayerKVData Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
// parse player data from PGOS backend.
void SomeUObjectClass::ParsePlayerAppearance(const FBatchGetPlayerKVDataRsp* Data)
{
if(!Data) return;
for (auto& Item : Data->data)
{
auto& PlayerID = Item.Key;
auto CharacterID = Item.Value.Find(TEXT("character"));
auto Matches = Item.Value.Find(TEXT("matches"));
auto Wins = Item.Value.Find(TEXT("wins"));
if(CharacterID && Matches && Wins)
{
RenderLobbyPlayer(PlayerID, *CharacterID, *Matches, *Wins);
}
}
}
// perform the actual rendering work.
void SomeUObjectClass::RenderLobbyPlayer(const FString& PlayerID, const FString& CharacterID, int32 Matches, int32 Wins)
{
...
}
When every player in the lobby is ready, the lobby owner will start the battle/match, and when the battle/match is over, the game server (DS) can update their player data based on the performance of each player.
#include "PgosSDKCpp.h"
// do settlement after the battle.
void SomeUObjectClass::DoSettlement(const TArray<FString>& WinerPlayerIds, const TArray<FString>& LoserPlayerIds)
{
auto PPAPI = IPgosSDKCpp::Get().GetServerPlayerProfileAPI();
if (!PPAPI) return;
TArray<FPlayerKVDataGroup> Kvdatas; // data rewritten: mmr
TArray<FServerPlayerKVDataIncrements> Increments; // data increment: matches, wins
for (const FString& WinerPlayerID: WinerPlayerIds)
{
FPlayerKVDataGroup DataGroup;
DataGroup.player_id = WinerPlayerID;
DataGroup.kvdata.Add(TEXT("mmr"), CalcNewMMR(WinerPlayerID)); // mmr = NewMMR
Kvdatas.Emplace(DataGroup);
FServerPlayerKVDataIncrements DataIncrement;
DataIncrement.player_id = WinerPlayerID;
DataIncrement.increments.Add(TEXT("matches"), 1); // matches += 1;
DataIncrement.increments.Add(TEXT("wins"), 1); // wins += 1;
Increments.Emplace(DataIncrement);
}
for (const FString& LoserPlayerID: LoserPlayerIds)
{
FPlayerKVDataGroup DataGroup;
DataGroup.player_id = WinerPlayerID;
DataGroup.kvdata.Add(TEXT("mmr"), CalcNewMMR(WinerPlayerID)); // mmr = NewMMR
Kvdatas.Emplace(DataGroup);
FServerPlayerKVDataIncrements DataIncrement;
DataIncrement.player_id = LoserPlayerID;
DataIncrement.increments.Add(TEXT("matches"), 1); // matches += 1;
Increments.Emplace(DataIncrement);
}
// update mmr for players.
PPAPI->BatchSetPlayerKVData(Kvdatas, [](const FPgosResult& Ret, const FServerBatchSetPlayerKVDataRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("BatchSetPlayerKVData Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("BatchSetPlayerKVData Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
// update matches and wins for players.
PPAPI->BatchIncrPlayerKVData(Increments, [](const FPgosResult& Ret, const FServerBatchIncrPlayerKVDataRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("BatchIncrPlayerKVData Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("BatchIncrPlayerKVData Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
// recalculate player's MMR
int32 SomeUObjectClass::CalcNewMMR(const FString& PlayerID)
{
...
}
2.3 View Player Profile on the Portal
For how to view the player profile, you can check here.