网易首页 > 网易号 > 正文 申请入驻

从登录验证到抽卡存储,一站式打造安全高效的云端游戏服务端逻辑

0
分享至

新年将至,Unity 中国祝所有小伙伴和家人们新年快乐!2024 年,。新的一年里,愿所有小伙伴在吉运常伴,创意无限,不断取得丰硕成果!点击下图,领取蛇年限定红包封面~

本文分享 UOS 实用教程,帮助大家在假期轻松提升技能。

之前,我们已经为大家分享了一篇教程《》,详细介绍了如何使用 Unity Online Services(UOS)提供的 C# 云函数服务,在 Unity 开发环境中快速实现和部署联网游戏的服务器端逻辑。在那篇教程中,我们展示了如何使用云数据库服务 CRUD Storage 来存储诸如玩家信息和抽卡记录等数据。

今天,我们将再次以“抽卡”功能为例,但深入探讨的技术内容有所不同

在本篇教程中,我们将借助 UOS 提供的Passport-Login服务 来完成用户的登录验证流程。随后,我们将利用云函数 Func-Stateless来部署抽卡功能的服务器端逻辑代码。最后,这些抽卡数据将通过CRUD-Save服务 实现云端存储,确保数据的持久性和安全性。

教程中涉及 UOS 服务包括:

  • 云函数服务 Func Stateless (C#):用于部署抽卡服务端逻辑代码

  • 云存档服务 CRUD Save:用于存储抽卡记录等数据

  • 玩家通行证服务 Passport Login:实现用户登录及实名认证

云函数服务 Func Stateless

在保障游戏或应用安全性的重要考量下, 确保关键逻辑和数据的权威性处理仅在服务器端执行显得尤为重要 。这是因为 客户端的代码包体容易被破解或篡改 ,从而引发不公平竞争、数据泄露等安全问题。UOS Func Stateless (C#) 云函数以其高效、灵活、安全且成本低的特点,为游戏开发者提供了一个理想的服务端逻辑解决方案,助力他们在快速迭代的市场环境中保持竞争优势。

  • Func Stateles s 支持在本地开发环境中直接调试云函数 ,无需部署到云端即可验证逻辑的正确性,降低了调试难度,加快了开发迭代速度。

  • Func Stateless 能够 自动将服务端逻辑代码打包并部署到云端 ,简化了部 署流程,减少了人为错误,让开发者更专注于业务逻辑的实现。

云存档服务 CRUD Save

借助 UOS Save 所提供的全面而专业的 玩家数据存储、检索及 管理服务 ,开发者能够极为便捷地为广大游戏玩家打造出一个跨越不同平台与设备的、安全且高度可用的游戏存档系统

这一服务不仅确保了玩家能够在任何时间、任何地点无缝地继续他们的游戏进程,而且通过严格的数据安全保障措施,让玩家的游戏数据始终处于严密的保护之下。同时,其高可用性的设计也保证了玩家数据的实时同步与持久存储,为玩家带来了更加流畅、稳定的游戏体验。

玩家通行证服务 Passport

Passport 是 UOS 官方提供的玩家通行证服务,包括玩家登录系统 (Login),以及与玩家相关的众多游戏功能 (Feature)。

  • Passport Login 是一个可以开箱即用的玩家登录系统,通过非常简便的集成方式, 便可以获得如下功能: 灵活可 配的 UI 登录界面,包含用户协议、登录、实名认证; 支持手机号登录,以及微信,QQ, Apple ID 等 主流第三方 OAuth 登录;可以配置游戏服务器,以及管理游戏角色。

  • Passport Feature 是以玩家为中心,包含了在线游戏中各种常见的功能。包括:排行榜、公会、游戏礼包、防沉迷系统、经济系统、邮件系统、成就系统、战令系统、公告、任务系统。

教程视频

教程学习大纲

  1. 在 UOS 官网下载示例项目工程

  2. 创建 UOS App 并启用 Func-Stateless / CRUD-Save / Passport-Login 服务

  3. 设置云函数的安装和配置目录

  4. 在本地模式下调试并运行项目,通过 Passport 实现用户登陆

  5. 通过 CRUD Save 实现用户抽卡数据的云存档

  6. 上传和部署云函数,切换到远程调用模式下测试云函数的调用

教程案例工程源文件

教程内学习用到的项目工程文件可以通过以下方式进行下载:

UOS 官网示例教程中在线下载:

https://uos.unity.cn/doc/func/stateless/csharp-tutorial#prepare

教程操作步骤

接下来让我们来看看 Func Stateless 云函数结合 CRUD Save 云存档在抽卡项目中的具体用法吧!

1. 在 UOS 官网下载示例项目工程

1.1 下载项目工程文件

首先前往上方提供的UOS 官网的教程链接地址,在「Func」的文档左侧页面选择「Stateless」下方的「示例教程(C#)」,在「准备工作」模块点击按钮「DEMO(CLOUDSAVE PASSPORT)工程」,就可以下载抽卡 Demo 的项目工程了。

1.2 解压缩项目工程文件

在下载完项目工程压缩包以后,请解压缩下图中的项目工程文件。

1.3 加载项目工程

然后打开 Unity Hub,选择电脑上已经安装过的 Unity 编辑器版本,来打开刚刚下载好的项目工程,教程这里使用的是Unity2022.3.48f1c1 版本

打开项目工程以后,在项目的 Project 窗口中,找到「Assets/Scenes/Demo.unity」场景并打开。

点击「Import TMP Essentials」导入 TextMeshPro 的字体资源。

2.绑定 UOS App 并开启服务

温馨提示:当前项目中已安装好 UOS Launcher,不需要再次安装了。大家可以参考之前的的公众号文章教程,来为你当前的项目绑定你创建好的 UOS App 。

2.1 绑定 UOS App

点击 Launcher 面板的「LinkApp」按钮,在弹出窗口中选择「By Unity project」,在「Select organization」这里选择一个自己的项目组织,然后在「Select project 」选项这里,我们选择「Create a new project 」,自行设置修改项目名字「Project name」。

教程中,我们就先选择绑定创建好的 FuncStatelessAndSaveDemo 应用了。

2.2 开启 Func-Stateless 服务

在编辑器内 Unity Online Services 窗口的下拉服务列表中,找到「Func - Stateless」,点击「Enable」按钮来一键开启服务, 进入 UOS 网页端可以看到,此时默认云函数的脚本语言是基于 Dotnet 的运行环境的。

如果你是在 UOS 网页端为当前 App 开启 Func-Stateless 服务的话,需要自行手动在脚本语言的下拉选项框中选择Dotnet环境 。

当前项目中已经安装过 Func - Stateless 服务 SDK,无需再次安装了。如果你自己的项目中没安装过的话,可以点击「Install SDK」的按钮进行安装。

2.3 开启 CRUD-Save 服务

接着继续在UOS Launcher 的下拉服务窗口列表中,找到CRUD-Save」,点击「Enable」开启服务,当然你也可以在 UOS 网站上直接开启服务。

当前项目中也是已经安装过 CRUD-Save 服务的 SDK了,无需再次安装了。

2.4 开启 Passport-Login 服务

接着继续在 UOS Launcher 的下拉服务窗口列表中,找到「Passport Login」,点击「Enable」开启服务。

当前项目中也是已经安装过 Passport Login 服务的 SDK了,无需再次安装了。

3.设置云函数的安装和配置目录

3.1 导入 NuGetForUnity 工具

在 Unity Editor 菜单栏中先点击「UOS -> Func Stateless -> Open Panel」按钮,打开 Func Stateless Tool 面板。

然后再点击「UOS -> Func Stateless -> Import NuGetForUnity」,来导入 UOS 版本的 NuGetForUnity 工具。

3.2 修改云函数的安装和配置目录

导入完成后,点击菜单栏中「NuGet -> Preferencs」打开配置界面。修改Packages Install Path 为云函数所在目录下的 Packages 目录;Packages Config Path 为云函数所在目录。

在这里我们就先使用给定的默认设置目录了,大家有需要自行修改

登录功能模块的 UI 界面和脚本,我们使用的是 UOS 的 Passport 服务为用户提供的示例模板,在之前点击导入「PassportUI」资源的时候已经导入过了,可以直接在项目中使用。

在这里我们使用的是手机号短信验证码的方式登录的, 由于 Passport 的「登录配置」默认是开启了真实短信验证和实名认证的。所以,这里请 输入真实的手机号和短信验证码 来完成登录。

当输入完短信验证码后,可以在 Console 控制台看到输出的日志信息:「完成登录」

日志信息是在 UIController.cs 脚本中调用输出的,找到场景中的 UIController 对象,可看到挂载的 UIController.cs 脚本:

进入 UIController.cs 脚本查看下代码,在 Start 方法中会调用 Passport SDK 进行 UI 初始化的方法 Init ,方法中需要传入配置 _config 和回调函数 _callback

_config 是 Passport SDK 进行初始化时的相关配置:来设置是否自动旋转屏幕方向、是否通过自行调用 Login 函数启动登录面板,以及设置 UI 风格主题是深色还是浅色等等。

_callback 是 Passport SDK 的回调函数:Passport 中已经封装注册好了,在用户拒绝协议、完成登录、完成所有流程、用户登出这几个状态下的回调事件。

所以当用户短信验证通过后,会自动进入LoggedIn状态下的回调事件,输出了日志「完成登录」

public class UIController : MonoBehaviour
{
// sdk 配置(Config 是 SDK 初始化时的配置)
private readonly PassportUIConfig _config = new()
{
AutoRotation = true, // 是否开启自动旋转,默认值为 false。
InvokeLoginManually = false, // 是否通过自行调用 Login 函数启动登录面板,默认值为 false。
Theme = PassportUITheme.Dark, // 风格主题配置。
UnityContainerId = "unity-container" // WebGL 场景下 Unity 实例容器 Id。
};

// sdk 回调
private async void _callback(PassportEvent e)
{
// event: 不同情况下的回调事件,详情可以参考下面的回调类型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用户拒绝了协议");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登录");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
break;
case PassportEvent.LoggedOut:
Debug.Log("用户登出");
break;
}

}
private void Start()
{
// 调用 SDK
PassportUI.Init(_config, _callback);
}
 }

然后会需要进行实名身份认证:

当实名验证成功后,会看到控制台日志输出:「完成所有流程」。

此时会自动回调PassportEvent.Completed 状态下的事件。

4.2 修改登录配置

接着可以点击 Passport Login 旁边的「Developer portal」按钮,进入 UOS 网页端的 Passport 模块来自行修改「登录配置」中的设置。

点击选中「模拟短信验证」,这样验证码直接输入「 111111」即可;取消选中「实名认证」,则暂时不再验证用户的身份信息。

修改完配置后,可以再次回到编辑器测试下效果。点击运行游戏,选择「退出登录」,然后「使用新的账号登录」,再重新输入一个手机号,然后输入短信验证码 111111 ,就可以成功登陆了,同时也跳过了身份验证,直接可以进入游戏。Console 控制台也能看到对应的日志输出。

4.3 选择角色

登陆完成后,会选择一个角色进入游戏。 在脚本的 _callback 方法的Completed 状态下,看到会调用选择角色的方法SelectPersona

private async void _callback(PassportEvent e)
{
// event: 不同情况下的回调事件,详情可以参考下面的回调类型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用户拒绝了协议");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登录");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
break;
case PassportEvent.LoggedOut:
Debug.Log("用户登出");
break;
    }
}

在方法 SelectPersona 中,会先调用PassportSDK.Identity.GetRealms()方法获取域列表,然后从域列表中根据需要选择一个域,在这里我们暂时先使用域列表的第一个域。

大家也可以手动填写你想要使用的某一个域。我们进入 UOS 网页端 Passport 的「服务器」模块,可以看到默认已经帮我们创建了一个域了。点击复制域的 UUID,然后赋值给脚本中的域变量 realmID 即可。

// 选择域
//var realms = await PassportSDK.Identity.GetRealms(); // 获取域列表
//var realmID = realms[0].RealmID; // 根据需要自行选择域
var realmID = "543b59d1-3008-4028-8591-83167e82feb0"; // 也可以填写固定的 RealmID 而不是动态获取

修改完代码后,可以再次运行,是可以正常进入游戏的。然后我们需要选择一个角色,先通过调用GetPersonas() 方法来获取到角色列表

由于 Demo 示例中我们默认启用的配置是:一个用户单服务器下只能创建一个角色,并没有开启单服务器下多角色的功能。所以目前是一个手机号对应一个用户 ID,一个用户 ID 对应一个角色 ID。

  • 如果角色列表中没有任何角色,则会调用 CreatePersona 方法,在指定的域 realmID 中来创建一个角色赋值给 persona 变量。

  • 如果角色列表中已经有角色的话,会直接选择已创建过的角色 personas[0] 赋值给 persona 变量。最后,再通过调用 SelectPersona 方法来指定选择的角色。


// 选择角色
private async Task SelectPersona()
{
// 选择域
//var realms = await PassportSDK.Identity.GetRealms(); // 获取域列表
//var realmID = realms[0].RealmID; // 根据需要自行选择域
    var realmID = "543b59d1-3008-4028-8591-83167e82feb0"; // 也可以填写固定的 RealmID 而不是动态获取

// 获取(或创建)与选择角色
Persona persona = null;
var personas = await PassportSDK.Identity.GetPersonas(); // 获取角色列表
if (!personas.Any())
{
// 若没有角色,则新建角色
persona = await PassportSDK.Identity.CreatePersona("YourDisplayName", realmID);
}
else
{
// 若有角色,则选择第一个角色
persona = personas[0];
}
// 选择角色
await PassportSDK.Identity.SelectPersona(persona.PersonaID);
}

4.4 创建一个新的域

用户也可自行创建一个新的域来使用。在 Passport 的【服务器】管理页面,点击【创建新的域】,自己设置下名称,点击【创建】即可。

然后可以将新创建的域的 UUID 赋值给脚本中的变量 realmID ,再次进行测试后,回到 UOS 网页端,点击查看域的详情,可以看到域中已经有创建的角色信息了。

4.5 进入游戏场景,获取用户登录信息

登陆成功后,会看到下面的游戏画面,我们先来看看【进入游戏】的 UI 对象上绑定的事件。

场景中的 MainUI 对象上挂载了 MainUIController.cs 脚本,当我们点击【进入游戏】的按钮时,会执行 MainUIController.cs 脚本中的 StartGame 的方法。

点击【进入游戏】按钮,可以进入游戏场景:

同时,Console 控制台会看到有下面的日志输出信息。

首先来看看输出的日志:“currentUserId, use as save name”,说明执行了 StartGame 方法 。会首先通过 PassportSDK 来获取到当前角色所属的用户ID。

public async void StartGame()
{
try
{
ShowLoading(true);
// await NetworkManager.Login(username, password);
var userId = PassportSDK.CurrentPersona.UserID;
Debug.Log($"currentUserId, use as save name: {userId}");
await NetworkManager.Login(userId);
}
catch (Exception e)
{
Debug.LogException(e);
Debug.Log(e.Message);
ShowLoading(false);
MessageUI.Show(e.Message);
}
}

刚才控制台对应输出的日志信息还有:“loginResult: 1000000001, 106104, 467”。因为代码中拿到 UserID 后,异步调用了NetworkManager.Login方法。

在 Login 方法中,会先通过调用云函数脚本 LoginService 中的云函数 Login 来获取到登陆结果 loginResult。然后再通过事件调用,将用户的昵称(Nickname)、金币数(Coins)、钻石数(Diamonds)的信息,同步更新到 UI 界面上。

稍后我们会展开来详细讲解:云函数脚本 LoginService 和 ActionService 的。

public async Task Login(string username)
{
try
{
var loginService = new LoginService();
var loginResult = await loginService.Login(username);
if (!loginResult.Ok)
{
throw new Exception(loginResult.Message);
}
_saveId = loginResult.SaveId;
_user = loginResult.User;
_as = new ActionService();

Debug.Log($"loginResult: {loginResult.User.Nickname}, {loginResult.User.Coins}, {loginResult.User.Diamonds}");

// invoke ui update
onLogin.Invoke(new Account
{
Nickname = loginResult.User.Nickname,
Coins = loginResult.User.Coins,
Diamonds = loginResult.User.Diamonds
});
}
catch (Exception e)
{
onError.Invoke(e.Message, 3);
}
}

5.云函数结合 CRUD Save 实现用户抽卡数据的云存档

接下来看看项目中如何使用 CloudSave 实现读档和存档的!

5.1查看网页端的云存档文件

运行游戏点击抽卡后,用户抽卡的相关结果数据信息,其实已经通过 UOS 的 CRUD-Save 服务实现云存档了。

我们进入 UOS 网页端CRUD Save的【存档管理】页面,可以看到当前登录的用户,然后点击最右边的【详情】按钮。

可以在弹出的界面中查看【存档详情】,点击【下载存档】

打开存档文件后,可以查看文件中存储的抽卡的信息。

用户也可以在网页上点击【对玩家隐藏存档】或者【删除存档】,现在我们演示下点击页面上的【删除存档】按钮,则会清空该存档 ID 对应的一整条信息的。

此时如果再次运行游戏,可以看到金币数和钻石数将显示为 0,英雄列表和背包列表也都为空了。

接下来我们看看云存档的功能具体是如何实现的!

5.2初始化 CloudSaveSDK

在 MainUIController.cs 脚本的 Start 方法中,会调用InitializeAsync方法,该方法会使用 UOS Launcher 中填写的 UOS APP 信息来实现对 CloudSaveSDK 的初始化。

private async void Start()
{
    //此处省略其它代码行......

try
{
ShowLoading(true);
await CloudSaveSDK.InitializeAsync();
ShowLoading(false);
}
catch (Exception e)
{
Debug.Log(e.Message);
ShowLoading(false);
MessageUI.Show(e.Message);
}
}

5.3登录云函数结合 CloudSaveSDK 实现读取已存档的用户信息

然后点击菜单栏「UOS -> Func Stateless -> Open Panel」按钮,打开「Func Stateless Tool」窗口。

在打开的工具的窗口中,可以看到当前项目中的 2 个云函数类LoginService 和 ActionService。我们先不上传云函数,先在本地进行调试运行项目

双击云函数「Login」,就可以打开云函数所在脚本了。 我们来分析下云函数 Login 的代码,会先通过调用 CloudSaveSDK 封装的方法ListAllAsync 来列出当前用户的所有存档元数据信息,存储在变量 saveItems 中。

[CloudService]
public class LoginService
{
[CloudFunc]
public async Task Login( string username)
{
            // 此处省略其它代码行......
            
            var saveItems = await CloudSaveSDK.Instance.Files.ListAllAsync();
         }
     }

然后判断下如果 saveItems 集合长度为 0,说明当前用户没有存档文件,是新用户第一次登录,那么我们就会调用CreateAsync 方法来创建一个存档文件

创建存档文件的时候,需要传入三个参数,存档名称 username、字节数组类型的存档文件 data、存档选项 options。

  • 我们创建了一个新用户对象 newUser,然后通过封装的SerializeToByteArray方法,将 newUser 的属性信息转换成字节数组变量 data,作为第二个参数传递到 CreateAsync 方法中。

  • 在存档选项 options 中,设置了当前云存档的模式为单存档 ProgressType.Linear,如果你要在你的项目中使用多存档模式的话,可以使用 ProgressType.Multi。

  • 大家可以自行查看封装的存档选项类 CreateOptions,根据需要自行传入更多的参数。

  • 最后会将用户登录的结果返回。


if (saveItems.Count == 0)
{
var newUser = new User
{
Username = username,
Nickname = username + Helper.GetRandomEmoji(),
Diamonds = 500,
Coins = 0,
LuckPoints = 0,
DrawCounts = 0,
DrawPool = Helper.NewDrawPool(),
Heroes = new Dictionary (),
Props = new Dictionary ()
};

var data = SerializeHelper.SerializeToByteArray(newUser);
var options = new CreateOptions
{
ProgressType = ProgressType.Linear
};
var saveId = await CloudSaveSDK.Instance.Files.CreateAsync(username, data, options);
return new LoginResult
{
Ok = true,
SaveId = saveId,
User = newUser
};
}

如果当前用户已存在存档文件,则验证下登录用户的密码,然后调用LoadBytesAsync方法,根据用户的存档 ID 来获取存档文件内容,赋值给变量 saveData。

然后通过封装的反序列化DeserializeFromByteArray方法将字节数组变量 saveData 转换成 User 类型,赋值给 user 变量,最后再将用户登录的结果返回。

// 验证密码
var saveItem = saveItems[0];
// 读取存档
var saveData = await CloudSaveSDK.Instance.Files.LoadBytesAsync(saveItem.SaveId);
var user = SerializeHelper.DeserializeFromByteArray (saveData);
return new LoginResult
{
Ok = true,
SaveId = saveItem.SaveId,
User = user
};

5.4抽卡云函数结合 CloudSaveSDK 实现读档和存档

当我们点击抽卡按钮时,抽到的金币、钻石、角色、物品等都会更新到 UI 面板上,也会同步更新到云存档文件中的。

现在我们再次运行游戏,然后找到场景中的抽卡的 Button 按钮,可以看到按钮响应的是 MainUIController 脚本中的DrawCard方法。

下面的示例代码给我们展示的是抽卡的方法(DrawCard)的客户端的逻辑代码

public async void DrawCard(int count = 1)
{
ShowLoading(true);
try
{
await NetworkManager.DrawCard(count);
}
catch (Exception e)
{
Debug.Log(e.Message);
ShowLoading(false);
MessageUI.Show(e.Message);
}
}

在 NetworkManager.DrawCard 代码中,会调用ActionService.cs 脚本中的抽卡云函数(Draw)来获取抽卡的结果

private ActionService _as;

public async Task DrawCard(int count)
{
try
{
        var drawResult = await _as.Draw(_saveId, count);    
        //此处省略其它代码行......
}
catch (Exception e)
{
onError.Invoke(e.Message, 3);
}
}

下面我们来看看抽卡云函数(Draw)的代码吧,代码中实现了从卡池中抽取卡,并返回抽取的结果。

大家在这里先思考一个问题:为什么需要把逻辑放在云函数里面呢?

原因是客户端包体可能会被破解,需要把扣除资源(扣钻石)这种逻辑放在云端,这样才安全,否则客户端被破解后,可以在客户端无限抽卡了。

我们接着来看下代码逻辑:

  • 在方法中先通过调用 CloudSaveSDK 封装的方法 LoadBytesAsync ,根据用户的存档 ID 来获取到存档文件内容 saveData。

  • 然后再通过封装的反序列化 DeserializeFromByteArray 方法,将字节数组类型的结果转换成 User 类型赋值给了变量 user,获取到了当前 user 的信息。

  • 接着开始进行抽卡,中间这段代码是抽卡的逻辑代码,大家有需要可以自行查看代码。

  • 抽卡结束后会调用UpdateAsync 方法来更新存档数据。更新存档时需要传入 2 个参数,分别是存档 ID 和更新存档选项 options。 在更新存档文件时 UpdateOptions 有不同的方式,可以是 ByFilePath、ByFileStream 或者 ByFileBytes。 在这里,我们通过UpdateFileWay.ByFileBytes 字节文件的方式来更新,而当前用户的信息会以 byte[] 数组的形式存在变量 data 中。

  • 最后该方法再将用户抽卡的结果信息返回。


[CloudFunc]
public async Task Draw( string saveId, int count)
{
Debug.Log("call to draw");
// read
var saveData = await CloudSaveSDK.Instance.Files.LoadBytesAsync(saveId);
var user = SerializeHelper.DeserializeFromByteArray (saveData);

if (count > user.Diamonds)
{
return new DrawResult
{
Ok = false,
Message = "钻石不够"
};
}

// start to draw
        // 此处省略其它代码行......

// write
user.Diamonds += diamonds - count;
user.Coins += coins;
user.LuckPoints += luckPoints;
user.DrawCounts += count;
user.DrawPool = pool;
user.Heroes = heroes;
user.Props = props;
var data = SerializeHelper.SerializeToByteArray(user);
var options = new UpdateOptions
{
File = new FileOptions
{
UpdateFileWay = UpdateFileWay.ByFileBytes,
FileBytes = data,
}
};
Debug.Log(saveId);
await CloudSaveSDK.Instance.Files.UpdateAsync(saveId, options);
return new DrawResult
{
Ok = true,
Items = res,
User = user
};
}
}

