文章目录
前言
2025年6月10日
Unity 2022.3.54f1
nginx-1.28.0
Node.js v22.16.0
“react-unity-webgl”: “^9.8.0”,
验证将 unity webgl 嵌入 React 前端界面的可行性和效果,以及双向通信逻辑
过程中找到的网址:
为 Web 构建设置 Nginx 服务器配置:https://docs.unity3d.com/2022.3/Documentation/Manual/web-server-config-nginx.html
Unity WebGL 嵌入前端网页并通信:https://developer.unity.cn/projects/66545214edbc2a001ddb2a0b
Unity WebGL和JavaScript通信:https://developer.unity.cn/projects/66b76790edbc2a001ef504af
Unity模块嵌入React项目:https://juejin.cn/post/7028100780364152846
前端插件 React Unity WebGL:https://react-unity-webgl.dev/
webgl 输入中文解决方案, IME for Unity WebGL:https://github.com/kou-yeung/WebGLInput
一些经验
1、unity 打包 webgl ,compression format 在开发过程中选择 disabled ,最终发布时再换为 gzip 。(本人是使用 trae 开发的 react 项目,使用 npm start 启动时 gzip 的服务配置不知道怎么改,尴尬
2、gzip 配置需修改 nginx 服务器文件(路径:D:\nginx-1.28.0\conf\nginx.conf),参考 unity 官方给出的配置指南
为 Web 构建设置 Nginx 服务器配置:https://docs.unity3d.com/2022.3/Documentation/Manual/web-server-config-nginx.html
3、配置好 nginx 后,react unity webgl 中填入的的 loaderUrl、dataUrl、frameworkUrl、codeUrl ,在 disabled 和 gzip 切换时是不需要更改的。(Brotli 没试,不清楚
const { unityProvider, isLoaded, loadingProgression, sendMessage,addEventListener,removeEventListener } = useUnityContext({
dataUrl: "Build/build_webgl.data",// build_webgl.data.gz 不用加.gz后缀
frameworkUrl: "Build/build_webgl.framework.js",// build_webgl.framework.js.gz 不用加.gz后缀
loaderUrl: "Build/build_webgl.loader.js",
codeUrl: "Build/build_webgl.wasm",// build_webgl.wasm.gz 不用加.gz后缀
});
4、React 向 Unity 通信时,sendMessage 的目标对象不能是子物体
一、Unity WebGL 部分
将 js.jslib 置于 Plugins 目录,Bridge.cs 挂载至场景对象。
点击 Message 按钮发送消息至 JS ,ReceiveMessage 接收 JS 消息并通过 alert 显示。
js.jslib, 置于 Plugins 目录
mergeInto(LibraryManager.library, {
PrintString: function (str) {
window.alert(UTF8ToString(str));
},
Message: function (str) {
window.dispatchReactUnityEvent("Message",UTF8ToString(str));
},
});
Bridge.cs,挂载至场景对象
public class Bridge: MonoBehaviour
{
[DllImport("__Internal")]
private static extern void PrintString(string str);
[DllImport("__Internal")]
private static extern void Message(string str);
private void OnGUI()
{
if (GUILayout.Button("Message"))
{
// 发送消息到JavaScript
Message($"UnityToJS:[{DateTime.Now}]");
}
}
/// <summary>
/// 接收来自JavaScript的消息,注意传参类型
/// </summary>
/// <param name="message"></param>
public void ReceiveMessage(string message)
{
PrintString($"UnityReceiveFromJS:[{message}]");
}
}
二、Nginx 部分,配置完成后,可先单独部署 webgl 测试是否能够顺利访问网页
关键配置项 gzip_static on;
为 Web 构建设置 Nginx 服务器配置:https://docs.unity3d.com/2022.3/Documentation/Manual/web-server-config-nginx.html
nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
server {
listen 18000;
server_name localhost;
gzip_static on;# react-unity-webgl 能加载 gzip 的关键
location / {
root html/build;
index index.html;
}
location ~ .+\.(data|symbols\.json)\.gz$ {
gzip off; # Do not attempt dynamic gzip compression on an already compressed file
add_header Content-Encoding gzip;
default_type application/gzip;
}
location ~ .+\.js\.gz$ {
gzip off; # Do not attempt dynamic gzip compression on an already compressed file
add_header Content-Encoding gzip; # The correct MIME type here would be application/octet-stream, but due to Safari bug https://bugs.webkit.org/show_bug.cgi?id=247421, it's preferable to use MIME Type application/gzip instead.
default_type application/javascript;
}
location ~ .+\.wasm\.gz$ {
gzip off; # Do not attempt dynamic gzip compression on an already compressed file
add_header Content-Encoding gzip;
# Enable streaming WebAssembly compilation by specifying the correct MIME type for
# Wasm files.
default_type application/wasm;
}
}
}
三、React 部分,将 webgl 嵌入前端界面展示+双向交互
官方示例:https://react-unity-webgl.dev/docs/advanced-examples/loading-overlay
主动发消息,From React to Unity:https://react-unity-webgl.dev/docs/api/send-message
监听消息,From Unity to React:https://react-unity-webgl.dev/docs/api/event-system
App.tsx
import './App.css';
import {useEffect,useCallback} from "react";
import { Unity, useUnityContext } from "react-unity-webgl";
function App() {
const { unityProvider, isLoaded, loadingProgression, sendMessage,addEventListener,removeEventListener } = useUnityContext({
dataUrl: "Build/build_webgl.data",// build_webgl.data.gz 不用加.gz后缀
frameworkUrl: "Build/build_webgl.framework.js",// build_webgl.framework.js.gz 不用加.gz后缀
loaderUrl: "Build/build_webgl.loader.js",
codeUrl: "Build/build_webgl.wasm",// build_webgl.wasm.gz 不用加.gz后缀
});
const handleMessage = useCallback((msg: string) => {
console.log(msg);
}, []);
useEffect(() => {
addEventListener("Message", (msg: any) => handleMessage(msg.toString()));
return () => {
removeEventListener("Message", (msg: any) => handleMessage(msg.toString()));
};
}, [addEventListener, removeEventListener, handleMessage]);
const loadingPercentage = Math.round(loadingProgression * 100);
function handleClick() {
sendMessage("Receive", "ReceiveMessage", "send-message");
}
return (
<div className="container">
{isLoaded === false && (
<div className="loading-overlay">
<p>Loading... ({loadingPercentage}%)</p>
</div>
)}
<Unity className="unity" unityProvider={unityProvider} />
<button onClick={handleClick} className="test-button">Send Message to Unity</button>
</div>
);
}
export default App;
App.css
.container {
width: 100vw;
height: 100vh;
margin: 0;
border: none;
}
.unity {
width: 100%;
height: 100%;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
color: white;
font-size: 2em;
}
.test-button {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
z-index: 100;
}
index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
index.css
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}