lxg 发表于 2023-4-18 13:56

游戏开发工具箱(3) 开箱即用的云游戏后端——PlayFab快速 ...

前言

你是一位有些游戏客户端开发经验的开发者或爱好者,有空的时候也想自己开发几款游戏发布到线上跑跑。但是对实现游戏服务端并不熟悉。
网上是有一些开源的游戏客户端+服务端双端的框架,比如——ET,但是所有的功能仍然需要新造一遍轮子。我们想要的游戏后端功能其实很简单,无外乎——登录、物品管理、成就、排行榜、以及内购活动,如果自带埋点统计功能,就完美了。
有没有一个开箱即用的游戏后端服务,已经内置好了这些基础的功能,开发者只要轻松修改一下后台的配置,简单写几行胶水性的代码,就能在客户端中快速接入这些功能?而且完全不用担心网络延迟、动态扩容、DDoS攻击等专业性的问题?
这样的游戏后端服务现在已经趋于成熟,PlayFab就是让你省事省心的一款云托管游戏后端服务。
这篇文章分为上、中、下三篇。
上篇介绍PlayFab的基本概念,手把手带大家熟悉PlayFab的基本操作和Unity下的接口调用;中篇带大家一览官方的案例教程;下篇准备带大家学习,开箱即用的Unity端的插件Cross-platform Backend Solution,一览PlayFab在开发中的全貌(不过下篇可能会延期)。
简介

PlayFab是一个全方位的游戏后端云服务,为开发者提供了一系列游戏开发所需的功能,例如玩家管理、云端存储、虚拟商品交易、统计分析等。
它于2018年被微软收购并被整合到了Azure云服务当中。
目前它已经支持了超过5000多款游戏,托管了超过25亿玩家的账户。像微软的《我的世界》、育碧的《彩虹六号:围攻》、Hello Game的《无人深空》等大作也都采用了它作为可靠的服务后端。



服务于众多游戏的PlayFab

接下来我们将一起探索——如何使用PlayFab的托管服务来轻松构建和管理游戏后端。
各位乘客请坐稳扶好,我们即将发车。
快速开始

注册PlayFab

在使用PlayFab之前,我们需要先注册一个PlayFab账号:https://developer.playfab.com/en-us/sign-up



注册PlayFab账号

需要注意一点,尽量使用邮箱地址注册而不是“Sign in with Microsoft”,使用后者在未来使用CloudScript时会遇到一些账号和权限的问题。
然后在邮箱中查收激活邮件,点击VERIFY YOUR EMAIL ADDRESS按钮。



激活邮件

接着,在跳转页面填写注册信息登录即可。



登录PlayFab

成功登录后,我们就第一次进入到了PlayFab的后台:



第一次进入到PlayFab后台

新建游戏后端项目

在第一次进入PlayFab后台时,PlayFab已经为我们默认生成了一个名为“My Game”的后台项目模板。
不过,还是让我们从手动创建一个新的游戏后台项目开始吧。
点击My Game Studio页签的右侧的“...”按钮,在弹出的列表中点选“New title”。



新建一个游戏后台项目

填写新项目的名称等信息(比如这里我命名新项目为HelloPlayfab),点击Create title按钮创建项目。



填写new title的名称

项目创建成功后,会自动跳转回最初的后台页面,现在就可以看到我们的新项目了。



新项目创建成功

红框圈出的新项目页签的右下角的ID:91135,未来在Unity客户端中会用到,需要留意一下。
点击HelloPlayfab页签,我们将进入到HelloPlayfab的项目后台。左侧是一些功能菜单,右侧是信息面板。



HelloPlayfab的项目后台

现在我们已经成功创建了HelloPlayfab项目的游戏后端,接下来我们将在Unity客户端项目中接入PlayFab的SDK,完成第一次客户端与游戏后端的网络通信。
新建Unity项目

我们先新建一个Unity工程,命名为HelloPlayfab。(你也可以直接打开自己想要接入PlayFab的Unity工程)



创建Unity工程

为Unity项目接入PlayFab SDK

我们从 https://learn.microsoft.com/zh-cn/gaming/playfab/sdks/unity3d/ 页面的下载链接中,点选“快速下载链接:PlayFab SDK的Unity编辑器扩展”。注意,只用下载“PlayFab SDK的Unity编辑器扩展”这一项就可以,它会在Unity中自动下载和管理Unity PlayFab SDK。



下载PlayFab SDK的Unity编辑器扩展

将下载完成的PlayFabEditorExtensions.unitypackage导入到项目工程(双击该文件)。
导入成功后,Unity中会自动弹出PlayFab的注册和登录页。前面我们已经注册过了,所以点击左下角的LOG IN按钮(看上去是个标签,实际是个按钮)。



点击LOG IN按钮