通过云函数 Draw 抽取卡片后,在 NetworkManager.cs 脚本的 DrawCard 方法中会更新本地缓存,同时处理抽卡结果

  • 抽卡成功后,会将 _user 变量赋值为更新后的用户信息 drawResult.User。

  • 同时将抽取到的卡片列表 drawResult.Items 转换为一个新的集合对象 listResult,来存储抽卡结果。

  • 通过 onDrawCard.Invoke 方法的调用,通知注册到 onDrawCard 事件的监听器,在用户界面上显示新抽取的卡片的信息。


public async Task DrawCard(int count)
{
try
{
var drawResult = await _as.Draw(_saveId, count);
// update cache
if (!drawResult.Ok)
{
throw new Exception(drawResult.Message);
}
_user = drawResult.User;
var listResult = new List (drawResult.Items.Count);
listResult.AddRange(drawResult.Items.Select(it => new Item
{
Type = it.Type switch
{
0 => ItemType.Hero,
1 => ItemType.Prop,
_ => ItemType.Other
},
Name = it.Name,
Count = it.Count,
Level = it.Level
}));

onDrawCard.Invoke(listResult);
}
catch (Exception e)
{
onError.Invoke(e.Message, 3);
}
}

6.上传和部署云函数,切换到远程调用模式下测试云函数的调用

