作为一名Unity开发者,当我们需要将游戏发布到Web平台时,WebGL与JavaScript的交互就成了必须掌握的技能。很多Unity开发者对前端技术不太熟悉,这给WebGL项目的开发带来了不小的挑战。本文将重点解决Unity与JavaScript的双向通信问题,通过一个简单的"点击移动物体并返回提示"的案例,带你快速上手这项技术。
在实际项目中,我们经常遇到这样的需求:网页上的按钮要控制游戏中的物体移动,或者游戏中的某个状态变化需要反馈到网页上。比如玩家点击网页按钮让角色跳跃,或者角色升级时在网页上弹出提示。这些都需要Unity和JavaScript之间建立可靠的通信机制。
注意:本文使用的Application.ExternalCall方法虽然已被标记为过时,但对于初学者来说更容易理解和调试。我会在后续内容中解释为什么暂时选择这种方法,以及未来如何迁移到推荐的.jslib方案。
首先确保你的Unity项目已正确设置为WebGL平台:
由于浏览器安全限制,WebGL内容必须通过服务器访问,不能直接打开本地文件。推荐以下几种本地服务器方案:
Node.js + http-server:
bash复制npm install -g http-server
http-server [你的项目文件夹] -p 8080
Python内置服务器:
bash复制python -m http.server 8000
Unity推荐的WebGL模板:
在Player Settings > Resolution and Presentation中,选择合适的WebGL模板
项目文件结构应如下所示:
code复制/WebGLProject
/Unity
index.html
Build/
TemplateData/
/js
custom.js
在Unity中创建一个C#脚本,声明可以被JavaScript调用的方法:
csharp复制using UnityEngine;
public class WebGLCommunication : MonoBehaviour {
public void MoveObject(float x, float y) {
transform.position = new Vector3(x, y, 0);
Debug.Log($"物体已移动到({x}, {y})");
}
}
然后在HTML中添加调用按钮和JavaScript代码:
html复制<button onclick="moveUnityObject()">移动物体</button>
<script>
function moveUnityObject() {
// 获取Unity实例
const unityInstance = GetUnity();
// 调用Unity中的方法
unityInstance.SendMessage(
"WebGLCommunication", // 游戏对象名称
"MoveObject", // 方法名称
"1.5,2.0" // 参数(多个参数用逗号分隔)
);
}
</script>
JavaScript向Unity传递参数时需要注意:
改进后的参数处理示例:
javascript复制function sendComplexData() {
const data = {
position: { x: 1.5, y: 2.0 },
color: "#FF0000",
speed: 2.5
};
unityInstance.SendMessage(
"WebGLController",
"ProcessData",
JSON.stringify(data)
);
}
对应的Unity C#方法:
csharp复制public void ProcessData(string jsonData) {
var data = JsonUtility.FromJson<DynamicData>(jsonData);
// 处理数据...
}
[Serializable]
private class DynamicData {
public Vector2 position;
public string color;
public float speed;
}
虽然这个方法已被标记为过时,但它的简单性使其仍适合初学者:
csharp复制// Unity中调用JavaScript
void OnCollisionEnter(Collision collision) {
Application.ExternalCall("showAlert", "物体发生了碰撞!");
}
对应的HTML/JavaScript:
html复制<script>
function showAlert(message) {
// 注意:现代浏览器可能会拦截alert弹窗
console.log("收到Unity消息:", message);
// alert(message); // 不推荐,可能被拦截
}
</script>
由于现代浏览器(特别是Edge和Chrome)会静默拦截通过Unity触发的alert,推荐以下替代方案:
控制台输出:
javascript复制function logToConsole(message) {
console.log("[Unity消息] " + message);
}
自定义DOM通知:
javascript复制function showNotification(message) {
const div = document.createElement('div');
div.className = 'unity-notification';
div.textContent = message;
document.body.appendChild(div);
setTimeout(() => div.remove(), 3000);
}
使用jslib的推荐方案(稍后介绍)
在Unity项目的Assets/Plugins/WebGL目录下创建communication.jslib:
javascript复制mergeInto(LibraryManager.library, {
ShowAlert: function(message) {
window.dispatchEvent(new CustomEvent('unity-message', {
detail: UTF8ToString(message)
}));
},
GetBrowserInfo: function(output, maxLength) {
const info = navigator.userAgent;
stringToUTF8(info, output, maxLength);
}
});
csharp复制[DllImport("__Internal")]
private static extern void ShowAlert(string message);
[DllImport("__Internal")]
private static extern void GetBrowserInfo(StringBuilder output, int maxLength);
void Start() {
#if UNITY_WEBGL && !UNITY_EDITOR
var browserInfo = new System.Text.StringBuilder(256);
GetBrowserInfo(browserInfo, 256);
Debug.Log("浏览器信息: " + browserInfo.ToString());
#endif
}
csharp复制public class ObjectController : MonoBehaviour {
public void MoveToPosition(float x, float y) {
StartCoroutine(SmoothMove(new Vector3(x, y, 0)));
}
IEnumerator SmoothMove(Vector3 target) {
while (Vector3.Distance(transform.position, target) > 0.1f) {
transform.position = Vector3.Lerp(transform.position, target, 0.1f);
yield return null;
}
// 移动完成后调用JavaScript
#if UNITY_WEBGL && !UNITY_EDITOR
Application.ExternalCall("onMoveComplete", transform.position.x, transform.position.y);
#endif
}
}
html复制<div class="controls">
<input type="number" id="posX" placeholder="X坐标" value="0">
<input type="number" id="posY" placeholder="Y坐标" value="0">
<button onclick="moveObject()">移动物体</button>
</div>
<script>
function moveObject() {
const x = parseFloat(document.getElementById('posX').value);
const y = parseFloat(document.getElementById('posY').value);
unityInstance.SendMessage(
"ObjectController",
"MoveToPosition",
`${x},${y}`
);
}
function onMoveComplete(x, y) {
const notification = document.createElement('div');
notification.className = 'move-notification';
notification.innerHTML = `
<p>物体已移动到 (${x.toFixed(2)}, ${y.toFixed(2)})</p>
<small>${new Date().toLocaleTimeString()}</small>
`;
document.querySelector('.notifications').appendChild(notification);
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => notification.remove(), 500);
}, 3000);
}
</script>
添加CSS使界面更友好:
css复制.controls {
margin: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 8px;
}
.move-notification {
background: rgba(0, 150, 255, 0.9);
color: white;
padding: 10px;
margin: 5px;
border-radius: 4px;
transition: opacity 0.5s;
}
.fade-out {
opacity: 0;
}
Unity方法未被调用:
JavaScript错误:
跨域问题:
Edge/Chrome静默拦截:
Safari限制:
移动浏览器:
与Vue/React等框架配合的示例:
javascript复制// Vue组件示例
export default {
methods: {
callUnityMethod() {
if (window.unityInstance) {
window.unityInstance.SendMessage(...);
}
}
},
mounted() {
window.addEventListener('unity-message', (e) => {
this.unityMessages.push(e.detail);
});
}
}
建立双向数据通道:
Unity到JavaScript:
csharp复制void Update() {
#if UNITY_WEBGL && !UNITY_EDITOR
Application.ExternalCall("updatePlayerData",
transform.position.x,
transform.position.y,
health,
score);
#endif
}
JavaScript到Unity:
javascript复制setInterval(() => {
const performanceData = {
fps: calculateFPS(),
memory: performance.memory.usedJSHeapSize
};
unityInstance.SendMessage("PerformanceMonitor", "UpdateStats",
JSON.stringify(performanceData));
}, 1000);
使用条件编译处理不同平台:
csharp复制public void ShowMessage(string msg) {
#if UNITY_WEBGL && !UNITY_EDITOR
Application.ExternalCall("showMessage", msg);
#else
Debug.Log(msg);
#endif
}
压缩优化:
CDN部署:
HTTPS要求:
缓存问题:
html复制<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
文件哈希:
回滚机制:
识别现有调用:
创建对应jslib函数:
javascript复制mergeInto(LibraryManager.library, {
ShowMessage: function(str) {
const message = UTF8ToString(str);
console.log("[Unity] " + message);
}
});
更新Unity代码:
csharp复制[DllImport("__Internal")]
private static extern void ShowMessage(string message);
// 替换原来的Application.ExternalCall
ShowMessage("Hello from Unity");
字符串处理:
异步操作:
类型映射:
在实际项目中,我建议先在新功能中使用jslib方案,然后逐步迁移旧代码。这样可以在保证项目正常运行的同时完成技术升级。