
Security News
New React Server Components Vulnerabilities: DoS and Source Code Exposure
New DoS and source code exposure bugs in React Server Components and Next.js: what’s affected and how to update safely.
@futureverse/react-unity-viewer
Advanced tools
Futureverse React Unity Viewer provides a modern solution for embedding Unity WebGL builds in your React Application while providing advanced APIs for two way communication and interaction between Unity and React.
When bringing your Unity Application to the web, you might need to communicate with Components on a webpage, build interactive interfaces or might want to implement functionality using Web APIs which Unity does not expose. Combining Unity with React is a great way to achieve these goals. Unity Viewer provides a modern solution for embedding Unity WebGL builds in your React Application while providing advanced APIs for two way communication and interaction between Unity and React.
Please use the MessageHandler script available on Futureverse - Asset Viewer package for Unity in order to receive messages from React.
Get started by installing Unity Viewer using the Node Package Manager or Yarn in your JavaScript or TypeScript React project.
npm install @futureverse/react-unity-viewer
# or
yarn install @futureverse/react-unity-viewer
# or
pnpm install @futureverse/react-unity-viewer
The following examples are also available on /examples folder.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
Open http://localhost:3000 with your browser to see the result.
Import the Unity Viewer Styles to your project using the command below on your css file.
@import url("./node_modules/@futureverse/react-unity-viewer/dist/style.css");
In order to use the unity viewer you will need the context <UnityViewerContextProvider> and the viewer <UnityViewer>, viewer requires the context to be able to control the state of the application such as loading, open and close states and also to receive react messages.
const {
openUnity,
closeUnity,
isLoaded,
isReady,
loadingProgression,
dispatchPlayerData,
} = useUnityViewerContext();
import {
UnityViewerContextProvider,
UnityViewer,
} from "@futureverse/react-unity-viewer";
<UnityViewerContextProvider>
<UnityViewer unityHostURL="UNITY_HOST_URL" unityFileName="UNITY_FILE_NAME" />
</UnityViewerContextProvider>;
by default the application won't load the unity player straight away, to load the unity player you need to use the openUnity hook.
import {
UnityViewerContextProvider,
UnityViewer,
UnityViewerControls,
UnityViewerPreloader,
useUnityViewerContext,
} from "@futureverse/react-unity-viewer";
const CustomViewerStarter = () => {
const { openUnity } = useUnityViewerContext();
const handleLoadUnity = () => {
openUnity({
initialMessageFunctionName: "INITIAL_FUNCTION_NAME_CALLED_ON_UNITY",
initialMessageData: "INITIAL_FUNCTION_DATA_CALLED_ON_UNITY",
});
};
return (
<div className="absolute inset-0 flex items-center justify-center">
<button
onClick={handleLoadUnity}
className="bg-black text-white rounded-md px-3 py-1.5 border border-white"
>
Click here to load Unity Application
</button>
</div>
);
};
const UnityViewerWithCustomUnityStarter = () => {
return (
<main className="absolute inset-0 bg-gray-50">
<UnityViewerContextProvider>
<CustomViewerStarter />
<UnityViewer
unityHostURL="https://fv-goblins.s3.us-west-2.amazonaws.com/gag-viewer-dev"
unityFileName="gag"
/>
<UnityViewerControls />
<UnityViewerPreloader />
</UnityViewerContextProvider>
</main>
);
};
import {
UnityViewerContextProvider,
UnityViewerStarter,
UnityViewer,
UnityViewerControls,
UnityViewerPreloader,
} from "@futureverse/react-unity-viewer";
<UnityViewerContextProvider>
<UnityViewerStarter
initialMessageFunctionName="INITIAL_FUNCTION_NAME_CALLED_ON_UNITY"
initialMessageData="INITIAL_FUNCTION_DATA_CALLED_ON_UNITY"
/>
<UnityViewer unityHostURL="UNITY_HOST_URL" unityFileName="UNITY_FILE_NAME" />
<UnityViewerControls />
<UnityViewerPreloader />
</UnityViewerContextProvider>;
import {
UnityViewerContextProvider,
UnityViewerStarter,
UnityViewer,
UnityViewerControls,
UnityViewerPreloader,
} from "@futureverse/react-unity-viewer";
<UnityViewerContextProvider>
<UnityViewerStarter
initialMessageFunctionName="update_model"
initialMessageData={{
URL: "https://fv-goblins.s3.us-west-2.amazonaws.com/goblins/000-34_050-18__010-5078_1V0-4922__0C1-0/glb/000-34_050-18__010-5078_1V0-4922__0C1-0.glb",
}}
/>
<UnityViewer
unityHostURL="https://fv-goblins.s3.us-west-2.amazonaws.com/gag-viewer-dev"
unityFileName="gag"
/>
<UnityViewerControls />
<UnityViewerPreloader />
</UnityViewerContextProvider>;
import { IconFutureVerse } from "@/icons/IconFutureVerse";
import { IconGodsAndGoblins } from "@/icons/IconGodsAndGoblins";
import {
UnityViewerContextProvider,
UnityViewerStarter,
UnityViewer,
UnityViewerControls,
useUnityViewerContext,
} from "@futureverse/react-unity-viewer";
const CustomViewerPreloader = () => {
const { isUnityOpened, isLoaded, loadingProgression } =
useUnityViewerContext();
return (
<>
{isUnityOpened && (
<>
{!isLoaded && (
<div className="absolute inset-0 flex items-center justify-center bg-black text-xs text-white">
<div className="flex w-full max-w-[500px] flex-col gap-10 px-10">
<div className="flex justify-center">
<IconGodsAndGoblins />
</div>
<div className="w-full overflow-hidden">
<div className="relative w-full overflow-hidden">
<div className="relative h-[3px] w-full bg-white opacity-10"></div>
<div
className="absolute left-0 top-0 w-full h-full transform bg-white"
style={{
width: `${loadingProgression * 100}%`,
}}
></div>
</div>
</div>
<div className="flex justify-center">
<a
href="https://www.futureverse.com/"
target="_blank"
className="flex items-center gap-1.5"
rel="noreferrer"
>
Powered by
<IconFutureVerse />
</a>
</div>
</div>
</div>
)}
</>
)}
</>
);
};
const UnityViewerWithCustomPreloader = () => {
return (
<main className="absolute inset-0 bg-gray-50">
<UnityViewerContextProvider>
<UnityViewerStarter
initialMessageFunctionName="update_model"
initialMessageData={{
URL: "https://fv-goblins.s3.us-west-2.amazonaws.com/goblins/000-34_050-18__010-5078_1V0-4922__0C1-0/glb/000-34_050-18__010-5078_1V0-4922__0C1-0.glb",
}}
/>
<UnityViewer
unityHostURL="https://fv-goblins.s3.us-west-2.amazonaws.com/gag-viewer-dev"
unityFileName="gag"
/>
<UnityViewerControls />
<CustomViewerPreloader />
</UnityViewerContextProvider>
</main>
);
};
import {
UnityViewerContextProvider,
UnityViewerStarter,
UnityViewer,
UnityViewerPreloader,
useUnityViewerContext,
} from "@futureverse/react-unity-viewer";
const CustomViewerControls = () => {
const { isReady, closeUnity, dispatchPlayerData } = useUnityViewerContext();
const handleFullBody = () => {
dispatchPlayerData("camera_composition_default", {});
};
const handleUpperBody = () => {
dispatchPlayerData("camera_composition_key", {
CompositionKey: "head",
});
};
return (
<>
{isReady && (
<div className="absolute left-0 right-0 bottom-0 p-2">
<div className="flex items-center justify-center gap-2 text-sm">
<button
className="bg-black text-white rounded-md px-3 py-1.5 border border-white"
onClick={closeUnity}
>
Exit
</button>
<button
className="bg-black text-white rounded-md px-3 py-1.5 border border-white"
onClick={handleFullBody}
>
Full body
</button>
<button
className="bg-black text-white rounded-md px-3 py-1.5 border border-white"
onClick={handleUpperBody}
>
Upper body
</button>
</div>
</div>
)}
</>
);
};
const UnityViewerWithCustomControls = () => {
return (
<main className="absolute inset-0 bg-gray-50">
<UnityViewerContextProvider>
<UnityViewerStarter
initialMessageFunctionName="update_model"
initialMessageData={{
URL: "https://fv-goblins.s3.us-west-2.amazonaws.com/goblins/000-34_050-18__010-5078_1V0-4922__0C1-0/glb/000-34_050-18__010-5078_1V0-4922__0C1-0.glb",
}}
/>
<UnityViewer
unityHostURL="https://fv-goblins.s3.us-west-2.amazonaws.com/gag-viewer-dev"
unityFileName="gag"
/>
<CustomViewerControls />
<UnityViewerPreloader />
</UnityViewerContextProvider>
</main>
);
};
import { useEffect } from "react";
import {
UnityViewerContextProvider,
UnityViewerStarter,
UnityViewer,
UnityViewerPreloader,
useUnityViewerContext,
} from "@futureverse/react-unity-viewer";
const CustomMousePosition = () => {
const { dispatchPlayerData } = useUnityViewerContext();
useEffect(() => {
const onMouseMove = (event: MouseEvent) => {
const target = event.target as HTMLElement;
const localX = event.clientX - target.offsetLeft;
const localY = event.clientY - target.offsetTop;
const localMousePosition = { x: localX, y: localY };
const globalMousePosition = { x: event.clientX, y: event.clientY };
dispatchPlayerData(`mouse_move_position`, {
globalMousePosition,
localMousePosition,
});
};
window.addEventListener("mousemove", onMouseMove);
return () => {
window.removeEventListener("mousemove", onMouseMove);
};
}, [dispatchPlayerData]);
return <></>;
};
const CustomPanel = () => {
const { isReady } = useUnityViewerContext();
return <>{isReady && <CustomMousePosition />}</>;
};
const UnityViewerWithCustomControls = () => {
return (
<main className="absolute inset-0 bg-gray-50">
<UnityViewerContextProvider>
<UnityViewerStarter
initialMessageFunctionName="update_model"
initialMessageData={{
URL: "https://fv-goblins.s3.us-west-2.amazonaws.com/goblins/000-34_050-18__010-5078_1V0-4922__0C1-0/glb/000-34_050-18__010-5078_1V0-4922__0C1-0.glb",
}}
/>
<UnityViewer
unityHostURL="https://fv-goblins.s3.us-west-2.amazonaws.com/gag-viewer-dev"
unityFileName="gag"
/>
<CustomPanel />
<UnityViewerPreloader />
</UnityViewerContextProvider>
</main>
);
};
import { useEffect, useRef } from "react";
import {
UnityViewerContextProvider,
UnityViewerStarter,
UnityViewer,
UnityViewerPreloader,
useUnityViewerContext,
} from "@futureverse/react-unity-viewer";
const CustomContainerPosition = ({
children,
}: {
children: React.ReactNode | React.ReactNode[],
}) => {
const containerRef = useRef < HTMLDivElement > null;
const { dispatchPlayerData } = useUnityViewerContext();
useEffect(() => {
let animationRequest: number;
const getContainerRect = () => {
return containerRef.current?.getBoundingClientRect();
};
const onRender = () => {
const containerRect = getContainerRect();
if (containerRect) {
dispatchPlayerData(`div_position`, {
x: Math.round(containerRect.x) * window.devicePixelRatio,
y: Math.round(containerRect.y) * window.devicePixelRatio,
width: Math.round(containerRect.width) * window.devicePixelRatio,
height: Math.round(containerRect.height) * window.devicePixelRatio,
});
}
animationRequest = requestAnimationFrame(onRender);
};
onRender();
window.addEventListener("resize", onRender);
window.addEventListener("scroll", onRender);
return () => {
window.removeEventListener("resize", onRender);
window.removeEventListener("scroll", onRender);
cancelAnimationFrame(animationRequest);
};
}, [dispatchPlayerData]);
return (
<>
<div
ref={containerRef}
style={{
position: "relative",
width: "100%",
}}
>
<div>{children}</div>
</div>
</>
);
};
const CustomPanel = () => {
const { isReady } = useUnityViewerContext();
return (
<>
{isReady && (
<div className="absolute inset-0 overflow-auto h-[2000px] gap-6 flex flex-col justify-between">
<div className="bg-red-500 opacity-20 h-[200px]"></div>
<div className="bg-red-500 opacity-20 h-[200px]"></div>
<div className="bg-red-500 opacity-20 h-[200px]"></div>
<div className="bg-red-500 opacity-20 h-[200px]"></div>
<div className="bg-red-500 opacity-20 h-[200px]"></div>
<CustomContainerPosition>
<div className="bg-red-500 opacity-20 h-[200px]"></div>
</CustomContainerPosition>
</div>
)}
</>
);
};
const UnityViewerWithCustomControls = () => {
return (
<main className="absolute inset-0 bg-gray-50">
<UnityViewerContextProvider>
<UnityViewerStarter
initialMessageFunctionName="update_model"
initialMessageData={{
URL: "https://fv-goblins.s3.us-west-2.amazonaws.com/goblins/000-34_050-18__010-5078_1V0-4922__0C1-0/glb/000-34_050-18__010-5078_1V0-4922__0C1-0.glb",
}}
/>
<UnityViewer
unityHostURL="https://fv-goblins.s3.us-west-2.amazonaws.com/gag-viewer-dev"
unityFileName="gag"
/>
<UnityViewerPreloader />
<CustomPanel />
</UnityViewerContextProvider>
</main>
);
};
The following tasks are available for npm run or yarn:
dev: Run Vite in host mode for a local development environment (not included in production build)watch: Run Vite in watch mode to detect changes to files during developmentbuild: Run Vite to build a production release distributableVite features a host mode to enables development with real time HMR updates directly from the library via the start script.
To test your library from within an app:
npm link or yarn link command to register the packagenpm link "@futureverse/react-unity-viewer" or yarn link "@futureverse/react-unity-viewer" command to use the library inside your app during developmentOnce development completes, unlink both your library and test app projects.
npm link "@futureverse/react-unity-viewer" or yarn link "@futureverse/react-unity-viewer" command to use the library inside your app during developmentnpm unlink or yarn unlink command to register the packageUpdate your package.json to next version number, and remember to tag a release.
Once ready to submit your package to the NPM Registry, execute the following task via npm (or yarn):
npm run build — Build the packageAssure the proper npm login:
npm login
Submit your package to the registry:
npm publish --access public
https://www.npmjs.com/package/@futureverse/react-unity-viewer
FAQs
Futureverse React Unity Viewer provides a modern solution for embedding Unity WebGL builds in your React Application while providing advanced APIs for two way communication and interaction between Unity and React.
We found that @futureverse/react-unity-viewer demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
New DoS and source code exposure bugs in React Server Components and Next.js: what’s affected and how to update safely.

Security News
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.

Security News
GitHub has revoked npm classic tokens for publishing; maintainers must migrate, but OpenJS warns OIDC trusted publishing still has risky gaps for critical projects.