Unity 华为快游戏JS桥接 实现写日志等功能

Unity 华为快游戏JS桥接 实现写日志等功能

之前接入微信小游戏本身代码js桥接比较完善,抖音小游戏有缺少但也没缺的这么多,华为这边的API,大残啊!官方转换插件Github仓库上一次提交在3月份。(截至现在)API给的很简略,接入js代码那里说看demo,但unity的demo里面没jslib,另一个比较完善的demo看起来像是cocos的,比较无奈。

还好用unity导出的webgl产物,和导出rpk这两部是分开的,测试调整jslib可以之间改xxx.framework.js来快速尝试,不用浪费大量的时间用来打包。

华为官方的JS API文档中给出的API全局对象是qg, 在转换插件内部的代码QGSDK-Call-JS.jslib中可以找到大量的QG_开头的函数,没有qg字段,但API看起来都使用ral作为入口,再看打包插件里面的ral.js是调用qg的。

测试发现 ral === window.ral, qg === window.qg, 直接通过Object.keys(x).ForEach()遍历打印qg和ral两个对象,键值对的文本是能对的上的。直接调用又有些内容不同,比如有qg.showToast但没有ral.showToast,都使用qg,测试qg.showToast是能用的,不知道会不会有其他问题。

以下为桥接代码的思路展示,一些接口基于实际需求进行了简化。

处理js返回C#的回调:

using LitJson;

using System.Collections.Generic;

using UnityEngine;

namespace Plugins.SDK {

    public delegate void SDKCallback(int code, string msg, string dataStr);

    public static class SDKCode {

        public const int Succeed = 1;

        public const int Failed = -1;

        public const int Cancel = -2;

    }

    // 用于处理回调

    public class CsJsEventHandler : MonoBehaviour

    {

        private static CsJsEventHandler s_Instance = null;

        public static CsJsEventHandler Instance

        {

            get

            {

                if (s_Instance == null)

                {

                    GameObject go = new GameObject("CsJsEventHandler");

                    GameObject.DontDestroyOnLoad(go);

                    s_Instance = go.AddComponent<CsJsEventHandler>();

                }

                return s_Instance;

            }

        }

    }

    private readonly Dictionary<string, SDKCallback> m_Callbacks = new Dictionary<string, SDKCallback>();

    private readonly HashSet<string> m_PersistentCallbacks = new HashSet<string>();

    public void StartUp()

    {

    }

    public string AddCallback(string funcName, SDKCallback callback, bool persistent = false)

    {

        string callbackId = $"{funcName}_{System.DateTime.Now:ddHHmmssfff}";

        m_Callbacks[callbackId] = callback;

        if (persistent)

        {

            m_PersistentCallbacks.Add(callbackId);

        }

        return callbackId;

    }

    public void RemoveCallback(string callbackId)

    {

        m_Callbacks.Remove(callbackId);

        m_PersistentCallbacks.Remove(callbackId);

    }

    // 经常调用又是异步的函数

    public string AddCallbackSingleton(string funcName, SDKCallback callback)

    {

        string callbackId = $"{funcName}_singleton";

        m_Callbacks[callbackId] = callback;

        m_PersistentCallbacks.Add(callbackId);

        return callbackId;

    }

    public string GetCallbackSingleton(string funcName)

    {

        string callbackId = $"{funcName}_singleton";

        return m_PersistentCallbacks.Contains(callbackId) ? callbackId : string.Empty;

    }

    public string RemoveCallbackSingleton(string funcName)

    {

        string callbackId = $"{funcName}_singleton";

        RemoveCallback(callbackId);

    }

    public void ClearCallbacks()

    {

        m_Callbacks.Clear();

        m_PersistentCallbacks.Clear();

    }

    private static string GetJsonString(JsonData obj, string key)

    {

        if (!obj.ContainsKey(key)) return string.Empty;

        return obj[key]?.ToString() ?? string.Empty;        

    }

    private static int GetJsonInt(JsonData obj, string key, int defaultValue = 0)

    {

        string s = GetJsonString(obj, key);

        if (int.TryParse(s, out int v))

        {

            return v;

        }

        return defaultValue;

    }

    public void HandleJsEvent(string jsonStr)