当全部验证完代码逻辑后,请先切换成远程模式上传云函数后再进行验证远程调用

6.1上传云函数

所以,我们现在就可以点击 Func Stateless Tool 面板的「上传云函数」的按钮,来等待云函数的构建和部署。

当云函数构建完成后,工具会自动将云函数部署到 Func Stateless。 可以通过点击工具中的时间戳,快速跳转到网页控制台查看云函数部署情况。

可以看到已经自动将云函数 loginservice 和 actionservice 部署到 Func Stateless 了。

点击云函数代码缓存路径下的文件,会自动为你下载 zip 文件的。

可以点击查看下,会自动将我们配置的云函数路径(Scripts/CloudService)下的脚本都缓存到 zip 压缩包中的。

6.2切换成远程调用模式

云函数的调用模式分为本地调用和远程调用,之前我们已经运行项目测试过,都属于在本地调用云函数。

现在,让我们在 Func Stateless Tool 工具中,将云函数的调用模式由「本地调用」切换到「远程调用」主要的目的还是防止客户端包体可能会被破解,所以权威性逻辑不能在客户端执行,而必须在服务端上执行。

远程调用:它指的是自动将本地函数调用请求改写成 HTTP 等网络服务请求。

切换到远程调用模式后,云函数代码发生了变化。打开脚本可以看到,云函数中的代码由原来的业务逻辑变成了远程调用的代码。如下图所示:

