之前接入微信小游戏本身代码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;
// ...
}
}
}

Unity 华为快游戏JS桥接 实现写日志等功能的更多相关文章

  1. .NET Core的日志[5]:利用TraceSource写日志

    从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和跟踪信息的日志记录.在.NET ...

  2. 利用TraceSource写日志

    利用TraceSource写日志 从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试 ...

  3. Siki_Unity_1-8_使用Unity开发2D游戏_PongGame

    Unity 1-8 使用Unity开发2D游戏 PongGame 任务1:演示 http://pan.baidu.com/s/1pKUHsev; up2i 任务2:案例介绍 创建PongGame,注意 ...

  4. Unity破窗游戏制作(简易版)

    Unity破窗游戏制作(简易版) 参考:"对不起,我选择摸鱼"-<扫雷>小游戏开发实战,算法.源代码,基于Unity3D开发 - 掘金 (juejin.cn) 到&qu ...

  5. kettle作业中的js如何写日志文件

    在kettle作业中JavaScript脚本有时候也扮演非常重要的角色,此时我们希望有一些日志记录.下面是job中JavaScript记录日志的方式. job的js写日志的方法. 得到日志输出实例 o ...

  6. Unity中制作游戏的快照游戏支持玩家拍快照

    Unity中制作游戏的快照游戏支持玩家拍快照 有些游戏支持玩家“拍快照”,也就是将游戏的精彩瞬间以图片的形式记录下来的功能.这个功能比较有趣,而且以后的用途也会很广,为此本节打算介绍:截取矩形区域内游 ...

  7. JS基于时间戳写的浏览访问人数

    Title:JS基于时间戳写的浏览访问人数  --2013-12-23 14:07 <script language="JavaScript"> var timesta ...

  8. 磁盘IO单线程顺序写时最快的,如果多线程写,磁盘的磁头要不断重新寻址,所以写入速度反而会慢

    (1) 读写最好还是不要多线程,硬盘读写的速度有限,单线程时已经满负荷了,多线程又会增加线程之间的切换,会增加时间. 如果想增加读写速度,应该增加硬盘,做raid (2)首先是硬盘的写入是串行的,CP ...

  9. Unity塔防游戏开发

    Unity3D塔防开发流程 配置环境及场景搭建编程语言:C#,略懂些许设计模式,如果不了解设计模式,BUG More开发工具:Unity3D编辑器.Visual Studio编译器开发建议:了解Uni ...

  10. 如何把js的代码写的更加容易维护(一)--面向对象编程

    总是头疼javascript的代码写起来不可维护,那么看看下面的代码: (function (w, $) { var app = { init: function () { var me = this ...

随机推荐

  1. Regardless of the outcome of the Russia-Ukraine war, how can Ukraine avoid paying the weapon fees to the United States after the war?

    According to the agreement between the Ukrainian government and the United States, regardless of the ...

  2. 关于“内网穿透”的一些知识(续3)—— NAT类型判断

    前文: 关于"内网穿透"的一些知识(续2)-- 端口预测 ------------------------------------------- 本文是对前面几篇文章的补充.这里要 ...

  3. cuda的slient模式下的安装

    实验室的师弟要搞cuda编译,不会安装cuda,其实这个主要原因还是服务器上是不允许个人随意安装软件的,尤其是nvidia的那些东西,很容易把整个服务器搞崩掉,虽然实验室的服务器集群我是唯一的一个管理 ...

  4. [POI2015] MOD 题解

    前言 题目链接:洛谷. 题意简述 给定一棵树,求断掉一条边再连上一条边所得的新树直径最小值和最大值,以及相应方案(你可以不进行任何操作,即断掉并连上同一条边). 题目分析 假设我们枚举断掉某一条边,得 ...

  5. 【Playwright+Python】系列教程(七)使用Playwright进行API接口测试

    playwright也是可以做接口测试的,但个人觉得还是没有requests库强大,但和selenium相比的话,略胜一筹,毕竟支持API登录,也就是说可以不用交互直接调用接口操作了. 怎么用 既然是 ...

  6. JavaWeb中的Tomcat,Servlet详解

    JavaWeb JavaWeb技术主要包括服务器技术(后端),如Tomcat,Servlet,JSP等待,以及客户端技术(前端)如HTML,CSS,JavaScript等等 Web服务器 Web服务器 ...

  7. 关于没使用Mybatis 分页,分页sql默认执行count(0) 的问题

    之前的Impl 的方法 :selectFromList(String uid, Integer pageNum, Integer pageSize) 之后的Impl 的方法 :selectFromLi ...

  8. SMU Summer 2024 Contest Round 8

    SMU Summer 2024 Contest Round 8 Product 思路 注意到 \(\prod\limits_{i=1}^NL_i\le10^5\),也就是说 N 不会超过 16,因为 ...

  9. 微服务架构springcloud

    码云地址:https://gitee.com/lpxs/lp-springcloud.git 有问题可以多沟通:136358344@qq.com. 微服务架构 一.服务化简介 服务化的核心就是将传统的 ...

  10. MPTCP(五):MPTCP路径管理工具iproute2/ip-mptcp编译及安装

    简介 ip-mptcp是一个MPTCP路径管理工具,它可以决定哪些链路允许作为MPTCP子流存在 仅对MPTCPv1有效 在支持MPTCPv1的内核中使用man ip-mptcp可以查看相关帮助 gi ...