
寫在最前
時光飛逝,物是人非
技術(shù)方案總是帶著時代的標(biāo)簽,曾經(jīng)前沿的技術(shù),經(jīng)過時間的洗禮,可能已成為經(jīng)典
如果你點進(jìn)來是因?qū)嶋H需求而不是隨意瞧瞧,請同時看看官方的最新進(jìn)展,指不定有意外的收獲
本故事主要講解在使用 Unity 開發(fā)微信小游戲時,如何第一時間用上騰訊云開發(fā)的新能力,以及可能的最佳開發(fā)實踐
故事背景
蛋先生:丹尼爾,好久不見,怎么愁眉苦臉的?
丹尼爾:蛋兄好,最近在折騰用 Unity 開發(fā)微信小游戲,服務(wù)用的是騰訊云開發(fā),不過碰到些問題,一時半會兒搞不定
蛋先生:哦?什么問題,說來聽聽
丹尼爾:蛋兄,你也懂 Unity 嗎?
蛋先生:略懂略懂
丹尼爾:如此甚好。我起初以為直接調(diào)用騰訊云開發(fā),用官方的 Unity SDK 就行了
蛋先生:恩,這個想法沒毛病
丹尼爾:但,官方并沒有 Unity 版本的 SDK
蛋先生:然后呢?
丹尼爾:后來,我在將 Unity 轉(zhuǎn)成微信小游戲的過程中,發(fā)現(xiàn)微信有提供 WeChatWASM 這個 Unity SDK,里面帶了個 WX.cloud
蛋先生:那不就解了嗎?
丹尼爾:我也天真地這么以為,可在 Unity 編輯器一運(yùn)行,就...

蛋先生:這也正常,畢竟 Unity 編輯器又不是微信開發(fā)的,Play Mode 下沒有微信小游戲運(yùn)行環(huán)境,自然是跑不起來的
丹尼爾:所以我需要寫代碼,構(gòu)建成 WebGL,再轉(zhuǎn)換成微信小游戲,最后才能在微信開發(fā)者工具看到運(yùn)行效果。而我運(yùn)氣不好,還遇到了開發(fā)者工具罷工,還得多走一步,用真機(jī)預(yù)覽。這一個流程下來感覺要 1 分鐘左右,每次改代碼都得走這一長征路,想想頭就大

蛋先生:會不會是使用姿勢不對呢?
丹尼爾:我也懷疑過,直到我看到官方文檔介紹的開發(fā)方式...

蛋先生:看來只能如此,不過對于微信本身的能力,大部分時間我們不需要怎么消費它的輸出,好像問題也不大。比如:
var bannerAd = WX.CreateBannerAd(new WXCreateBannerAdParam()
{
adUnitId = "xxxx",
adIntervals = 30,
style = new Style()
{
left = 0,
top = 0,
width = 600,
height = 200
}
});
bannerAd.OnLoad(()=> {
bannerAd.Show();
});
bannerAd.OnError((WXADErrorResponse res)=>
{
Debug.Log(res.errCode);
});
丹尼爾:恩,這倒是。但對于服務(wù)接口,我們是重度消費輸出數(shù)據(jù)的,比如排行榜,好友列表等。總不能來回盲寫代碼,走一遍長征路調(diào)試吧
蛋先生:這長征路是難免的,但可以減少次數(shù)。比如在 Unity 編輯器開發(fā)時使用 Mock 數(shù)據(jù),等業(yè)務(wù)邏輯走通再走長征路
丹尼爾:我還是希望盡早看到集成了云開發(fā)服務(wù)后的實際效果,這樣可以早點發(fā)現(xiàn)問題,減少在長征路上浪費的時間
蛋先生:恩...
丹尼爾:還有個問題,WeChatWASM 對云開發(fā) SDK 的支持,存在滯后的問題,當(dāng)前只支持 CallFunction,不支持最新的數(shù)據(jù)模型。我就是沖著騰訊云開發(fā)剛新鮮出爐不久的數(shù)據(jù)模型來的
蛋先生:哦...
丹尼爾:煩死了,這也不行,那也不行,蛋兄,你給出出主意唄
蛋先生:自己搞一個吧
丹尼爾:這,蛋兄,你不是開玩笑的吧?這可不是我的主業(yè)務(wù),我只想用云開發(fā)服務(wù)而已
蛋先生:恩,可以搞,但不能瞎搞。我們來分析一下要解決的核心問題:
一 要能用上云開發(fā)的最新功能,比如數(shù)據(jù)模型;
二 爭取可以早點看到實際效果,避免走長征路;
三 實現(xiàn)成本要低,畢竟這不是主業(yè)務(wù)
丹尼爾:恩,那怎么辦?
解決用上云開發(fā)最新功能的問題
蛋先生:云開發(fā)推出新功能,總是第一時間在 JS 環(huán)境(包括云函數(shù),小程序和 Web 頁面)提供的,對吧


