Unity模块嵌入React项目

3,911 阅读1分钟

本文旨在说明怎样将Unity项目打包成WebGL资源引入React项目并实现通信

本文使用 Unity2020.3

本文使用 create-react-app 创建React项目,React版本为16.4.0 本文使用 react-unity-webgl 插件的8.x版本 github.com/jeffreylant…

Unity打包为WebGL

image.png

  1. 在Unity的主菜单中选择File -> Build Settings... 打开打包弹窗👇

image.png

Unity 默认不安装打包为 WebGL 的插件,需要自己在 Unity Hub 按照提示安装插件

  1. 点击👆弹窗左下角的 Player Settings... 按钮,打开打包配置弹窗,这一步最重要的就是将 Publishing Settings 配置中的 Compression Format 压缩方式改为 Disabled 不压缩,其他配置可参考文档进行配置也可直接使用默认条件

image.png

image.png

  1. 点击 Build Settings 弹窗右下角的 Build / Build And Run 按钮,区别在于 Build And Run 按钮不仅会打包还会自动开启一个本地服务器运行打包好的 WebGl 项目。这一步需要注意的是,点击打包后需要选择打包项目存放的文件夹,一定要放在本项目的根目录下,即与Assets文件夹同级。
  2. 打包得到index.html入口文件和Build、TemplateData两个文件夹,index.html对后续引入无用。

image.png

React项目引入

本项目使用 create-react-app 创建一个单页的React项目,官方文档:zh-hans.reactjs.org/docs/create…

本项目使用第三方插件 react-unity-webgl 在 React 项目中加载由 Unity 项目打包得到的 WebGL 资源,官方文档:github.com/jeffreylant…

插件安装

$ npm install react-unity-webgl@8.x  # For Unity 2020 and 2021 (Current)

使用npm安装,因本项目使用的Unity版本为2020,因此需安装8.x版本

模块引入与显示

  1. 将打包得到的 Build、TemplateData 文件放在React项目的public文件夹下

image.png

  1. 在组件中使用资源,示例代码:
// App.js

import React from "react";
import Unity, { UnityContext } from "react-unity-webgl";

const unityContext = new UnityContext({
  loaderUrl: "Build/beidou3Dweb.loader.js", // public下目录
  dataUrl: "Build/beidou3Dweb.data",
  frameworkUrl: "Build/beidou3Dweb.framework.js",
  codeUrl: "Build/beidou3Dweb.wasm",
});

function App() {
  // 一定要给Unity组件设置width和height属性,否则Canvas将无限增大最终导致浏览器卡死
  return <Unity style={{'width': '100%', 'height': '100%'}} unityContext={unityContext} />;
}

export default App;

此时页面中会显示一个Canvas,Canvas中即是Unity项目的Game页面。

React到Unity的通信

通信是通过调用 UnityContext 实例的 send 方法实现的,该 send 方法可以指定 Unity 项目中需要调用的 GameObject 名称以及该 GameObject 上绑定的需要执行函数的名称,此外还可以传递一个 number / string / boolean 类型的参数,注意只能传一个(或不传)。在 React 触发 send 后,Unity 中的对应函数将执行,从而完成一次通信。

  • React中的代码:
// React App.js

import React, { useState, useEffect }  from "react";
import Unity, { UnityContext } from "react-unity-webgl";
import { Button } from 'antd';
import './index.less';

const unityContext = new UnityContext({
  loaderUrl: "Build/beidou3Dweb.loader.js",
  dataUrl: "Build/beidou3Dweb.data",
  frameworkUrl: "Build/beidou3Dweb.framework.js",
  codeUrl: "Build/beidou3Dweb.wasm",
});

