编写自定义 CloudScript
CloudScript 是 PlayFab 用途最多的功能之一。 它使客户端代码可以请求执行可实现的任何类型的自定义服务器端功能,并且可以与几乎任何内容 结合使用。 除了来自客户端或服务器代码的显式执行请求外,还可以(通过创建 规则) 或作为计划任务的一部分来执行 CloudScript 以响应 PlayStream 事件 。
注意
使用 Azure Functions 的 CloudScript 通过更多支持的语言和更好的调试工作流进一步提高了 CloudScript 的优势。
本教程介绍如何编写 CloudScript 代码。 有关将 CloudScript 文件上传到游戏的帮助,请参阅 CloudScript 快速入门。
注意
本教程介绍的是 Unity 代码示例,但 CloudScript 对于所有 SDK 的工作方式都与此类似。
本教程的先决条件:
-
使用 PlayFab Unity SDK 设置的 Unity 环境
- 游戏 ID 在
PlayFabSharedSettings
对象中设置。 - 该项目可成功登录一个用户。
- 游戏 ID 在
入门:helloWorld
我们的 helloWorld
示例适用于未在 Game Manager 中进行任何修改的全新游戏。 新游戏的默认 CloudScript 文件包含一个名为 helloWorld
的处理程序。 它使用了一些基本功能、输入参数、日志记录、currentPlayerId 和返回参数。
以下示例显示了默认的 helloWorld
函数代码(不包含注释)。
// CloudScript (JavaScript)
handlers.helloWorld = function (args, context) {
var message = "Hello " + currentPlayerId + "!";
log.info(message);
var inputValue = null;
if (args && args.hasOwnProperty("inputValue"))
inputValue = args.inputValue;
log.debug("helloWorld:", { input: inputValue });
return { messageValue: message };
}
解析此代码
处理程序对象已在 PlayFab CloudScript 环境中预定义。 您应该将任何 CloudScript 函数添加到此对象。
helloWorld
是可用于游戏和 SDK 的函数,因为它是在处理程序对象中定义的。args
是来自调用方的任意对象。 它解析自 JSON,可包含以任意方式格式化的任何数据。
请参阅下一节中的 FunctionParameter。
警告
应该不信任此对象。 被黑客入侵的客户端或恶意用户可能在此提供任意 格式的任何 信息。
Context
是一个高级参数。 在本例中,它为 null。 此参数是由服务器控制的安全参数。currentPlayerId
是一个全局变量,该变量设置为请求此调用的玩家的 PlayFabId。 此参数是由服务器控制的安全参数。 注意: 使用 ExecuteEntityCloudScript API 时,除非实体在其实体链中具有 MasterPlayerID,否则此参数为 null。log.info
:log
是全局对象。 它主要用于调试 CloudScript。log
对象公开以下方法:info
、debug
和error
。 本教程稍后部分会更详细地介绍。return
:返回的任何对象都将序列化为 JSON,并返回给调用方。 您可以返回包含任意所需数据的任何 JSON 可序列化对象。
警告
如果 CloudScript 将机密数据返回给客户端,则安全性由您自行负责。 被黑客入侵的客户端或恶意用户可以查看返回的数据 - 即使 您不在常规游戏中向用户显示它。
从 Unity 游戏客户端执行 CloudScript 函数
从客户端调用 CloudScript 函数非常简单。 首先必须创建一个 ExecuteCloudScriptRequest
,并将 ActionId
属性设置为要执行的 CloudScript 函数的名称(在本例中为 helloWorld
),然后通过我们的 API 将对象发送到 PlayFab。
注意
您只能调用附加到处理程序 JavaScript 对象的 CloudScript 方法。
若要执行 CloudScript 方法,客户端中需要拥有以下代码行。
// Build the request object and access the API
private static void StartCloudHelloWorld()
{
PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest()
{
FunctionName = "helloWorld", // Arbitrary function name (must exist in your uploaded cloud.js file)
FunctionParameter = new { inputValue = "YOUR NAME" }, // The parameter provided to your function
GeneratePlayStreamEvent = true, // Optional - Shows this event in PlayStream
}, OnCloudHelloWorld, OnErrorShared);
}
// OnCloudHelloWorld defined in the next code block
解析此代码
ExecuteCloudScriptRequest 是对 PlayFabClientAPI.ExecuteCloudScript 的任何调用的请求类型。
ExecuteCloudScriptRequest.FunctionName
是一个字符串。 值应与 CloudScript 中定义的函数名称匹配。 在本例中为helloWorld
。ExecuteCloudScriptRequest.FunctionParameter
可以是任何对象,可以序列化为 JSON。 它成为helloWorld
函数中的第一个 args 参数(参阅上一节中的 args)。ExecuteCloudScriptRequest.GeneratePlayStreamEvent
是可选的。 如果为 true,则将事件发布到 PlayStream,您可以在 Game Manager 中查看此事件,或将其用于其他 PlayStream 触发器。
根据使用的语言,ExecuteCloudScript
行的最后一部分涉及向 PlayFab CloudScript 服务器提出请求,以及特定于语言的结果和错误处理部分。
例如,在 Unity、JavaScript 或 AS3 中,使用回调函数提供错误和结果处理。
下面是错误处理方法的示例。
private static void OnCloudHelloWorld(ExecuteCloudScriptResult result) {
// CloudScript returns arbitrary results, so you have to evaluate them one step and one parameter at a time
Debug.Log(JsonWrapper.SerializeObject(result.FunctionResult));
JsonObject jsonResult = (JsonObject)result.FunctionResult;
object messageValue;
jsonResult.TryGetValue("messageValue", out messageValue); // note how "messageValue" directly corresponds to the JSON values set in CloudScript
Debug.Log((string)messageValue);
}
private static void OnErrorShared(PlayFabError error)
{
Debug.Log(error.GenerateErrorReport());
}
中级概述:全局和高级参数
CloudScript 是一组使用 V8 编译并托管在 PlayFab 服务器上的 JavaScript 函数。 它能够访问 PlayFab API 参考文档中列出的任何服务器 API,以及 logger、发出 CloudScript 请求的玩家的 PlayFab ID 和请求中包含的任何信息(都以预设对象的形式提供)。
CloudScript 函数本身是一个全局处理程序对象的属性。 下表是这些预定义变量的完整列表。
名称 | 使用 |
---|---|
server | 有权访问 PlayFab API 参考文档中列出的所有服务器端 API 调用。 可以按如下所示(同步)调用它们:var result = server.AuthenticateUserTicket(request); |
http | 执行同步 HTTP 请求,如下所示: http.request(url, method, content, contentType, headers, logRequestAndResponse) 。
headers 对象包含与各种标头及其值对应的属性。
logRequestAndResponse 是一个布尔值,用于确定游戏是否应在响应过程中记录请求中的任何错误。 |
log | 创建日志语句并将其添加到响应中。 日志有三个级别: log.info() 、 log.debug() 和 log.error() 。 所有三个级别都接收一个消息字符串,以及一个向日志中包含额外数据的可选对象。 例如,log.info('hello!', { time: new Date() }); |
currentPlayerId | 触发 CloudScript 调用的玩家的 PlayFab ID。 |
handlers | 包含游戏的所有 CloudScript 函数的全局对象。 可以通过此对象添加或调用函数。 例如, handlers.pop = function() {}; 、 handlers.pop(); 。 |
脚本 | 包含 Revision 和 titleId 的全局对象。
Revision 表示当前正在执行的 CloudScript 的 修订号,titleId 表示当前游戏的 ID。 |
此外,所有处理程序函数都传递两个参数,详见下文。
名称 | 使用 |
---|---|
args | 处理程序函数的第一个参数。
ExecuteCloudscript 请求的 FunctionParameter 字段的对象表示。 |
context | 处理程序函数的第二个参数。 有关被 PlayStream 事件操作触发时的请求的附加信息,包括触发操作的事件数据 (context.playStreamEvent) 和玩家的档案数据。 (context.playerProfile)。 |
可以通过 ExecuteCloudScript
API 或预设的 PlayStream 事件操作调用 CloudScript 函数。
有关对 ExecuteCloudScript
的响应的完整详情,请参阅 ExecuteCloudScriptResult。
中级:FunctionParameter 和 args
在上一节中,介绍了如何填充 request.FunctionParameter
和在 args
参数中查看此信息。
CloudScript 快速入门说明如何上传新的 CloudScript。
将两者加以组合,我们可以提供另一个有关如何将参数从客户端传递到 CloudScript 的示例。 以前面的示例为例,修改 CloudScript 代码和客户端代码,如下所示。
handlers.helloWorld = function (args) {
// ALWAYS validate args parameter passed in from clients (Better than we do here)
var message = "Hello " + args.name + "!"; // Utilize the name parameter sent from client
log.info(message);
return { messageValue: message };
}
// Build the request object and access the API
private static void StartCloudHelloWorld()
{
PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest()
{
FunctionName = "helloWorld", // Arbitrary function name (must exist in your uploaded cloud.js file)
FunctionParameter = new { name = "YOUR NAME" }, // The parameter provided to your function
GeneratePlayStreamEvent = true, // Optional - Shows this event in PlayStream
}, OnCloudHelloWorld, OnErrorShared);
}
private static void OnCloudHelloWorld(ExecuteCloudScriptResult result) {
// CloudScript returns arbitrary results, so you have to evaluate them one step and one parameter at a time
Debug.Log(JsonWrapper.SerializeObject(result.FunctionResult));
JsonObject jsonResult = (JsonObject)result.FunctionResult;
object messageValue;
jsonResult.TryGetValue("messageValue", out messageValue); // note how "messageValue" directly corresponds to the JSON values set in CloudScript
Debug.Log((string)messageValue);
}
private static void OnErrorShared(PlayFabError error)
{
Debug.Log(error.GenerateErrorReport());
}
完成上述更改后,现在可以轻松地在 CloudScript 与客户端之间发送和接收数据。
注意
值得注意的是,来自客户端的任何数据都容易受到黑客攻击和利用。
在更新后端之前,应始终对输入参数进行验证。 验证输入参数的过程因游戏而异,但最基本的验证应通过检查来确保输入处于可接受的范围和周期以内。
中级:调用服务器 API
如前所述,在 CloudScript 方法中,可以访问完整的服务器 API 调用集。 这样,云代码能够充当专用服务器。
常见服务器任务:
- 更新玩家统计信息和数据。
- 授予物品和货币。
- 随机生成游戏数据。
- 安全地计算战斗结果等...
有关必需参数和对象结构的信息,请参阅 PlayFab API 参考文档中列出的服务器 API。
以下示例摘自一个潜在的 CloudScript 处理程序。
// CloudScript (JavaScript)
//See: JSON.parse, JSON.stringify, parseInt and other built-in javascript helper functions for manipulating data
var currentState; // here we are calculating the current player's game state
// here we are fetching the "SaveState" key from PlayFab,
var playerData = server.GetUserReadOnlyData({"PlayFabId" : currentPlayerId, "Keys" : ["SaveState"]});
var previousState = {}; //if we return a matching key-value pair, then we can proceed otherwise we will need to create a new record.
if(playerData.Data.hasOwnProperty("SaveState"))
{
previousState = playerData.Data["SaveState"];
}
var writeToServer = {};
writeToServer["SaveState"] = previousState + currentState; // pseudo Code showing that the previous state is updated to the current state
var result = server.UpdateUserReadOnlyData({"PlayFabId" : currentPlayerId, "Data" : writeToServer, "Permission":"Public" });
if(result)
{
log.info(result);
}
else
{
log.error(result);
}
高级:PlayStream 事件操作
可以将 CloudScript 函数配置为运行以响应 PlayStream 事件。
- 在任何浏览器中:
- 访问 PlayFab Game Manager。
- 找到您的游戏。
- 在边栏中的“生成”下,转到“自动化”选项卡。
- 转到 PlayStream 选项卡。
页面看起来类似于下面提供的示例。
使用“新建规则”按钮创建新规则。
- 为新 规则 提供名称。
- 选择将用作条件或操作触发器的事件类型。
- 若要使规则触发 CloudScript 函数,请添加一个包含该部分中按钮的操作。
- 然后在 Type 下拉菜单中选择此选项。
- 在 CloudScript function 下拉菜单中选择 helloWorld 函数。
- 选择 Save Action 按钮。
此 规则 现在设置为针对所选类型的任何事件触发。 进行测试:
- 选中 Publish results as PlayStream Event 框。
- 保存 Action。
- 然后触发一个事件。
- 在 PlayStream 监视器中,应显示与 CloudScript 执行相对应的新事件,其中包含相应的信息。
- 有关在调试器中检查 PlayStream 事件的详细信息,请参阅以下部分 高级:调试 CloudScript。
注意
事件操作只能使用调用 CloudScript 函数时的实时版本。 如果在下拉列表中找不到 helloWorld 函数,很可能就是这个原因导致的。
高级:调试 CloudScript
注意
通过使用 Azure Functions 的 CloudScript 进行调试要容易得多。 详细了解如何使用 Azure Functions 对 CloudScript 使用 本地调试。
日志记录
调试代码时,最重要的工具之一是日志记录。 CloudScript 提供了执行此功能的实用程序。
这采用 log
对象的形式,该对象可以记录使用 Info
、 Debug
和 Error
方法所需的任何消息。
此外,HTTP 对象将通过设置 logRequestAndResponse
参数来记录在发出请求时遇到的任何错误。 虽然设置这些日志很简单,但访问它们却需要一定 的技巧。
以下是使用所有 4 种类型的日志的 CloudScript 函数示例。
handlers.logTest = function(args, context) {
log.info("This is a log statement!");
log.debug("This is a debug statement.");
log.error("This is... an error statement?");
// the last parameter indicates we want logging on errors
http.request('https://httpbin.org/status/404', 'post', '', 'text/plain', null, true);
};
要运行此示例,请先将此函数添加到您的实时版本中,然后再继续。
可以使用 ExecuteCloudScript
调用 logTest
函数,如下所示。
// Invoke this on start of your application
void Login() {
PlayFabClientAPI.LoginWithCustomID(new LoginWithCustomIDRequest {
CreateAccount = true,
CustomId = "Starter"
}, result => RunLogTest(), null);
}
void RunLogTest() {
PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest {
FunctionName = "logTest",
// duplicates the response of the request to PlayStream
GeneratePlayStreamEvent = true
}, null, null);
}
// Logs evaluated in next code block
设置 GeneratePlayStreamEvent
使 CloudScript 函数调用生成一个 PlayStream 事件,其中包含响应的内容。 查找 PlayStream 事件的内容:
转到您的游戏的 Game Manager 主页或其 PlayStream 选项卡。
PlayStream Debugger 将实时显示发生的事件。
当显示事件时,选择事件右上角的小型蓝色信息图标,如下所示。
选择此图标将显示事件的原始 JSON,此处对每一个事件进行了详细说明。 可以在下面的示例中看到此 JSON 的示例。
如果我们将
LogScript
MonoBehavior 添加到场景,则运行游戏将在 PlayStream 中生成此内容。
ExecuteCloudScript
调用的结果包括名为 Logs
的字段,该字段是 CloudScript 函数生成的日志对象的列表。
您可以看到三个日志调用,以及无效 HTTP 请求产生的日志。 HTTP 请求日志还使用 Data
字段,这与日志调用不同。
此字段是一个 JavaScript 对象,可以使用与日志语句关联的任何信息填充。 对 log 的调用也可以通过第二个参数使用此字段,如下所示。
handlers.logTest = function(args, context) {
log.info("This is a log statement!", { what: "Here on business." });
log.debug("This is a debug statement.", { who: "I am a doctor, sir" });
log.error("This is... an error statement?", { why: "I'm here to fix the plumbing. Probably.", errCode: 123 });
};
这些调用将使用第二个参数填充结果中的 Data
字段。
日志包含在结果中,因此客户端代码可以响应日志语句。
logTest
函数中的错误是强制的,但可以调整客户端代码来响应它。
void RunLogTest()
{
PlayFabClientAPI.ExecuteCloudScript(
new ExecuteCloudScriptRequest
{
FunctionName = "logTest",
// handy for logs because the response will be duplicated on PlayStream
GeneratePlayStreamEvent = true
},
result =>
{
var error123Present = false;
foreach (var log in result.Logs)
{
if (log.Level != "Error") continue;
var errData = (JsonObject) log.Data;
object errCode;
var errCodePresent = errData.TryGetValue("errCode", out errCode);
if (errCodePresent && (ulong) errCode == 123) error123Present = true;
}
if (error123Present)
Debug.Log("There was a bad, bad error!");
else
Debug.Log("Nice weather we're having.");
}, null);
}
如果运行此代码,输出应指示存在错误。 现实的错误响应可能是在 UI 中显示错误,或将值保存在日志文件中。
高级:错误
在开发中,通常不会手动触发 CloudScript 错误 -如 log.error
一样。
幸运的是,对 ExecuteCloudScript 的响应包含 ExecuteCloudScriptResult,后者包括 ScriptExecutionError 字段。 对日志记录部分的最后一个示例进行调整,我们可能按如下方式使用它。
void RunLogTest() {
PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest {
FunctionName = "logTest",
// handy for logs because the response will be duplicated on PlayStream
GeneratePlayStreamEvent = true
}, result => {
if(result.Error != null) {
Debug.Log(string.Format("There was error in the CloudScript function {0}:\n Error Code: {1}\n Message: {2}"
, result.FunctionName, result.Error.Error, result.Error.Message));
}
},
null);
}
如果出现错误,此代码将在日志中显示该错误。