丹尼爾:沒錯
蛋先生:所以我們實際應(yīng)該直接調(diào)用這些 SDK,在 Web 環(huán)境下就調(diào)用 js-sdk,在小游戲環(huán)境下就調(diào)用小程序 sdk,這樣就能確保享受到最新的功能了
丹尼爾:這些是運(yùn)行在 JS 環(huán)境的吧
蛋先生:沒錯!所以我們需要實現(xiàn)一個包裹層,這個包裹層對內(nèi)使用 Unity 腳本去調(diào)用 Javascript 函數(shù),對外提供云開發(fā)的 Unity 版本 SDK

丹尼爾:具體怎么實現(xiàn)呢?
蛋先生:實現(xiàn)細(xì)節(jié)咱們后面再講
解決少走長征路,盡早看到實際效果的問題
丹尼爾:好吧,我還有一個問題,為什么要考慮 Web 環(huán)境呢?我只想開發(fā)微信小游戲而已啊
蛋先生:這就是為了解決第二個核心問題 - 縮短調(diào)試路徑。雖然我們在 Unity 編輯器的 Play Mode 無法預(yù)覽效果,但我們可以退而求其次,構(gòu)建成 WebGL。當(dāng)你開發(fā)完實際的調(diào)用云開發(fā)的代碼后,按 Command + B ,這會構(gòu)建成 WebGL 應(yīng)用,并自動在瀏覽器打開,你就可以直接預(yù)覽實際效果了,這樣可以省去轉(zhuǎn)換成小游戲的步驟,時間上也會節(jié)省不少
丹尼爾:雖然不能在 Unity 編輯器直接預(yù)覽,還是有點小遺憾,但這樣的調(diào)試路徑還是可以接受的
蛋先生:恩,是時候總結(jié)一下這種方案的可能的最佳開發(fā)流程了
【Unity 編輯器開發(fā)階段】:使用 Mock 數(shù)據(jù),這樣可省去構(gòu)建成 WebGL 的時間,同時可確定業(yè)務(wù)邏輯所使用接口的輸入和輸出數(shù)據(jù)結(jié)構(gòu)
【瀏覽器調(diào)試階段】:根據(jù)確定的業(yè)務(wù)邏輯接口,通過 Unity TCB SDK Wrapper 開發(fā)調(diào)用云開發(fā)服務(wù)的代碼,構(gòu)建成 WebGL,預(yù)覽實際的效果
【微信小游戲預(yù)覽階段】:最后直接構(gòu)建成微信小游戲,進(jìn)行最終效果預(yù)覽

