真机打包Webgl平台
报错
实测发现真机打包PC平台,可以直接调用mqtt基于u3d的类库,进行连接、订阅、发送等操作。
而真机打包Webgl平台,发现连接Mqtt会报错,无法连接,具体报错如下
Exception connecting to the broker , ex:System.Net.Sockets.SocketException (0x80004005): Success
at System.Net.Sockets.Socket..ctor (System.Net.Sockets.AddressFamily addressFamily, System.Net.Sockets.SocketType socketType, System.Net.Sockets.ProtocolType protocolType) [0x00000] in <00000000000000000000000000000000>:0
b50e31cb-83d9-46d1-85b8-b4f05b6e2d2b:3:35047
InvalidOperationException: Operation is not valid due to the current state of the object.
at System.Runtime.InteropServices.SafeHandle.DangerousAddRef (System.Boolean& success) [0x00000] in <00000000000000000000000000000000>:0
错位位置,mqtt基于u3d的类库中的MqttClient.cs 449 => Connect(xxx)
解决方案
根据查阅资料得知,Unity打包Webgl想要调用Mqtt通信,无法使用上述基于u3d的类库,需要在h5中具体实现mqtt连接、订阅、发送、取消订阅、断开方法,而Unity中通过.jslib文件调用前者h5各个方法
具体流程
在Unity编辑器预先创建xxx.jslib类库,声明mqtt的连接、订阅、发送、取消订阅、断开方法,在各个方法体中调用指定方法,在打包后的h5页面(index.html)中具体实现前者的指定方法(连接、订阅、发送、取消订阅、断开逻辑),h5完成具体逻辑后回调给Unity内部,其中会涉及到Webgl平台Unity与js通信。
解决方案参考
Unity3D打包WebGL并使用MQTT(一)_unity3d webgl-CSDN博客
UnityWeb端和Js互调(MQTT通讯篇)unity与web端通信北特的博客-CSDN博客
Unity 导出WebGL 嵌入网页并通信_unity导出webgl-CSDN博客
Webgl平台Unity与js通信
Interaction with browser scripting - Unity 手册
h5(index.html)调用Unity代码
1.在Unity目录下导入依赖包 WebGLSupport
2.打包webgl包后,手动修改index.html文件,使用api,unityInstance.SendMessage('场景对象名', '对象所挂载的脚本的方法名', '方法参数可空');
Unity调用h5代码(index.html)
1.在Unity目录下新建并配置 Assets/Plugins/xxx.jslab文件,在文件中创建方法调用h5中函数
2.在unity脚本中调用xxx.jslab文件中的方法
3.打包webgl包后,手动修改index.html文件,实现/xxx.jslab文件中的方法
具体可参考WebglUseJslibToJs项目
Unity客户端配置
创建 xxx.jslib
xxx.jslib放置在Plugins 文件夹下,Plugins可以不放置在Assets根目录下
mergeInto(LibraryManager.library, {
//Hello: function () {
// window.alert("测试Unity的Webgl平台通过H5调用MQTT通信");
//},
Jslib_Connect: function (host, port, clientId, username, password, destination) {
mqttConnect(UTF8ToString(host), UTF8ToString(port), UTF8ToString(clientId), UTF8ToString(username), UTF8ToString(password), UTF8ToString(destination));
},
Jslib_Subscribe: function (topic) {
mqttSubscribe(UTF8ToString(topic))
},
Jslib_Publish: function (topic, payload) {
mqttSend(UTF8ToString(topic), UTF8ToString(payload))
},
Jslib_Unsubscribe: function(topic) {
mqttUnsubscribe(UTF8ToString(topic));
},
Jslib_Disconnect: function() {
mqttDisconnect();
}
});
导入WebGLSupport到Unity工程目录
用于index.html中基于js使用unity API调用unity中的方法
API:unityInstance.SendMessage('场景对象名', '对象所挂载的脚本的方法名', '方法参数可空');
调用xxx.jslib方法
[DllImport("__Internal")]
private static extern void Jslib_Connect(string host, string port, string clientId, string username, string password, string destination);
[DllImport("__Internal")]
private static extern void Jslib_Subscribe(string topic);
[DllImport("__Internal")]
private static extern void Jslib_Publish(string topic, string payload);
[DllImport("__Internal")]
private static extern void Jslib_Unsubscribe(string topic);
[DllImport("__Internal")]
private static extern void Jslib_Disconnect();
private MqttRecvMsgCallback m_RecvMsgCallback;
public void Connect(string clientIP, int clientPort, string clientId, string username, string password, string destination = "Unity_Test_Destination")
{
Jslib_Connect(clientIP, clientPort.ToString(), clientId, username, password, destination);
}
/// <summary>
/// 订阅消息,为Unity提供
/// </summary>
/// <param name="topics"></param>
public void Subscribe(params string[] topics)
{
foreach (string topic in topics)
{
Jslib_Subscribe(topic);
}
}
/// <summary>
/// 取消订消息,为Unity提供
/// </summary>
public void Unsubscribe(params string[] topics)
{
foreach (string topic in topics)
{
Jslib_Unsubscribe(topic);
}
}
/// <summary>
/// 监听订阅过的消息,为Unity提供
/// </summary>
/// <param name="topic"></param>
/// <param name="msg"></param>
public void AddListenerSubscribe(MqttRecvMsgCallback mqttRecvMsgCallback)
{
m_RecvMsgCallback += mqttRecvMsgCallback;
}
/// <summary>
/// 监听订阅过的消息,为Unity提供
/// </summary>
/// <param name="topic"></param>
/// <param name="msg"></param>
public void RemoveListenerSubscribe(MqttRecvMsgCallback mqttRecvMsgCallback)
{
m_RecvMsgCallback -= mqttRecvMsgCallback;
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="topic"></param>
/// <param name="msg"></param>
public void Publish(string topic, string msg)
{
Jslib_Publish(topic, msg);
}
/// <summary>
/// 断开连接
/// </summary>
public void DisConnect()
{
Jslib_Disconnect();
}
/// <summary>
/// 接收订阅的消息,为外部h5提供api,晚点测试使用private能否调用
/// </summary>
/// <param name="topic"></param>
/// <param name="msg"></param>
void RecvMsg(string jsonStr)
{
string topic = jsonStr.Split('|')[0];
string msg = jsonStr.Split('|')[1];
Debug.Log("[Unity] RecvMsg,topic:" + topic + ",msg:" + msg);
m_RecvMsgCallback?.Invoke(topic, msg);
}
/// <summary>
/// mqtt连接成功后回调
/// </summary>
public void ConnSuc()
{
Debug.Log("[Unity] ConnSuc");
NetworkMqtt.GetInstance. ConnSucCallbackHandle?.Invoke();
}
打包后配置
修改index.html
需要在打包webgl包后,手动修改index.html文件,添加xxx.jslib中调用的方法
在<body>标签中新增以下代码
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script>
//#region MQTT 连接、断开、订阅监听消息、发送消息
/*
MQTT类库https://unpkg.com/mqtt/dist/mqtt.min.js
API文档:https://www.emqx.com/zh/blog/mqtt-js-tutorial
*/
var client
var gameUnityInstance
var subscribeIdObj = {}
const options = {
Name: 'Name_Test_MQTT_WebGl',
connectTimeout: 40000,
clientId: '',
username: '',
password: '',
cleanSession: false,
keepAlive: 60
}
//ArrayBuffer二进制转字符串
function ab2str(message) {
const utf8decoder = new TextDecoder()
if (!message?.length) return null
const type = Object.prototype.toString.call(message)
let res = null
if (type === '[object Uint8Array]') {
// @ts-ignore
res = utf8decoder.decode(new Uint8Array(message))
} else {
res = JSON.stringify(message)
}
if (Object.prototype.toString.call(res) !== '[object String]') {
res = JSON.parse(res)
}
return res
}
//连接
function mqttConnect(host, port, clientId, username, password, destination) {
let url = 'ws://' + host + ':' + port + '/mqtt'
//创建一个client实例
options.clientId = clientId
options.username = username
options.password = password
//Test
url = 'ws://10.5.24.27:8083/mqtt'
client = mqtt.connect(url, options)
log("尝试链接mqtt IP:" + host + ",port:" + port + ",username:" + options.username + "clientId: " + options.clientId);
var firstConnSuc = false;
client.on('connect', function (connack) {
if (!firstConnSuc) {
firstConnSuc = true
log("mqtt首次连接成功! username :" + options.username + "clientId: " + options.clientId, true);
gameUnityInstance.SendMessage('[UnityObjectForWebglMsg]', 'ConnSuc')
}
})
}
//订阅监听消息
function mqttSubscribe(topic) {
log("尝试订阅监听消息 topic:" + topic);
//topic: 可传入一个字符串,或者一个字符串数组,也可以是一个 topic 对象,{'test1': {qos: 0}, 'test2': {qos: 1}}
//options: 可选值,订阅 Topic 时的配置信息,主要是填写订阅的 Topic 的 QoS 等级的
//callback: 订阅 Topic 后的回调函数,参数为 error 和 granted,当订阅失败时 error 参数才存在, granted 是一个 { topic, qos } 的数组,其中 topic 是一个被订阅的主题,qos 是 Topic 是被授予的 QoS 等级
subscribeIdObj[topic] = client.subscribe(topic, { qos: 0 }, function (error, granted) {
if (error) {
error("订阅监听消息失败 error:" + error)
} else {
if (granted.length > 0) {
log(`订阅监听消息成功 topic: ${granted[0].topic}`)
}
else {
error("订阅监听消息失败,当前标题已订阅 topic:" + topic)
}
}
})
client.on('message', function (_topic, message) {
//二进制ArrayBuffer消息转字符串
var msgStr = ab2str(message)
log("接收到消息,msg:" + msgStr);
gameUnityInstance.SendMessage('[UnityObjectForWebglMsg]', 'RecvMsg', _topic + "|" + message)
})
}
//发送消息
function publish(topic, payload) {
log("尝试发送消息 , topic:" + topic + ", msg:" + payload);
// 发布消息
client.publish(topic, payload, { qos: 0, retain: false }, function (error) {
if (error) {
error("发送消息失败 error:" + error + ',topic:' + topic + ',msg:' + payload)
} else {
log('发送消息成功 topic:' + topic + ',msg:' + payload)
}
})
}
//取消消息订阅
function mqttUnsubscribe(topic) {
log('尝试取消消息订阅 topic:' + topic)
client.unsubscribe(topic, function (error) {
if (error) {
log('取消消息订阅失败 topic:' + topic)
} else {
log('取消消息订阅成功 topic:' + topic)
}
})
}
// 断开连接
function mqttDisconnect() {
log("尝试断开mqtt链接");
client.end(true, null, () => {
log('已断开mqtt链接')
})
}
//封装js日志打印
function log(msg, isShow = true) {
if (isShow) {
console.log("[html]:" + msg);
}
}
function error(msg, isShow = true) {
if (isShow) {
console.error("[html]:" + msg);
}
}
//#endregion MQTT
</script>
找到此代码段,只需新增一行代码
var script = document.createElement("script");
script.src = loaderUrl;
script.onload = () => {
createUnityInstance(canvas, config, (progress) => {
progressBarFull.style.width = 100 * progress + "%";
}).then((unityInstance) => {
gameUnityInstance = unityInstance //当前代码端仅仅新增此行,当前代码端仅仅新增此行,当前代码端仅仅新增此行
loadingBar.style.display = "none";
fullscreenButton.onclick = () => {
unityInstance.SetFullscreen(1);
};
}).catch((message) => {
alert(message);
});
};
document.body.appendChild(script);
注意
1.xxx.jslib父目录Plugins可以不放在Assets/根目录下