模块结构

Cloud Code 模块是简单的 .NET 项目,其中公开了游戏客户端可以调用的终端。模块采用与 .NET 类库类似的范式。例如,一个标准 C# 类可能如下所示:

namespace ModulesExample;

public class Example
{
    private static string[] GetCheatCodes()
    {
        return new string[]
        {
            "LEAVEMEALONE",
            "ASPIRINE",
            "BIGBANG"
        };
    }

    public void ChangeHaircut(string haircutName)
    {

    }
}

编译后,任何对 Example 类的引用都只能使用 ChangeHaircut() 方法。

using ModulesExample;

class UsageExample
{
    public void GameLoop()
    {
        Example.ChangeHaircut("buzzcut");

        // Compile error "Cannot access private method 'GetCheatCodes()' here"
        var cheatCodes = Example.GetCheatCodes();
    }
}

开发者可以通过访问修饰符选择库接口。Cloud Code 模块的工作方式相同,但使用的是 [CloudCodeFunction("ChangeHaircut")] 属性。

借助 Cloud Code 模块,您可以创建独立的服务器授权库。因此,您可以在服务器上运行游戏逻辑,并从客户端调用游戏逻辑,从而将游戏逻辑与客户端分离并提高安全性。模块支持代码重用,并提供可用于管理游戏逻辑的接口。

C# 项目

Cloud Code 模块项目是类库 C# 项目。此项目也可以是某个解决方案的一部分。最简单的项目结构如下所示:

├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs

如果是在一个解决方案中,可能如下所示:

Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs

这个解决方案还可以包含相互引用的多个项目。因此,您可以轻松添加测试项目,并启用代码重用。以下示例显示了一个有效的项目结构:

Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs
├─ Tests.csproj
    └─ Dependencies
    └─ Tests.cs
├─ Other.csproj
    └─ Dependencies
    └─ Class1.cs

但是,只有主项目可以包含 Cloud Code 函数。

请参阅编写单元测试,详细了解如何为 Cloud Code 模块编写单元测试。

请参阅 Cloud Code 限制,了解适用的存储限制。

主项目

尽管解决方案可以包含多个 C# 项目,但每个模块只能有一个主项目。主项目包含所有模块入口点(即具有 CloudCodeFunction 属性的函数)。

重要:如果您将任何具有 CloudCodeFunction 属性的函数置于主项目外部,则 Cloud Code 服务不会将此类函数识别为入口点。

主项目也是您发布到本地文件夹并随后压缩到存档文件以部署到 Cloud Code 服务的项目。主项目中未引用的项目和依赖项不会部署到 Cloud Code 服务。

您需要让主项目的名称与部署到 Cloud Code 服务的存档文件的名称匹配。

请参阅打包代码,详细了解如何编译和打包 Cloud Code 模块。

注意:您可以创建一个包含多个 Cloud Code 模块主项目的解决方案。在这种情况下,每个主项目对应一个不同的模块。

示例项目

最简单的模块项目可以具有以下结构:

Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs

以下示例定义的 Cloud Code 模块包含一个返回 Hello World! 字符串的 Hello 函数:

using Unity.Services.CloudCode.Core;

namespace Sample;

public class HelloWorld
{
    [CloudCodeFunction("Hello")]
    public string PrintHello()
    {
        return "Hello World!";
    }
}

Cloud Code NuGet 包

作为 .NET 项目,Cloud Code 模块项目也使用 NuGet 来共享打包的代码。

要了解有关 NuGet 的更多信息,请参阅 Microsoft 文档

Cloud Code 提供了两个可以包含在模块中的包:

  • com.Unity.Services.CloudCode.Core:此包中包含 Cloud Code 的核心功能,包括 CloudCodeFunction 属性。所有 Cloud Code 模块都需要此包。
  • com.Unity.Services.CloudCode.Apis:这是一个可选包,其中包含可用于与 Cloud Code 服务交互的 Unity Gaming Services(Unity 游戏服务)API。您可以使用此包调用其他 Cloud Code SDK,例如 Cloud Save。

如需了解更多信息,请参阅有关如何使用 Jetbrains RiderVisual Studio 在项目中安装和使用 NuGet 包的文档。

