新年將至,Unity 中國(guó)祝所有小伙伴和家人們新年快樂(lè)!2024 年,。新的一年里,愿所有小伙伴在吉運(yùn)常伴,創(chuàng)意無(wú)限,不斷取得豐碩成果!點(diǎn)擊下圖,領(lǐng)取蛇年限定紅包封面~
本文分享 UOS 實(shí)用教程,幫助大家在假期輕松提升技能。
之前,我們已經(jīng)為大家分享了一篇教程《》,詳細(xì)介紹了如何使用 Unity Online Services(UOS)提供的 C# 云函數(shù)服務(wù),在 Unity 開(kāi)發(fā)環(huán)境中快速實(shí)現(xiàn)和部署聯(lián)網(wǎng)游戲的服務(wù)器端邏輯。在那篇教程中,我們展示了如何使用云數(shù)據(jù)庫(kù)服務(wù) CRUD Storage 來(lái)存儲(chǔ)諸如玩家信息和抽卡記錄等數(shù)據(jù)。
今天,我們將再次以“抽卡”功能為例,但深入探討的技術(shù)內(nèi)容有所不同。
在本篇教程中,我們將借助 UOS 提供的Passport-Login服務(wù) 來(lái)完成用戶的登錄驗(yàn)證流程。隨后,我們將利用云函數(shù) Func-Stateless來(lái)部署抽卡功能的服務(wù)器端邏輯代碼。最后,這些抽卡數(shù)據(jù)將通過(guò)CRUD-Save服務(wù) 實(shí)現(xiàn)云端存儲(chǔ),確保數(shù)據(jù)的持久性和安全性。
本教程中涉及 UOS 服務(wù)包括:
云函數(shù)服務(wù) Func Stateless (C#):用于部署抽卡服務(wù)端邏輯代碼
云存檔服務(wù) CRUD Save:用于存儲(chǔ)抽卡記錄等數(shù)據(jù)
玩家通行證服務(wù) Passport Login:實(shí)現(xiàn)用戶登錄及實(shí)名認(rèn)證
云函數(shù)服務(wù) Func Stateless
在保障游戲或應(yīng)用安全性的重要考量下, 確保關(guān)鍵邏輯和數(shù)據(jù)的權(quán)威性處理僅在服務(wù)器端執(zhí)行顯得尤為重要 。這是因?yàn)?客戶端的代碼包體容易被破解或篡改 ,從而引發(fā)不公平競(jìng)爭(zhēng)、數(shù)據(jù)泄露等安全問(wèn)題。UOS Func Stateless (C#) 云函數(shù)以其高效、靈活、安全且成本低的特點(diǎn),為游戲開(kāi)發(fā)者提供了一個(gè)理想的服務(wù)端邏輯解決方案,助力他們?cè)诳焖俚氖袌?chǎng)環(huán)境中保持競(jìng)爭(zhēng)優(yōu)勢(shì)。
Func Stateles s 支持在本地開(kāi)發(fā)環(huán)境中直接調(diào)試云函數(shù) ,無(wú)需部署到云端即可驗(yàn)證邏輯的正確性,降低了調(diào)試難度,加快了開(kāi)發(fā)迭代速度。
Func Stateless 能夠 自動(dòng)將服務(wù)端邏輯代碼打包并部署到云端 ,簡(jiǎn)化了部 署流程,減少了人為錯(cuò)誤,讓開(kāi)發(fā)者更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。
云存檔服務(wù) CRUD Save
借助 UOS Save 所提供的全面而專業(yè)的 玩家數(shù)據(jù)存儲(chǔ)、檢索及 管理服務(wù) ,開(kāi)發(fā)者能夠極為便捷地為廣大游戲玩家打造出一個(gè)跨越不同平臺(tái)與設(shè)備的、安全且高度可用的游戲存檔系統(tǒng)。
這一服務(wù)不僅確保了玩家能夠在任何時(shí)間、任何地點(diǎn)無(wú)縫地繼續(xù)他們的游戲進(jìn)程,而且通過(guò)嚴(yán)格的數(shù)據(jù)安全保障措施,讓玩家的游戲數(shù)據(jù)始終處于嚴(yán)密的保護(hù)之下。同時(shí),其高可用性的設(shè)計(jì)也保證了玩家數(shù)據(jù)的實(shí)時(shí)同步與持久存儲(chǔ),為玩家?guī)?lái)了更加流暢、穩(wěn)定的游戲體驗(yàn)。
玩家通行證服務(wù) Passport
Passport 是 UOS 官方提供的玩家通行證服務(wù),包括玩家登錄系統(tǒng) (Login),以及與玩家相關(guān)的眾多游戲功能 (Feature)。
Passport Login 是一個(gè)可以開(kāi)箱即用的玩家登錄系統(tǒng),通過(guò)非常簡(jiǎn)便的集成方式, 便可以獲得如下功能: 靈活可 配的 UI 登錄界面,包含用戶協(xié)議、登錄、實(shí)名認(rèn)證; 支持手機(jī)號(hào)登錄,以及微信,QQ, Apple ID 等 主流第三方 OAuth 登錄;可以配置游戲服務(wù)器,以及管理游戲角色。
Passport Feature 是以玩家為中心,包含了在線游戲中各種常見(jiàn)的功能。包括:排行榜、公會(huì)、游戲禮包、防沉迷系統(tǒng)、經(jīng)濟(jì)系統(tǒng)、郵件系統(tǒng)、成就系統(tǒng)、戰(zhàn)令系統(tǒng)、公告、任務(wù)系統(tǒng)。
教程視頻
教程學(xué)習(xí)大綱
在 UOS 官網(wǎng)下載示例項(xiàng)目工程
創(chuàng)建 UOS App 并啟用 Func-Stateless / CRUD-Save / Passport-Login 服務(wù)
設(shè)置云函數(shù)的安裝和配置目錄
在本地模式下調(diào)試并運(yùn)行項(xiàng)目,通過(guò) Passport 實(shí)現(xiàn)用戶登陸
通過(guò) CRUD Save 實(shí)現(xiàn)用戶抽卡數(shù)據(jù)的云存檔
上傳和部署云函數(shù),切換到遠(yuǎn)程調(diào)用模式下測(cè)試云函數(shù)的調(diào)用
教程案例工程源文件
教程內(nèi)學(xué)習(xí)用到的項(xiàng)目工程文件可以通過(guò)以下方式進(jìn)行下載:
UOS 官網(wǎng)示例教程中在線下載:
https://uos.unity.cn/doc/func/stateless/csharp-tutorial#prepare
教程操作步驟
接下來(lái)讓我們來(lái)看看 Func Stateless 云函數(shù)結(jié)合 CRUD Save 云存檔在抽卡項(xiàng)目中的具體用法吧!
1. 在 UOS 官網(wǎng)下載示例項(xiàng)目工程
1.1 下載項(xiàng)目工程文件
首先前往上方提供的UOS 官網(wǎng)的教程鏈接地址,在「Func」的文檔左側(cè)頁(yè)面選擇「Stateless」下方的「示例教程(C#)」,在「準(zhǔn)備工作」模塊點(diǎn)擊按鈕「DEMO(CLOUDSAVE PASSPORT)工程」,就可以下載抽卡 Demo 的項(xiàng)目工程了。
1.2 解壓縮項(xiàng)目工程文件
在下載完項(xiàng)目工程壓縮包以后,請(qǐng)解壓縮下圖中的項(xiàng)目工程文件。
1.3 加載項(xiàng)目工程
然后打開(kāi) Unity Hub,選擇電腦上已經(jīng)安裝過(guò)的 Unity 編輯器版本,來(lái)打開(kāi)剛剛下載好的項(xiàng)目工程,教程這里使用的是Unity2022.3.48f1c1 版本。
打開(kāi)項(xiàng)目工程以后,在項(xiàng)目的 Project 窗口中,找到「Assets/Scenes/Demo.unity」場(chǎng)景并打開(kāi)。
點(diǎn)擊「Import TMP Essentials」導(dǎo)入 TextMeshPro 的字體資源。
2.綁定 UOS App 并開(kāi)啟服務(wù)
溫馨提示:當(dāng)前項(xiàng)目中已安裝好 UOS Launcher,不需要再次安裝了。大家可以參考之前的的公眾號(hào)文章教程,來(lái)為你當(dāng)前的項(xiàng)目綁定你創(chuàng)建好的 UOS App 。
2.1 綁定 UOS App
點(diǎn)擊 Launcher 面板的「LinkApp」按鈕,在彈出窗口中選擇「By Unity project」,在「Select organization」這里選擇一個(gè)自己的項(xiàng)目組織,然后在「Select project 」選項(xiàng)這里,我們選擇「Create a new project 」,自行設(shè)置修改項(xiàng)目名字「Project name」。
教程中,我們就先選擇綁定創(chuàng)建好的 FuncStatelessAndSaveDemo 應(yīng)用了。
2.2 開(kāi)啟 Func-Stateless 服務(wù)
在編輯器內(nèi) Unity Online Services 窗口的下拉服務(wù)列表中,找到「Func - Stateless」,點(diǎn)擊「Enable」按鈕來(lái)一鍵開(kāi)啟服務(wù), 進(jìn)入 UOS 網(wǎng)頁(yè)端可以看到,此時(shí)默認(rèn)云函數(shù)的腳本語(yǔ)言是基于 Dotnet 的運(yùn)行環(huán)境的。
如果你是在 UOS 網(wǎng)頁(yè)端為當(dāng)前 App 開(kāi)啟 Func-Stateless 服務(wù)的話,需要自行手動(dòng)在腳本語(yǔ)言的下拉選項(xiàng)框中選擇「Dotnet」環(huán)境 。
當(dāng)前項(xiàng)目中已經(jīng)安裝過(guò) Func - Stateless 服務(wù) SDK,無(wú)需再次安裝了。如果你自己的項(xiàng)目中沒(méi)安裝過(guò)的話,可以點(diǎn)擊「Install SDK」的按鈕進(jìn)行安裝。
2.3 開(kāi)啟 CRUD-Save 服務(wù)
接著繼續(xù)在UOS Launcher 的下拉服務(wù)窗口列表中,找到「CRUD-Save」,點(diǎn)擊「Enable」開(kāi)啟服務(wù),當(dāng)然你也可以在 UOS 網(wǎng)站上直接開(kāi)啟服務(wù)。
當(dāng)前項(xiàng)目中也是已經(jīng)安裝過(guò) CRUD-Save 服務(wù)的 SDK了,無(wú)需再次安裝了。
2.4 開(kāi)啟 Passport-Login 服務(wù)
接著繼續(xù)在 UOS Launcher 的下拉服務(wù)窗口列表中,找到「Passport Login」,點(diǎn)擊「Enable」開(kāi)啟服務(wù)。
當(dāng)前項(xiàng)目中也是已經(jīng)安裝過(guò) Passport Login 服務(wù)的 SDK了,無(wú)需再次安裝了。
3.設(shè)置云函數(shù)的安裝和配置目錄
3.1 導(dǎo)入 NuGetForUnity 工具
在 Unity Editor 菜單欄中先點(diǎn)擊「UOS -> Func Stateless -> Open Panel」按鈕,打開(kāi) Func Stateless Tool 面板。
然后再點(diǎn)擊「UOS -> Func Stateless -> Import NuGetForUnity」,來(lái)導(dǎo)入 UOS 版本的 NuGetForUnity 工具。
3.2 修改云函數(shù)的安裝和配置目錄
導(dǎo)入完成后,點(diǎn)擊菜單欄中「NuGet -> Preferencs」打開(kāi)配置界面。修改Packages Install Path 為云函數(shù)所在目錄下的 Packages 目錄;Packages Config Path 為云函數(shù)所在目錄。
在這里我們就先使用給定的默認(rèn)設(shè)置目錄了,大家有需要自行修改
登錄功能模塊的 UI 界面和腳本,我們使用的是 UOS 的 Passport 服務(wù)為用戶提供的示例模板,在之前點(diǎn)擊導(dǎo)入「PassportUI」資源的時(shí)候已經(jīng)導(dǎo)入過(guò)了,可以直接在項(xiàng)目中使用。
在這里我們使用的是手機(jī)號(hào)短信驗(yàn)證碼的方式登錄的, 由于 Passport 的「登錄配置」默認(rèn)是開(kāi)啟了真實(shí)短信驗(yàn)證和實(shí)名認(rèn)證的。所以,這里請(qǐng) 輸入真實(shí)的手機(jī)號(hào)和短信驗(yàn)證碼 來(lái)完成登錄。
當(dāng)輸入完短信驗(yàn)證碼后,可以在 Console 控制臺(tái)看到輸出的日志信息:「完成登錄」。
日志信息是在 UIController.cs 腳本中調(diào)用輸出的,找到場(chǎng)景中的 UIController 對(duì)象,可看到掛載的 UIController.cs 腳本:
進(jìn)入 UIController.cs 腳本查看下代碼,在 Start 方法中會(huì)調(diào)用 Passport SDK 進(jìn)行 UI 初始化的方法 Init ,方法中需要傳入配置 _config 和回調(diào)函數(shù) _callback。
_config 是 Passport SDK 進(jìn)行初始化時(shí)的相關(guān)配置:來(lái)設(shè)置是否自動(dòng)旋轉(zhuǎn)屏幕方向、是否通過(guò)自行調(diào)用 Login 函數(shù)啟動(dòng)登錄面板,以及設(shè)置 UI 風(fēng)格主題是深色還是淺色等等。
_callback 是 Passport SDK 的回調(diào)函數(shù):Passport 中已經(jīng)封裝注冊(cè)好了,在用戶拒絕協(xié)議、完成登錄、完成所有流程、用戶登出這幾個(gè)狀態(tài)下的回調(diào)事件。
所以當(dāng)用戶短信驗(yàn)證通過(guò)后,會(huì)自動(dòng)進(jìn)入LoggedIn狀態(tài)下的回調(diào)事件,輸出了日志「完成登錄」。
public class UIController : MonoBehaviour
{
// sdk 配置(Config 是 SDK 初始化時(shí)的配置)
private readonly PassportUIConfig _config = new()
{
AutoRotation = true, // 是否開(kāi)啟自動(dòng)旋轉(zhuǎn),默認(rèn)值為 false。
InvokeLoginManually = false, // 是否通過(guò)自行調(diào)用 Login 函數(shù)啟動(dòng)登錄面板,默認(rèn)值為 false。
Theme = PassportUITheme.Dark, // 風(fēng)格主題配置。
UnityContainerId = "unity-container" // WebGL 場(chǎng)景下 Unity 實(shí)例容器 Id。
};
// sdk 回調(diào)
private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調(diào)事件,詳情可以參考下面的回調(diào)類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協(xié)議");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登錄");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
break;
case PassportEvent.LoggedOut:
Debug.Log("用戶登出");
break;
}
}
private void Start()
{
// 調(diào)用 SDK
PassportUI.Init(_config, _callback);
}
}
然后會(huì)需要進(jìn)行實(shí)名身份認(rèn)證:
當(dāng)實(shí)名驗(yàn)證成功后,會(huì)看到控制臺(tái)日志輸出:「完成所有流程」。
此時(shí)會(huì)自動(dòng)回調(diào)PassportEvent.Completed 狀態(tài)下的事件。
4.2 修改登錄配置
接著可以點(diǎn)擊 Passport Login 旁邊的「Developer portal」按鈕,進(jìn)入 UOS 網(wǎng)頁(yè)端的 Passport 模塊來(lái)自行修改「登錄配置」中的設(shè)置。
點(diǎn)擊選中「模擬短信驗(yàn)證」,這樣驗(yàn)證碼直接輸入「 111111」即可;取消選中「實(shí)名認(rèn)證」,則暫時(shí)不再驗(yàn)證用戶的身份信息。
修改完配置后,可以再次回到編輯器測(cè)試下效果。點(diǎn)擊運(yùn)行游戲,選擇「退出登錄」,然后「使用新的賬號(hào)登錄」,再重新輸入一個(gè)手機(jī)號(hào),然后輸入短信驗(yàn)證碼 111111 ,就可以成功登陸了,同時(shí)也跳過(guò)了身份驗(yàn)證,直接可以進(jìn)入游戲。Console 控制臺(tái)也能看到對(duì)應(yīng)的日志輸出。
4.3 選擇角色
登陸完成后,會(huì)選擇一個(gè)角色進(jìn)入游戲。 在腳本的 _callback 方法的Completed 狀態(tài)下,看到會(huì)調(diào)用選擇角色的方法SelectPersona。
private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調(diào)事件,詳情可以參考下面的回調(diào)類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協(xié)議");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登錄");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
break;
case PassportEvent.LoggedOut:
Debug.Log("用戶登出");
break;
}
}
在方法 SelectPersona 中,會(huì)先調(diào)用PassportSDK.Identity.GetRealms()方法獲取域列表,然后從域列表中根據(jù)需要選擇一個(gè)域,在這里我們暫時(shí)先使用域列表的第一個(gè)域。
大家也可以手動(dòng)填寫你想要使用的某一個(gè)域。我們進(jìn)入 UOS 網(wǎng)頁(yè)端 Passport 的「服務(wù)器」模塊,可以看到默認(rèn)已經(jīng)幫我們創(chuàng)建了一個(gè)域了。點(diǎn)擊復(fù)制域的 UUID,然后賦值給腳本中的域變量 realmID 即可。
// 選擇域
//var realms = await PassportSDK.Identity.GetRealms(); // 獲取域列表
//var realmID = realms[0].RealmID; // 根據(jù)需要自行選擇域
var realmID = "543b59d1-3008-4028-8591-83167e82feb0"; // 也可以填寫固定的 RealmID 而不是動(dòng)態(tài)獲取
修改完代碼后,可以再次運(yùn)行,是可以正常進(jìn)入游戲的。然后我們需要選擇一個(gè)角色,先通過(guò)調(diào)用GetPersonas() 方法來(lái)獲取到角色列表。
由于 Demo 示例中我們默認(rèn)啟用的配置是:一個(gè)用戶單服務(wù)器下只能創(chuàng)建一個(gè)角色,并沒(méi)有開(kāi)啟單服務(wù)器下多角色的功能。所以目前是一個(gè)手機(jī)號(hào)對(duì)應(yīng)一個(gè)用戶 ID,一個(gè)用戶 ID 對(duì)應(yīng)一個(gè)角色 ID。
如果角色列表中沒(méi)有任何角色,則會(huì)調(diào)用 CreatePersona 方法,在指定的域 realmID 中來(lái)創(chuàng)建一個(gè)角色賦值給 persona 變量。
如果角色列表中已經(jīng)有角色的話,會(huì)直接選擇已創(chuàng)建過(guò)的角色 personas[0] 賦值給 persona 變量。最后,再通過(guò)調(diào)用 SelectPersona 方法來(lái)指定選擇的角色。
// 選擇角色
private async Task SelectPersona()
{
// 選擇域
//var realms = await PassportSDK.Identity.GetRealms(); // 獲取域列表
//var realmID = realms[0].RealmID; // 根據(jù)需要自行選擇域
var realmID = "543b59d1-3008-4028-8591-83167e82feb0"; // 也可以填寫固定的 RealmID 而不是動(dòng)態(tài)獲取
// 獲取(或創(chuàng)建)與選擇角色
Persona persona = null;
var personas = await PassportSDK.Identity.GetPersonas(); // 獲取角色列表
if (!personas.Any())
{
// 若沒(méi)有角色,則新建角色
persona = await PassportSDK.Identity.CreatePersona("YourDisplayName", realmID);
}
else
{
// 若有角色,則選擇第一個(gè)角色
persona = personas[0];
}
// 選擇角色
await PassportSDK.Identity.SelectPersona(persona.PersonaID);
}
4.4 創(chuàng)建一個(gè)新的域
用戶也可自行創(chuàng)建一個(gè)新的域來(lái)使用。在 Passport 的【服務(wù)器】管理頁(yè)面,點(diǎn)擊【創(chuàng)建新的域】,自己設(shè)置下名稱,點(diǎn)擊【創(chuàng)建】即可。
然后可以將新創(chuàng)建的域的 UUID 賦值給腳本中的變量 realmID ,再次進(jìn)行測(cè)試后,回到 UOS 網(wǎng)頁(yè)端,點(diǎn)擊查看域的詳情,可以看到域中已經(jīng)有創(chuàng)建的角色信息了。
4.5 進(jìn)入游戲場(chǎng)景,獲取用戶登錄信息
登陸成功后,會(huì)看到下面的游戲畫面,我們先來(lái)看看【進(jìn)入游戲】的 UI 對(duì)象上綁定的事件。
場(chǎng)景中的 MainUI 對(duì)象上掛載了 MainUIController.cs 腳本,當(dāng)我們點(diǎn)擊【進(jìn)入游戲】的按鈕時(shí),會(huì)執(zhí)行 MainUIController.cs 腳本中的 StartGame 的方法。
點(diǎn)擊【進(jìn)入游戲】按鈕,可以進(jìn)入游戲場(chǎng)景:
同時(shí),Console 控制臺(tái)會(huì)看到有下面的日志輸出信息。
首先來(lái)看看輸出的日志:“currentUserId, use as save name”,說(shuō)明執(zhí)行了 StartGame 方法 。會(huì)首先通過(guò) PassportSDK 來(lái)獲取到當(dāng)前角色所屬的用戶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);
}
}
剛才控制臺(tái)對(duì)應(yīng)輸出的日志信息還有:“l(fā)oginResult: 1000000001, 106104, 467”。因?yàn)榇a中拿到 UserID 后,異步調(diào)用了NetworkManager.Login方法。
在 Login 方法中,會(huì)先通過(guò)調(diào)用云函數(shù)腳本 LoginService 中的云函數(shù) Login 來(lái)獲取到登陸結(jié)果 loginResult。然后再通過(guò)事件調(diào)用,將用戶的昵稱(Nickname)、金幣數(shù)(Coins)、鉆石數(shù)(Diamonds)的信息,同步更新到 UI 界面上。
稍后我們會(huì)展開(kāi)來(lái)詳細(xì)講解:云函數(shù)腳本 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.云函數(shù)結(jié)合 CRUD Save 實(shí)現(xiàn)用戶抽卡數(shù)據(jù)的云存檔
接下來(lái)看看項(xiàng)目中如何使用 CloudSave 實(shí)現(xiàn)讀檔和存檔的!
5.1查看網(wǎng)頁(yè)端的云存檔文件
運(yùn)行游戲點(diǎn)擊抽卡后,用戶抽卡的相關(guān)結(jié)果數(shù)據(jù)信息,其實(shí)已經(jīng)通過(guò) UOS 的 CRUD-Save 服務(wù)實(shí)現(xiàn)云存檔了。
我們進(jìn)入 UOS 網(wǎng)頁(yè)端CRUD Save的【存檔管理】頁(yè)面,可以看到當(dāng)前登錄的用戶,然后點(diǎn)擊最右邊的【詳情】按鈕。
可以在彈出的界面中查看【存檔詳情】,點(diǎn)擊【下載存檔】。
打開(kāi)存檔文件后,可以查看文件中存儲(chǔ)的抽卡的信息。
用戶也可以在網(wǎng)頁(yè)上點(diǎn)擊【對(duì)玩家隱藏存檔】或者【刪除存檔】,現(xiàn)在我們演示下點(diǎn)擊頁(yè)面上的【刪除存檔】按鈕,則會(huì)清空該存檔 ID 對(duì)應(yīng)的一整條信息的。
此時(shí)如果再次運(yùn)行游戲,可以看到金幣數(shù)和鉆石數(shù)將顯示為 0,英雄列表和背包列表也都為空了。
接下來(lái)我們看看云存檔的功能具體是如何實(shí)現(xiàn)的!
5.2初始化 CloudSaveSDK
在 MainUIController.cs 腳本的 Start 方法中,會(huì)調(diào)用InitializeAsync方法,該方法會(huì)使用 UOS Launcher 中填寫的 UOS APP 信息來(lái)實(shí)現(xiàn)對(duì) 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登錄云函數(shù)結(jié)合 CloudSaveSDK 實(shí)現(xiàn)讀取已存檔的用戶信息
然后點(diǎn)擊菜單欄「UOS -> Func Stateless -> Open Panel」按鈕,打開(kāi)「Func Stateless Tool」窗口。
在打開(kāi)的工具的窗口中,可以看到當(dāng)前項(xiàng)目中的 2 個(gè)云函數(shù)類LoginService 和 ActionService。我們先不上傳云函數(shù),先在本地進(jìn)行調(diào)試運(yùn)行項(xiàng)目。
雙擊云函數(shù)「Login」,就可以打開(kāi)云函數(shù)所在腳本了。 我們來(lái)分析下云函數(shù) Login 的代碼,會(huì)先通過(guò)調(diào)用 CloudSaveSDK 封裝的方法ListAllAsync 來(lái)列出當(dāng)前用戶的所有存檔元數(shù)據(jù)信息,存儲(chǔ)在變量 saveItems 中。
[CloudService]
public class LoginService
{
[CloudFunc]
public async Task Login( string username)
{
// 此處省略其它代碼行......
var saveItems = await CloudSaveSDK.Instance.Files.ListAllAsync();
}
}
然后判斷下如果 saveItems 集合長(zhǎng)度為 0,說(shuō)明當(dāng)前用戶沒(méi)有存檔文件,是新用戶第一次登錄,那么我們就會(huì)調(diào)用CreateAsync 方法來(lái)創(chuàng)建一個(gè)存檔文件。
創(chuàng)建存檔文件的時(shí)候,需要傳入三個(gè)參數(shù),存檔名稱 username、字節(jié)數(shù)組類型的存檔文件 data、存檔選項(xiàng) options。
我們創(chuàng)建了一個(gè)新用戶對(duì)象 newUser,然后通過(guò)封裝的SerializeToByteArray方法,將 newUser 的屬性信息轉(zhuǎn)換成字節(jié)數(shù)組變量 data,作為第二個(gè)參數(shù)傳遞到 CreateAsync 方法中。
在存檔選項(xiàng) options 中,設(shè)置了當(dāng)前云存檔的模式為單存檔 ProgressType.Linear,如果你要在你的項(xiàng)目中使用多存檔模式的話,可以使用 ProgressType.Multi。
大家可以自行查看封裝的存檔選項(xiàng)類 CreateOptions,根據(jù)需要自行傳入更多的參數(shù)。
最后會(huì)將用戶登錄的結(jié)果返回。
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
};
}
如果當(dāng)前用戶已存在存檔文件,則驗(yàn)證下登錄用戶的密碼,然后調(diào)用LoadBytesAsync方法,根據(jù)用戶的存檔 ID 來(lái)獲取存檔文件內(nèi)容,賦值給變量 saveData。
然后通過(guò)封裝的反序列化DeserializeFromByteArray方法將字節(jié)數(shù)組變量 saveData 轉(zhuǎn)換成 User 類型,賦值給 user 變量,最后再將用戶登錄的結(jié)果返回。
// 驗(yàn)證密碼
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抽卡云函數(shù)結(jié)合 CloudSaveSDK 實(shí)現(xiàn)讀檔和存檔
當(dāng)我們點(diǎn)擊抽卡按鈕時(shí),抽到的金幣、鉆石、角色、物品等都會(huì)更新到 UI 面板上,也會(huì)同步更新到云存檔文件中的。
現(xiàn)在我們?cè)俅芜\(yùn)行游戲,然后找到場(chǎng)景中的抽卡的 Button 按鈕,可以看到按鈕響應(yīng)的是 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 代碼中,會(huì)調(diào)用ActionService.cs 腳本中的抽卡云函數(shù)(Draw),來(lái)獲取抽卡的結(jié)果。
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);
}
}
下面我們來(lái)看看抽卡云函數(shù)(Draw)的代碼吧,代碼中實(shí)現(xiàn)了從卡池中抽取卡,并返回抽取的結(jié)果。
大家在這里先思考一個(gè)問(wèn)題:為什么需要把邏輯放在云函數(shù)里面呢?
原因是客戶端包體可能會(huì)被破解,需要把扣除資源(扣鉆石)這種邏輯放在云端,這樣才安全,否則客戶端被破解后,可以在客戶端無(wú)限抽卡了。
我們接著來(lái)看下代碼邏輯:
在方法中先通過(guò)調(diào)用 CloudSaveSDK 封裝的方法 LoadBytesAsync ,根據(jù)用戶的存檔 ID 來(lái)獲取到存檔文件內(nèi)容 saveData。
然后再通過(guò)封裝的反序列化 DeserializeFromByteArray 方法,將字節(jié)數(shù)組類型的結(jié)果轉(zhuǎn)換成 User 類型賦值給了變量 user,獲取到了當(dāng)前 user 的信息。
接著開(kāi)始進(jìn)行抽卡,中間這段代碼是抽卡的邏輯代碼,大家有需要可以自行查看代碼。
抽卡結(jié)束后會(huì)調(diào)用UpdateAsync 方法來(lái)更新存檔數(shù)據(jù)。更新存檔時(shí)需要傳入 2 個(gè)參數(shù),分別是存檔 ID 和更新存檔選項(xiàng) options。 在更新存檔文件時(shí) UpdateOptions 有不同的方式,可以是 ByFilePath、ByFileStream 或者 ByFileBytes。 在這里,我們通過(guò)UpdateFileWay.ByFileBytes 字節(jié)文件的方式來(lái)更新,而當(dāng)前用戶的信息會(huì)以 byte[] 數(shù)組的形式存在變量 data 中。
最后該方法再將用戶抽卡的結(jié)果信息返回。
[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
};
}
}
通過(guò)云函數(shù) Draw 抽取卡片后,在 NetworkManager.cs 腳本的 DrawCard 方法中會(huì)更新本地緩存,同時(shí)處理抽卡結(jié)果。
抽卡成功后,會(huì)將 _user 變量賦值為更新后的用戶信息 drawResult.User。
同時(shí)將抽取到的卡片列表 drawResult.Items 轉(zhuǎn)換為一個(gè)新的集合對(duì)象 listResult,來(lái)存儲(chǔ)抽卡結(jié)果。
通過(guò) onDrawCard.Invoke 方法的調(diào)用,通知注冊(cè)到 onDrawCard 事件的監(jiān)聽(tīng)器,在用戶界面上顯示新抽取的卡片的信息。
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.上傳和部署云函數(shù),切換到遠(yuǎn)程調(diào)用模式下測(cè)試云函數(shù)的調(diào)用
當(dāng)全部驗(yàn)證完代碼邏輯后,請(qǐng)先切換成遠(yuǎn)程模式,上傳云函數(shù)后再進(jìn)行驗(yàn)證遠(yuǎn)程調(diào)用。
6.1上傳云函數(shù)
所以,我們現(xiàn)在就可以點(diǎn)擊 Func Stateless Tool 面板的「上傳云函數(shù)」的按鈕,來(lái)等待云函數(shù)的構(gòu)建和部署。
當(dāng)云函數(shù)構(gòu)建完成后,工具會(huì)自動(dòng)將云函數(shù)部署到 Func Stateless。 可以通過(guò)點(diǎn)擊工具中的時(shí)間戳,快速跳轉(zhuǎn)到網(wǎng)頁(yè)控制臺(tái)查看云函數(shù)部署情況。
可以看到已經(jīng)自動(dòng)將云函數(shù) loginservice 和 actionservice 部署到 Func Stateless 了。
點(diǎn)擊云函數(shù)代碼緩存路徑下的文件,會(huì)自動(dòng)為你下載 zip 文件的。
可以點(diǎn)擊查看下,會(huì)自動(dòng)將我們配置的云函數(shù)路徑(Scripts/CloudService)下的腳本都緩存到 zip 壓縮包中的。
6.2切換成遠(yuǎn)程調(diào)用模式
云函數(shù)的調(diào)用模式分為本地調(diào)用和遠(yuǎn)程調(diào)用,之前我們已經(jīng)運(yùn)行項(xiàng)目測(cè)試過(guò),都屬于在本地調(diào)用云函數(shù)。
現(xiàn)在,讓我們?cè)?Func Stateless Tool 工具中,將云函數(shù)的調(diào)用模式由「本地調(diào)用」切換到「遠(yuǎn)程調(diào)用」。主要的目的還是防止客戶端包體可能會(huì)被破解,所以權(quán)威性邏輯不能在客戶端執(zhí)行,而必須在服務(wù)端上執(zhí)行。
遠(yuǎn)程調(diào)用:它指的是自動(dòng)將本地函數(shù)調(diào)用請(qǐng)求改寫成 HTTP 等網(wǎng)絡(luò)服務(wù)請(qǐng)求。
切換到遠(yuǎn)程調(diào)用模式后,云函數(shù)代碼發(fā)生了變化。打開(kāi)腳本可以看到,云函數(shù)中的代碼由原來(lái)的業(yè)務(wù)邏輯變成了遠(yuǎn)程調(diào)用的代碼。如下圖所示:
LoginService.cs腳本中的登錄云函數(shù) 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腳本中的抽卡云函數(shù) 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 查看調(diào)用的日志信息
切換成【遠(yuǎn)程調(diào)用】模式后,再次運(yùn)行游戲項(xiàng)目。
然后回到 UOS 網(wǎng)頁(yè)端的云函數(shù)頁(yè)面,可以查看云函數(shù)的調(diào)用日志,可以看到項(xiàng)目已經(jīng)成功調(diào)用了上傳的云函數(shù)。
6.4重置云函數(shù)
Func Stateless Tool 工具還給我們提供了重置云函數(shù)功能。如果點(diǎn)擊【重置云函數(shù)】按鈕后,會(huì)將云函數(shù)回退到上一個(gè)版本的云函數(shù)。然后當(dāng)我們修改云函數(shù)的代碼后,可以再次上傳新的云函數(shù)。
本期教程我們先講解到這里,大家趕快下載 Demo 項(xiàng)目試試吧!我們下一期再見(jiàn)哦!
Unity Online Services (UOS) 是一個(gè)專為游戲開(kāi)發(fā)者設(shè)計(jì)的一站式游戲云服務(wù)平臺(tái),提供覆蓋游戲全生命周期的開(kāi)發(fā)、運(yùn)營(yíng)和推廣支持。
了解更多 UOS 相關(guān)信息:
官網(wǎng):https://uos.unity.cn
技術(shù)交流 QQ 群:823878269
公眾號(hào):UOS 游戲云服務(wù)
Unity 官方微信
第一時(shí)間了解Unity引擎動(dòng)向,學(xué)習(xí)進(jìn)階開(kāi)發(fā)技能
每一個(gè)“點(diǎn)贊”、“在看”,都是我們前進(jìn)的動(dòng)力
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
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.