Installation and platform setup

Install the package and set up Expo, bare React Native or a browser host.

Start from the template

The CLI creates a Bun workspace with an Expo app, a Vite app and the Car Racing example they share, already set up as below. Skip to your own scene, or install the package into an existing app instead.

Create a project

Terminal / configuration

Create a project
npx @nayan-ui/cli new my-game -t games
cd my-game
bun install
bun run web # or: bun run ios / bun run android (development build)

Install the package

One package serves both hosts; react is a peer dependency, plus react-native for apps. expo-asset is optional and only needed to load require(...) files in Expo.

React (web)

React · browser

React (web)
npm install @nayan-ui/engine react react-dom

React Native (Expo)

React Native · iOS and Android

React Native (Expo)
npm install @nayan-ui/engine
npx expo install expo-asset expo-build-properties

Expo: bundle 3D models

Metro doesn't bundle glTF files by default. Add glb and gltf to its asset extensions in metro.config.js.

metro.config.js

React Native · iOS and Android

metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push("glb", "gltf");
module.exports = config;

Expo: Android settings

Android needs minSdkVersion 26 (Android 8.0), set through expo-build-properties. Haptics need the VIBRATE permission.

app.json

React Native · iOS and Android

app.json
{
"expo": {
"android": { "permissions": ["android.permission.VIBRATE"] },
"plugins": [["expo-build-properties", { "android": { "minSdkVersion": 26 } }]]
}
}

Make a development build

The engine is native code, so it runs in a development or store build; Expo Go doesn't include it. Prebuilt libraries ship in the package, so no Rust toolchain is needed, but rebuild the app after installing or upgrading the engine.

Build and run

React Native · iOS and Android

Build and run
npx expo run:ios
npx expo run:android
# or in the cloud:
eas build --profile development

Bare React Native

Bare apps autolink the module. Install the pods, set minSdkVersion 26 in Gradle and add the VIBRATE permission.

Bare setup

React Native · iOS and Android

Bare setup
npm install @nayan-ui/engine
cd ios && pod install && cd ..
# android/build.gradle: minSdkVersion = 26
# android/app/src/main/AndroidManifest.xml:
# <uses-permission android:name="android.permission.VIBRATE" />

Guard builds without the engine

isEngineAvailable is false when the native module isn't in the build, as in Expo Go or a build made before installing. Check it before opening a game screen; useWorld also reports it through onError.

EngineGate.tsx

React Native · iOS and Android

EngineGate.tsx
import type { ReactNode } from "react";
import { Text } from "react-native";
import { isEngineAvailable } from "@nayan-ui/engine";
export function EngineGate({ children }: { children: ReactNode }) {
if (!isEngineAvailable) return <Text>This screen needs a development build.</Text>;
return <>{children}</>;
}

TypeScript and exports

Types ship with the package. Bundlers pick the build by export condition: browser for the web, react-native (or react-native-strict-api) for Metro, so web apps never install React Native.

Import @nayan-ui/engine/web to force the browser build, for example in server-rendered code. TypeScript follows the same conditions with moduleResolution bundler.

tsconfig.json

React Native · iOS and Android

For a React Native app; web apps leave out customConditions.

tsconfig.json
{
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"moduleResolution": "bundler",
"customConditions": ["react-native"]
}
}

Browser hosting

Serve the page over HTTPS or localhost so WebGPU and AudioWorklet are available; WebGL2 is the fallback, and no cross-origin isolation headers are needed. useWorld loads the WebAssembly core; if you create worlds yourself, await warmUpEngine() first.

Keep index.js, engine_core_bg.wasm and audio-worklet.js together (Vite emits them for you) and serve the wasm as application/wasm. Assets on another origin need CORS, and a strict CSP must allow WebAssembly.

Check the wasm response

Terminal / configuration

Use the wasm URL from the browser's Network tab.

Check the wasm response
curl -sI https://your-site.example/assets/engine_core_bg.wasm | grep -i content-type
# content-type: application/wasm (not text/html from a fallback page)