典型用例场景
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 在门户网站上查看玩家资料
关于如何查看玩家资料,您可以点击这里.