    {

        if (string.IsNullOrEmpty(jsonStr)) return;

        JsonData jsonData = JsonMapper.ToObject(jsonStr);

        string callbackId = GetJsonString(jsonData, "callbackId");

        if (m_Callbacks.TryGetValue(callbackId, out SDKCallback callback))

        {

            int code = GetJsonInt(jsonData, "code", SDKCode.Failed);

            string msg = GetJsonString(jsonData, "msg");

            string dataStr = GetJsonString(jsonData, "data");

            if (!m_PersistentCallbacks.Contains(callbackId))

            {

                m_Callbacks.Remove(callbackId);

            }

            callback.Invoke(code, msg, dataStr);

        }

    }

}

jslib

/*

* handler return code:

* SUCCEED: 1

* FAILED: -1

* CANCEL: -2

*

* 只展示部分函数,请根据实际需求自行添加

*/

var js_bridge_sdk_api = {

// File System

// 主要是调试\日志用, 能用同步的都用同步了

    JSB_GetUserDataPath: function() {

        return JSBHelper.stringToBuffer(qg.env.USER_DATA_PATH);

    },

    JSB_FSMAccessSync: function(path) {

        var fsm = qg.getFileSystemManager();

        try {

            fsm.accessSync(UTF8ToString(path));

            return JSBHelper.stringToBuffer("access:ok");

        } catch(error) {

            return JSBHelper.stringToBuffer(`error:${error}`);

        }

    },

    JSB_FSMCopyFileSync: function(path) { /* ... */ },

    JSB_FSMMkdirSync: function(dirPath) { /* ... */ },

    JSB_FSMRmdirSync: function(dirPath) { /* ... */ },

    JSB_FSMReaddirSync: function(dirPath) {

        var fsm = qg.getFileSystemManager();

        try {

            var result = fsm.readdirSync(UTF8ToString(dirPath));

            var text = JSON.stringify(result);

            return JSBHelper.stringToBuffer(text);

        } catch(error) {

            return JSBHelper.stringToBuffer("[]");

        }

    },

    JSB_FSMUnlinkSync: function(path) { /* ... */ },

    JSB_FSMReadTxtFile: function(filePath) {

        // readFileSync可以读utf8和binary, 不会写byte[]数组怎么从js传递到C#, 但因为没有需求, 先直接跳过了

        var fsm = qg.getFileSystemManager();

        try {

            var result = fsm.readFileSync(UTF8ToString(dirPath), "utf8");

            return JSBHelper.stringToBuffer(result);

        } catch(error) {

            return JSBHelper.stringToBuffer("");

        }

    },

    JSB_FSMWriteTxtFile: function(filePath, text) {

        // 同上

        var fsm = qg.getFileSystemManager();

        try {

            fsm.readFileSync(UTF8ToString(filePath), UTF8ToString(text), "utf8");

            return JSBHelper.stringToBuffer("ok");

        } catch(error) {

            return JSBHelper.stringToBuffer(`error:${error}`);

        }

    },

    JSB_FSMAppendTxtFile: function(filePath, text) {

        // 同上

        var fsm = qg.getFileSystemManager();

        try {

            fsm.appendFileSync(UTF8ToString(filePath), UTF8ToString(text), "utf8");

            return JSBHelper.stringToBuffer("ok");

        } catch(error) {

            return JSBHelper.stringToBuffer(`error:${error}`);

        }

    },

// UI

    JSB_ShowToast: function(config) {

        // 参数尽量简单, 用一个json字符串传递

        var options = JSON.parse(UTF8ToString(config));

        qg.showToast(options);

    },

    JSB_HideToast: function(config) {

        qg.hideToast({});

    },

    JSB_ShowModal: function(config) {

        // 快应用加载器里面这个modal好像能点穿的, 略坑

        var options = JSON.parse(UTF8ToString(config));

        var callbackId = options.callbackId; // 这里的callbackId是C#传递过来的, 后面展示

        delete options.callbackId;

        options.success = function(res) {

            if (res.confirm) {

                JSBHelper.returnEvent(callbackId, 1, "", "");

            }

            else if (res.cancel) {

                JSBHelper.returnEvent(callbackId, -2, "", "");

            }

        };

        options.fail = function() {

            JSBHelper.returnEvent(callbackId, -1, "", "");

        };

        qg.showModal(options);

    },

    JSB_ShowLoading: function(title) {

        qg.showLoading({

            title: UTF8ToString(title),

            mask: true,

        });

    },

    JSB_HideLoading: function() {

        qg.hideLoading({});

    },

// Misc

    JSB_GetBatteryLevel: function() {

        var info = qg.getBatteryInfoSync();

        return parseInt(info.level);

    },

    JSB_TriggerGC: function() {

        qg.triggerGC();

    },

    JSB_GetNetworkType: function() {

        var callbackId_j = UTF8ToString(callbackId);

        qg.getNetworkType({

            success: function(res) {

                var networkType = res.networkType;

                if (networkType === undefined) networkType = "unknown";

                JSBHelper.returnEvent(callbackId_j, 1, "", networkType);

            },

            fail: function() {

                JSBHelper.returnEvent(callbackId_j, -1, "", "");

            }

        });

    },

    JSB_OnNetworkStatusChange: function(callbackId) {

        // 因为回调一旦挂上就没有移除的需求, 没有把callback存起来

        var callbackId_j = UTF8ToString(callbackId);

        qg.onNetworkStatusChange(function(res) {

            var data = JSON.stringify(res);

            JSBHelper.returnEvent(callbackId_j, 1, "", data);

        });

    },

    JSB_Vibrate: function(mode) {

        if (mode == 0) {

            qg.vibrateShort({});

        }

        else {

            qg.vibrateLong({});

        }

    },

    JSB_PreviewImage: function(url) {

        qg.previeImage({

            urls: [

                UTF8ToString(url)

            ]

        });

    },

// Helper

    $JSBHelper: {

        stringToBuffer: function(valueStr) {

            var bufferSize = lengthBytesUTF8(valueStr) + 1;

            var buffer = _malloc(bufferSize);

            stringToUTF8(valueStr, buffer, bufferSize);

            return buffer;

        },

        returnEvent: function(callbackId, code, msg, dataStr) {

            var obj = {

                callbackId: callbackId,

                code: code,

                msg: msg,

                data: dataStr,

            };

            var text = JSON.stringify(obj);

            qg.unityInstance.Module.SendMessage("CsJsEventHandler", "HandleJsEvent", text);

            // Unity2021 文档给的是"MyGameInstance", 需要到index.html里面自己新建一个var并获取unityInstance

            // 但在这里不行, 测试"SendMessage"和"qg.unityInstance.Module.SendMessage"可用

        }

    }

};