注意:还可以安装要在模块项目中使用的任何外部 NuGet 包。如需了解更多信息,请参阅与外部服务集成

代码结构

为了避免代码结构问题,请遵循以下准则。

方法签名

使用以下结构作为模块函数的方法签名。

[CloudCodeFunction("FunctionName")]
    public async Task<ReturnType> FunctionName(IGameApiClient gameApiClient, IExecutionContext ctx, string parameter1, string parameter2)
    {
        ...
    }
  • async void 模式会导致错误。始终返回 TaskTask<T>
  • Cloud Code 不支持静态方法。

对模块使用单例模式

您可以使用单例模式来与 Cloud Code C# 包和外部服务调用进行集成。单例模式可提高 Cloud Code 中的性能和可扩展性,并确保您不会为同一对象创建多个实例。

重要:不要依赖模块函数调用之间的共享状态,因为当有多个工作线程时,不能保证 API 调用会落在同一个工作线程上。

重要:Cloud Code 工作线程的内存限制为 256 MB。超过此限制的工作线程最终会崩溃。

CloudCodeFunction 属性

要将方法标记为 Cloud Code 函数并使其成为可行的 Cloud Code 模块终端,请使用 CloudCodeFunction 属性。您只能在模块的主类中使用 Cloud Code 函数属性。为了调用该函数,需要调用属性的名称。

IGameClient 接口

IGameClientApi 接口提供了一种访问 Cloud Code C# SDK 的方法。您可以使用依赖项注入IGameApiClient 注入到模块中。

  1. 定义一个实现 ICloudCodeSetup 接口的类,并添加 GameApiClient 为单例:
    public class ModuleConfig : ICloudCodeSetup
        {
            public void Setup(ICloudCodeConfig config)
            {
                config.Dependencies.AddSingleton(GameApiClient.Create());
            }
        }
  2. IGameClient 接口作为函数参数传入模块函数。

IPushClient 接口

IPushClient 接口提供了一种向玩家发送推送通知的方法。您可以使用依赖项注入IPushClient 注入到模块中。

  1. 定义一个实现 ICloudCodeSetup 接口的类,并添加 PushClient 为单例:
    public class ModuleConfig : ICloudCodeSetup
        {
            public void Setup(ICloudCodeConfig config)
            {
                config.Dependencies.AddSingleton(PushClient.Create());
            }
        }
  2. PushClient 对象作为函数参数传入模块函数。

要详细了解如何从 Cloud Code 发送推送消息,请参阅推送消息

IExecution 上下文

要在模块中调用其他 UGS 服务,可以使用 IExecutionContext 接口获取有关当前调用的额外信息。您可以使用以下值访问服务 SDK,并查看哪个玩家发出了请求。

  • ProjectId:调用方通过身份验证的 Project ID。
  • PlayerId:执行脚本的玩家 ID。注意:当服务调用脚本时,玩家 ID 不可用。
  • EnvironmentId:模块的环境 ID。
  • EnvironmentName:模块的环境名称。
  • AccessToken:玩家用于向 Cloud Code 进行身份验证的 JSON Web Token (JWT) 凭据。
  • UserId:服务帐户的用户 ID(注意:当服务调用脚本时,用户 ID 不可用)。
  • Issuer:签发者或服务帐户令牌(注意:当玩家调用脚本时,签发者不可用)。例如,如果 Multiplay 发出调用,则签发者为 multiplay
  • ServiceToken:Cloud Code 服务帐户 JWT 凭据。
  • AnalyticsUserId:玩家的 Analytics 用户 ID(注意:当服务调用脚本时,Analytics 用户 ID 不可用)。
  • UnityInstallationId:玩家的 Unity 设备安装 ID(注意:当服务调用脚本时,Unity 安装 ID 不可用)。

您可以使用执行上下文获取玩家 ID 并向不同的 Unity Gaming Services(Unity 游戏服务)进行身份验证。您可以通过类的构造函数以及具有 CloudCodeFunction 属性的任何方法获取执行上下文。