在登录页面填写自己的邮箱和前面注册的密码,点击LOG IN按钮。



填写账号信息

登录完成后,该页面会提示我们需要安装SDK,我们点击Install PlayFab SDK按钮。



点击Install PlayFab SDK

插件会自动下载最新的PlayFab SDK并完成安装,我们在安装完成的页面点击SET MY TITLE按钮,来完成对PlayFab后端项目的连接。



点击SET MY TITLE

我们选择STUDIO下的我们在上文【新建后端项目】中创建的HelloPlayfab项目,将REQUEST TYPE设置为Unity Web Request。



设置与游戏后端项目的连接

现在,我们已经完成了Unity项目对PlayFab SDK的接入,接下来就让我们开始写第一行Hello PlayFab吧!
Hello PlayFab

我们在工程中新建一个名为PlayFabHello.cs的脚本文件,将其挂载到场景中新建的名为PlayFabHello的GameObject上。



创建并挂载PlayFabHello脚本到GameObject上

将PlayFabHello.cs的内容替换为如下的代码:
using System;
using PlayFab;
using PlayFab.ClientModels;
using UnityEngine;

public class PlayFabHello : MonoBehaviour
{   
    public void Start()
    {
      if (string.IsNullOrEmpty(PlayFabSettings.staticSettings.TitleId))
      {
            //如果没有设置TitleId,就设置一个默认值
            PlayFabSettings.staticSettings.TitleId = "91135";
      }
      
      var request = new LoginWithCustomIDRequest { CustomId = "GettingStartedGuide", CreateAccount = true};
      PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure);
    }

    private void OnLoginSuccess(LoginResult result)
    {
      Debug.Log($"Hello PlayFab! {result.PlayFabId}");
    }

    private void OnLoginFailure(PlayFabError error)
    {
      Debug.LogError(error.GenerateErrorReport());
    }
}
这段代码完成了为玩家在游戏后端进行注册和登录的功能。
点击运行游戏,如果从Console窗口看到如下日志,则第一次客户端同游戏云后台通信就成功了。



Hello PlayFab! 的日志

让我们回到游戏的后台,会看到已经有了第一个用户的登录统计信息了。



第一个用户登录的信息

选择页面左侧的Players页签,在右侧的信息页点击Search按钮,在查询到的玩家列表中点选第一个条目。



在Player页面中搜索自己

在弹出的页面中即可看到这个玩家(也就是我们自己)所有相关的数据信息了。玩家信息页的条目很多,第一次看到会觉得有点复杂,不过里面的概念和内容我们即将在后文中进行讲解。



玩家信息页的内容

截至目前,我们完成了PlayFab后端项目的创建,以及在Unity工程中SDK的接入,并且完成了第一次客户端向服务后端的注册和登录请求。在开始真正使用PlayFab提供的各种功能前,让我们先来了解一些PlayFab的基本概念,这将有助于我们更好地使用它。
基本概念

PlayFab数据(PlayFab Data)

PlayFab数据主要包含了:游戏数据(Title Data)、玩家数据(Player Data)、角色数据(Character Data)和组数据(Group Data)等数据类型。同时提供了实体(Entity)、内容分发网络(CDN)以及Webhooks等数据管理和配置的功能。
游戏数据(Title Data)

游戏数据是一组纯文本的键值对(Key/Value Pairs,简称KVP),用于在服务器后端存储和管理游戏的配置信息。游戏数据又细分为基本游戏数据(Title Data)和内部游戏数据(Title Internal Data),前者客户端可以直接访问,而后者只能被PlayFab后端或者开发者自己部署的经过授权的游戏服务所访问。
玩家数据(Player Data)

玩家数据,也称为User Data,在PlayFab系统里,User Data与Player Data是完全等价的。
玩家数据是应用于单个玩家或玩家组(共享)的数据,PlayFab提供两种存储玩家数据的方式:

[*]实体(Entity):允许跨玩家、角色和组将数据存储在对象和文件中。
[*]玩家数据(UserData):由PlayFab存储为键值对(KVP)形式的信息。
官方推荐新的项目使用实体的方式。
玩家数据的访问权限有三种类型:

[*]客户端:游戏客户端可以直接增删查改该类型的数据。
[*]只读:服务器可以增删查改该类型的数据,而客户端只能读取,不能进行操作。
[*]内部:只有服务器能增删查改的数据,客户端是无法访问的。
PlayFab内置了一些基本的玩家数据集合和系统,比如玩家档案(Player Profiles)、玩家背包(Player Inventory)、玩家详情(Player Details)、玩家封号系统(Player Ban System)、玩家细分(Player Segments)等。这些内容可以从文末的扩展阅读中进一步了解和学习。
实体(Entity)

