GENEARE BY SD
Published on

Unity 和 JS

背景:

  1. 最近做的一个虚拟演播室需要有比较友好的交互方式,也就是通过拖拽的方式可视化地调整物体的位置,unity本身作为游戏开发的引擎,开发体验会比较好
  2. 后端使用unity作为了渲染引擎,如果能够做到渲染效果一致那么体验是最好的
  3. 在都是依赖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的物体加两个脚本SphereManager2SphereManager3 图片 这两个脚本上都有一个叫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