例如,您可以使用执行上下文来调用 Cloud Save 获取玩家密钥:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;

namespace Sample;

public class HelloWorld
{
    private static ILogger<HelloWorld> _logger;
    public CloudSaveSdkSample(ILogger<HelloWorld> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("GetData")]
    public async Task<object> GetData(IExecutionContext context, IGameApiClient gameApiClient, string key)
    {
        try
        {
            var result = await gameApiClient.CloudSaveData.GetItemsAsync(context, context.AccessToken,
                context.ProjectId, context.PlayerId, new List<string> { key });
            return result.Data.Results.First().Value;
        }
        catch (ApiException ex)
        {
            _logger.LogError("Failed to get data from Cloud Save. Error: {Error}", ex.Message);
            throw new Exception($"Failed to get data for playerId {ctx.PlayerId}. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }


}

令牌身份验证

AccessTokenServiceTokenJWT,也是 IExecutionContext 接口的属性。

您可以使用这些令牌对 Cloud Code 的其他 Unity Gaming Services(Unity 游戏服务)调用进行身份验证。

令牌类型来源数据访问用途
accessTokenAuthentication 服务生成。仅限经过身份验证的玩家。AccessToken 是用于对 Cloud Code 调用进行身份验证的 JWT。您可以将此令牌传递给其他 UGS 服务来访问经过身份验证的玩家的数据。
serviceToken由 Cloud Code 生成。跨玩家数据访问。ServiceToken 是用于调用其他 UGS 服务并处理跨玩家数据的令牌。

您配置的任何访问控制规则都会影响 AccessToken

以下示例显示了如何使用 AccessToken 通过 Cloud Save 访问经过身份验证的玩家的数据:

using Microsoft.Extensions.DependencyInjection;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.CloudSave.Model;

namespace TokenSample;

public class TokenSample
{
    [CloudCodeFunction("GetKeysForAuthenticatedPlayer")]
    public  Task<ApiResponse<GetKeysResponse>> GetPlayerKeys(IExecutionContext ctx, IGameApiClient gameApiClient)
    {
        try
        {
            // Using ctx.AccessToken to access the data for the player calling the function
            return gameApiClient.CloudSaveData.GetKeysAsync(ctx, ctx.AccessToken, ctx.ProjectId, ctx.PlayerId);
        }
        catch (ApiException e)
        {
            throw new Exception($"Failed to get keys for playerId {ctx.PlayerId}. Error: {e.Message}");

        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}

要访问所有玩家的数据,您可以使用 ServiceToken 并传入 playerId 的参数:

using Microsoft.Extensions.DependencyInjection;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.CloudSave.Model;

namespace TokenSample;

public class TokenSample
{
    [CloudCodeFunction("GetKeysForAnyPlayer")]
    public  Task<ApiResponse<GetKeysResponse>> GetPlayerKeys(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId)
    {
        try
        {
            // Using ctx.ServiceToken to access the data for any player
            return gameApiClient.CloudSaveData.GetKeysAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId);
        } catch (ApiException e)
        {
            throw new Exception($"Failed to get keys for playerId {ctx.PlayerId}. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}

如需了解这两个令牌之间差异的深入说明,请参阅服务和访问令牌文档。

Cloud Code 模块元数据

要获取 Cloud Code 模块及其元数据,您可以向 Cloud Code 服务发送 GET 请求。响应是具有以下结构的 JSON 对象:

{
  "name": "string",
  "language": "CS",
  "tags": {
    "property1": "string",
    "property2": "string"
  },
  "signedDownloadURL": "string",
  "dateCreated": "2022-04-05T09:12:13Z",
  "dateModified": "2022-04-05T09:12:13Z"
}

标签

标签是可以附加到 Cloud Code 模块的键/值对。您可以在标签中记录所需的任何信息,例如模块的版本、作者或提交哈希(如果使用版本控制)。

模块最多可以存储 10 个标签,每个标签键和标签值最多可以使用 128 个字符。

注意:UGS CLI 不提供支持标签,会忽略它们。

签名下载 URL

签名下载 URL (Signed Download URL) 是一种可用于下载模块存档文件的临时 URL。