实体是PlayFab API在操作时最基本的可寻址“内容”。 每个实体都有一个“类型”和 ID,它们共同组成了该实体的唯一标识。 有些类型的实体是PlayFab内置的,例如命名空间(namespace)、游戏(title)、组(group)、所有游戏共享玩家实体(master_player_account)和 本款游戏玩家实体(title_player_account)等。
每个实体都有一个配置文件,其中包含该实体拥有的各种资源。 例如,对象、文件、语言设置、策略等。
实体之间存在父子级关系,这些关系决定了实体间资源访问的权限。
PlayFab经济(PlayFab Economy)

PlayFab经济系统可以帮助游戏开发者轻松创建、管理和优化游戏中的虚拟经济系统。它可以管理虚拟货币、道具、消费品等虚拟物品,并提供了像收入跟踪、道具管理、虚拟商店等的相关功能。目前PlayFab已经提供了最新的v2预览版,不过在本文中,我们先从原始版本进行讲解。
PlayFab经济系统涉及到了如下的关键概念:

[*]目录(Catalog):目录是所有可用虚拟物品的列表。
[*]目录项(Catalog Item):目录项是代表了所有可用的虚拟商品,从捆绑包到宝箱,都属于它的范畴。
[*]背包/库存(Inventory):每个玩家账号都有一个背包,它包含了玩家拥有的所有物品实例以及获得这些物品的历史记录。
[*]商店(Store):商店是目录的子集,在商店中的物品售价会替代原始目录中的售价。
[*]商店细分(Store Segments):为特定分组的玩家提供的不同的虚拟商品目录。
[*]虚拟货币(Virtual Currency):v1提供10余种虚拟货币(v2提供1000种)。虚拟货币可以从目录或商店中购买虚拟物品。
[*]用户生成内容(User Generate Content):只有v2提供,玩家能够创建、上传和搜索的游戏内容。
PlayFab分析(PlayFab Analytics)

PlayFab还提供了一系列的工具来帮助开发者进行数据统计和分析,甚至进行A/B测试(A/B Test)。



就此打住

我猜大家看到这里,已经手痒难耐,不想再看这些枯燥的概念了。那就让我们直接上手快速地使用一下PlayFab提供的基础API吧。
速览API

现在让我们快速过一遍PlayFab的常用API。这主要涉及到两个脚本。
准备Unity工程

打开我们在[快速开始->Hello PlayFab]一节中创建的Unity项目,在工程中新建一个脚本命名为PlayFabUsage.cs,将默认生成的代码替换为如下内容:
using System;
using System.Collections.Generic;
using PlayFab;
using PlayFab.ClientModels;
using PlayFab.DataModels;
using UnityEngine;

public class PlayFabUsage
{
    #region 使用游戏数据(TitleData)

    public static void GetTitleData()
    {
      var request = new PlayFab.ClientModels.GetTitleDataRequest();
      PlayFab.PlayFabClientAPI.GetTitleData(request, result =>
            {
                foreach (var dataPair in result.Data)
                {
                  Debug.Log($"GetTitleData == {dataPair.Key} == {dataPair.Value}");
                }
            },
            error =>
            {
                Debug.LogError($"GetTitleData == {error.GenerateErrorReport()}");
            }
      );
    }

    #endregion
   
    #region 使用用户数据(UserData)

    public static void UpdateUserDataRequest(string userId, string loginTime)
    {
      var updateUserDataRequest = new PlayFab.ClientModels.UpdateUserDataRequest
      {
            Data = new Dictionary<string, string>
            {
                {"UserId", userId},
                {"LoginTime", loginTime}
            }
      };
      
      PlayFab.PlayFabClientAPI.UpdateUserData(updateUserDataRequest, result =>
            {
                Debug.Log($"UpdateUserDataRequest == Success {result.DataVersion}");
            },
            error =>
            {
                Debug.LogError($"UpdateUserDataRequest == {error.GenerateErrorReport()}");
            }
      );
    }

    public static void GetUserDataRequest()
    {
      var request = new PlayFab.ClientModels.GetUserDataRequest();
      
      PlayFab.PlayFabClientAPI.GetUserData(request, result =>
            {
                foreach (var dataPair in result.Data)
                {
                  Debug.Log($"GetUserDataRequest == {dataPair.Key} == {dataPair.Value.Value}");
                }
            },
            error =>
            {
                Debug.LogError($"GetUserDataRequest == {error.GenerateErrorReport()}");
            }
      );
    }
   
    #endregion
   
    #region 使用实体数据(EntityData)

