- Published on
Unity 和 JS
背景:
- 最近做的一个虚拟演播室需要有比较友好的交互方式,也就是通过拖拽的方式可视化地调整物体的位置,unity本身作为游戏开发的引擎,开发体验会比较好
- 后端使用unity作为了渲染引擎,如果能够做到渲染效果一致那么体验是最好的
- 在都是依赖unity的情况下在与后端的交互上会变得很简便,不存在坐标系转化之类的问题。
这个文档主要记录unity和js通信的方式,以及动态加载的方式,对应了虚拟演播室中
- 用户输入数值 => unity场景
- unity场景 => 用户输入数值
- 加载任意模型
这三个能力
开始
打包试试
在unity的file中选择webgl平台并进行切换 切换后就可以选择 Developement Build 然后执行 Build And Run 等待完成后就会自动弹出浏览器的页面
就是我们在unity中看到的场景 生成的打包产物中主要包含了这么几个文件 unity相关的逻辑被打包进入了wasm中 两个js文件则负责创建unity运行过程中的实例以及与打包完成后的unity项目之间的交互 看一眼代码 可以看到html文件中执行的js脚本就是创建了一个unityInstance,我们打印一下看看
其中sendMessage就是我们后续和unity交互的手段 Module则包含了一些unity导出的工具函数供我们调用 比如 UTFXXToString
当我们试图在unity运行过程中执行我们的JS脚本并传递一些文本信息的时候可以使用这个方法 https://docs.unity3d.com/Manual/web-interacting-code-example.html
Unity调用JS脚本
我们可以在场景中加一个小球 然后为它添加一个脚本AddForce
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
public class AddForce : MonoBehaviour
{
public float forceAmount = 3f; // 施加力的大小
[DllImport("__Internal")]
private static extern void onSphereClick(string name);
private void Update()
{
// 检测鼠标点击
if (Input.GetMouseButtonDown(0)) // 0代表鼠标左键
{
//...
#if !UNITY_EDITOR && UNITY_WEBGL
onSphereClick(hit.collider.gameObject.name);
#endif
}
}
}
}
}
其中,这行代码用于从外部引入JS函数onSphereClick
在c#中进行调用
[DllImport("__Internal")]
private static extern void onSphereClick(string name);
这行代码用于表示下面onSphereClick这个函数在webgl环境中才执行,否则如果在unity中运行的话由于缺少JS的代码会报错。
#if !UNITY_EDITOR && UNITY_WEBGL
onSphereClick(hit.collider.gameObject.name);
#endif
然后我们需要在unity项目添加一个Plugin文件夹,并增加一个jslib文件 在这个文件中,我们向C#暴露了
onSphereClick
这个方法。这个方法是在浏览器环境中执行的。这里我们看到了上面Module中提到的UTF8ToString
函数mergeInto(LibraryManager.library, {
onSphereClick: function (name) {
window.onSphereClick && window.onSphereClick(UTF8ToString(name));
},
});
下面我们在JS中写个函数
window.onSphereClick = function (p) {
console.log("【debug In JS】onSphereClick: ", p);
};
然后我们执行一下build and run跑一下
可以看到在点击小球的时候控制台打印出了小球名字。 如果不加
UTF8ToString
呢? 得到的就是数字。JS调用Unity脚本
JS调用Unity脚本就可以使用UnityInstance上的sendMessage
sendMessage
接受三个参数
- unity中的物体名称
- 物体上的方法
- 传给方法的参数 举个例子 我们给一个叫
SphereManager
的物体加两个脚本SphereManager2
和SphereManager3
这两个脚本上都有一个叫SphereManagerMethod的函数
//SphereManager3
public void SphereManagerMethod(string param)
{
Debug.Log("【Debug In SphereManager3】param:" + param);
}
//SphereManager2
public void SphereManagerMethod(string param)
{
Debug.Log("【Debug In SphereManager2】param:" + param);
}
这时候我在JS中执行代码
document.querySelector('#changeColor').addEventListener('click', () => {
if (!unityInstance) return
unityInstance.SendMessage('SphereManager', 'ChangeColor', 'Sphere2')
unityInstance.SendMessage('SphereManager', 'SphereManagerMethod', 'some params')
})
可以看到这个物体上的两个脚本的函数都被执行了,并且打印出了参数。 控制台中另一个输出则是在
ChangeColor
函数中执行的,这个函数根据Sphere2
这个参数拿到了对应名字的小球,并把它变成了黑色。如果要传递更复杂的参数,那么就要使用JSON序列化对象后在c#中反序列化进行处理了。
JS代码
document.querySelector('#addObj').addEventListener('click', () => {
if (!unityInstance) return
console.log(document.getElementById('color').value)
unityInstance.SendMessage(
'SphereManager',
'AddSphere',
JSON.stringify(hexToRgbNormalized(document.getElementById('color').value))
)
})
C#代码
public void AddSphere(string param)
{
Color c = JsonUtility.FromJson<Color>(param);
Debug.Log("【Debug In SphereManager】param:" + c);
GameObject newChild = Instantiate(childPrefab);
// 将新实例化的物体设置为parentObject的子物体
newChild.transform.SetParent(gameObject.transform);
// 可选:重置子物体的本地位置、旋转和缩放
newChild.transform.localPosition = Vector3.zero;
newChild.transform.localRotation = Quaternion.identity;
newChild.transform.localScale = Vector3.one;
newChild.GetComponent<Renderer>().material.color = c;
}
在Unity中动态增加模型
unity本身并不支持这个能力,主要依赖了GLTFast这个库 我们在unity中添加一个对象并给他挂上一个脚本GLTFRuntime 在这个脚本中我们通过url引入一个gltf模型并加载到对应的位置上。
public void CreateDuck(float x)
{
GameObject go = new GameObject();
go.transform.SetParent(gameObject.transform);
BoxCollider c = go.AddComponent<BoxCollider>();
c.size = new Vector3(1, 1, 1);
c.center = new Vector3(0, 0.7f, 0);
go.AddComponent<Rigidbody>();
var gltf = go.AddComponent<GLTFast.GltfAsset>();
gltf.Url = "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Duck/glTF/Duck.gltf";
Debug.Log(gltf.transform.position.x + "" + gltf.transform.position.y + "" + gltf.transform.position.z);
gltf.transform.position = new Vector3(x, 0, 0);
DateTime now = DateTime.Now;
#if !UNITY_EDITOR && UNITY_WEBGL
onDuckCreated(now.ToString());
#endif
}
在JS中
document.querySelector('#addDuck').addEventListener('click', () => {
if (!unityInstance) return
unityInstance.SendMessage('GltfManager', 'CreateDuck', 16)
})
效果,能够成功加载模型,并且在开启缓存后能明显提高加载速度
DONE