autoAddDeps(js_bridge_sdk_api, "$JSBHelper");

mergeInfo(LibraryManager.library, js_bridge_sdk_api);

C#调用

using LitJson;

using System.Runtime.InteropServices;

namespace Plugins

{

    public static partial class JSBridgeExterns

    {

#if !UNITY_EDITOR

        [DllImport("__Internal")]

        public static extern string JSB_GetUserDataPath();

        // ... 省略, 函数签名对上即可

#else

        // 如果调用的代码不想写#if, 可以再写一遍UNITY_EDITOR的版本

        public static string JSB_GetUserDataPath() => default;

        // ...

#endif

    }

    // 调用展示

    public static class Test

    {

        public static void Test()

        {

            // 基础调用

            // 获取文件目录            

            string userDataPath = JSBridgeExterns.JSB_GetUserDataPath();

            // toast

            JsonData toastOptions = new JsonData();

            toastOptions["title"] = "Hello, world!";

            toastOptions["icon"] = "none";

            toastOptions["mask"] = true;

            toastOptions["duration"] = 2000;

            JSBridgeExterns.JSB_ShowToast(toastOptions.ToJson());

            // 带回调的调用

            SDKCallback callback = (code, msg, dataStr) => {

                if (code == SDKCode.Succeed)

                {

                    // ...

                }

                else if (code == SDKCode.Cancel)

                {

                    // ...

                }

                else if (code == SDKCode.Failed)

                {

                    // ...

                }

            };

            string callbackId = CsJsEventHandler.Instance.AddCallback("showModal", callback);

            JsonData modalOptions = new JsonData();

            modalOptions["title"] = "系统";

            modalOptions["content"] = "您有新短消息, 请注意查收";

            modal

Options["callbackId"] = callbackId;

            JSBridgeExterns.JSB_ShowModal(modalOptions.ToJson());

            // 挂网络状态监听

            string singletonCallbackId = CsJsEventHandler.Instance.AddCallbackSingleton("onNetworkStatusChange", _OnNetworkStatusChange);

            JSBridgeExterns.JSB_OnNetworkStatusChange(singletonCallbackId);

        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛马程序员2025

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值