    public static void SetEntityObject(string entityKey, string entityType)
    {
      var debugData = new Dictionary<string, object>()
      {
            {"Atk", 100},
            {"Hp", 1000},
      };
      var dataList = new List<SetObject>()
      {
            new SetObject()
            {
                ObjectName = "MyPlayerData",
                DataObject = debugData,
            },
      };

      var newSetObjectRequest = new SetObjectsRequest()
      {
            Entity = new PlayFab.DataModels.EntityKey(){Id = entityKey, Type = entityType},
            Objects = dataList,
      };
      PlayFabDataAPI.SetObjects(newSetObjectRequest, setResult =>
            {
                Debug.Log($"SetEntityObject == Success with version {setResult.ProfileVersion}");
            },
            error =>
            {
                Debug.LogError(error.ErrorMessage);
            });
    }
   
    public static void GetEntityObject(string entityKey, string entityType)
    {
      var newGetObjectRequest = new GetObjectsRequest()
      {
            Entity = new PlayFab.DataModels.EntityKey(){Id = entityKey, Type = entityType},
      };
      PlayFabDataAPI.GetObjects(newGetObjectRequest, getResult =>
            {
                Debug.Log($"GetEntityObject == Success with version {getResult.ProfileVersion}");
                foreach (var dataPair in getResult.Objects)
                {
                  Debug.Log($"GetEntityObject == {dataPair.Key} == {dataPair.Value.DataObject}");
                }
            },
            error =>
            {
                Debug.LogError(error.ErrorMessage);
            });
    }

    #endregion

    #region 购买物品

    public static void PurchaseHealthPotion(Action<string> onPurchaseFinish)
    {
      var purchaseItemRequest = new PlayFab.ClientModels.PurchaseItemRequest
      {
            CatalogVersion = "Items",
            ItemId = "HealthPotion",
            Price = 10,
            VirtualCurrency = "CN"
      };
      
      PlayFab.PlayFabClientAPI.PurchaseItem(purchaseItemRequest, result =>
            {
                Debug.Log($"PurchaseHealthPotion == {result.Request.GetType().Name} Success {result.Items.ItemId}");
                onPurchaseFinish?.Invoke(result.Items.ItemInstanceId);
            },
            error =>
            {
                Debug.LogError($"PurchaseHealthPotion == {error.GenerateErrorReport()}");
                onPurchaseFinish?.Invoke(string.Empty);
            }
      );
    }

    public static void GetUserInventory()
    {
      var request = new PlayFab.ClientModels.GetUserInventoryRequest();
      
      PlayFab.PlayFabClientAPI.GetUserInventory(request, result =>
            {
                foreach (var item in result.Inventory)
                {
                  Debug.Log($"GetUserInventory == {item.ItemId} == {item.DisplayName} : {item.ItemInstanceId} count: {(item.RemainingUses.HasValue ? item.RemainingUses.Value : 0)}");
                }
            },
            error =>
            {
                Debug.LogError($"GetUserInventory == {error.GenerateErrorReport()}");
            }
      );
    }

    public static void ConsumePotion(string itemInstanceId)
    {
      var consumeItemRequest = new PlayFab.ClientModels.ConsumeItemRequest
      {
            ItemInstanceId = itemInstanceId,
            ConsumeCount = 1
      };
      
      PlayFab.PlayFabClientAPI.ConsumeItem(consumeItemRequest, result =>
            {
                Debug.Log($"ConsumePotion == {result.Request.GetType().Name}-{itemInstanceId} Success");
            },
            error =>
            {
                Debug.LogError($"ConsumePotion == {error.GenerateErrorReport()}");
            }
      );
    }

    #endregion

    #region 使用排行榜
   
    public static void SubmitHighScore(int highScore)
    {
      var request = new PlayFab.ClientModels.UpdatePlayerStatisticsRequest
      {
            Statistics = new List<PlayFab.ClientModels.StatisticUpdate>
            {
                new PlayFab.ClientModels.StatisticUpdate
                {
                  StatisticName = "Daily High Score",
                  Value = highScore
                }
            }
      };
      
      PlayFab.PlayFabClientAPI.UpdatePlayerStatistics(request, result =>
            {
                Debug.Log($"SubmitHighScore == {result.Request.GetType().Name} Success");
            },
            error =>
            {
                Debug.LogError($"SubmitHighScore == {error.GenerateErrorReport()}");
            }
      );
    }
   
    public static void GetLeaderboard(int highScoreCount)
    {
      var request = new PlayFab.ClientModels.GetLeaderboardRequest
      {
            StatisticName = "Daily High Score",
            StartPosition = 0,
            MaxResultsCount = 10
      };
      
      PlayFab.PlayFabClientAPI.GetLeaderboard(request, result =>
            {
                Debug.Log($"GetLeaderboard == {result.Request.GetType().Name} Success");
               
                foreach (var player in result.Leaderboard)
                {
                  Debug.Log($"GetLeaderboard == {player.Position} == {player.PlayFabId} == {player.StatValue}");
                }
            },
            error =>
            {
                Debug.LogError($"GetLeaderboard == {error.GenerateErrorReport()}");
            }
      );
    }

