React中useUnityContext集成Unity WebGL的问题求助
React集成Unity WebGL(useUnityContext)问题解决方案
问题描述
- 切换浏览器标签页后出现运行异常
- 首次加载时React可向Unity发送消息,页面刷新后无法发送
解决方案
1. 修复切换标签页异常问题
浏览器对后台标签页的性能限制会导致Unity WebGL自动暂停/恢复时状态异常,通过手动控制暂停逻辑解决:
步骤1:关闭Unity自动暂停
修改useUnityContext配置,禁用默认的后台自动暂停:
const { unityProvider, // ...其他属性 } = useUnityContext({ loaderUrl: "Kiwi/Build/Kiwi.loader.js", dataUrl: "Kiwi/Build/Kiwi.data", frameworkUrl: "Kiwi/Build/Kiwi.framework.js", codeUrl: "Kiwi/Build/Kiwi.wasm", streamingAssetsUrl: "Kiwi/StreamingAssets", autoPauseOnHidden: false, // 关闭自动暂停 });
步骤2:手动监听页面可见性
添加页面可见性监听,手动控制Unity的暂停与恢复:
useEffect(() => { const handleVisibilityChange = () => { document.hidden ? unityProvider.pause() : unityProvider.resume(); }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => document.removeEventListener("visibilitychange", handleVisibilityChange); }, [unityProvider]);
2. 修复刷新后无法发送消息问题
刷新后消息发送失败通常是Unity未完全加载就执行发送操作,或事件监听依赖缺失导致回调未正确绑定。
步骤1:封装安全的消息发送函数
确保仅在Unity加载完成后发送消息:
const safeSendMessage = useCallback((objectName, methodName, params) => { if (isLoaded && unityProvider?.instance) { sendMessage(objectName, methodName, params); } else { console.warn("Unity未加载完成,无法发送消息"); } }, [isLoaded, unityProvider, sendMessage]);
步骤2:调整消息发送时机
在getRecord中等待Unity加载完成后再发送历史记录:
const getRecord = useCallback(() => { axios.post(API_URL + "/api/auth/joingame", { wallet_address: account }) .then((res) => { setUserId(res.data.userId); axios.get(API_URL + "/api/record/getRecord") .then(records => { if (isLoaded) { safeSendMessage("LoginController", "SetHistory", JSON.stringify(records.data)); } else { // 未加载完成则监听加载事件后发送 const handleLoaded = () => { safeSendMessage("LoginController", "SetHistory", JSON.stringify(records.data)); removeEventListener("loaded", handleLoaded); }; addEventListener("loaded", handleLoaded); } }); }); }, [account, isLoaded, safeSendMessage, addEventListener, removeEventListener]);
步骤3:优化事件监听依赖
移除不必要的依赖,确保事件监听正确绑定:
useEffect(() => { addEventListener("ReactSetRecord", setRecord); addEventListener("DisconnectWallet", disconnectWallet); addEventListener("ConnectWallet", connectWallet); return () => { removeEventListener("ReactSetRecord", setRecord); removeEventListener("ConnectWallet", connectWallet); removeEventListener("DisconnectWallet", disconnectWallet); }; }, [addEventListener, removeEventListener, connectWallet, disconnectWallet, setRecord]);
完整修改后的组件代码
import styled from "styled-components"; import Footer from "./Footer"; import { Unity, useUnityContext } from "react-unity-webgl"; import { useCallback, useEffect, useState } from "react"; import { useEthers } from "@usedapp/core"; import axios from "axios"; import Button from "./Button"; const API_URL = "http://XXXXXXXXXX"; const StyledLicense = styled.div` padding: 0 5rem; width: 100%; min-height: 100vh; display: flex; flex-direction: column; align-items: center; background: var(--bg-02); @media (max-width: 720px) { padding: 2rem; padding-top: 7rem; } `; const Content = styled.div` display: flex; flex-direction: column; max-width: 150em; `; const Loading = styled.h1` font-weight: 700; color: var(--sub); text-transform: uppercase; margin-bottom: 0.3rem; margin-top: 3rem; font-size: 2.4rem; @media only screen and (max-width: 600px) { font-size: 1.6rem; } `; const CyberRunner = () => { const { activateBrowserWallet, deactivate, account } = useEthers(); const [userId, setUserId] = useState(); const { unityProvider, loadingProgression, isLoaded, addEventListener, removeEventListener, requestFullscreen, sendMessage } = useUnityContext({ loaderUrl: "Kiwi/Build/Kiwi.loader.js", dataUrl: "Kiwi/Build/Kiwi.data", frameworkUrl: "Kiwi/Build/Kiwi.framework.js", codeUrl: "Kiwi/Build/Kiwi.wasm", streamingAssetsUrl: "Kiwi/StreamingAssets", autoPauseOnHidden: false, }); const safeSendMessage = useCallback((objectName, methodName, params) => { if (isLoaded && unityProvider?.instance) { sendMessage(objectName, methodName, params); } else { console.warn("Unity未加载完成,无法发送消息"); } }, [isLoaded, unityProvider, sendMessage]); const connectWallet = useCallback(() => { if (!account) { activateBrowserWallet(); } else { getRecord(); } }, [account, getRecord]); const disconnectWallet = useCallback(() => { if (!account) { deactivate(); } else { safeSendMessage("LoginController", "SetDisconnectWallet"); } }, [account, deactivate, safeSendMessage]); const setRecord = useCallback((_distance: number, _kill: number) => { axios.post(API_URL + "/api/record/setRecord", { user_id: userId, distance: _distance, kill: _kill }) .then((res) => { console.log(res); }); }, [userId]); const getRecord = useCallback(() => { axios.post(API_URL + "/api/auth/joingame", { wallet_address: account }) .then((res) => { setUserId(res.data.userId); axios.get(API_URL + "/api/record/getRecord") .then(records => { if (isLoaded) { safeSendMessage("LoginController", "SetHistory", JSON.stringify(records.data)); } else { const handleLoaded = () => { safeSendMessage("LoginController", "SetHistory", JSON.stringify(records.data)); removeEventListener("loaded", handleLoaded); }; addEventListener("loaded", handleLoaded); } }); }); }, [account, isLoaded, safeSendMessage, addEventListener, removeEventListener]); useEffect(() => { const handleVisibilityChange = () => { document.hidden ? unityProvider.pause() : unityProvider.resume(); }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => document.removeEventListener("visibilitychange", handleVisibilityChange); }, [unityProvider]); useEffect(() => { addEventListener("ReactSetRecord", setRecord); addEventListener("DisconnectWallet", disconnectWallet); addEventListener("ConnectWallet", connectWallet); return () => { removeEventListener("ReactSetRecord", setRecord); removeEventListener("ConnectWallet", connectWallet); removeEventListener("DisconnectWallet", disconnectWallet); }; }, [addEventListener, removeEventListener, connectWallet, disconnectWallet, setRecord]); return ( <StyledLicense> {!isLoaded && ( <Loading>Loading Application... {Math.round(loadingProgression * 100)}%</Loading> )} <Content> <Unity unityProvider={unityProvider} style={{ width: 1280, height: 720 }} /> </Content> <Button text="Fullscreen" click={() => requestFullscreen(true)} primary /> <Footer /> </StyledLicense> ); }; export default CyberRunner;
内容的提问来源于stack exchange,提问作者BlackDragon94
相关产品推荐
相关产品推荐