function App() {
  function spawnEnemies() {
    unityContext.send("ConnectObject", "SpawnEnemies", "Some Message"); // 调用send函数
    // ConnectObject为游戏对象名称(全局唯一)
    // SpawnEnemies为ConnectObject上的回调函数名
    // "Some Message"为需要传递的参数,此处为string类型

  return (
    <div className="container">
      <Unity style={{'width': '100%', 'height': '100%'}} unityContext={unityContext} />
      <Button type="primary" onClick={spawnEnemies}>To Unity</Button>
    </div>
  );
}

export default App;
  • Unity中的代码:
// Unity中的 CummunicateTest.cs 文件,一般放在Script文件夹下
// 绑定在名为 ConnectObject 的 Panel 游戏对象上

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CummunicateTest : MonoBehaviour
{
    public Text text; // 绑定为Panel下的一个Text

    public void SpawnEnemies (string amount) { // 该方法必须是公共的
      text.text = amount; // 根据传参改变Text的文字
    }
}

image.png

Unity到React的通信

通信是通过在Unity中的C#文件引入并调用中间层.jslib文件中的函数触发事件并携带需要传递的参数,而后在React中的UnityContext实例上通过on监听该事件并执行回调函数拿到传递参数实现的。

需要注意的是.jslib文件必须放在 Assets/Plugins/WebGL 目录下,如果默认没有这个目录就自己建!

image.png

关于怎么创建.jslib,使用mac的同学直接改后缀即可,使用Windows的同学就自己加油Google吧~

  • Unity 中的文件
// MyPlugin.jslib

mergeInto(LibraryManager.library, {
  GameOver: function (userName, score) {
    ReactUnityWebGL.GameOver(Pointer_stringify(userName), score);
  },
});
// Unity中的 CummunicateTest.cs 文件,一般放在Script文件夹下
// 绑定在名为 ConnectObject 的 Panel 游戏对象上

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;


public class CummunicateTest : MonoBehaviour
{
    public Button btn;
    public Text text;

    [DllImport("__Internal")] // 引入并声明.jslib中定义的GameOver函数
    private static extern void GameOver(string userName, int score);

    // Start is called before the first frame update
    void Start()
    {
        btn.onClick.AddListener(() => { // 点击按钮时调用GameOver函数传参
            GameOver("xinghui", 100);
        });
    }

    public void SpawnEnemies (string amount) {
      text.text = amount;
    }
}
  • React中的文件
import React, { useState, useEffect }  from "react";
import Unity, { UnityContext } from "react-unity-webgl";
import { Button } from 'antd';
import './index.less';
import fullScreenIconImage from '../../images/threeShow/fullScreenIcon.png';

const unityContext = new UnityContext({
  loaderUrl: "Build/beidou3Dweb.loader.js",
  dataUrl: "Build/beidou3Dweb.data",
  frameworkUrl: "Build/beidou3Dweb.framework.js",
  codeUrl: "Build/beidou3Dweb.wasm",
});

function App() {
  const [isGameOver, setIsGameOver] = useState(false);
  const [userName, setUserName] = useState("");
  const [score, setScore] = useState(0);

  useEffect(function () {
    unityContext.on("GameOver", function (userName, score) { // 监听GameOver事件
      setIsGameOver(true);
      setUserName(userName);
      setScore(score);
    });
  }, []);

  function spawnEnemies() {
    unityContext.send("ConnectObject", "SpawnEnemies", "Some Message");
  }

  function setUnityFullScreen() {
    unityContext.setFullscreen(true); // 设置Unity的Canvas全屏展示
  }

  return (
    <div className="three-show-container">
      <img className="full-screen-icon" src={fullScreenIconImage} alt="全屏" onClick={setUnityFullScreen} />
      <Unity style={{'width': '100%', 'height': '100%'}} unityContext={unityContext} />
      {isGameOver === true && <p>{`From Unity ${userName} ${score}`}</p>}
      <Button type="primary" onClick={spawnEnemies}>To Unity</Button>
    </div>
  );
}

export default App;

最终实现效果是点击React中的按钮,Unity中的文字变为“Some Message”;点击Unity中的按钮,React中显示“From Unity xinghui 100”,双向通信实现。

其他插件功能可参考官方文档github.com/jeffreylant…尝试。