LoginService.cs脚本中的登录云函数 Login:

[CloudService]
public class LoginService
{
[CloudFunc]
public async Task Login( string username)
{
var jwtToken = await AuthTokenManager.GetAccessToken(Settings.AppID);
FuncContext.SetAppIdSecret(Settings.AppID, Settings.AppSecret);
FuncContext.SetToken(jwtToken);
var json = "{" + $"\"username\":{JsonConvert.SerializeObject(username)}" + "}";
var jsonResult = await HttpClient.Call($"https://{LoginServiceq3k9FwJi5A.Domain}/release/c1ac2450-730c-4f5a-a15a-7a314c6b9a9e/loginservice", "login", json);
try
{
return JsonConvert.DeserializeObject (jsonResult);
}
catch (Exception ex)
{
Debug.LogException(ex);
throw new ExecuteException(jsonResult);
}
}
}

ActionService.cs脚本中的抽卡云函数 Draw:

[CloudFunc]
public async Task Draw( string saveId, int count)
{
var jwtToken = await AuthTokenManager.GetAccessToken(Settings.AppID);
FuncContext.SetAppIdSecret(Settings.AppID, Settings.AppSecret);
FuncContext.SetToken(jwtToken);
var json = "{" + $"\"saveId\":{JsonConvert.SerializeObject(saveId)},\"count\":{JsonConvert.SerializeObject(count)}" + "}";
var jsonResult = await HttpClient.Call($"https://{ActionServiceq3k9FwJi5A.Domain}/release/c1ac2450-730c-4f5a-a15a-7a314c6b9a9e/actionservice", "draw", json);
try
{
return JsonConvert.DeserializeObject (jsonResult);
}
catch (Exception ex)
{
Debug.LogException(ex);
throw new ExecuteException(jsonResult);
}
}