    public static void GetLeaderboardAroundPlayer(int highScoreCount)
    {
      var request = new PlayFab.ClientModels.GetLeaderboardAroundPlayerRequest
      {
            StatisticName = "Daily High Score",
            MaxResultsCount = highScoreCount
      };
      
      PlayFab.PlayFabClientAPI.GetLeaderboardAroundPlayer(request, result =>
            {
                Debug.Log($"GetLeaderboardAroundPlayer == {result.Request.GetType().Name} Success");
               
                foreach (var player in result.Leaderboard)
                {
                  Debug.Log($"GetLeaderboardAroundPlayer == {player.Position} == {player.PlayFabId} == {player.StatValue}");
                }
            },
            error =>
            {
                Debug.LogError($"GetLeaderboardAroundPlayer == {error.GenerateErrorReport()}");
            }
      );
    }

    #endregion
}
然后将我们之前创建的PlayFabHello.cs的代码替换为如下:
using System;
using PlayFab;
using PlayFab.ClientModels;
using UnityEngine;

public class PlayFabHello : MonoBehaviour
{
    private string _loginUserId= string.Empty;
    private string _loginTime = string.Empty;
    private string _itemInstanceId = string.Empty;
   
    private string _entityId = string.Empty;
    private string _entityType = string.Empty;
   
    public void Start()
    {
      if (string.IsNullOrEmpty(PlayFabSettings.staticSettings.TitleId))
      {
            //如果没有设置TitleId,就设置一个默认值
            PlayFabSettings.staticSettings.TitleId = "91135";
      }
      
      var request = new LoginWithCustomIDRequest { CustomId = "GettingStartedGuide", CreateAccount = true};
      PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure);
    }

    public void Update()
    {
      if (Input.GetKeyDown(KeyCode.A))
      {
            PlayFabUsage.GetTitleData();
      }
      
      if (Input.GetKeyDown(KeyCode.B))
      {
            if(string.IsNullOrEmpty(_loginUserId) || string.IsNullOrEmpty(_loginTime))
            {
                Debug.LogError("请先登录");
                return;
            }
            
            PlayFabUsage.UpdateUserDataRequest(_loginUserId, _loginTime);
      }

      if (Input.GetKeyDown(KeyCode.C))
      {
            PlayFabUsage.GetUserDataRequest();
      }

      if(Input.GetKeyDown(KeyCode.D))
      {
            if(string.IsNullOrEmpty(_entityId) || string.IsNullOrEmpty(_entityType))
            {
                Debug.LogError("请先登录");
                return;
            }
            
            PlayFabUsage.SetEntityObject(_entityId, _entityType);
      }

      if (Input.GetKeyDown(KeyCode.E))
      {
            if(string.IsNullOrEmpty(_entityId) || string.IsNullOrEmpty(_entityType))
            {
                Debug.LogError("请先登录");
                return;
            }
            
            PlayFabUsage.GetEntityObject(_entityId, _entityType);
      }

      if (Input.GetKeyDown(KeyCode.F))
      {
            PlayFabUsage.PurchaseHealthPotion(itemInstanceId =>
            {
                _itemInstanceId = itemInstanceId;   
            });
      }

      if (Input.GetKeyDown(KeyCode.G))
      {
            PlayFabUsage.GetUserInventory();
      }
      
      if(Input.GetKeyDown(KeyCode.H))
      {
            if(string.IsNullOrEmpty(_itemInstanceId))
            {
                Debug.LogError("请先购买物品");
                return;
            }
            
            PlayFabUsage.ConsumePotion(_itemInstanceId);
      }

      if (Input.GetKeyDown(KeyCode.I))
      {
            PlayFabUsage.SubmitHighScore(UnityEngine.Random.Range(0,1000));
      }
      
      if (Input.GetKeyDown(KeyCode.J))
      {
            PlayFabUsage.GetLeaderboard(10);
      }
      
      if (Input.GetKeyDown(KeyCode.K))
      {
            PlayFabUsage.GetLeaderboardAroundPlayer(10);
      }
    }

    private void OnLoginSuccess(LoginResult result)
    {
      Debug.Log($"Hello PlayFab! {result.PlayFabId}");
      
      _loginUserId = result.PlayFabId;
      _loginTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

      _entityId = result.EntityToken.Entity.Id;
      _entityType = result.EntityToken.Entity.Type;
    }

    private void OnLoginFailure(PlayFabError error)
    {
      Debug.LogError(error.GenerateErrorReport());
    }
}替换完成后,让我们来逐一调试PlayFab的提供的各种功能。
获取游戏数据(Title Data)

