本文档描述如何在 HarmonyOS NEXT 平台上,通过团结引擎(Tuanjie Engine)内嵌 Unity 的方式接入 MID SDK。bridge_unity 模块提供了 Unity C# 与 MID SDK ArkTS 层之间的双向通信桥梁。
接入 MID SDK 的 Unity(团结引擎)客户端开发者。
| 对比项 | Android | HarmonyOS |
|---|---|---|
| ArkTS/Java → Unity | UnityPlayer.UnitySendMessage |
tuanjieLib.TuanjieSendMessage |
| Unity → ArkTS/Java | Unity C# 直接调用 Activity 方法 | OpenHarmonyJSObject.Call() + .etslib 插件 |
| 回调格式 | {"method":"类型","code":N,"data":...} |
相同 |
| 构建工具 | Gradle | hvigorw + ohpm |
| 包格式 | AAR | HAR |
| Native C++ 层 | 无 | 无(纯 ArkTS + 团结引擎官方 API) |
┌──────────────────────┐ ┌──────────────────────┐│ Unity C# │ │ ArkTS ││ │ OpenHarmonyJSObject│ ││ 调用 SDK 功能 ──────┼────────────────────>│ MIDBaseUnityInterface││ │ .etslib 插件 │ (调用 MIDBaseSDK) ││ │ │ ││ 接收回调结果 <──────┼────────────────────│ MIDUnityCallbackManager││ OnOMGCallback(json) │ TuanjieSendMessage │ callUnity(json) │└──────────────────────┘ └──────────────────────┘
调用方向(Unity → SDK):Unity C# → OpenHarmonyJSObject("MIDBridge").Call(method, params) → .etslib 插件 → ArkTS dispatchFromUnity()
回调方向(SDK → Unity):MIDBaseSDK → ArkTS callback() → tuanjieLib.TuanjieSendMessage("OMGSdk", "OnOMGCallback", json) → Unity C#
从 MID SDK 团队获取以下文件:
| 文件 | 放置位置 | 说明 |
|---|---|---|
WPMIDBase.har |
HarmonyOS 项目 libs/ |
MID SDK 核心库 |
channel_xxx.har |
HarmonyOS 项目 libs/ |
渠道实现(华为/途游等) |
MIDBridge.etslib |
Unity 项目 Assets/Plugins/OpenHarmony/ |
ArkTS 导出插件 |
entry/oh-package.json5:
{"dependencies": {"@acegame/wpmidbase": "file:../libs/WPMIDBase.har","@acegame/channel-huawei": "file:../libs/channel_huawei.har"}}
执行 ohpm install 安装依赖。
在 EntryAbility.ets 中初始化 Bridge 管理器并注册渠道:
import { MIDBridgeManager } from '@acegame/bridge-unity';import '@acegame/channel-huawei'; // 渠道 side-effect importexport default class EntryAbility extends UIAbility {onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {// 初始化 Bridge 管理器(Unity 调用前必须完成)MIDBridgeManager.init(this.context);const midBridge = MIDBridgeManager.getInstance();// SDK 生命周期MIDBaseSDK.getInstance(this.context).onCreate();}onDestroy() {MIDBaseSDK.getInstance(this.context).onDestroy();}}
将 MIDBridge.etslib 文件复制到 Unity 项目的 Assets/Plugins/OpenHarmony/ 目录下。团结引擎会自动识别并加载此插件。
通过团结引擎的 OpenHarmonyJSObject 调用 .etslib 插件中注册的方法:
using UnityEngine;public class MIDSDKManager : MonoBehaviour{// 通过插件名 "MIDBridge" 获取 ArkTS 桥接对象// OpenHarmonyJSObject 由团结引擎提供void Start(){// 初始化 SDK(传入 Unity 回调目标)var initParams = JsonUtility.ToJson(new {unityObjectName = "OMGSdk",unityCallBackFunc = "OnOMGCallback",resVer = "1.0.0"});CallArkTS("init", initParams);// 开启调试日志CallArkTS("setLogs", "{\"enable\":true}");}/// <summary>/// 调用 ArkTS 侧方法/// </summary>private void CallArkTS(string method, string paramsJson = ""){// 团结引擎 OpenHarmonyJSObject 调用示例// var midBridge = new OpenHarmonyJSObject("MIDBridge");// midBridge.Call(method, paramsJson);Debug.Log($"[MIDSDK] Call {method}: {paramsJson}");}/// <summary>/// 调用 ArkTS 侧方法并获取返回值/// </summary>private string CallArkTSWithReturn(string method, string paramsJson = ""){// var midBridge = new OpenHarmonyJSObject("MIDBridge");// return midBridge.Call<string>(method, paramsJson);return "";}}
所有 SDK 回调统一发往 OMGSdk 对象的 OnOMGCallback 方法,JSON 格式为:
{"method": "类型", "code": 状态码, "data": 数据}
/// <summary>/// 统一回调入口,挂在名为 "OMGSdk" 的 GameObject 上/// </summary>public void OnOMGCallback(string json){Debug.Log($"[MIDSDK] Callback: {json}");var callback = JsonUtility.FromJson<SDKCallback>(json);switch (callback.method){case "init":OnSDKInit(callback.code, callback.data);break;case "login":OnSDKLogin(callback.code, callback.data);break;case "switchLogin":OnSDKSwitchLogin(callback.code, callback.data);break;case "logout":OnSDKLogout(callback.code, callback.data);break;case "exitGame":OnSDKExitGame(callback.code, callback.data);break;case "pay":OnSDKPay(callback.code, callback.data);break;case "userAgree":OnSDKUserAgree(callback.code);break;case "getcdn":OnSDKGetCDN(callback.code, callback.data);break;case "giftCode":OnSDKGiftCode(callback.code, callback.data);break;case "onGetGameNotice":OnSDKGetGameNotice(callback.code, callback.data);break;}}[System.Serializable]public class SDKCallback{public string method;public int code;public string data; // JSON string,按 method 解析}
调用:
CallArkTS("init", JsonUtility.ToJson(new {unityObjectName = "OMGSdk", // 接收回调的 GameObject 名称unityCallBackFunc = "OnOMGCallback", // 接收回调的方法名resVer = "1.0.0" // 游戏资源版本号,无可传空}));
回调:
| method | code | data | 说明 |
|---|---|---|---|
init |
1 | {"arg1": "success"} |
初始化成功 |
init |
0 | {"arg1": "错误描述"} |
初始化失败 |
调用:
CallArkTS("login");
回调:
| method | code | data | 说明 |
|---|---|---|---|
login |
1 | 见下表 | 登录成功 |
login |
0 | "msg:登录失败" |
登录失败 |
登录成功 data 字段(对标 Android):
| 字段 | 类型 | 说明 |
|---|---|---|
uid |
string | 用户ID(游戏应以此作为唯一标识) |
uidV1 |
string | 用户ID V1 |
token |
string | 用户token(需上传到游戏服务器验证) |
channelid |
string | 渠道ID |
ext |
string | 扩展信息 |
username |
string | 用户名 |
nickname |
string | 昵称 |
logintype |
string | 登录类型(speedy/common/phone/email/thirdHidden/accessToken) |
returnJson |
string | 渠道返回的原始数据 |
userServiceCode |
string | 用户ServiceCode |
isFirstLoginStatus |
bool | 是否首次登录 |
firstLoginTimestamp |
string | 首次登录时间戳 |
bindChannelIds |
string | 绑定渠道ID列表 |
调用:
CallArkTS("logout");
回调:
| method | code | data | 说明 |
|---|---|---|---|
logout |
1 | {"arg1": "msg"} |
注销成功,游戏应返回登录界面 |
调用:
CallArkTS("switchAccount");
回调: 格式同登录,method 为 switchLogin。
调用:
CallArkTS("exitGame");
回调:
| method | code | data | 说明 |
|---|---|---|---|
exitGame |
1 | {"arg1": ""} |
退出游戏 |
调用:
// 注册角色CallArkTS("createRole", JsonUtility.ToJson(new {Rolename = "玩家名", Roleid = "10001", RoleLv = "1",RoleVipLv = "0", ServerId = "s1", ServerName = "一区", GuildName = ""}));// 登录角色CallArkTS("loginRole", JsonUtility.ToJson(new {Rolename = "玩家名", Roleid = "10001", RoleLv = "30",RoleVipLv = "3", ServerId = "s1", ServerName = "一区", GuildName = ""}));// 角色等级变化CallArkTS("lvUpRole", JsonUtility.ToJson(new {Rolename = "玩家名", Roleid = "10001", RoleLv = "31",RoleVipLv = "3", ServerId = "s1", ServerName = "一区", GuildName = ""}));
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
Rolename |
string | 角色名称 |
Roleid |
string | 角色ID |
RoleLv |
string | 角色等级 |
RoleVipLv |
string | VIP等级,无则传 "0" |
ServerId |
string | 服务器ID |
ServerName |
string | 服务器名称 |
GuildName |
string | 公会名称 |
注册角色 →
createRole,登录角色 →loginRole,等级变化 →lvUpRole。首次登录需先调createRole再调loginRole。
调用:
CallArkTS("pay", JsonUtility.ToJson(new {productId = "item_001",productName = "钻石礼包",productDes = "100钻石",price = "600",buyNum = "100",currencyType = "1", // 1=人民币(分) 2=美元(美分) 3=日元(円)...currency = "钻石",deliverUrl = "",playerLv = "30",playerVipLv = "3",extension = "自定义透传数据"}));
currencyType 货币类型:
| 值 | 货币 | 单位 | 值 | 货币 | 单位 |
|---|---|---|---|---|---|
| 1 | 人民币 | 分 | 6 | 新加坡币 | 分 |
| 2 | 美元 | 美分 | 7 | 越南盾 | 盾 |
| 3 | 日元 | 円 | 8 | 台币 | 元 |
| 4 | 港币 | 分 | 9 | 韩元 | 元 |
| 5 | 英镑 | 便士 | 10 | 泰铢 | 萨当 |
回调:
| method | code | data | 说明 |
|---|---|---|---|
pay |
1 | {"sdkOrderId": "订单号", "payType": "商品ID"} |
支付成功 |
pay |
0 | 同上 | 支付失败 |
pay |
2 | 同上 | 支付结果未知,以服务端通知为准 |
调用:
CallArkTS("giftCodeExchange", JsonUtility.ToJson(new {giftCode = "礼包码",url = "http://发货地址",extendParams = "透传参数"}));
回调:
| method | code | data | 说明 |
|---|---|---|---|
giftCode |
1 | "兑换成功" |
兑换成功 |
giftCode |
非1 | "错误描述" |
兑换失败 |
调用:
CallArkTS("getGameNotice", "{\"type\":0}"); // 0=登录公告, 1=活动公告
回调:
| method | code | data | 说明 |
|---|---|---|---|
onGetGameNotice |
1 | JSON 数组 | 获取成功 |
onGetGameNotice |
0 | "fail" |
获取失败 |
调用:
CallArkTS("getCDNPath");
回调:
| method | code | data | 说明 |
|---|---|---|---|
getcdn |
0 | "http://cdn1,http://cdn2" |
获取成功,逗号分隔 |
getcdn |
-1 | "fail" |
获取失败 |
调用:
CallArkTS("agreePrivacy");
回调:
| method | code | data | 说明 |
|---|---|---|---|
userAgree |
1 | "" |
用户同意协议 |
userAgree |
0 | "" |
用户拒绝协议 |
调用:
CallArkTS("startHeartbeat"); // 登录成功后开启CallArkTS("stopHeartbeat"); // 退出时停止
调用:
CallArkTS("setLogs", "{\"enable\":true}"); // 开启CallArkTS("setLogs", "{\"enable\":false}"); // 关闭
注意: 出正式包时务必关闭。
调用(同步返回):
string serviceId = CallArkTSWithReturn("getServiceId");string channelId = CallArkTSWithReturn("getChannelId");string deviceGroupId = CallArkTSWithReturn("getDeviceGroupId");string serviceCode = CallArkTSWithReturn("getServiceCode");
调用:
// 不带额外参数CallArkTS("analytics", JsonUtility.ToJson(new {logID = "4001",key = "sdk-init-start"}));// 带自定义参数CallArkTS("analytics", JsonUtility.ToJson(new {logID = "4002",key = "role_login",content = new { roleLevel = "30", vipLevel = "3", serverId = "s1" }}));
参数说明:
| 参数 | 类型 | 必须 | 说明 |
|---|---|---|---|
logID |
string | 是 | 日志ID |
key |
string | 是 | 日志KEY |
content |
object | 否 | 自定义 key-value 参数 |
无回调,打点数据发送到服务端统计系统。
调用:
CallArkTS("realNameQuery");
回调:
| method | code | data | 说明 |
|---|---|---|---|
realNameQuery |
0 | 实名信息 JSON 字符串 | 查询成功 |
realNameQuery |
非0 | {"msg": "错误描述"} |
查询失败 |
调用:
CallArkTS("realNameVerification", JsonUtility.ToJson(new {id = "身份证号",name = "姓名"}));
参数说明:
| 参数 | 类型 | 必须 | 说明 |
|---|---|---|---|
id |
string | 是 | 身份证号 |
name |
string | 是 | 姓名 |
回调:
| method | code | data | 说明 |
|---|---|---|---|
realNameVerification |
0 | 验证结果 JSON 字符串 | 验证成功 |
realNameVerification |
非0 | {"msg": "错误描述"} |
验证失败 |
调用:
CallArkTS("onDestroy");
void Start(){// 1. 初始化 SDK(设置回调目标和游戏版本)CallArkTS("init", JsonUtility.ToJson(new {unityObjectName = "OMGSdk",unityCallBackFunc = "OnOMGCallback",resVer = "1.0.0"}));// 2. 开启调试(可选)CallArkTS("setLogs", "{\"enable\":true}");}// 回调: method="init", code=1void OnSDKInit(int code, string data){if (code == 1) {// 3. 登录CallArkTS("login");}}// 回调: method="login", code=1void OnSDKLogin(int code, string data){if (code == 1) {var loginData = JsonUtility.FromJson<LoginData>(data);string uid = loginData.uid; // 用户唯一标识string token = loginData.token; // 上传游戏服务器验证// 4. 注册角色 + 登录角色CallArkTS("createRole", roleJson);CallArkTS("loginRole", roleJson);// 5. 开启心跳CallArkTS("startHeartbeat");}}// 支付void OnPayButtonClick(){CallArkTS("pay", productJson);}// 退出void OnApplicationQuit(){CallArkTS("stopHeartbeat");CallArkTS("onDestroy");}
登录成功后,游戏客户端拿到 token 后需上传到游戏服务器,游戏服务器通过 token 向 MID 用户中心服务器发起登录验证请求,验证通过后游戏服务器再通知游戏客户端登录成功。
游戏客户端 ──login──> SDK ──> 渠道SDK ──> 返回 token+用户信息│├── 上传 token 到游戏服务器│└── 游戏服务器 ──验证──> MID 用户中心 ──> 返回用户信息 ──> 通知客户端
所有回调统一格式:{"method":"类型", "code":状态码, "data":数据}
| method | code | data | 触发时机 |
|---|---|---|---|
init |
1/0 | {"arg1":"msg"} |
初始化结果 |
login |
1/0 | 成功:用户JSON / 失败:"msg:..." |
登录结果 |
switchLogin |
1/0 | 同 login | 切换账号结果 |
logout |
1 | {"arg1":"msg"} |
注销成功 |
exitGame |
1 | {"arg1":""} |
退出游戏 |
pay |
1/0/2 | {"sdkOrderId":"...","payType":"..."} |
支付结果 |
userAgree |
1/0 | "" |
隐私协议结果 |
getcdn |
0/-1 | 成功:域名列表 / 失败:"fail" |
CDN地址 |
giftCode |
1/N | 成功:"兑换成功" / 失败:"msg" |
礼包码兑换 |
onGetGameNotice |
1/0 | 成功:JSON数组 / 失败:code | 游戏公告 |
realNameQuery |
0/N | 成功:实名信息JSON / 失败:{"msg":"..."} |
实名查询结果 |
realNameVerification |
0/N | 成功:验证结果JSON / 失败:{"msg":"..."} |
实名验证结果 |
| code | 含义 |
|---|---|
| 1 | 成功 |
| 0 | 失败 / 错误 |
| 2 | 取消 / 结果未知(仅支付) |
| -1 | 异常 |
| method | 参数 | 返回值 | 说明 |
|---|---|---|---|
init |
{unityObjectName, unityCallBackFunc, resVer} |
— | 初始化 |
login |
— | — | 登录 |
logout |
— | — | 注销 |
switchAccount |
— | — | 切换账号 |
exitGame |
— | — | 退出游戏 |
createRole |
{Rolename, Roleid, RoleLv, RoleVipLv, ServerId, ServerName, GuildName} |
— | 注册角色 |
loginRole |
同 createRole | — | 登录角色 |
lvUpRole |
同 createRole | — | 角色升级 |
pay |
{productId, productName, productDes, price, buyNum, currencyType, currency, deliverUrl, playerLv, playerVipLv, extension} |
— | 支付 |
setLogs |
{enable: bool} |
— | 调试日志 |
giftCodeExchange |
{giftCode, url, extendParams} |
— | 礼包码兑换 |
getCDNPath |
— | — | 获取CDN |
getGameNotice |
{type: int} |
— | 获取公告 |
agreePrivacy |
— | — | 同意隐私 |
startHeartbeat |
— | — | 开启心跳 |
stopHeartbeat |
— | — | 停止心跳 |
getServiceId |
— | string | 获取 serviceId |
getDeviceGroupId |
— | string | 获取机型组ID |
getServiceCode |
— | string | 获取 ServiceCode |
getChannelId |
— | string | 获取渠道ID |
analytics |
{logID, key, content} |
— | 游戏自定义打点 |
realNameQuery |
— | — | 实名查询(异步回调) |
realNameVerification |
{id, name} |
— | 实名验证(异步回调) |
onDestroy |
— | — | 销毁 |
init 时传入的 unityObjectName(默认 "OMGSdk")必须与 Unity 场景中挂载 OnOMGCallback 方法的 GameObject 名称一致unityCallBackFunc(默认 "OnOMGCallback")必须是 GameObject 上的 public 方法init 设置回调目标,后续回调才能正确送达setLogs {enable: false}TuanjieSendMessage 已处理线程安全,无需额外转发