技術(shù)實現(xiàn)細(xì)節(jié)
丹尼爾:哎呦,不錯哦!不過,我現(xiàn)在比較好奇的是怎么實現(xiàn)這個,以及實現(xiàn)的成本咋樣?
蛋先生:那咱們接著繼續(xù)聊
Unity 調(diào)用 Javascript 同步方法
丹尼爾:首先,Unity 怎么調(diào)用 Javascript 方法呢?
蛋先生:分兩種情況,同步和異步。同步方法比較簡單,官方文檔 已經(jīng)很詳細(xì)了,但咱們也來個小示例
首先,定義個要被 Unity 調(diào)用的 JavaScript 方法。我們在 Assets/Plugins 目錄下創(chuàng)建 .jslib 后綴的文件,比如叫 tcbsdk.jslib
// Assets/Plugins/tcbsdk.jslib
const asmLibraryArg = {
Hello: function () {
console.log("Hello, world!");
},
...
}
mergeInto(LibraryManager.library, asmLibraryArg);
接著,在 C# 腳本里進(jìn)行映射,比如 Assets/Scripts/DemoSDK.cs
// Assets/Scripts/DemoSDK.cs
public class DemoSDK
{
[DllImport("__Internal")]
public static extern void Hello();
}
然后,你就可以通過 DemoSDK.Hello() 調(diào)用 tcbsdk.jslib 里的 Hello 方法了
丹尼爾:看上去挺簡單的,有啥需要注意的嗎?
蛋先生:還真有,tcbsdk.jslib 里的 asmLibraryArg 這個變量的存在是有意義的,且不能修改成其它變量名
丹尼爾:為啥?
蛋先生:它的存在主要是為了方法間互相調(diào)用。來,我們繼續(xù)看例子,看看 HelloCallOtherFn 是怎么調(diào)用 Hello 的
// Assets/Plugins/tcbsdk.jslib
const asmLibraryArg = {
Hello: function () {
console.log("Hello, world!");
},
HelloCallOtherFn: function () {
console.log("Call HelloCallOtherFn");
// 方法間調(diào)用方式一
_Hello();
// 方法間調(diào)用方式二
asmLibraryArg.Hello();
},
...
}
mergeInto(LibraryManager.library, asmLibraryArg);
丹尼爾:asmLibraryArg.Hello(); 這個我懂,但為啥是 _Hello();?明明方法名是 Hello 啊
蛋先生:嘿嘿,jslib 里的方法在構(gòu)建成 WebGL 時都會經(jīng)過加工后合并進(jìn) webgl.wasm.framework.unityweb.js ,這個文件你可以在生成的 WebGL 產(chǎn)物里找到,來看個代碼片段
// webgl.wasm.framework.unityweb.js
var unityFramework = (() => {
return function (unityFramework) {
...
function _Hello() {
console.log("Hello, world!");
}
function _HelloCallOtherFn() {
console.log("Call HelloCallOtherFn");
// 方法間調(diào)用方式一
_Hello();
// 方法間調(diào)用方式二
asmLibraryArg.Hello();
},
...
var asmLibraryArg = {
Hello: _Hello,
...
}
...
}
})
看到?jīng)]?真相就在這兒了。不過,我不推薦用 _Hello(),因為這樣你就沒法用編輯器的功能,比如點擊跳轉(zhuǎn)到方法定義。

我們前面提到變量名必須是 asmLibraryArg,這其實是一種取巧的方式,這樣即可以實現(xiàn)方法間調(diào)用,又可以充分享受編輯器的智能輔助體驗,一箭雙雕

