Unity—Js和Unity互相调用
Unity项目可以打包成WebGl,打包后的项目文件:
Build中是打包后的Js代码;
Index.html是web项目的入口,里面可以调整web的自适应,也可以拿去嵌套;
TemplateData是打包时候选的webGl模板;
web端游戏可能Unity只负责做游戏部分,而官网由另外的团队制作,之间就需要Unity和Js代码之间的相互调用;
Unity调用JavaScript
声明一下,这里说的都是Unity和外部JS代码的互相调用,项目内调用有其他方法;
老版本提供一个过时的方法:
1.在WebGL项目中的Index.html中添加要调用的JS方法
function Unity2JavaScript() { alert("UnityToWeb") }
2.Unity中调用
Application.ExternalCall("Unity2JavaScript");
//可以有参数,没有返回值
//Application.ExternalCall("Unity2JavaScript",a,10,"aaaa");
Unity建议使用的方法:
1.在Plugins文件夹中,创建后缀为.jslib的文件,在其中写需要调用的js代码
mergeInto(LibraryManager.library, {
Hello: function () {
window.alert("Hello, world!");
},
HelloString: function (str) {
window.alert(Pointer_stringify(str));
},
PrintFloatArray: function (array, size) {
for(var i = 0; i < size; i++)
console.log(HEAPF32[(array >> 2) + size]);
},
AddNumbers: function (x, y) {
return x + y;
},
StringReturnValueFunction: function () {
var returnStr = "bla";
var buffer = _malloc(lengthBytesUTF8(returnStr) + 1);
writeStringToMemory(returnStr, buffer);
return buffer;
},
BindWebGLTexture: function (texture) {
GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[texture]);
},
});
2.Unity中调用——__Internal.jslib
using UnityEngine;
using System.Runtime.InteropServices;
public class NewBehaviourScript : MonoBehaviour {
[DllImport("__Internal")]
private static extern void Hello();
[DllImport("__Internal")]
private static extern void HelloString(string str);
[DllImport("__Internal")]
private static extern void PrintFloatArray(float[] array, int size);
[DllImport("__Internal")]
private static extern int AddNumbers(int x, int y);
[DllImport("__Internal")]
private static extern string StringReturnValueFunction();
[DllImport("__Internal")]
private static extern void BindWebGLTexture(int texture);
void Start() {
Hello();
HelloString("This is a string.");
float[] myArray = new float[10];
PrintFloatArray(myArray, myArray.Length);
int result = AddNumbers(5, 7);
Debug.Log(result);
Debug.Log(StringReturnValueFunction());
var texture = new Texture2D(0, 0, TextureFormat.ARGB32, false);
BindWebGLTexture(texture.GetNativeTextureID());
}
}
新方法多了可以返回值,但是每次修改必须打包才能测试;
JavaScript调用Unity
这里面有巨坑,天坑,人都坑傻了!!!
官方文档中有这几行字
恰好我用的2020版本的Unity;
主要使用这个API——
SendMessage("游戏对象名","方法名","参数"); 这个和参数和lua调用c#差不多了,但是怎么调用这个api就很玄学了;
首先如果你调用这个方法需要在Unity的资源已经加载完成才可以,这个好解决,js加个button;
<button Type="button" onclick="TestSend()">WebToUnity</button>
其次在调用这个方法前需要先实例化UnityInstance变量;
var gameInstance = null;
script.onload = () => {
gameInstance = createUnityInstance(document.querySelector("#unity-canvas"), {
dataUrl: "Build/Test.data",
frameworkUrl: "Build/Test.framework.js",
codeUrl: "Build/Test.wasm",
streamingAssetsUrl: "StreamingAssets",
companyName: "DefaultCompany",
productName: "UnityToWeb",
productVersion: "0.1",
});
};
//以上的参数都可以在unity的playersetting界面找到;
最后调用时要在then中用lamda表达式
function TestSend() {
gameInstance.then((unityInstance) => {
unityInstance.SendMessage("Canvas","OnLogin","dqwreqweraf");
});
}
完整的index.html
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Unity WebGL Player | UnityToWeb</title>
<link rel="shortcut icon" href="TemplateData/favicon.ico">
<link rel="stylesheet" href="TemplateData/style.css">
</head>
<body>
<div id="unity-container" class="unity-desktop">
<button Type="button" onclick="TestSend()">WebToUnity</button>
<canvas id="unity-canvas" width=960 height=600></canvas>
<div id="unity-loading-bar">
<div id="unity-logo"></div>
<div id="unity-progress-bar-empty">
<div id="unity-progress-bar-full"></div>
</div>
</div>
<div id="unity-mobile-warning">
WebGL builds are not supported on mobile devices.
</div>
<div id="unity-footer">
<div id="unity-webgl-logo"></div>
<div id="unity-fullscreen-button"></div>
<div id="unity-build-title">UnityToWeb</div>
</div>
</div>
<script>
var buildUrl = "Build";
var loaderUrl = buildUrl + "/Test.loader.js";
var config = {
dataUrl: buildUrl + "/Test.data",
frameworkUrl: buildUrl + "/Test.framework.js",
codeUrl: buildUrl + "/Test.wasm",
streamingAssetsUrl: "StreamingAssets",
companyName: "DefaultCompany",
productName: "UnityToWeb",
productVersion: "0.1",
};
var container = document.querySelector("#unity-container");
var canvas = document.querySelector("#unity-canvas");
var loadingBar = document.querySelector("#unity-loading-bar");
var progressBarFull = document.querySelector("#unity-progress-bar-full");
var fullscreenButton = document.querySelector("#unity-fullscreen-button");
var mobileWarning = document.querySelector("#unity-mobile-warning");
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
container.className = "unity-mobile";
// Avoid draining fillrate performance on mobile devices,
// and default/override low DPI mode on mobile browsers.
config.devicePixelRatio = 1;
mobileWarning.style.display = "block";
setTimeout(() => {
mobileWarning.style.display = "none";
}, 5000);
} else {
canvas.style.width = "960px";
canvas.style.height = "600px";
}
loadingBar.style.display = "block";
var script = document.createElement("script");
script.src = loaderUrl;
var gameInstance = null;
script.onload = () => {
gameInstance = createUnityInstance(document.querySelector("#unity-canvas"), {
dataUrl: "Build/Test.data",
frameworkUrl: "Build/Test.framework.js",
codeUrl: "Build/Test.wasm",
streamingAssetsUrl: "StreamingAssets",
companyName: "DefaultCompany",
productName: "UnityToWeb",
productVersion: "0.1",
});
};
function TestSend() {
gameInstance.then((unityInstance) => {
unityInstance.SendMessage("Canvas","OnLogin","dqwreqweraf");
});
}
document.body.appendChild(script);
</script>
</body>
</html>
更新新坑
Unity和JavaScript代码互相调用会有很严重的时序问题,比如需要在Unity场景加载完成开始调用的登录等方法;
不知道Unity打包成WebGL底层怎么处理的,我在单例的构造里写初始化会被多次执行,如果在这里调用js方法后果就是反复横跳导致栈内存爆炸;
解决办法是在Unity生命周期函数中调用(Awake或者Start)js函数,目的是通知加载完成,然后这个js函数中给Unity传登录信息(这里的登录信息可以是js从浏览器cookies获取的,unity做不到);
更新2022.3版本Unity
index.html创建canvas的方法变更为createUnityInstance返回promise,获取unityinstance需要在then中
var gameInstance = null
var script = document.createElement("script");
script.src = loaderUrl;
script.onload = () => {
createUnityInstance(canvas, config, (progress) => {
progressBarFull.style.width = 100 * progress + "%";
}).then((unityInstance) => {
gameInstance = unityInstance;
loadingBar.style.display = "none";
fullscreenButton.onclick = () => {
unityInstance.SetFullscreen(1);
};
}).catch((message) => {
alert(message);
});
};
function SendMsgToUnity() {
gameInstance.SendMessage("GameEntry","SendMsg","Send To Unity");
}
Life is too short for so much sorrow.