Add rigid bodies, sensors, layers, impulses and raycasts.
dynamic bodies move under gravity and contact, kinematic ones are moved by you and push dynamic ones, and fixed ones never move. The collider matches the mesh (models get a box) and is sized from the entity when the body is created.
Later scale changes don't resize it, so set explicit sizes for gameplay volumes. set(entity, { physics: null }) removes the body.
Floor, ball and paddle
React and React Native · shared engine API
import type { World } from "@nayan-ui/engine";export function setupPhysics(world: World) {world.spawn({ mesh: "cube", position: [0, -0.5, 0], scale: [30, 1, 30], physics: "fixed" });const ball = world.spawn({ mesh: "sphere", position: [0, 5, 0], physics: { type: "dynamic", bounce: 0.6 } });const paddle = world.spawn({ mesh: "cube", position: [0, 0.5, 4], scale: [3, 0.5, 0.5], physics: "kinematic" });return { ball, paddle };}
physics takes a BodyType string for defaults, or PhysicsOptions.
| Option | Behavior |
|---|---|
| type | dynamic, kinematic or fixed. |
| shape | ball, box, cylinder, capsule or cone; default matches the mesh. |
| radius / height | Ball, cylinder, capsule or cone radius and total height. |
| size | Full box size Vec3; default the entity's size. |
| layer / mask | Layer bits (default 1) and layers it meets (default all). |
| sensor | Reports touches without pushing. |
| friction / bounce / density | Defaults 0.5, 0 (0..1) and 1 (mass per volume). |
| drag / angularDrag | Linear and spin damping; defaults 0 and 0.05. |
| gravityScale | Multiplier on world gravity; default 1. |
| upright | Never tumbles (characters). |
| ccd | Stops fast, small bodies passing through walls. |
| planar | 2D: stays in XY and spins around z; camera looks down -z. |
Character capsule
React and React Native · shared engine API
import type { PhysicsOptions, World } from "@nayan-ui/engine";const body: PhysicsOptions = {type: "dynamic", shape: "capsule", radius: 0.4, height: 1.8,upright: true, ccd: true, friction: 0.2, drag: 0.5,};export function addHero(world: World) {return world.spawn({ mesh: "capsule", position: [0, 2, 0], scale: [0.8, 1.8, 0.8], physics: body });}
groundVelocity sets horizontal [x, z] speed and keeps the vertical one, so a steered body still falls. impulse(entity, vec) is a one-off push for dynamic bodies: jumps, knockback.
Steer and jump
React and React Native · shared engine API
Call steer from onUpdate with joystick input.
import type { Entity, World } from "@nayan-ui/engine";export function steer(world: World, player: Entity, x: number, z: number) {world.set(player, { groundVelocity: [x * 6, z * 6] });}export function jump(world: World, player: Entity) {world.impulse(player, [0, 5, 0]);}
A sensor reports touches but never pushes: pickups and trigger zones. Two colliders interact when either one's mask includes the other's layer.
Pickup on its own layer
React and React Native · shared engine API
import type { World } from "@nayan-ui/engine";export const PLAYER = 2;export const PICKUP = 4;export function addPickup(world: World, x: number, z: number) {return world.spawn({mesh: "sphere", position: [x, 1, z], color: "yellow",physics: { type: "fixed", shape: "ball", radius: 0.7, sensor: true, layer: PICKUP, mask: PLAYER },});}
forEachCollision in onUpdate visits the touches that started or ended during the last step. CollisionInfo (started, sensor, speed) is reused, and either entity may already be despawned by an earlier callback.
Collect pickups
React and React Native · shared engine API
Call from onUpdate; GameView steps the world right after.
import type { Entity, World } from "@nayan-ui/engine";export function collect(world: World, player: Entity, pickups: Set<Entity>) {world.forEachCollision((a, b, info) => {if (!info.started || !info.sensor) return;const other = a === player ? b : b === player ? a : null;if (other === null || !pickups.delete(other)) return;world.despawn(other);world.scoreboard.add(100);});}
raycast(origin, direction, maxDistance, mask?, out?) returns the first solid, non-sensor collider as of the last step, as { entity, distance, normal, point }, or null. Reuse an out hit per frame.
Ground probe
React and React Native · shared engine API
import type { World } from "@nayan-ui/engine";export function groundHeight(world: World, x: number, y: number, z: number) {const hit = world.raycast([x, y + 1, z], [0, -1, 0], 20, 1);return hit ? hit.point[1] : null;}
impact makes the core play a sound and haptic by itself when the entity hits something solid, scaled by speed. Sounds pan relative to world.listener; leave impact off when the player muted sounds.
| ImpactFeedback | Behavior |
|---|---|
| sound | A loaded Sound; omit for haptics only. |
| minSpeed / maxSpeed | Silent below; full strength at; defaults 1 and 10. |
| volume | Volume at full strength, 0..2; default 1. |
| haptic | Vibration at full strength, 0..1; default 0. |
Bouncing crate
React and React Native · shared engine API
import type { Sound, World } from "@nayan-ui/engine";export function addCrate(world: World, thud: Sound, sounds: boolean) {return world.spawn({mesh: "cube", position: [0, 6, 0], physics: "dynamic",impact: sounds ? { sound: thud, minSpeed: 2, maxSpeed: 12, haptic: 0.4 } : null,});}