编写自定义 CloudScript

CloudScript 是 PlayFab 用途最多的功能之一。 它使客户端代码可以请求执行可实现的任何类型的自定义服务器端功能,并且可以与几乎任何内容 结合使用。 除了来自客户端或服务器代码的显式执行请求外,还可以(通过创建 规则) 或作为计划任务的一部分来执行 CloudScript 以响应 PlayStream 事件 。

注意

使用 Azure Functions 的 CloudScript 通过更多支持的语言和更好的调试工作流进一步提高了 CloudScript 的优势。

本教程介绍如何编写 CloudScript 代码。 有关将 CloudScript 文件上传到游戏的帮助,请参阅 CloudScript 快速入门

注意

本教程介绍的是 Unity 代码示例,但 CloudScript 对于所有 SDK 的工作方式都与此类似。

本教程的先决条件:

  • 使用 PlayFab Unity SDK 设置的 Unity 环境
    • 游戏 ID 在 PlayFabSharedSettings 对象中设置。
    • 该项目可成功登录一个用户。

入门: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.infolog是全局对象。 它主要用于调试 CloudScript。 log对象公开以下方法:infodebugerror。 本教程稍后部分会更详细地介绍。

  • 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();
脚本 包含 RevisiontitleId的全局对象。 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 事件。

  1. 在任何浏览器中:
    • 访问 PlayFab Game Manager
    • 找到您的游戏
    • 在边栏中的“生成”下,转到“自动化”选项卡。
    • 转到 PlayStream 选项卡。

页面看起来类似于下面提供的示例。

Game Manager - PlayStream - event actions

  1. 使用“新建规则”按钮创建新规则。

    • 为新 规则 提供名称。
    • 选择将用作条件或操作触发器的事件类型
    • 若要使规则触发 CloudScript 函数,请添加一个包含该部分中按钮的操作
    • 然后在 Type 下拉菜单中选择此选项。
    • CloudScript function 下拉菜单中选择 helloWorld 函数。
    • 选择 Save Action 按钮。

    Game Manager - PlayStream - save action

  2. 规则 现在设置为针对所选类型的任何事件触发。 进行测试:

    • 选中 Publish results as PlayStream Event 框。
    • 保存 Action
    • 然后触发一个事件。
    • PlayStream 监视器中,应显示与 CloudScript 执行相对应的新事件,其中包含相应的信息。
    • 有关在调试器中检查 PlayStream 事件的详细信息,请参阅以下部分 高级:调试 CloudScript

    注意

    事件操作只能使用调用 CloudScript 函数时的实时版本。 如果在下拉列表中找不到 helloWorld 函数,很可能就是这个原因导致的。

高级:调试 CloudScript

注意

通过使用 Azure Functions 的 CloudScript 进行调试要容易得多。 详细了解如何使用 Azure Functions 对 CloudScript 使用 本地调试。

日志记录

调试代码时,最重要的工具之一是日志记录。 CloudScript 提供了执行此功能的实用程序。

这采用 log 对象的形式,该对象可以记录使用 InfoDebugError 方法所需的任何消息。

此外,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 将实时显示发生的事件。

  • 当显示事件时,选择事件右上角的小型蓝色信息图标,如下所示。

    Game Manager - PlayStream - debugger

选择此图标将显示事件的原始 JSON,此处对每一个事件进行了详细说明。 可以在下面的示例中看到此 JSON 的示例。

  • 如果我们将 LogScript MonoBehavior 添加到场景,则运行游戏将在 PlayStream 中生成此内容。

    Game Manager - PlayStream - JSON 事件日志

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);
}

如果出现错误,此代码将在日志中显示该错误。