典型用例场景
1. 案例1:识别玩家并为其创建档案
相关内容参见 FAS, PlayerAuth, PlayerInfo. Unicorn Studio 正在计划开发一款新的在线游戏。目前棘手的问题是他们尚未决定要集成哪个账号服务,而且集成工作本身也是一项繁重的任务。因此他们暂时需要一个简单的账号服务,为游戏中的玩家提供身份标识和一些基本档案信息(如昵称、头像等),以帮助游戏完成整个游戏流程。当游戏进入发布阶段时,将会在游戏项目中集成一个正式的账号服务,且无需对现有代码做太多更改。 该工作室使用 PGOS 来实现这一目标。
1.1 使用临时账号服务(FAS)登录
使用临时账号服务获取临时 open ID 及其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 登录PGOS服务
使用模拟open id及其token登录PGOS并获取player_id和is_first_login,您可以保存它们以供后续使用。
#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 查询玩家信息并在游戏中渲染
如果玩家首次登录该区域,则为玩家分配默认昵称和头像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 在门户网站上查看玩家资料
关于如何查看玩家资料,您可以点击这里.
2. 案例:设置玩家属性
相关内容请参考玩家数据。 独角兽工作室认为仅有基础的玩家资料还不足以实现游戏玩法。作为一款射击游戏,他们需要为玩家添加更多属性:玩家MMR(用于匹配)、玩家比赛场次、玩家胜利场次、当前使用的角色等。 该工作室使用PGOS来实现这一目标。
2.1 自定义数据模板
登录网页门户,进入控制台并前往玩家 > 数据模板:

根据当前需求,工作室已在数据模板中添加了4个项目。
- mmr:用于匹配的整数值。它仅在服务器(如DS)上使用且对玩家客户端不可见,因此勾选 
Server Only。 - matches:用于记录玩家对局数的整数值。它由服务器更新且对所有玩家客户端可见,因此勾选 
Client Public。 - wins:用于记录玩家胜利数的整数值。它由服务器更新且对所有玩家客户端可见,因此勾选 
Client Public。 - character:用于记录玩家正在使用的角色ID的字符串值。它允许由拥有者玩家更新且对所有玩家客户端可见,因此勾选 
Client Writable和Client Public。 完成后,当创建一个玩家时,它将自动拥有这4个属性(值为默认值)。 
2.2 访问玩家属性(玩家数据)
在这个游戏中,玩家可以在进入房间前更换自己的角色,游戏客户端可以通过更新玩家数据中 character 项的值来实现这个功能。
#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);
        }
    });
}
游戏有一个房间场景:房间中的玩家将以其当前角色形象出现,并且会显示他们的对战次数和胜利次数的UI文本。因此当玩家进入房间时,游戏客户端会查询这些数据并将其渲染到游戏中。
#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)
{
    ...
}
当房间内的所有玩家都准备就绪后,房主将开始战斗/对局,当战斗/对局结束时,游戏服务器(DS)可以根据每个玩家的表现更新其玩家数据。
#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 在门户网站上查看玩家资料
关于如何查看玩家资料,您可以点击这里.