"Hello, world!" "Hello, 小游戲宿主!" 本文簡要記錄了 Unity Connect 作為小游戲宿主的第一塊試驗田,從 SDK 接入、體驗優(yōu)化,到平臺能力接入的全過程。如果文中有任何錯誤,歡迎指出與交流,期待與你一起共建更加繁榮的小游戲宿主生態(tài)!
一、小游戲宿主的第一塊試驗田
有沒有小伙伴已經(jīng)發(fā)現(xiàn)了?最近 Unity Connect 首頁底部的導航欄悄悄多了一個小手柄按鈕,點進去就能進入全新的「Unity 小游戲中心」啦 !
在這里,你可以用十來分鐘來一局緊張刺激的卡牌肉鴿,也可以當個島主悠哉地建設自己的專屬島嶼,甚至還能施展魔法,培養(yǎng)一位未來的龍騎法師。不管是午休時間,還是 上班摸魚 ,都多了一個放松的好去處!
當然,我們在 Connect 上加入小游戲中心,不只是為了讓大家在忙碌工作之余多一個“快樂角落”。更重要的是——Unity Connect 是我們接入「小游戲宿主」的第一塊試驗田。
通過這次接入,我們希望向開發(fā)者們展示:小游戲宿主不僅兼容多種游戲引擎,還能為移動 App 提供流暢、輕量的游戲運行環(huán)境。未來,我們也期待更多移動應用和小游戲開發(fā)者關注并使用這一能力,一起共建 Unity 小游戲生態(tài)!
這篇文章就簡單介紹和分享小游戲宿主的前世今生以及 Connect 接入小游戲宿主的過程吧,如果有謬誤請幫忙指出!
二、走進小游戲與宿主的世界
在小游戲的世界里,「宿主」其實就是小游戲的“家”——它是一個平臺或容器,負責加載、運行和管理小游戲,還會提供各種運行時能力和平臺服務,確保小游戲能順利啟動、順暢運行。而運行在這個“家”里的小游戲,我們通常叫它「宿主小游戲」,也常被稱為「Instant Game」。
小游戲因為“輕量化、即點即玩”的特性,自 2017 年問世以來就一路高歌猛進:2019 年初,開發(fā)者數(shù)量突破 10 萬;同年 5 月,用戶規(guī)模破億;到了 2021 年,年流水破千萬的小游戲產(chǎn)品已經(jīng)超過 50 款!游戲類型也從最初的超休閑,逐漸拓展到了中重度玩法,比如 MMO、策略類等,越來越豐富。
各大平臺自然也不甘落后,紛紛打造自己的小游戲生態(tài),推出小程序/小游戲運行環(huán)境,力求在自家 App 里集成功能、留住用戶、提升體驗。
不過,并不是所有平臺型公司都已經(jīng)有了現(xiàn)成的宿主解決方案。有些是還沒來得及做,有些是想更高效地把其他平臺的小游戲“搬”到自己的 App 上。這時,一個靈活、易用的宿主方案就顯得尤為重要——于是,Unity 小游戲宿主應運而生!
事實上,Unity 小游戲宿主是融合了客戶端 SDK、服務端 API 以及管理后臺的一體化綜合性小游戲運行平臺,平臺開發(fā)者能夠在管理后臺創(chuàng)建宿主應用,上傳小游戲,建立關聯(lián)關系,通過服務端 API 獲取游戲列表,小游戲開發(fā)者則能通過集成了小游戲宿主的應用調(diào)試小游戲。
三、簡單但重要的從 0 到 1
Connect 接入小游戲宿主的流程可參考官方文檔:
https://minihost.tuanjie.cn/help/docs/welcome
小游戲運行環(huán)境可參考:
https://developers.weixin.qq.com/minigame/dev/guide/best-practice/game-engine.html
這里我們以 Android 平臺為例,和大家一起快速過一遍整個接入過程。整體可以分為兩個步驟:
在宿主管理平臺創(chuàng)建應用并上傳小游戲
客戶端集成宿主小游戲 SDK
感興趣的小伙伴也可以跟著我們一步步操作,創(chuàng)建一個屬于自己的宿主應用哦!
3.1 創(chuàng)建宿主應用 & 小游戲
參考文檔:https://minihost.tuanjie.cn/help/docs/quickstart/create-host-app
在正式接入客戶端 SDK 之前,我們需要先去宿主管理控制臺新建一個“宿主應用”。這一步主要是為了配置 Bundle ID,并獲取對應的 App Key 和 App Secret,這兩個參數(shù)后面在初始化 SDK 時會用到。
接下來,我們需要為這個宿主應用創(chuàng)建一些小游戲。在管理控制臺的「小游戲」頁面中,我們可以先創(chuàng)建一個最簡單的示例游戲,比如 Spinning Cube,并將它關聯(lián)到剛才創(chuàng)建的宿主應用上。
小游戲創(chuàng)建好之后,我們還需要上傳一個對應的游戲包。這個游戲包可以直接通過 Unity 提供的新增的 Build Target 導出。為了方便大家調(diào)試,我們也提供了一個現(xiàn)成的 Spinning Cube 游戲包 。
Spinning Cube 游戲包:
https://minihost.cdn.tuanjie.cn/release/game-pkg/spinning-cube-20250418.zip
上傳時,控制臺會自動檢查游戲包是否符合規(guī)范。上傳成功后,我們需要發(fā)布并上線這個游戲包作為其最新的線上版本,別忘了回到小游戲列表中確認這個小游戲已經(jīng)成功啟用,并且記住它的 Game ID (注意是 Game ID 不是游戲包 ID),在后續(xù)啟動游戲時會用到!
到這里,在控制臺上的準備工作就完成啦 !
3.2 客戶端集成宿主 SDK
參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/quick-integrate
客戶端接入小游戲的步驟也非常簡單,首先,從小游戲宿主官方下載頁獲取最新版的 Android SDK 壓縮包。
小游戲宿主 SDK 下載:https://minihost.tuanjie.cn/download
里面包含兩個 AAR 文件:
host-runtime-dynamic.aar(動態(tài)加載包)
host-runtime-static.aar(靜態(tài)加載包)
這兩個包的主要區(qū)別在于:動態(tài)包支持按需下載小游戲運行環(huán)境,更加靈活;而靜態(tài)包則是將運行環(huán)境直接集成進 App,適合快速接入,所以我們這里先使用靜態(tài)包。
接下來,按照官方文檔的說明,把宿主 SDK 和它依賴的其他 SDK 一起添加到項目的 build.gradle 文件中的 dependencies 里。
dependencies{
//Host SDK
implementationfiles('host-runtime-static.aar')
//dependencies for host SDK
implementationlibs.okhttp
implementationlibs.mmkv
implementationlibs.gson
implementationlibs.java.websocket
implementationlibs.camera.core
implementationlibs.camera.camera2
implementationlibs.camera.lifecycle
implementationlibs.camera.video
implementationlibs.camera.view
implementationlibs.camera.extensions
implementationlibs.tbssdk
}
# libs.versions.toml
[versions]
okhttp = "3.12.13"
mmkv = "1.3.2"
gson = "2.10.1"
javaWebsocket = "1.5.1"
cameraCamera2 = "1.2.3"
tbssdk = "44286"
[libraries]
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "javaWebsocket" }
camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCamera2" }
camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCamera2" }
camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCamera2" }
camera-video = { module = "androidx.camera:camera-video", version.ref = "cameraCamera2" }
camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCamera2" }
camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "cameraCamera2" }
tbssdk = { module = "com.tencent.tbs:tbssdk", version.ref = "tbssdk" }
配置好依賴之后,我們就可以開始寫代碼啦。首先,新建一個簡單的布局和對應的 Activity。在布局中,暫時只需要一個用于承載游戲的容器視圖就夠了。并且我們可以給 Connect 中的某個按鈕綁定點擊事件跳轉到這個 Activity 用于調(diào)試。
"1.0" encoding="utf-8"?>
android:id="@+id/gameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
FrameLayout>
在宿主 SDK 中,啟動一個小游戲大致包括以下五個步驟:
initialize
createGameHandle
setGameStartOptions
start
play
每個步驟都有其特定的作用,我們可以在每個步驟的成功回調(diào)中觸發(fā)下一個步驟,這樣整個流程就能順利串起來。
第一步,在 Activity 的 onCreate 中調(diào)用 TJHostHandle.initialize 來初始化宿主 SDK。在這一步中,我們需要傳入控制臺上創(chuàng)建宿主應用時生成的 App Key 和 App Secret,用于驗證接入身份。
同時,我們還可以傳入一個游戲容器,這樣宿主 SDK 的加載頁、彈窗等 UI 元素就會被限制在這個容器內(nèi),避免覆蓋整個界面。
初始化成功后,我們可以通過 TJHostHandle.getInstance() 獲取 SDK 的單例,后續(xù)的所有操作都通過它來完成。
初始化成功之后,我們能夠獲取 TJHostHandle 單例,后續(xù)的控制 API 都通過這個單例調(diào)用。
FrameLayout gameContainer;
privatestatic final String _TAG _= "[GameActivity]";
private final Handler handler = new Handler(Looper._getMainLooper_());
private TJHostHandle hostHandle;
@Override
publicvoidonCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout._activity_game_);
gameContainer = findViewById(R.id._gameContainer_);
TJHostHandle._initialize_(this, "**APP Key**", "**APP Secret**", gameContainer, new OnTJHostHandleInitializeListener() {
@Override
publicvoidonSuccess(TJHostHandle hostHandle) {
GameActivity.this.hostHandle = hostHandle;
handler.post(() -> createGameHandle());
}
@Override
publicvoidonFailure(Throwable throwable) {
Log._e_(_TAG_, "TJHostHandle.initialize failed: " + throwable.getMessage());
}
});
}
?接著,調(diào)用 createGameHandle 創(chuàng)建游戲句柄。這個過程支持通過 Bundle 傳入一些初始化參數(shù),比如 HTTP 緩存目錄等,具體參數(shù)可以參考文檔。
宿主管理文檔:
https://minihost.tuanjie.cn/help/docs/api/android/management#%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
private void createGameHandle(){
hostHandle.createGameHandle(this, new Bundle(), new OnTJHostCreateHandleListener() {
@Override
public void onSuccess(TJHostHandle result){
setGameStartOptions();
}
@Override
public void onFailure(Throwable throwable){
Log._e_(_TAG_, "Host createGameHandle failed", throwable);
}
});
}
? 然后,我們需要設置游戲的啟動參數(shù)。啟動一個小游戲至少需要兩個關鍵參數(shù):
LAUNCH_KEY:小游戲的啟動參數(shù)(可以是小游戲的 Game ID,具體定義參考文檔)
USER_ID:用戶 ID,用于區(qū)分不同用戶的數(shù)據(jù),以及記錄用戶啟動游戲的歷史
我們把前面創(chuàng)建的 Spinning Cube 的 Game ID 填入 LAUNCH_KEY,宿主 SDK 就會根據(jù)它去獲取對應的游戲包下載地址及類型。USER_ID 可以先隨便填一個測試用的值,后續(xù)接入正式用戶體系時再替換。
除此之外,setGameStartOptions 還支持一些可選參數(shù),比如是否展示展示更多按鈕、是否開啟調(diào)試模式等,具體可參考 文檔 。
小游戲管理:
https://minihost.tuanjie.cn/help/docs/api/android/minigame-management#%E6%B8%B8%E6%88%8F%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0-launch_key-%E8%AF%B4%E6%98%8E
privatevoidsetGameStartOptions(){
Bundle bundle = new Bundle();
bundle.putString(TJRuntimeConstants._LAUNCH_KEY_, "Game ID");
bundle.putString(TJRuntimeConstants._USER_ID_, "User ID");
hostHandle.setGameStartOptions(bundle, new OnTJHostHandleListener() {
@Override
publicvoid onSuccess() {
start();
}
@Override
publicvoid onFailure(Throwable throwable) {
Log._e_(_TAG_, "Host set game start options failure", throwable);
}
});
}
參數(shù)設置好之后,調(diào)用 start 開始加載游戲資源。加載成功后,我們就可以將游戲界面添加到之前準備好的容器視圖中,并請求焦點。
private void start(){
hostHandle.start(_TAG_, new OnTJHostHandleListener() {
@Override
public void onSuccess(){
View gameView = hostHandle.getGameView();
if (gameContainer.indexOfChild(gameView) == -1) {
gameContainer.addView(gameView);
}
gameView.requestFocus();
play();
}
@Override
public void onFailure(Throwable throwable){
Log._e_(_TAG_, "Host start game failure", throwable);
}
});
}
最后,調(diào)用 play 啟動游戲,小游戲就正式跑起來啦 !
privatevoidplay(){
// Resume game
hostHandle.play(new OnTJHostHandleListener() {
@Override
public void onSuccess(){
Log._i_(_TAG_, "Host play game success");
}
@Override
public void onFailure(Throwable throwable){
Log._e_(_TAG_, "Host play game failure", throwable);
}
});
}
整個流程雖然看起來有些步驟,但實際上操作起來還是比較順暢的,只要前期配置好,后續(xù)想接入更多小游戲也很簡單,只需要在控制臺創(chuàng)建新的小游戲并且上傳對應的游戲包,啟動游戲時設置不同的 LAUNCH_KEY 即可輕松地實現(xiàn)橫向拓展!
四、讓宿主更聰明的一點點魔法 ?
在小游戲成功跑起來之后,我們并沒有就此收工。畢竟,“能跑”只是第一步,“跑得好”才是我們真正的目標。于是,我們開始通過宿主 SDK 提供的能力給宿主應用施加一些“魔法加成”,讓它變得更聰明更貼心。
這一階段的優(yōu)化,主要圍繞小游戲宿主生態(tài)中的三類角色展開:
宿主應用的使用者
小游戲的開發(fā)者
宿主應用的開發(fā)者
4.1宿主應用使用者
對于宿主應用使用者(最終用戶)來說,我們的目標是:讓感興趣的用戶可以方便地發(fā)現(xiàn)并啟動想玩的游戲,同時確保小游戲的引入不會影響到 APP 原有的穩(wěn)定性和功能;而對于不感興趣的用戶,我們希望盡可能降低宿主 SDK 的存在感,做到“無感接入”。
4.1.1 獲取游戲列表:讓用戶看到能玩什么
參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/server-integrate
在接入小游戲宿主之后,我們要面對的第一個問題就是:怎么把可玩的小游戲清晰地展示給用戶?畢竟,游戲再好玩,用戶找不到也是白搭。
好消息是,宿主平臺已經(jīng)為我們準備好了相關的服務端 API,專門用來獲取當前宿主應用下已關聯(lián)的小游戲列表:
curl --location 'https://minihost.tuanjie.cn/api/game/list?appId=**AppID**' \
--header 'Authorization: Bearer **ServiceToken**'
這個接口需要兩個參數(shù):
AppID:宿主應用的 ID
ServiceToken:服務端訪問令牌
它們和前面提到的App Key/App Secret類似,都可以在宿主控制臺中獲取。
接口返回的內(nèi)容包含了所有已發(fā)布小游戲的元信息,其中的 launchKey 字段就是啟動游戲時要用到的關鍵參數(shù)LAUNCH_KEY。
[
{
"id": "67fe09f272ef630b77c67089",
"appId": "67fe05f972ef630b77c663b3",
"name": "SpinningCube",
"gameType": "weixinminigame",
"tags": [
"Cube"
],
"iconUrl":
"https://minihost.cdn.tuanjie.cn/game_app/67bab0495795e8aac375737e/app_icon/cube.9c353a166f724813b0b2f5742bc1a19f.png",
"briefIntro": "A Spinning Cube",
"launchKey": "67fe09f272ef630b77c67089"
}
]
雖然我們可以直接在客戶端請求這個 API 并展示游戲列表,但這樣做靈活性有限,比如想要實現(xiàn):
搜索 / 關鍵詞過濾
自定義排序(按熱度、更新時間等)
分頁加載
添加自定義字段(如“推薦標簽”、“開發(fā)者信息”等)
這些功能在客戶端處理起來就比較麻煩了。
所以,我們更推薦的做法是:
由 Connect 的服務端定期或按需調(diào)用宿主平臺的 API,拉取并緩存游戲列表
然后由 Connect 自己的服務端封裝一個更適合客戶端使用的 API(結構更簡潔、字段更定制)
客戶端只需請求這個自定義 API,即可獲取游戲列表并展示在 UI 中
當用戶點擊“啟動”按鈕時,只需要將對應游戲的 launchKey 作為參數(shù)傳入宿主 SDK,即可啟動該小游戲。通過這一套流程,我們不僅讓用戶能快速看到可玩的游戲,也為后續(xù)的個性化推薦、運營配置等功能打下了基礎。畢竟,游戲列表是入口,入口體驗好,用戶才有可能留下來玩更多
4.1.2 游玩歷史:讓用戶不錯過每一場精彩
參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/advanced-usage-%E6%9C%80%E8%BF%91%E6%B8%B8%E7%8E%A9%E5%88%97%E8%A1%A8
隨著宿主應用中接入的小游戲越來越多,我們也意識到:用戶可能需要快速找回之前玩過的游戲。于是,我們加入了「游玩歷史」功能。宿主平臺也提供了相關 API,支持通過 userId(與設置游戲啟動參數(shù)時的 USER_ID 一致)獲取用戶的歷史記錄。例如:
publicstatic List
getUserHistory(Context context, String userId) // e.g. List historyList = TJHostHandle.getUserHistory(context, "user_id");
返回的歷史記錄是一個 HistoryModel 列表,每個 HistoryModel 包含了啟動游戲的詳細信息,字段說明如下:
publicclass HistoryModel {
String id; // Game ID
String versionId; // Game Version ID
String gameType; // Game Type
String name; // Game Name
List
tags; // Game Tags String iconUrl; // Game Icon Url String briefIntro; // Game Brief Introduction String launchKey; // Game Launch Key, **Primary Key** boolean dev; // Is Development Version String userId; // User ID long lastLaunchTime; // Last Launched Time }
需要注意的是,同一個小游戲可能會出現(xiàn)多個版本(比如用戶通過掃碼體驗了某個測試包),這時返回的 launchKey 會包含具體的版本信息。通過該 launchKey 啟動游戲時,宿主 SDK 會自動選擇對應版本的游戲包進行加載,確保用戶體驗一致。
? 更進一步:自定義存儲游玩歷史
當然,如果你希望自行管理這些歷史記錄,宿主 SDK 還支持通過注入自定義的 HistorySaver 接口來自定義存儲邏輯。
public void setHistorySaver(HistorySaver saver)
public interface HistorySaver {
void save(HistoryModel history);
}
我們可以在宿主應用里實現(xiàn)一個 HistorySaver,在 save 方法中將用戶的游玩記錄上傳到自己的服務端數(shù)據(jù)庫中。這樣,不僅可以實現(xiàn)跨設備同步,也能為后續(xù)的個性化推薦打好基礎。
4.1.3 多進程 & 多任務棧:小游戲也能“開多個窗口”
參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/quick-integrate#31-%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%90%AF%E5%8A%A8%E5%99%A8
有時候,用戶希望能在宿主應用主界面和小游戲之間自由切換——畢竟上班摸魚的時候,反應要快,可不能被發(fā)現(xiàn)了。另外,如果某個游戲包出現(xiàn)了 bug 導致崩潰或卡死,我們也不希望影響到整個宿主 App 的穩(wěn)定性。
為了解決這些問題,我們采用了多進程 + 多任務棧的方案,讓每個小游戲都能在獨立的“窗口”中運行,互不干擾、互不拖累,資源也能各自獨立管理。
最簡單的實現(xiàn)方式是我們可以在 AndroidManifest.xml 中為游戲 Activity 指定一個獨立的進程名和任務棧名,讓它運行在自己的空間里:
".host.GameActivity"
android:screenOrientation="portrait"
android:configChanges="keyboard|orientation|screenSize|keyboardHidden|navigation|screenLayout"
android:launchMode="singleTask"
android:process=":hostProc"
android:taskAffinity="com.unity3d.unityconnect.host.affinity">
如果我們希望支持多個小游戲同時運行,只需要多聲明幾個繼承自 GameActivity 的子類,并在 Manifest 中為它們配置不同的進程名和任務棧名:
public class GemeActivity1 extends GameActivity {}
public class GemeActivity2 extends GameActivity {}
這樣,在運行時我們就可以通過 startActivity 啟動不同的游戲 Activity,實現(xiàn)多個小游戲并行運行,各自獨立互不干擾。
如果游戲已經(jīng)在后臺,再次點擊還能“喚醒”嗎?
當然可以!為了更方便地管理這些多進程的小游戲,宿主 SDK 提供了一個實用的工具類:MultiProcessLauncher。它專門用來解決以下幾個問題:
啟動新的小游戲時,自動頂?shù)糇钤鐔拥呐f游戲
如果游戲已經(jīng)在后臺,點擊時自動拉到前臺
管理多個游戲容器的生命周期和狀態(tài)
它主要包含兩個接口:
configContainer:注冊所有可用的游戲容器(Activity + 進程)
launch:通過 launchKey 啟動或喚醒游戲
還包含一個默認的生命周期監(jiān)視器實現(xiàn) MultiProcessLauncher.MiniGameLifeCycle 用于監(jiān)測小游戲狀態(tài)。通過它們我們能直接實現(xiàn)各小游戲進程的管理。
我們先需要在游戲 Activity onCreate 中添加生命周期監(jiān)視器,從而使得多進程監(jiān)視器能夠動態(tài)檢測到游戲 Activity 的各項生命周期回調(diào)。
privatevoidinitLifeCycleObserver() {
String launchKey = getIntent().getStringExtra("launchKey");
gameLifeCycle = new MultiProcessLauncher.MiniGameLifeCycle(this, launchKey);
getLifecycle().addObserver(gameLifeCycle);
}
假設我們希望最多支持兩個小游戲同時運行,那么可以在 AndroidManifest 中聲明兩個容器,并使用 configContainer 將它們注冊進多進程啟動器:
privatevoidinitMultiProcessLauncher(){
Map
map = new HashMap<>(); map.put(":hostProc1", GameActivity1.class); map.put(":hostProc2", GameActivity2.class); MultiProcessLauncher.configContainer(map); }
在用戶點擊某個游戲時,調(diào)用 launch 方法并傳入 launchKey,啟動器會自動判斷:
如果游戲已經(jīng)在后臺,會將其拉到前臺
如果沒有在后臺,但容器已滿,則頂?shù)糇钤鐔拥哪莻€游戲
MultiProcessLauncher.launch(context, "launchKey", intent -> {
intent.putExtra("launchKey", launchkey);
// And other params if needed
});
此外,Android 10 后 WebView 在多進程中的使用做了限制,每個進程只能訪問自己的網(wǎng)絡數(shù)據(jù),因此需要為每個進程制定數(shù)據(jù)存儲目錄。我們可以在游戲 Activity 初始化時,簡單使用進程名作為數(shù)據(jù)目錄名稱以避免報錯。
WebView.setDataDirectorySuffix("processName");
通過這種方式,我們不僅實現(xiàn)了小游戲之間的并行運行,也大大提升了宿主應用的穩(wěn)定性和靈活性。用戶可以像在瀏覽器中切換標簽頁一樣,在多個小游戲間自由切換,真正實現(xiàn)“多開不是夢”。
4.1.4 支持動態(tài)加載:精簡 APP 體積的魔法
參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/quick-integrate#32-%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD-so-%E5%BA%93
前文提到,宿主 SDK 提供了兩種版本:靜態(tài)版和動態(tài)版。
靜態(tài)版的優(yōu)勢在于接入簡單、運行穩(wěn)定,并且能一定程度上保證 SDK 的完整性和安全性。但它也有一個明顯的短板——體積大。打包后 AAR 文件將近 40MB,對于宿主 App 來說,這可能會顯著增加安裝包大小,尤其對新用戶或對小游戲不感興趣的用戶來說,這部分體積是“沉重”的負擔 。
為了解決這個問題,Connect 選擇使用了宿主 SDK 的動態(tài)版本。它的核心優(yōu)勢是輕量,AAR 包體不到 1MB。小游戲運行環(huán)境不再一開始就集成進 App,而是在真正需要的時候“按需加載”。
比如,當用戶點擊進入「游戲中心」時,我們再觸發(fā)動態(tài)加載邏輯,下載小游戲運行所需的依賴。
因此宿主 SDK 提供了 prepare 方法,用于動態(tài)加載運行環(huán)境:
TJHost._prepare_(this, new OnLoadSharedLibraryListener() {
@Override
publicvoidonSuccess(String s){
l_ibPath _= s;
_isTJHostHandleInitialized _= true;
}
@Override
public void onDownloading(int progress){
// update UI
}
@Override
public void onFailure(Throwable throwable){
// retry or toast
}
});
這個接口支持加載進度回調(diào),可以配合 UI 展示加載動畫或進度條,提升用戶體驗。
加載成功后,我們會拿到一個 libPath,也就是運行環(huán)境的本地路徑。接下來,在啟動游戲的 Activity 中,我們只需要在游戲 Activity onCreate 的時候調(diào)用以下接口,將這個路徑注入到運行環(huán)境中即可:
boolean result = TJHost._installNativeLibraryPath_(getApplicationContext(), l_ibPath_);
一旦注入成功,小游戲就可以像靜態(tài)版一樣正常運行,用戶幾乎感知不到差異。通過動態(tài)加載機制,我們成功地將小游戲運行環(huán)境“從包里搬到了云上”,不僅減輕了宿主 App 的初始體積負擔,也為后續(xù)更靈活的按需加載、分模塊更新打下了基礎。
4.2 小游戲開發(fā)者
對于小游戲開發(fā)者來說,完整走一遍“創(chuàng)建宿主應用 → 創(chuàng)建宿主小游戲 → 上傳游戲包 → 發(fā)布”這一整套流程,雖然是正式上線所必需的,但如果只是想快速驗證一個小游戲包的運行效果,流程就顯得有些繁瑣了。
尤其是在開發(fā)階段,我們更希望有一種便捷的方式,能快速啟動某個小游戲包進行調(diào)試,甚至還能將這個方式分享給其他開發(fā)者或測試人員一起使用。同時,如果能配合一些調(diào)試工具,那開發(fā)效率自然也能大大提升。
4.2.1 掃碼啟動小游戲:一鍵體驗更高效
參考文檔:https://minihost.tuanjie.cn/help/docs/quickstart/create-game#3-%E6%89%AB%E7%A0%81%E4%BD%93%E9%AA%8C%E6%B8%B8%E6%88%8F
為了解決這個問題,我們首先想到的就是——掃碼啟動。
在宿主控制臺中,每一個小游戲的線上版本,以及每一個上傳的游戲包版本,都會自動生成一個二維碼,支持掃碼體驗:
宿主應用通過這個二維碼啟動游戲的方式也非常簡單:
掃描二維碼,獲取其中的字符串內(nèi)容(這是一個特殊格式的 launchKey)
將該字符串作為游戲啟動參數(shù)中的 LAUNCH_KEY 傳入宿主 SDK
宿主 SDK 會自動識別該 launchKey 對應的游戲包信息,下載對應版本并啟動游戲
宿主應用通過這個二維碼啟動游戲的方式也非常簡單,只需要掃描獲取二維碼的對應的字符串,并且設置為游戲啟動參數(shù)中的 LAUNCH_KEY 即可。
這個二維碼啟動方式的最大優(yōu)勢在于——沒有組織權限限制。也就是說,即使你的宿主應用與小游戲不屬于同一個組織,也可以通過掃碼啟動游戲,非常適合在開發(fā)、測試階段跨團隊協(xié)作使用。
此外,通過掃碼啟動的游戲,其游玩歷史記錄中的 dev 字段會被標記為 true,表示這是一個開發(fā)版本。同時,如果你多次掃碼體驗同一個小游戲的不同版本(比如測試包、灰度包等),系統(tǒng)也會為每個版本記錄一條獨立的歷史記錄,因為此時的 launchKey 會攜帶具體的版本信息。
掃碼啟動不僅讓小游戲開發(fā)者調(diào)試更高效,也為測試人員、產(chǎn)品經(jīng)理等非技術角色提供了一個無需打包、無需配置就能「一鍵體驗」的便捷入口。再配合宿主 SDK 提供的調(diào)試工具,整個開發(fā)流程也就更加順暢了。
4.2.2 小游戲 C# 代碼調(diào)試支持:讓問題排查更高效
在開發(fā)過程中,遇到小游戲中出現(xiàn)一些不符合預期的行為是常有的事。雖然我們可以通過游戲內(nèi)的日志來初步判斷問題的大致范圍,但要精準定位問題根源,很多開發(fā)者還是希望能夠通過調(diào)試工具直接查看代碼運行邏輯。
不過,與在 Unity 編輯器中直接運行游戲不同,在宿主環(huán)境中運行小游戲時,我們無法像平常那樣使用 IDE(如 Rider 或 Visual Studio)直接啟動調(diào)試模式。這就對調(diào)試過程提出了新的挑戰(zhàn)。
為了幫助開發(fā)者更高效地排查問題,宿主 SDK 提供了一個便捷的調(diào)試入口,允許宿主應用在合適的時機(比如用戶點擊某個按鈕時)打開調(diào)試面板:
hostHandle.showDebugMenu();
調(diào)試面板中包含了查看小游戲運行過程中的性能數(shù)據(jù)、啟用 C# 代碼等多個實用功能,
當點擊「C# 調(diào)試」按鈕后,宿主 SDK 會自動啟動一個遠程調(diào)試服務。此時,我們可以在 Rider 中通過 Attach to Process 的方式,連接到正在運行的小游戲進程,從而實現(xiàn)在項目代碼中設置斷點、逐步調(diào)試等功能。
通過這個調(diào)試面板,開發(fā)者不僅可以快速了解小游戲的運行狀態(tài),還能像在本地調(diào)試一樣深入排查問題邏輯,大大提升了開發(fā)和測試效率。
4.3宿主應用開發(fā)者
對于宿主應用的開發(fā)者來說,無論是為了打造“超級 App”而引入宿主 SDK,還是計劃用宿主方案替代原有的小游戲平臺,最關鍵的問題始終是:小游戲接入之后,實際表現(xiàn)到底如何?
這里的“表現(xiàn)”不僅僅是訪問量、啟動次數(shù)這些表層數(shù)據(jù),更重要的是小游戲在運行過程中的性能表現(xiàn)、穩(wěn)定性、用戶留存等核心指標。這些數(shù)據(jù)直接關系到:
小游戲是否值得繼續(xù)推廣
哪些內(nèi)容更受用戶歡迎
如何有針對性地進行優(yōu)化和迭代
通過對這些數(shù)據(jù)的深入分析,我們可以更科學地評估宿主小游戲的整體質(zhì)量和用戶接受度,從而做出更明智的產(chǎn)品決策。
4.3.1 小游戲數(shù)據(jù)看板:一目了然的表現(xiàn)評估
為了解決上述問題,宿主控制臺提供了專門的數(shù)據(jù)分析頁面,幫助我們快速掌握小游戲的關鍵表現(xiàn)指標。
在這個頁面中,我們可以按時間維度篩選數(shù)據(jù),查看每個小游戲的:
性能表現(xiàn)(如加載耗時、幀率等)
用戶訪問情況(如啟動次數(shù)、活躍用戶數(shù))
留存情況(次日、7 日留存等)
這些數(shù)據(jù)不僅幫助我們與小游戲開發(fā)者一起定位性能瓶頸、優(yōu)化用戶體驗,也為后續(xù)的推廣策略和買量投放提供了重要參考。
數(shù)據(jù)是最好的反饋機制。通過宿主控制臺的數(shù)據(jù)看板,我們可以從“感覺不錯”轉向“數(shù)據(jù)說話”,讓小游戲生態(tài)的優(yōu)化更加精準、可持續(xù)。
上述這一系列優(yōu)化改動,雖然看起來只是“錦上添花”,但它們讓宿主應用的體驗更加完整,也讓我們在產(chǎn)品層面有了更多可發(fā)揮的空間!
五、樂高積木式的平臺能力接入
小游戲跑起來之后,我們的工作并沒有結束。下一步,就是思考如何把宿主應用的“平臺能力”開放給小游戲使用。
在運行過程中,小游戲可能會調(diào)用一些平臺能力的 JS 接口,比如登錄、廣告、支付等。由于宿主 SDK 本身并不內(nèi)置這些功能(例如賬號體系或廣告服務),所以這些接口的具體實現(xiàn)就需要由宿主應用來完成。
那問題來了:如何像搭樂高積木一樣,按需接入這些模塊化的能力,并通過統(tǒng)一的方式暴露給小游戲使用呢?
5.1接入平臺登錄能力
參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/platform-integrate#1-%E7%99%BB%E5%BD%95-sdk
首先,我們來看看宿主小游戲的登錄流程:
當小游戲啟動時,通常會調(diào)用平臺的登錄接口(如微信中的 wx.login,在宿主環(huán)境下對應的是 tj.login)。這個調(diào)用會提示宿主應用拉起登錄浮層,引導用戶完成登錄。
登錄成功后,宿主應用會返回一個臨時憑證 code,小游戲客戶端將該 code 傳給自己的服務端,服務端再通過宿主提供的 Code2Session 接口換取用戶的登錄態(tài)信息(Session)。這個 Session 中包含了用戶在當前小游戲中的唯一標識 OpenID,游戲可以通過這個 OpenID 來讀取玩家的云存檔或本地數(shù)據(jù)。
所以,宿主應用如果想支持登錄能力,需要完成兩件事:
在客戶端實現(xiàn) tj.login 這個 JS API,讓小游戲能拿到 code
在服務端實現(xiàn) Code2Session 接口,供小游戲服務端換取 Session
5.1.1 宿主應用實現(xiàn) JS API:把 Native 能力暴露給 JS
有些小伙伴可能會疑惑:宿主是用 Java/Kotlin 開發(fā)的,小游戲是運行在 JS 環(huán)境中的,怎么把 Native 能力暴露給 JS 呢?
別擔心,宿主 SDK 已經(jīng)為我們準備好了 橋接接口 ,支持從 JS 調(diào)用 Native 方法 :
tj.customCommand(Object object, string methodName, ...);
tj.customCommandSync(Object object, String methodName, ...);
橋接接口:https://minihost.tuanjie.cn/help/docs/api/android/custom-command#js-%E5%B1%82%E8%B0%83%E7%94%A8-java-%E5%B1%82%E6%8E%A5%E5%8F%A3
那么下一個想到的問題是,在宿主應用內(nèi)如何介入小游戲所運行在的 JS 環(huán)境呢?我們可以通過啟動參數(shù) BUILTIN_CUSTOM_SCRIPTS_PATH 向小游戲環(huán)境中注入自定義 JS 文件。例如:
bundle.putString(TJConstants._BUILTIN_CUSTOM_SCRIPTS_PATH_, "customScripts");
這樣,宿主應用就會加載 assets/customScripts 目錄下的 JS 文件,并注入到小游戲的運行環(huán)境中。
那么有了上面這些信息,我們知道在宿主應用內(nèi)實現(xiàn)一個 JS API 可以通過在啟動游戲時注入一些 JS 文件,文件中定義了小游戲需要調(diào)用的 JS API 如 tj.login 的方式實現(xiàn),而這些 JS API 的最終實現(xiàn)則可以通過 customCommand 接口將其從 JS 層轉移到 Native 層。
那么讓我們來大展身手吧!我們可以在 assets/customScripts 目錄下創(chuàng)建一個 auth.js 文件,定義 tj.login 方法。它的核心邏輯是調(diào)用 customCommand 接口,轉發(fā)到 Native 層的 loginUnity 方法:
tj.login = function (obj) {
tj.customCommand({
success: function(res) {
let resObj = JSON.parse(res);
obj.success(resObj);
},
fail: function(res) {
let apiRes = {
errMsg: res.errorMsg,
errno: res.errorCode
};
obj.fail(apiRes);
},
complete: function() {
obj.complete();
}
}, "loginUnity");
};
在 Java 端,我們需要在 setGameStartOptions 之后,設置一個自定義命令監(jiān)聽,處理來自 JS 的 loginUnity 調(diào)用:
hostHandle.setCustomCommandListener("loginUnity", (customCommandHandle, bundle) -> {
try {
String code = LoginSDK.getInstance().getUserCode();
JSONObject loginRes = new JSONObject();
loginRes.put("code", code);
customCommandHandle.pushResult(loginRes.toString());
customCommandHandle.success();
} catch (RemoteException e) {
customCommandHandle.fail(1001, "Failed to connect Login Service: " + e.getMessage());
} catch (Exception e) {
customCommandHandle.fail(1002, "Failed to serialize login result: " + e.getMessage());
}
});
完成以上配置后,小游戲就可以通過調(diào)用 tj.login 獲取宿主應用返回的 code,完成登錄流程。整個過程就像拼裝樂高一樣:JS 和 Native 各自實現(xiàn)一塊,通過宿主 SDK 提供的橋梁拼接在一起,既靈活又高效。
5.1.2 宿主應用服務端配置:Code 換 Session 的“最后一公里”
參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/server-integrate#jscode2session
前面我們已經(jīng)完成了客戶端的登錄流程:小游戲通過調(diào)用 tj.login 拿到了登錄憑證 code。接下來,就輪到服務端登場啦!
小游戲客戶端會把這個 code 傳給自己的游戲服務器,而游戲服務器則需要通過宿主應用的服務端接口,將 code 換成包含用戶身份信息的 Session(比如 openId、session_key 等)。這一步就相當于登錄流程的“最后一公里”。
為了讓這個流程順利跑通,我們需要做兩件小事:
在宿主應用的服務端實現(xiàn)一個 Code2Session 接口,負責接收 code 并返回用戶的 Session 信息。
在宿主控制臺中,配置這個接口的地址,也就是“平臺方接入 URL”。
配置完成后,宿主平臺會自動將小游戲傳來的 code 轉發(fā)給你配置的接口,并將返回的 Session 信息傳回給小游戲服務端。
通過這一步,小游戲的登錄流程就真正打通了:從宿主應用拉起登錄 → 拿到 Code → 宿主應用服務端換取 Session → 獲取用戶身份,是不是感覺已經(jīng)可以愉快地登錄了?
登錄能力的接入,主要是為了確保每個需要用戶身份的小游戲都能順利運行。而除了登錄之外,另一個我們非常關注的平臺能力就是——廣告。
作為小游戲變現(xiàn)的重要手段,廣告能力的接入自然是優(yōu)先級很高的一項工作。無論是激勵視頻、插屏廣告,還是 Banner 展示,合理的廣告接入不僅能帶來收入,也能為小游戲生態(tài)帶來更強的可持續(xù)性。
具體的廣告能力實現(xiàn)方式可以參考宿主 SDK 的官方文檔,里面對接入流程、回調(diào)處理等內(nèi)容都有詳細說明。這里我們就不展開贅述啦,感興趣的小伙伴可以自行查閱。
六、我們的下一站
從 Connect 成為小游戲宿主的第一塊“試驗田”開始,我們一步步搭起了這片數(shù)字田地的基礎設施:小游戲能跑起來了,宿主能變得更聰明了,平臺能力也像樂高一樣拼裝上了。
但這只是開始。
未來,我們還將持續(xù)優(yōu)化宿主 SDK 的接入體驗,降低平臺能力擴展的門檻,提供更豐富的調(diào)試工具、更靈活的運營能力、更智能的數(shù)據(jù)分析支持。我們也在探索更多引擎的兼容性、更廣泛的平臺適配,以及更輕量的運行時架構,力求讓每一個 App 都能輕松擁有自己的小游戲生態(tài)。
當然,一個生態(tài)的繁榮,永遠不是靠一個宿主應用就能完成的。我們希望有更多開發(fā)者、內(nèi)容創(chuàng)作者、平臺伙伴加入我們,一起耕耘、一起試驗、一起收獲——把這片“宿主田地”種得更大、更好、更有生命力。
畢竟,在這個賽博世界里,我們都是數(shù)字田地里的“農(nóng)夫”——用代碼種下希望,用創(chuàng)意收獲樂趣。未來的宿主生態(tài),就等我們一起開墾、一起灌溉、一起收成。
廣闊天地,大有可為。我們的下一站,就在前方。
Unity 官方微信
第一時間了解Unity引擎動向,學習進階開發(fā)技能
每一個“點贊”、“在看”,都是我們前進的動力
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務。
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.