6.3 查看调用的日志信息

切换成【远程调用】模式后,再次运行游戏项目。

然后回到 UOS 网页端的云函数页面,可以查看云函数的调用日志,可以看到项目已经成功调用了上传的云函数。

6.4重置云函数

Func Stateless Tool 工具还给我们提供了重置云函数功能。如果点击【重置云函数】按钮后,会将云函数回退到上一个版本的云函数。然后当我们修改云函数的代码后,可以再次上传新的云函数。

本期教程我们先讲解到这里,大家赶快下载 Demo 项目试试吧!我们下一期再见哦!

Unity Online Services (UOS) 是一个专为游戏开发者设计的一站式游戏云服务平台,提供覆盖游戏全生命周期的开发、运营和推广支持。

了解更多 UOS 相关信息:

官网:https://uos.unity.cn

技术交流 QQ 群:823878269

公众号:UOS 游戏云服务

Unity 官方微信

第一时间了解Unity引擎动向,学习进阶开发技能

每一个“点赞”、“在看”,都是我们前进的动力

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
华为余承东:尊界新车价格在200万左右

华为余承东:尊界新车价格在200万左右

界面新闻
2026-04-26 13:08:56
3年怀5次,谢贤前女友曝三胎生父,王晶终于坦白张柏芝婚变真相

