GameView, quality and overlays

Draw a world, run the frame loop, choose quality and layer overlays.

GameView props

GameView draws a RenderSource (usually a World) and calls onUpdate once a frame. Other props are read live; changing source restarts the view.

GameView props reference
PropBehavior
sourceRequired RenderSource; never pass a disposed world.
onUpdate(dt)Game logic per frame; GameView steps source and overlay after it.
camera / lightMutable Camera and Light objects, read every frame.
background / fogSky and fog color (CSS or tuple); fog density, default 0.
qualityauto (default), high, medium, low or lowest.
pausedStops updates and drawing, cancels gestures, keeps the surface.
styleViewStyle on React Native; CSS properties on the web.
onTap / onLongPress(x, y) in view points; world.pick(x, y) finds the entity.
onSwipe / onDrag / onPinchFlick direction, drag phases and two-finger scale/rotation.
onPrimaryActionKeyboard primary action, such as Start or Play Again.
overlay / onOverlayTouchScreen-space RenderSource drawn on top, and its touch handler.
onStatsGameViewStats once a second.
onErrorSetup and rendering errors; default console.error.

Scene.tsx

React and React Native · shared engine API

Scene.tsx
import { GameView, useWorld } from "@nayan-ui/engine";
export function Scene() {
const world = useWorld(100, (w) => {
w.spawn({ mesh: "plane", scale: [20, 1, 20], color: "#4C9A4C", physics: "fixed" });
w.spawn({ mesh: "sphere", position: [0, 4, 0], color: "#3399FF", physics: "dynamic" });
});
if (!world) return null;
return (
<GameView source={world} style={{ flex: 1 }} background="#87CEEB" quality="auto"
camera={{ eye: [0, 8, 14], target: [0, 0, 0], fov: Math.PI / 3 }}
onTap={(x, y) => {
const hit = world.pick(x, y);
if (hit) world.impulse(hit.entity, [0, 6, 0]);
}} />
);
}

Frames and dt

On iOS and Android frames come from the display clock at 60 Hz: onUpdate steps the game and hands a copy to the engine's render thread, so JavaScript never waits on the GPU. A frame not yet drawn is replaced by a newer one, not queued; the web uses requestAnimationFrame.

dt is capped at 0.1 s and is 0 until the first frame is on screen, so intros don't play unseen. Step the world yourself only for a different dt, such as slow motion.

SlowMotion.tsx

React and React Native · shared engine API

Calling world.update in onUpdate replaces GameView's own step for that frame (updateCount changed).

SlowMotion.tsx
import { GameView, type World } from "@nayan-ui/engine";
export function SlowMotion({ world, slow }: { world: World; slow: boolean }) {
return (
<GameView source={world} style={{ flex: 1 }}
onUpdate={(dt) => {
if (slow) world.update(dt * 0.25); // GameView skips its step this frame
}} />
);
}

Render quality

auto starts at high and steps down only while drawing can't keep up: after one second under ~33 fps, or a few seconds missing ~50 fps. It drops shadow detail first, then MSAA, then resolution, and climbs back after about ten smooth seconds.

Quality can't fix a slow game loop; that shows in updateMs, not renderMs. The chosen level survives pauses and only restarts with a new source.

Render quality reference
LevelPixel ratio capMSAAShadow map
high34×2048, soft
medium34×1024, soft
low3off1024, hard
lowest2off512, hard

Battery saver

React and React Native · shared engine API

Battery saver
import { GameView, type RenderQualitySetting, type World } from "@nayan-ui/engine";
export function Game({ world, saver }: { world: World; saver: boolean }) {
const quality: RenderQualitySetting = saver ? "low" : "auto";
return <GameView source={world} quality={quality} style={{ flex: 1 }} />;
}

Frame statistics

onStats receives GameViewStats once a second. On iOS and Android fps counts frames actually presented and renderMs is the render thread's time per frame.

Frame statistics reference
FieldMeaning
fpsFrames per second (presented frames on native).
updateMsMean milliseconds in onUpdate and the step.
renderMsMean milliseconds drawing (render thread on native, CPU side on web).
qualityLevel drawn with; with auto, where it settled so far.
backendmetal, vulkan or gl on native; webgpu or gl on the web.

Log rendering cost

React and React Native · shared engine API

Log rendering cost
import { GameView, type GameViewStats, type World } from "@nayan-ui/engine";
const log = ({ fps, updateMs, renderMs, quality, backend }: GameViewStats) =>
console.log(`${fps.toFixed(0)} fps, update ${updateMs.toFixed(1)} ms, render ${renderMs.toFixed(1)} ms, ${quality}, ${backend}`);
export function Profiled({ world }: { world: World }) {
return <GameView source={world} onStats={log} onError={(e) => console.error(e.message)} />;
}

Overlays

overlay draws a second World in view points from the top-left, y up (visible y ≤ 0) and z within -100..100. It is unlit, always in front and stepped after onUpdate like the scene.

Return true from onOverlayTouch("start") to claim a touch: its moves and end then go to the overlay, never to scene gestures. "cancel" arrives when the system takes the touch.

Overlay button

React and React Native · shared engine API

Overlay button
import { GameView, type World } from "@nayan-ui/engine";
export function WithHud({ world, hud, onStart }: { world: World; hud: World; onStart: () => void }) {
const inside = (x: number, y: number) => x >= 20 && x <= 140 && y >= 20 && y <= 70;
return (
<GameView source={world} overlay={hud} style={{ flex: 1 }}
onOverlayTouch={(phase, x, y) => {
if (phase === "start") return inside(x, y);
if (phase === "end" && inside(x, y)) onStart();
return true;
}} />
);
}

Pause

paused stops onUpdate, stepping and drawing and cancels gestures while the surface stays mounted. The view also suspends itself when the app goes to the background.

Pause behind a settings sheet

React and React Native · shared engine API

Pause behind a settings sheet
import { GameView, type World } from "@nayan-ui/engine";
export function Paused({ world, settingsOpen }: { world: World; settingsOpen: boolean }) {
return <GameView source={world} paused={settingsOpen} style={{ flex: 1 }} />;
}