丹尼爾:蛋兄,你可真是個小機(jī)靈鬼
蛋先生:咳咳~
Unity 調(diào)用 Javascript 異步方法
丹尼爾:那接下來咱們聊聊如何調(diào)用異步方法
蛋先生:異步調(diào)用是個環(huán),咱們得從一次異步方法調(diào)用的整個過程說起
丹尼爾:你說吧,反正那些又臭又長的代碼我是不想看的
蛋先生:嘿嘿,代碼是不可避免的,還得結(jié)合下邊代碼【腳本C】和【腳本J】來看(溫馨提示:【腳本C】和【腳本J】為往下一點點的兩個大的代碼片段)。假設(shè)我們現(xiàn)在要調(diào)用以下異步方法
HelloWithReturnResult result = await DemoSDK.Instance.HelloAsyncFn(new HelloWithInputParams() { name = "daniel666" });
在【腳本C】中,HelloAsyncFn 方法執(zhí)行時,會先通過 GetAsyncTask() 取得這次調(diào)用的 callbackId 和對應(yīng)的 TaskCompletionSource 異步任務(wù),然后調(diào)用 JavaScript 方法,并等待 TaskCompletionSource 任務(wù)的完成。
丹尼爾:那誰來通知 TaskCompletionSource 任務(wù)完成呢?
蛋先生:好問題!看【腳本J】中 HelloAsyncFn 的實現(xiàn)
異步任務(wù)執(zhí)行完后,會執(zhí)行 asmLibraryArg.Utils().sendMessage(callbackId, result);
這個方法實際執(zhí)行的發(fā)送消息代碼是 Module.SendMessage("DemoSDK", "OnAsyncFnCompleted", param),它會通知名為 "DemoSDK" 的 GameObject 去執(zhí)行腳本組件 DemoSDK 的 OnAsyncFnCompleted 方法
丹尼爾:哦哦,我剛才就想問【腳本C】里的這些代碼是干嘛的,原來是為了創(chuàng)建一個 GameObject 來接收消息。
GameObject gameObject = new("DemoSDK");
DemoSDK demoSDK = gameObject.AddComponent<DemoSDK>();
蛋先生:正解!你可真是個小聰明,這么快就看出來了
接下來【腳本C】的 OnAsyncFnCompleted 方法就會收到消息,然后根據(jù) callbackId 讓具體的 TaskCompletionSource 任務(wù)執(zhí)行 SetResult,從而完成任務(wù),這樣整個異步調(diào)用就閉環(huán)了。
丹尼爾:懂了。但為什么是 Module.SendMessage 呢?這個 Module 是從哪兒冒出來的?
蛋先生:真相依然在 webgl.wasm.framework.unityweb.js 里
// webgl.wasm.framework.unityweb.js
var unityFramework = (() => {
return function (unityFramework) {
...
var Module = typeof unityFramework != "undefined" ? unityFramework : {};
...
function SendMessage(gameObject, func, param) {
...
}
Module["SendMessage"] = SendMessage;
...
}
})
丹尼爾:666,簡直洞若觀火
//【腳本C】:Assets/Scripts/DemoSDK.cs
public class DemoSDK : MonoBehaviour
{
private static readonly Lazy<DemoSDK> _instance = new Lazy<DemoSDK>(() =>
{
GameObject gameObject = new("DemoSDK");
DemoSDK demoSDK = gameObject.AddComponent<DemoSDK>();
return demoSDK;
});
public static DemoSDK Instance => _instance.Value;
private DemoSDK() { }
private Dictionary<string, TaskCompletionSource<string>> tcsDictionary = new();
public async Task<HelloWithReturnResult> HelloAsyncFn(HelloWithInputParams input)
{
(string, TaskCompletionSource<string>) asyncTask = GetAsyncTask();
// 調(diào)用 JavaScript 方法
Internal.HelloAsyncFn(asyncTask.Item1, Internal.ParseInputParams(input));
// 返回 Task 讓調(diào)用方等待
var result = await asyncTask.Item2.Task;
return Internal.ParseOutputResult<HelloWithReturnResult>(result);
}
public void OnAsyncFnCompleted(string result)
{
AsyncResponse<string> res = Internal.ParseOutputResult<AsyncResponse<string>>(result);
tcsDictionary[res.callbackId].SetResult(res.result);
tcsDictionary.Remove(res.callbackId);
}
private (string, TaskCompletionSource<string>) GetAsyncTask()
{
string uuid = Guid.NewGuid().ToString();
TaskCompletionSource<string> tcs = new();
tcsDictionary.Add(uuid, tcs);
return (uuid, tcs);
}
private class Internal
{
[DllImport("__Internal")]
public static extern void HelloAsyncFn(string callbackId, string input);
}
private class AsyncResponse<T>
{
public string callbackId { get; set; }
public T result { get; set; }
}
}
//【腳本J】:Assets/Plugins/tcbsdk.jslib
const asmLibraryArg = {
HelloAsyncFn: async function (callbackId, input) {
...
const result = await new Promise((resolve) =>
setTimeout(() => {
resolve(input), 2000;
})
);
asmLibraryArg.Utils().sendMessage(callbackId, result);
},
Utils: function () {
const utils = {
...
sendMessage(callbackId, result) {
const unityInstance = Module;
const constants = asmLibraryArg.Constants();
unityInstance.SendMessage(
constants.DEMO_CALLBACK_OBJECT_NAME,
constants.CALLBACK_METHOD_NAME,
JSON.stringify({
callbackId,
result: JSON.stringify(result || ""),
})
);
},
};
return utils;
},
Constants: function () {
return {
...
DEMO_CALLBACK_OBJECT_NAME: "DemoSDK",
CALLBACK_METHOD_NAME: "OnAsyncFnCompleted",
};
},
...
}
mergeInto(LibraryManager.library, asmLibraryArg);
集成騰訊云開發(fā) js-sdk 和小程序 sdk
丹尼爾:說了這么多,好像還不知道要怎么集成 js-sdk 和小程序 sdk
蛋先生:搞清楚了如何實現(xiàn) Unity 調(diào)用 Javascript 之后,問題不就迎刃而解了嘛
因為 @cloudbase/wx-cloud-client-sdk 和 @cloudbase/js-sdk 都提供了 UMD 格式的完整版本,所以我們只需要將 SDK 的完整代碼復(fù)制放到 jslib 里面(請留意 CloudbaseJSSdkScript 和 CloudbaseWXCloudClientSdkScript 方法)即可。以下是 init 的實現(xiàn)示例
// Assets/Plugins/tcbsdk.jslib
const asmLibraryArg = {
/**
* params.env: 環(huán)境ID
*/
CloudInit: async function (callbackId, params) {
...
const input = asmLibraryArg.Utils().parseInputParams(params);
if (platform === constants.PLATFROM.WX) {
const { init } = asmLibraryArg.GetCloudbaseWXCloudClientSdkInstance();
await wx.cloud.init({
env: input.env,
});
const app = init(wx.cloud);
...
} else if (platform === constants.PLATFROM.WEB) {
const cloudbase = asmLibraryArg.GetCloudbaseJSSdkScriptInstance();
const app = cloudbase.init({
env: input.env,
});
...
}
asmLibraryArg.Utils().sendMessage(callbackId);
},
/**
* 獲取 @cloudbase/wx-cloud-client-sdk 實例
*/
GetCloudbaseWXCloudClientSdkInstance: function () {
const global = asmLibraryArg.GetGlobalData();
if (!global.wxCloudClientSDK) {
asmLibraryArg.CloudbaseWXCloudClientSdkScript.call(global);
}
return global.wxCloudClientSDK;
},
/**
* 獲取 cloudbase-js-sdk 實例
*/
GetCloudbaseJSSdkScriptInstance: function () {
const global = asmLibraryArg.GetGlobalData();
if (!global.cloudbase) {
asmLibraryArg.CloudbaseJSSdkScript.call(global);
}
return global.cloudbase;
},
/**
* 加載 @cloudbase/wx-cloud-client-sdk
*/
CloudbaseWXCloudClientSdkScript: function () {
(function (exports) {
/**
* 以下代碼來自于 https://unpkg.com/@cloudbase/wx-cloud-client-sdk@1.2.1/lib/wxCloudClientSDK.umd.js
*/
...
})();
},
/**
* 加載 cloudbase-js-sdk
*/
CloudbaseJSSdkScript: function () {
(function (exports) {
/**
* 以下代碼來自于 https://static.cloudbase.net/cloudbase-js-sdk/2.7.13-beta.0/cloudbase.full.js
*/
...
})();
},
};
mergeInto(LibraryManager.library, asmLibraryArg);
丹尼爾:在微信小游戲中并不需要 js-sdk,這樣豈不是會將 js-sdk 打包進(jìn)小游戲里,不太合適吧
蛋先生:沒錯,不過別擔(dān)心,辦法是有滴。在 jslib 定義的方法并非都會打包,只有那些顯示聲明 [DllImport("__Internal")] 的才會打包進(jìn)去,如下
[DllImport("__Internal")]
public static extern void Hello();
所以,我們可以定義一個 Preprocessor Symbols(比如 WEIXINMINIGAME),在準(zhǔn)備轉(zhuǎn)換成小游戲時,可以在構(gòu)建 WebGL 的設(shè)置中提供這個預(yù)定義符號,這樣 CloudbaseJSSdkScript 方法就會忽略掉了
#if !WEIXINMINIGAME
[DllImport("__Internal")]
private static extern void CloudbaseJSSdkScript();
#endif
丹尼爾:Nice
云開發(fā)數(shù)據(jù)模型的實現(xiàn)建議
丹尼爾:感覺我現(xiàn)在就可以動手了,想要什么功能就搞什么功能,主動權(quán)在手上的感覺真好。就先拿數(shù)據(jù)模型開刀吧,蛋兄有什么建議嗎?
蛋先生:通過查閱 云開發(fā)數(shù)據(jù)模型 SDK 文檔,我們可以發(fā)現(xiàn)一個規(guī)律,就是所有方法都是同一種固定模式 models.[model name].[api name](JSON input)
models.post.create({...})
models.post.delete({...})
models.post.get({...})
models.post.update({...})
所以呢,我們只需要在 .jslib 文件里定一個萬能的 Models_API 方法,就全都搞定了
// Assets/Plugins/tcbsdk.jslib
Models_API: async function (callbackId, apiName, params) {
callbackId = UTF8ToString(callbackId);
apiName = UTF8ToString(apiName);
const { modelName, options: optionsStr } = asmLibraryArg
.Utils()
.parseInputParams(params);
const options = JSON.parse(optionsStr);
const app = asmLibraryArg.Utils().getApp();
const models = app.models;
const { data } = await models[modelName][apiName](options);
asmLibraryArg.Utils().sendMessage(callbackId, data);
},
同樣在 C# 腳本中實現(xiàn)一個萬能的 ModelAPI 方法,而 Get, Create 等方法其實就是通過 ModelAPI 方法來實現(xiàn)的
// Assets/Scripts/TCBSDK.cs
public class TCBSDK : MonoBehaviour
{
private class Models : IModels
{
private static readonly Lazy<Models> _instance = new Lazy<Models>(() => new());
public static Models Instance => _instance.Value;
private Models() { }
public Task<T> Get<T>(ModelsReqParams input)
{
return ModelAPI<T>(input, "get");
}
public Task<T> Create<T>(ModelsReqParams input)
{
return ModelAPI<T>(input, "create");
}
...
private async Task<T> ModelAPI<T>(ModelsReqParams input, string apiName)
{
var realInput = new Dictionary<string, object>
{
["modelName"] = input.modelName,
["options"] = JsonConvert.SerializeObject(input.options)
};
(string, TaskCompletionSource<string>) asyncTask = Internal.GetAsyncTask();
Internal.Models_API(asyncTask.Item1, apiName, Internal.ParseInputParams(realInput));
string result = await asyncTask.Item2.Task;
return Internal.ParseOutputResult<T>(result);
}
}
private class Internal
{
[DllImport("__Internal")]
public static extern void Models_API(string callbackId, string apiName, string input);
}
}
public class ModelsReqParams
{
public string modelName { get; set; }
public Dictionary<string, object> options { get; set; }
}
丹尼爾:入?yún)?options 是個 Dictionary 類型,會不會不太好,是不是還是明確類型好點?
蛋先生:這個問題問得好!如果從代碼編寫規(guī)范來說,當(dāng)然是要定義類型的,裸奔畢竟不太好。但從實際使用角度來看,Dictionary 反而可能是個不錯的選擇。因為它省時省力,還對開發(fā)者相當(dāng)友好。
丹尼爾:對開發(fā)者友好,這怎么說?
蛋先生:你看看騰訊云開發(fā)的云后臺,在每個數(shù)據(jù)模型旁邊,都會貼心地給出常用 API 的代碼范例,基本上復(fù)制過來,稍微改改就能用。