3年怀5次,谢贤前女友曝三胎生父,王晶终于坦白张柏芝婚变真相

小兰聊历史
2026-04-26 00:25:56
网红猴哥恋情曝光,他手在女生身上到处摸,女友疑04年身份引争议

网红猴哥恋情曝光,他手在女生身上到处摸,女友疑04年身份引争议

一娱三分地
2026-04-27 14:35:50
9级雷暴大风!广州新一轮强降水来袭!至于“五一”天气……

9级雷暴大风!广州新一轮强降水来袭!至于“五一”天气……

羊城攻略
2026-04-27 22:45:37
002360,将被“ST”!

002360,将被“ST”!

中国基金报
2026-04-28 00:14:10
广东3消息!球票罕见滞销,胡明轩登上杂志封面,萨林杰正式辟谣

广东3消息!球票罕见滞销,胡明轩登上杂志封面,萨林杰正式辟谣

多特体育说
2026-04-27 22:47:09
1976年,华国锋宣布关于邓小平的3条要求,事后看很高明

1976年,华国锋宣布关于邓小平的3条要求,事后看很高明

大运河时空
2026-04-27 22:10:03
2028大选无望?郑丽文最新民调支持率4%,访陆成功助力蒋万安登顶

2028大选无望?郑丽文最新民调支持率4%,访陆成功助力蒋万安登顶