我们先在PlayFab后台左侧选中Content,在右侧顶部选中Title Data,然后在TITLE DATA条目中点击蓝色的New Title Data按钮,创建属于游戏的配置数据:



创建title data

然后在新页面中添加两个键值对: 以及 ,记得点击保存(Save)按钮。



新建两个kvp

运行Unity工程,在游戏运行后,用键盘按A键,即可在Console窗口看到获取到游戏数据的日志:



获取游戏数据的日志

获取游戏数据的API是PlayFab.PlayFabClientAPI.GetTitleData,示例代码在PlayFabUsage.cs中:
    public static void GetTitleData()
    {
      var request = new PlayFab.ClientModels.GetTitleDataRequest();
      PlayFab.PlayFabClientAPI.GetTitleData(request, result =>
            {
                foreach (var dataPair in result.Data)
                {
                  Debug.Log($"GetTitleData == {dataPair.Key} == {dataPair.Value}");
                }
            },
            error =>
            {
                Debug.LogError($"GetTitleData == {error.GenerateErrorReport()}");
            }
      );
    }如果你认真阅读上文中的基本概念了,就不会问“那么写游戏数据是什么API呢?”之类的问题了。
使用用户数据(User Data)

因为客户端有对玩家数据的增删查改的权限,所以我们直接运行客户端,先用键盘按B键发出更新玩家数据的请求,然后按C键,拉取玩家数据。Console窗口会显示如下日志:



修改和获取玩家数据的日志

在PlayFab后台,筛选出玩家后也能看到他的玩家数据:



PlayFab后台查看玩家数据

新建或者修改玩家数据的API是:PlayFab.PlayFabClientAPI.UpdateUserData,读取玩家数据的API为:PlayFab.PlayFabClientAPI.GetUserData。示例代码在PlayFabUsage.cs中:
    public static void UpdateUserDataRequest(string userId, string loginTime)
    {
      var updateUserDataRequest = new PlayFab.ClientModels.UpdateUserDataRequest
      {
            Data = new Dictionary<string, string>
            {
                {"UserId", userId},
                {"LoginTime", loginTime}
            }
      };
      
      PlayFab.PlayFabClientAPI.UpdateUserData(updateUserDataRequest, result =>
            {
                Debug.Log($"UpdateUserDataRequest == Success {result.DataVersion}");
            },
            error =>
            {
                Debug.LogError($"UpdateUserDataRequest == {error.GenerateErrorReport()}");
            }
      );
    }

    public static void GetUserDataRequest()
    {
      var request = new PlayFab.ClientModels.GetUserDataRequest();
      
      PlayFab.PlayFabClientAPI.GetUserData(request, result =>
            {
                foreach (var dataPair in result.Data)
                {
                  Debug.Log($"GetUserDataRequest == {dataPair.Key} == {dataPair.Value.Value}");
                }
            },
            error =>
            {
                Debug.LogError($"GetUserDataRequest == {error.GenerateErrorReport()}");
            }
      );
    }使用实体数据(Entity Data)

同样的,客户端也对Entity Data有增删查改的权限,让我们再次运行Unity客户端。在键盘上按下E,读取后端实体数据;然后按下D设置玩家的实体数据;最后再按下E,重新读取后端的实体数据。Console中的日志如下:



修改并查询Entity Data

在PlayFab后台,筛选出玩家后能看到他的实体数据:



PlayFab后台查看玩家实体数据

特别的,需要留意蓝色框选的文字,PlayFab的免费账号,只能为每个玩家提供5个1000字节以下的实体数据对象。
新建或者修改实体数据对象的API是:PlayFabDataAPI.SetObjects,读取实体数据对象的API为:PlayFabDataAPI.GetObjects。示例代码在PlayFabUsage.cs中:
    public static void SetEntityObject(string entityKey, string entityType)
    {
      var debugData = new Dictionary<string, object>()
      {
            {"Atk", 100},
            {"Hp", 1000},
      };
      var dataList = new List<SetObject>()
      {
            new SetObject()
            {
                ObjectName = "MyPlayerData",
                DataObject = debugData,
            },
      };

      var newSetObjectRequest = new SetObjectsRequest()
      {
            Entity = new PlayFab.DataModels.EntityKey(){Id = entityKey, Type = entityType},
            Objects = dataList,
      };
      PlayFabDataAPI.SetObjects(newSetObjectRequest, setResult =>
            {
                Debug.Log($"SetEntityObject == Success with version {setResult.ProfileVersion}");
            },
            error =>
            {
                Debug.LogError(error.ErrorMessage);
            });
    }
   
    public static void GetEntityObject(string entityKey, string entityType)
    {
      var newGetObjectRequest = new GetObjectsRequest()
      {
            Entity = new PlayFab.DataModels.EntityKey(){Id = entityKey, Type = entityType},
      };
      PlayFabDataAPI.GetObjects(newGetObjectRequest, getResult =>
            {
                Debug.Log($"GetEntityObject == Success with version {getResult.ProfileVersion}");
                foreach (var dataPair in getResult.Objects)
                {
                  Debug.Log($"GetEntityObject == {dataPair.Key} == {dataPair.Value.DataObject}");
                }
            },
            error =>
            {
                Debug.LogError(error.ErrorMessage);
            });
    }
