首页 > 其他分享 >Unity 华为快游戏JS桥接 实现写日志等功能

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

时间:2024-10-18 16:36:23浏览次数:6  
标签:function return string JSB 桥接 JS callbackId Unity qg

之前接入微信小游戏本身代码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()遍历打印qgral两个对象,键值对的文本是能对的上的。直接调用又有些内容不同,比如有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"] = "您有新短消息, 请注意查收";
            modalOptions["callbackId"] = callbackId;
            JSBridgeExterns.JSB_ShowModal(modalOptions.ToJson());

            // 挂网络状态监听
            string singletonCallbackId = CsJsEventHandler.Instance.AddCallbackSingleton("onNetworkStatusChange", _OnNetworkStatusChange);
            JSBridgeExterns.JSB_OnNetworkStatusChange(singletonCallbackId);
        }

        private static void _OnNetworkStatusChange(int code, string msg, string dataStr)
        {
            string json = dataStr;
            // ...
        }
    }
}

标签:function,return,string,JSB,桥接,JS,callbackId,Unity,qg
From: https://www.cnblogs.com/lunoctis/p/18474551

相关文章

  • Unity网络
    基础概念Unity网络Unity发送网络请求UnityWebRequestUnityWebRequest提供了一个模块化系统,用于构成HTTP请求和处理HTTP响应。UnityWebRequest系统的主要目标是让Unity游戏与Web浏览器后端进行交互。基本支持所有版本的Unity编辑器和某些播放器WebGliOS和An......
  • js 中断循环的几种方式
    在JavaScript中,`forEach`方法是用来遍历数组的元素,并且它不能被中断或退出。如果你需要在满足某个条件时中断循环,你可以考虑使用其他循环结构,比如`for`循环、`for...of`循环或者`while`循环。下面是一些替代方案:1.**使用`for`循环**:```javascriptletarray=......
  • js 通过链接下载文件
    通过url单文件下载,并且解决a.download修改文件名不生效问题 /***通过url单文件下载,并且解决a.download修改文件名不生效问题*@param{String}url文件url*@param{String}filename文件自定义名称*@returnsPromise*/exportfunctiondownloadFileByUr......
  • Nuxt.js 应用中的 app:templates 事件钩子详解
    title:Nuxt.js应用中的app:templates事件钩子详解date:2024/10/18updated:2024/10/18author:cmdragonexcerpt:app:templates是Nuxt.js中一个强大的生命周期钩子,它在NuxtApp生成过程中调用。这一钩子允许开发者自定义、修改或添加新文件到构建目录,提供了极大的......
  • Unity Apple Vision Pro 保姆级开发教程-环境配置、导入 PolySpatial 案例、程序发布
    视频教程Unity环境配置、导入PolySpatial案例、程序发布到设备教程说明这期教程我将介绍使用Unity开发AppleVisionPro应用所需要的Unity环境配置,以及如何导入PolySpatial样例场景、将Unity程序打包到头显中运行。开发前期准备(软硬件要求,开启visionpro......
  • Unity Apple Vision Pro 保姆级开发教程-准备阶段
    视频教程:UnityPolySpatial开发AppleVisionPro教程,三十分钟快速了解开发AppleVisionPro使用原生开发和unity开发有什么区别如果你的项目需要充分利用AppleVisionPro的独特功能、追求最佳的性能表现,或者针对特定于VisionOS的开发场景,原生开发可能是更好的......
  • 根据json转idea 自带Http Client脚本
    importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;publicclassApiDocConverter{publicstaticvoidmain(String[]args)throwsException{Stringjson="{\n"+&quo......
  • Unity生命周期
    基础概念游戏的本质就是一个死循环,每一次循环都会处理游戏逻辑并更新一次游戏画面之所以能看到画面在动,是因为切换画面速度达到一定速度时人眼就会认为画面是动态且流畅的一帧就是执行了一次循环,Unity底层已经封装好了这个死循环我们只需要利用Unity的生命周期函数的规则来执行游......
  • 基于Node.js+vue核酸检测管理系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于核酸检测管理系统的研究,现有研究主要以核酸检测技术本身为主,如检测准确性、检测速度等方面的研究。专门针对核酸检测管理系统的全面研究较少。在国......
  • 基于Node.js+vue防疫监测系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于防疫监测系统的研究,现有研究主要集中在宏观层面的疫情数据统计与分析,专门针对包含小区信息、用户以及防疫信息等多方面综合防疫监测系统的研究较少......