混沌录
2026-04-27 19:52:08
扎克·施奈德首曝《蝙蝠侠大战超人》未公开海报!

扎克·施奈德首曝《蝙蝠侠大战超人》未公开海报!

3DM游戏
2026-04-27 09:20:10
广东公开通报七起违反中央八项规定精神典型问题

广东公开通报七起违反中央八项规定精神典型问题

新浪财经
2026-04-27 19:33:34
第一集就全裸出镜,女神新剧破格出演了

第一集就全裸出镜,女神新剧破格出演了

来看美剧
2026-04-27 16:21:10
能道歉吗,近五年哈登天王山之战场均仅得10.8分,命中率28%

能道歉吗,近五年哈登天王山之战场均仅得10.8分,命中率28%

懂球帝
2026-04-27 14:44:26
香烟又被关注!医生研究发现:抽得越多,寿命或越短?告诉你真相

香烟又被关注!医生研究发现:抽得越多,寿命或越短?告诉你真相

医学科普汇
2026-04-27 21:15:06
打败韭菜荠菜!唯一不含草酸的菜,春天多吃,高钙高蛋白,别错过

打败韭菜荠菜!唯一不含草酸的菜,春天多吃,高钙高蛋白,别错过

江江食研社
2026-04-26 23:30:03
作案前10分钟,白宫晚宴枪手给家属发“宣言”,家属火速报警!特朗普:该早点告诉我们