用虚拟货币购买物品

接下来让我们看一下更复杂的功能——用虚拟货币购买物品(并自动装进背包)。
我们先在PlayFab后台定义一个虚拟货币。点击左侧的Economy,选中右侧顶部的Currency,在信息页中点击New Currency按钮。



创建新虚拟货币

在新虚拟货币页填写虚拟货币的基本信息,此处我们将货币code设为“CN”,显示名称为Coin,并设置玩家的初始Coin数量为1000。



填写Coin货币数据

接着,我们定义一个新目录(Catalogs)。点击左侧Economy,在右侧选中Catalogs,在信息页中点击New catalog按钮。



创建新目录

在新目录页面将其命名为“Items”,并点击保存。



创建Items目录

在新的Items页面中,点击右侧的New Item按钮,创建一个新的Item



创建新Item

我们新添加的Item为HealthPotion(生命药水),填写好相应的信息后,注意下部的PRICES栏,将Currency设置为CN(我们上一步新建的虚拟货币),Amount设为10。即玩家可以消耗10CN币获得一个HealthPotion。



添加新的生命药水

现在,我们已经准备好了虚拟货币和物品,就差完成购买,然后装进背包了(这一步是自动的)。
运行Unity,我们在键盘上按F键请求购买生命药水;然后按G键获得背包所有内容;最后按H键消耗一瓶购买的生命药水。Console窗口输出的日志如下:



购买和消耗生命药水日志

细心的你可能已经发现了,为什么一次购买就获得了10瓶生命药水呢?因为在前面配置Catalog Item信息的时候,右侧的Consumable->By Count填写的是10。
PlayFab购买物品的API是PlayFab.PlayFabClientAPI.PurchaseItem;获取背包的API为:PlayFab.PlayFabClientAPI.GetUserInventory;消耗物品的API为:PlayFab.PlayFabClientAPI.ConsumeItem。示例代码在PlayFabUsage.cs中:
    public static void PurchaseHealthPotion(Action<string> onPurchaseFinish)
    {
      var purchaseItemRequest = new PlayFab.ClientModels.PurchaseItemRequest
      {
            CatalogVersion = "Items",
            ItemId = "HealthPotion",
            Price = 10,
            VirtualCurrency = "CN"
      };
      
      PlayFab.PlayFabClientAPI.PurchaseItem(purchaseItemRequest, result =>
            {
                Debug.Log($"PurchaseHealthPotion == {result.Request.GetType().Name} Success {result.Items.ItemId}");
                onPurchaseFinish?.Invoke(result.Items.ItemInstanceId);
            },
            error =>
            {
                Debug.LogError($"PurchaseHealthPotion == {error.GenerateErrorReport()}");
                onPurchaseFinish?.Invoke(string.Empty);
            }
      );
    }

    public static void GetUserInventory()
    {
      var request = new PlayFab.ClientModels.GetUserInventoryRequest();
      
      PlayFab.PlayFabClientAPI.GetUserInventory(request, result =>
            {
                foreach (var item in result.Inventory)
                {
                  Debug.Log($"GetUserInventory == {item.ItemId} == {item.DisplayName} : {item.ItemInstanceId} count: {(item.RemainingUses.HasValue ? item.RemainingUses.Value : 0)}");
                }
            },
            error =>
            {
                Debug.LogError($"GetUserInventory == {error.GenerateErrorReport()}");
            }
      );
    }

    public static void ConsumePotion(string itemInstanceId)
    {
      var consumeItemRequest = new PlayFab.ClientModels.ConsumeItemRequest
      {
            ItemInstanceId = itemInstanceId,
            ConsumeCount = 1
      };

      PlayFab.PlayFabClientAPI.ConsumeItem(consumeItemRequest, result =>
            {
                Debug.Log($"ConsumePotion == {result.Request.GetType().Name}-{itemInstanceId} Success");
            },
            error =>
            {
                Debug.LogError($"ConsumePotion == {error.GenerateErrorReport()}");
            }
      );
    }使用排行榜

要使用排行榜,我们需要先在PlayFab后台新建一个排行榜。选中左侧的Leaderboard,在右侧信息页面点击New Leaderboard按钮。



创建新排行榜

PlayFab支持不同刷新频率和排行方法的排行榜,我们就新建一个简单的每日高分排行榜如下:



填写排行榜信息

特别注意,因为排行榜依赖于玩家上传的分数信息,所以需要在后台项目中开启相应的权限,具体操作如下:
点击页面左上角的齿轮设置按钮,选择Title settings



选择Title settings

在设置页面中选中右侧顶部的API Features,勾选红色框选的Allow client to post player statistics选项,最后点击Save保存。


我们已经在PlayFab后台完成了排行榜的基本设置,只需运行Unity即可。先在键盘上按下I(大写字母I,不是数字1)来上传一个随机的成绩;再按下J来获取最高的10条排行榜信息;最后按下K来获取玩家排行附近的10条信息。
Console窗口输出的日志如下:(不过因为目前只有我们自己一个用户,所以J和K返回的数据一样)



输出排行榜信息

玩家上传成绩到排行榜的API是PlayFab.PlayFabClientAPI.UpdatePlayerStatistics;获取排行榜最高的N个玩家数据的API为PlayFab.PlayFabClientAPI.GetLeaderboard;获取玩家排行附近N个玩家数据的API为PlayFab.PlayFabClientAPI.GetLeaderboardAroundPlayer。示例代码在PlayFabUsage.cs中:
    public static void SubmitHighScore(int highScore)
    {
      var request = new PlayFab.ClientModels.UpdatePlayerStatisticsRequest
      {
            Statistics = new List<PlayFab.ClientModels.StatisticUpdate>
            {
                new PlayFab.ClientModels.StatisticUpdate
                {
                  StatisticName = "Daily High Score",
                  Value = highScore
                }
            }
      };
      
      PlayFab.PlayFabClientAPI.UpdatePlayerStatistics(request, result =>
            {
                Debug.Log($"SubmitHighScore == {result.Request.GetType().Name} Success");
            },
            error =>
            {
                Debug.LogError($"SubmitHighScore == {error.GenerateErrorReport()}");
            }
      );
    }
   
    public static void GetLeaderboard(int highScoreCount)
    {
      var request = new PlayFab.ClientModels.GetLeaderboardRequest
      {
            StatisticName = "Daily High Score",
            StartPosition = 0,
            MaxResultsCount = 10
      };
      
      PlayFab.PlayFabClientAPI.GetLeaderboard(request, result =>
            {
                Debug.Log($"GetLeaderboard == {result.Request.GetType().Name} Success");
               
                foreach (var player in result.Leaderboard)
                {
                  Debug.Log($"GetLeaderboard == {player.Position} == {player.PlayFabId} == {player.StatValue}");
                }
            },
            error =>
            {
                Debug.LogError($"GetLeaderboard == {error.GenerateErrorReport()}");
            }
      );
    }

    public static void GetLeaderboardAroundPlayer(int highScoreCount)
    {
      var request = new PlayFab.ClientModels.GetLeaderboardAroundPlayerRequest
      {
            StatisticName = "Daily High Score",
            MaxResultsCount = highScoreCount
      };
      
      PlayFab.PlayFabClientAPI.GetLeaderboardAroundPlayer(request, result =>
            {
                Debug.Log($"GetLeaderboardAroundPlayer == {result.Request.GetType().Name} Success");
               
                foreach (var player in result.Leaderboard)
                {
                  Debug.Log($"GetLeaderboardAroundPlayer == {player.Position} == {player.PlayFabId} == {player.StatValue}");
                }
            },
            error =>
            {
                Debug.LogError($"GetLeaderboardAroundPlayer == {error.GenerateErrorReport()}");
            }
      );
    }


以上就是 PlayFab快速上手指南(上) 的全部内容。
我们使用PlayFab+Unity快速调通了:玩家匿名注册和登录、客户端获取游戏配置、增删查改玩家数据及实体数据、用虚拟货币购买虚拟物品并管理背包,以及每日排行榜等基本功能。
还有一些重要的功能比如CloudScript我们尚未触及,不过会在下一篇(中篇)进行讲解,并剖析几个PlayFab官方提供的示例Demo来深入学习这套系统。
如果你对此感兴趣的话记得收藏哟,我们下(中)期见~
往期回顾

【游戏开发工具箱(1) 打造游戏感的利器——Unity Feel 插件浅析】
【游戏开发工具箱(2) 为所有人设计游戏——休闲向左硬核向右(上)】
【游戏开发工具箱(101) 未来已来——AI画图工具 Midjourney 漫游指南】
扩展阅读

PlayFab简介:

PlayFab账号登录基础知识和最佳实践:
PlayFab数据概览:
PlayFab经济快速入门:
PlayFab排行榜快速入门:
页: [1]
查看完整版本: 游戏开发工具箱(3) 开箱即用的云游戏后端——PlayFab快速 ...