所以,你可以在 web 環(huán)境下先盡情調(diào)試 JSON 入?yún)?,直到結(jié)果符合預(yù)期。然后再把這 JSON 扔給 GPT,讓它幫你生成對應(yīng)的 C# Dictionary,一氣呵成

最后,把代碼貼過來,大功告成~
var options = new Dictionary<string, object>
{
["filter"] = new Dictionary<string, object>
{
["where"] = new Dictionary<string, object>
{
["$and"] = new List<Dictionary<string, object>>
{
new Dictionary<string, object>
{
["_id"] = new Dictionary<string, string>
{
["$eq"] = id
}
}
}
}
}
};
ModelHello hello = await TCBSDK.Instance.ModelsGet<ModelHello>(new ModelsReqParams() { modelName = "hello", options = options });
丹尼爾:也對,真正的類型校驗,云開發(fā)的數(shù)據(jù)模型會嚴(yán)格把關(guān),SDK 只要開發(fā)者用得順手,問題不大
業(yè)務(wù)邏輯服務(wù)接口實現(xiàn)
丹尼爾:最后聊一下業(yè)務(wù)邏輯服務(wù)接口的實現(xiàn)吧,你覺得 Mock 數(shù)據(jù)要如何實現(xiàn)比較優(yōu)雅?
蛋先生:通過 interface 聲明接口,然后通過 UNITY_EDITOR 預(yù)處理符號來判斷應(yīng)該采用哪種實現(xiàn),編輯器環(huán)境用 MockGameAPI,其它則用 RealGameAPI
public interface IGameAPI
{
Task<ModelsList<GameRealm>> GetRealmList();
...
}
public class GameAPI
{
#if UNITY_EDITOR
public static IGameAPI Instance = new MockGameAPI();
#else
public static IGameAPI Instance = new RealGameAPI();
#endif
}
public class MockGameAPI : IGameAPI
{
public Task<ModelsList<GameRealm>> GetRealmList()
{
return Task.FromResult(new ModelsList<GameRealm>() { records = new List<GameRealm> { new() { _id = "mock_realm_id", name = "默認(rèn)" } }, total = 1 });
}
...
}
public class RealGameAPI : IGameAPI
{
public Task<ModelsList<GameRealm>> GetRealmList()
{
var options = new Dictionary<string, object>
{
...
};
return TCBSDK.Instance.ModelsList<ModelsList<GameRealm>>(new ModelsReqParams() { modelName = Constants.ModelName.GameRealm, options = options });
}
}
這樣在調(diào)用服務(wù)接口來實現(xiàn)游戲業(yè)務(wù)邏輯時就不用關(guān)心環(huán)境了
var tcbList = await GameAPI.Instance.GetRealmList();
丹尼爾:明白了,感謝蛋兄,我準(zhǔn)備大干一場了
蛋先生:加油,再見
以上完整代碼請移步到倉庫:https://github.com/daniel-dx/unity-cloudbase-demo
代碼有點粗糙,僅供參考,還望見諒!
寫在最后,別有用心
作為一個前端開發(fā)者,零 Unity 零 C# 基礎(chǔ),2 周時間從入門到“精通”( _( ?Д?)? 從沒見過如此厚顏無恥之人),交出這份作業(yè)對我來說還算滿意
但還是想通過這篇文章來拋磚引玉,指不定有哪位大神能提供其它意想不到的解決方案呢,您說,是吧!( ̄︶ ̄)↗