作案前10分钟,白宫晚宴枪手给家属发“宣言”,家属火速报警!特朗普:该早点告诉我们

红星新闻
2026-04-27 13:37:08
陕西广电《都市快报》记者王景文因病去世,年仅51岁

陕西广电《都市快报》记者王景文因病去世,年仅51岁

澎湃新闻
2026-04-27 10:18:27
事态升级,中方开打第二波反击,高市或突然辞职,石破茂已扛旗

事态升级,中方开打第二波反击,高市或突然辞职,石破茂已扛旗

兰妮搞笑分享
2026-04-27 22:46:30
泪目 赵心童晒儿时与丁俊晖合照:偶像晖哥让我加油 你也要加油啊

泪目 赵心童晒儿时与丁俊晖合照:偶像晖哥让我加油 你也要加油啊

风过乡
2026-04-27 06:15:09
“天价赔偿570亿美元:六年了,我们一分钱没拿到”

“天价赔偿570亿美元:六年了,我们一分钱没拿到”

观察者网
2026-04-26 17:21:11
累到瘫软也不换!雷迪克故意整勒布朗?美记暗示为延续144场上双

累到瘫软也不换!雷迪克故意整勒布朗?美记暗示为延续144场上双

颜小白的篮球梦
2026-04-27 14:10:25
2026-04-28 00:56:49
Unity incentive-icons
Unity
Unity中国官方帐户
2462文章数 6731关注度
往期回顾 全部

科技要闻

DeepSeek V4上线三天,第一批实测出来了

头条要闻

坐在特朗普身边亲历枪击案的女记者 身份非常不一般

头条要闻

坐在特朗普身边亲历枪击案的女记者 身份非常不一般

体育要闻

人类马拉松"破二"新纪元,一场跑鞋军备竞赛

娱乐要闻

黄杨钿甜为“耳环风波”出镜道歉:谣言已澄清

财经要闻

Meta 140亿收购Manus遭中国发改委否决

汽车要闻

不那么小众也可以 smart的路会越走越宽

态度原创

房产
本地
数码
亲子
公开课

房产要闻

信号!海南商业版图,迎来大变局!

本地新闻

云游中国|逛世界风筝都 留学生探秘中国传统文化

数码要闻

LABUBU冰箱还未正式发售就已溢价3000元

亲子要闻

小姨又给妹妹买了好多衣服,姐做兼职给你买

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版