@nayan-ui/engine 1.0.0-beta.4 → 1.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +135 -117
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,13 +4,29 @@
4
4
 
5
5
  A fast 3D game engine for **React** and **React Native**. One TypeScript API on one Rust core: rendering (wgpu), physics (Rapier), skeletal and tween animation, particles, audio, haptics and 2D drawing.
6
6
 
7
- **[Documentation](https://www.nayanui.com/game-engine)** · **[First game tutorial](https://www.nayanui.com/game-engine/first-game)** · **[API reference](https://www.nayanui.com/game-engine/api-reference)** · **[Guide for AI agents](https://www.nayanui.com/game-engine/agent-guide)**
7
+ **[Documentation](https://www.nayanui.com/game-engine)** · **[First game tutorial](https://www.nayanui.com/game-engine/first-game)** · **[API reference](https://www.nayanui.com/game-engine/api-reference)** · **[Guide for AI agents](https://www.nayanui.com/game-engine/agent-guide)** · **[Prebuilt games](https://www.nayanui.com/games)**
8
+
9
+ ## Contents
10
+
11
+ - [Features](#features)
12
+ - [Install](#install)
13
+ - [Quick start](#quick-start)
14
+ - [Core concepts](#core-concepts)
15
+ - [Guides](#guides): [Entities](#entities) · [Game loop](#game-loop) · [Physics](#physics) · [Animation](#animation) · [Characters](#characters) · [Effects](#effects) · [Input](#input) · [Assets](#assets) · [Sound and haptics](#sound-and-haptics) · [Score and timers](#score-and-timers) · [Shapes in code](#shapes-in-code) · [Flat boards](#flat-boards-and-puzzles) · [Drawing](#drawing) · [Colors](#colors)
16
+ - [Performance](#performance)
17
+ - [Troubleshooting](#troubleshooting)
18
+ - [Prebuilt games](#prebuilt-games)
19
+ - [Learn more](#learn-more)
20
+
21
+ ## Features
8
22
 
9
23
  - **Native speed on phones.** Rendering, physics, animation, culling and audio mixing run in Rust. On iOS and Android the game loop follows the display's clock, and frames are drawn on the engine's own render thread, so your JavaScript never waits on the GPU.
10
24
  - **Small API.** `useWorld` + `GameView` is a complete scene. Spawn entities, move them, animate them: the engine steps, draws and cleans up.
11
25
  - **Fast loading.** Images, glTF models, fonts and sounds decode on native background threads, and procedural meshes can be cached on disk between launches.
12
26
  - **Same code on the web.** The core compiles to WebAssembly and draws with WebGPU, falling back to WebGL2.
13
27
 
28
+ ### Platforms
29
+
14
30
  | Platform | Renders with | Needs |
15
31
  | ----------- | ------------------------------ | --------------------------------------------------------- |
16
32
  | iOS | Metal | A development or store build (Expo Go doesn't include it) |
@@ -78,7 +94,7 @@ export function BouncingBall() {
78
94
 
79
95
  `useWorld` warms the engine up, creates the world (100 entity slots), runs your setup and disposes it on unmount. `GameView` draws it and steps it every frame.
80
96
 
81
- ## The five rules
97
+ ## Core concepts
82
98
 
83
99
  1. **Own** a world with `useWorld(capacity, setup)`, or create it yourself after `await warmUpEngine()` and `dispose()` it on unmount.
84
100
  2. **Play** in `GameView`'s `onUpdate(dt)`: read input, move things, spawn. The world is stepped for you right after (call `world.update(dt)` yourself only to step it earlier or with another `dt`).
@@ -86,148 +102,79 @@ export function BouncingBall() {
86
102
  4. **Batch per-frame moves** in `world.transforms`: they are applied in one native call when the world steps (`flush()` yourself before reading positions).
87
103
  5. **Don't track disposal yourself**: check `world.isDisposed`. Cleanup calls are safe no-ops after `dispose()`.
88
104
 
89
- ## Performance
105
+ ## Guides
90
106
 
91
- The engine is built so that a game written the obvious way stays smooth on mid-range Android phones.
107
+ Each snippet assumes a `world` from `useWorld`.
92
108
 
93
- - **Display clock and render thread (iOS and Android).** The display's vsync (`CADisplayLink` on iOS, `Choreographer` on Android) asks for each frame at 60 Hz. `onUpdate` steps the game and hands a copy of the frame to a native render thread, which culls, submits and presents it. The JS thread never waits on the GPU, and a frame that is still waiting when a newer one arrives is replaced, not queued. The clock stops while the app is in the background or the view is off screen. On the web, `requestAnimationFrame` drives the same loop.
94
- - **Culling in Rust.** Everything outside the camera's view (and outside the light's box for shadows) is skipped every frame, so open levels and long tracks only pay for what is on screen. Don't cull scenery in JS.
95
- - **Automatic quality.** `quality="auto"` (the default) starts at `high` and steps down (shadow detail first, then anti-aliasing, resolution last) only while the device can't keep up. `onStats` reports fps, update and render time, the quality chosen and the graphics backend.
96
- - **Background loading.** `loadTexture`, `loadModel`, `loadFont` and `audio.load` decode on native threads in parallel, and meshes are registered with the renderer inside Rust.
97
- - **Work between frames.** `afterNextFrame(fn)` runs `fn` right after the next frame leaves, so you can spread long setup work (map generation, mesh building) over several frames behind a loading screen.
98
- - **Cached procedural assets.** `ProceduralCache` saves built geometry on disk keyed by a recipe, so later launches restore it instead of rebuilding; `peek(key)` returns a saved value without building anything.
109
+ ### Entities
99
110
 
100
- Keep these habits:
111
+ ```ts
112
+ const box = world.spawn({ mesh: 'cube', position: [0, 1, 0], color: '#FF4757' });
113
+ world.set(box, { color: '#2ED573', scale: 2 }); // only the options you pass change
114
+ world.despawn(box);
115
+ ```
101
116
 
102
- - Build meshes once, at module scope or behind a cache, and spawn new entities from them. Mesh ids are app-wide and never freed (`getMeshIdLimit()` tells you how many there are), so never create a mesh per spawn or per frame.
103
- - Move many things each frame through `world.transforms` and the `out` arguments of queries, which allocate nothing.
104
- - Keep long one-off jobs out of gameplay frames: a single slow frame stalls Android's Hermes far longer than desktop JavaScript suggests.
117
+ Built-in meshes include `'cube'`, `'sphere'`, `'plane'` and `'cylinder'`.
105
118
 
106
- ## Common tasks
119
+ ### Game loop
107
120
 
108
- **Physics and collisions**
121
+ ```tsx
122
+ <GameView
123
+ source={world}
124
+ onUpdate={(dt) => {
125
+ world.transforms.set(player, { position: [x, 0, z] }); // batched, applied as the world steps
126
+ }}
127
+ />
128
+ ```
129
+
130
+ ### Physics
109
131
 
110
132
  ```ts
111
133
  const ball = world.spawn({ mesh: 'sphere', position: [0, 5, 0], physics: { type: 'dynamic', bounce: 0.6 } });
112
134
  const coin = world.spawn({ mesh: 'cylinder', position: [2, 1, 0], physics: { type: 'fixed', sensor: true } });
135
+
113
136
  world.impulse(ball, [0, 4, 0]);
114
137
  world.forEachCollision((a, b, { started }) => {
115
138
  if (started && (a === coin || b === coin)) world.despawn(coin);
116
139
  });
117
140
  ```
118
141
 
119
- **Animation** (runs in Rust and returns a Promise)
142
+ ### Animation
143
+
144
+ Tweens run in Rust and return a Promise.
120
145
 
121
146
  ```ts
122
- await world.animate(piece, { position: [2, 0, 3] }, { duration: 0.25, easing: 'back' });
147
+ await world.animate(box, { position: [2, 0, 3] }, { duration: 0.25, easing: 'back' });
123
148
  world.animate(star, { scale: 1.2 }, { repeat: 'forever', yoyo: true });
124
149
  ```
125
150
 
126
- **Skinned characters**: glTF clips play natively, with crossfades
151
+ ### Characters
152
+
153
+ glTF clips play natively, with crossfades.
127
154
 
128
155
  ```ts
129
156
  const hero = await loadModel(require('./hero.glb'));
130
- console.log(hero.animations.map((clip) => clip.name)); // e.g. ['idle', 'walk', 'run']
131
157
  const player = world.spawn({ mesh: hero });
132
- world.playClip(player, 'idle', { speed: 0 }); // hold the first pose on the ready screen
133
- world.playClip(player, 'run', { fade: 0.2 }); // crossfade when play starts
158
+
159
+ world.playClip(player, 'idle');
160
+ world.playClip(player, 'run', { fade: 0.2 });
134
161
  world.stopClip(player);
135
162
  ```
136
163
 
137
- `world.sampleSkinnedVertices` reads chosen vertices of the next pose (for planting feet on uneven ground) without skinning the whole character in JS.
164
+ Clip names are in `hero.animations`. `world.sampleSkinnedVertices` reads chosen vertices of the next pose (for planting feet on uneven ground).
138
165
 
139
- **Ready-made effects**
166
+ ### Effects
140
167
 
141
168
  ```ts
142
169
  import { effects } from '@nayan-ui/engine';
143
170
 
144
- effects.confetti(world, {
145
- position: [0, 2, 0],
146
- colors: ['#FF4D4D', '#FFD933'],
147
- size: 0.12,
148
- speed: 6,
149
- count: 40,
150
- lifetime: 1.2,
151
- gravity: -6
152
- });
153
- effects.dust(world, { position: [0, 0, 0], color: [0.8, 0.75, 0.65, 0.6], size: 0.3, speed: 1.5, count: 6, lifetime: 0.5 });
154
- await effects.pop(world, piece, { scale: 1, color: '#FF4D4D' }); // swell, flash white, shrink away
155
- world.despawn(piece);
156
- ```
157
-
158
- Also available: `sparkle`, `shatter`, `splash`, `sparks`, `explosion` and `flash`.
159
-
160
- **Per-frame movement without allocations**
161
-
162
- ```ts
163
- import { type MutableQuat, quat } from '@nayan-ui/engine';
164
-
165
- const turn: MutableQuat = [0, 0, 0, 1];
166
- const at: [number, number, number] = [0, 0, 0];
167
-
168
- function onUpdate(dt: number) {
169
- world.position(player, at); // queries can fill an `out` array
170
- quat.y(heading, turn);
171
- world.transforms.transform(car, carX, 0, carZ, turn[0], turn[1], turn[2], turn[3]); // applied in one native call as the world steps
172
- }
171
+ effects.confetti(world, { position: [0, 2, 0], colors: ['#FF4D4D', '#FFD933'], count: 40 });
172
+ await effects.pop(world, box, { scale: 1, color: '#FF4D4D' }); // swell, flash, shrink away
173
173
  ```
174
174
 
175
- **Models built in code**: bake a shape once, spawn it many times
175
+ Also available: `dust`, `sparkle`, `shatter`, `splash`, `sparks`, `explosion` and `flash`.
176
176
 
177
- ```ts
178
- import { createShape, lathe, merge, tube } from '@nayan-ui/engine';
179
-
180
- // Built once per app (after warmUpEngine), at module scope.
181
- const vase = createShape(
182
- merge([
183
- {
184
- geometry: lathe(
185
- [
186
- [0, 0],
187
- [0.4, 0.1],
188
- [0.25, 0.8],
189
- [0.3, 1]
190
- ],
191
- { segments: 24 }
192
- )
193
- },
194
- {
195
- geometry: tube(
196
- [
197
- [0.3, 0.9, 0],
198
- [0.5, 0.7, 0],
199
- [0.3, 0.5, 0]
200
- ],
201
- { radius: 0.04 }
202
- ),
203
- color: [0.5, 0.25, 0.1]
204
- } // part colors are 0..1 tuples
205
- ])
206
- );
207
- world.spawn({ mesh: vase, color: '#C8763A' });
208
- ```
209
-
210
- Also available: `extrude`, `loft`, `block`, `ellipsoid`, `parametricSurface`, `Solid` and `shapeGeometry` for the built-in shapes.
211
-
212
- **Score, lives and timers**
213
-
214
- ```ts
215
- world.scoreboard.handlers = { onTimeUp: () => endGame('lose') }; // the game's rules
216
- const stopListening = world.scoreboard.listen({ onScore: setScore, onSecond: setTime }); // UI follows along
217
- world.scoreboard.reset(3, 60); // 3 lives and a stopped 60 s countdown for the ready screen
218
- world.scoreboard.resume(); // Start
219
- world.scoreboard.add(10);
220
- const timer = world.after(0.5, spawnWave); // game time: pauses with the game
221
- world.cancelTimer(timer);
222
- ```
223
-
224
- **Flat boards and puzzles**: `frontLight` keeps camera-facing surfaces at their exact color
225
-
226
- ```tsx
227
- <GameView source={world} camera={{ eye: [0, 0, 50], target: [0, 0, 0], fov: 1, ortho: 10 }} light={frontLight([-0.4, 0.6, 1], 0.5)} />
228
- ```
229
-
230
- **Input**
177
+ ### Input
231
178
 
232
179
  ```tsx
233
180
  <GameView
@@ -237,46 +184,109 @@ world.cancelTimer(timer);
237
184
  if (hit) world.animate(hit.entity, { scale: 1.2 }, { yoyo: true, repeat: 1 });
238
185
  }}
239
186
  onSwipe={(direction) => move(direction)}
240
- onDrag={({ phase, x, y }) => drag(phase, x, y)}
241
187
  />
242
188
  ```
243
189
 
244
- `onLongPress`, `onPinch`, the on-screen `Joystick` and `createKeyboardInput` (web) cover other controls.
190
+ Also available: `onDrag`, `onLongPress`, `onPinch`, the on-screen `Joystick` and `createKeyboardInput` (web).
245
191
 
246
- **Models, textures, text, sound and haptics**
192
+ ### Assets
247
193
 
248
194
  ```ts
249
- import { audio, haptics, loadFont, loadModel, loadTexture } from '@nayan-ui/engine';
195
+ import { loadFont, loadModel, loadTexture } from '@nayan-ui/engine';
250
196
 
251
- const car = await loadModel(require('./car.glb'), { fit: 2 }); // or a URL string on the web
197
+ const car = await loadModel(require('./car.glb'), { fit: 2 }); // or a URL on the web
252
198
  const crate = await loadTexture(require('./crate.png'));
253
199
  const font = await loadFont(require('./Inter-Bold.ttf'));
254
- world.spawn({ mesh: car, position: [0, 0, 0] });
200
+
201
+ world.spawn({ mesh: car });
255
202
  world.spawn({ mesh: 'cube', texture: crate });
256
203
  world.spawn({ text: '2048', font, position: [0, 2, 0], scale: 0.5 });
204
+ ```
205
+
206
+ ### Sound and haptics
207
+
208
+ ```ts
209
+ import { audio, haptics } from '@nayan-ui/engine';
257
210
 
258
211
  const sfx = audio.load({ coin: require('./coin.wav') });
259
212
  sfx.play('coin');
260
213
  haptics.impact(0.5);
214
+
261
215
  audio.enabled = settings.sounds; // respect the player's preferences
262
216
  haptics.enabled = settings.haptics;
263
217
  ```
264
218
 
265
- **Colors** are CSS strings (`'#FF4757'`, `'rgba(0, 0, 0, 0.5)'`) or linear 0..1 tuples (`[1, 0.3, 0.3]`; channels above 1 glow) for entities, animations, particles, lights and the background. Vertex colors baked into geometry are tuples. `toColor('#FF4757')` converts a CSS color when you need the numbers.
219
+ ### Score and timers
220
+
221
+ ```ts
222
+ world.scoreboard.handlers = { onTimeUp: () => endGame('lose') };
223
+ world.scoreboard.listen({ onScore: setScore, onSecond: setTime }); // keep your UI in sync
224
+ world.scoreboard.reset(3, 60); // 3 lives, 60 s countdown
225
+ world.scoreboard.resume();
226
+ world.scoreboard.add(10);
227
+
228
+ const timer = world.after(0.5, spawnWave); // game time: pauses with the game
229
+ world.cancelTimer(timer);
230
+ ```
266
231
 
267
- **Drawing**: a native paint canvas, also usable as a live texture inside games
232
+ ### Shapes in code
233
+
234
+ Build a mesh once at module scope (after `warmUpEngine()`), then spawn it many times.
235
+
236
+ ```ts
237
+ import { createShape, lathe } from '@nayan-ui/engine';
238
+
239
+ const vase = createShape(lathe([[0, 0], [0.4, 0.1], [0.25, 0.8], [0.3, 1]], { segments: 24 }));
240
+ world.spawn({ mesh: vase, color: '#C8763A' });
241
+ ```
242
+
243
+ Also available: `merge`, `tube`, `extrude`, `loft`, `block`, `ellipsoid`, `parametricSurface`, `Solid` and `shapeGeometry`.
244
+
245
+ ### Flat boards and puzzles
246
+
247
+ `frontLight` keeps camera-facing surfaces at their exact color.
248
+
249
+ ```tsx
250
+ <GameView source={world} camera={{ eye: [0, 0, 50], target: [0, 0, 0], fov: 1, ortho: 10 }} light={frontLight([-0.4, 0.6, 1], 0.5)} />
251
+ ```
252
+
253
+ ### Drawing
254
+
255
+ A native paint canvas, also usable as a live texture inside games.
268
256
 
269
257
  ```tsx
270
258
  import { DrawingCanvas, DrawingDocument } from '@nayan-ui/engine';
271
259
 
272
260
  const paper = new DrawingDocument(512, 512);
273
261
  paper.setBrush({ brushType: 'MARKER', color: '#e53935', width: 12, opacity: 1 });
262
+
274
263
  <DrawingCanvas document={paper} mode="draw" />;
275
264
  world.spawn({ mesh: 'plane', texture: paper.texture() }); // paint on a 3D object
276
265
  ```
277
266
 
278
267
  Drawings save as SVG paths and export transparent PNG snapshots.
279
268
 
269
+ ### Colors
270
+
271
+ Use CSS strings (`'#FF4757'`, `'rgba(0, 0, 0, 0.5)'`) or linear 0..1 tuples (`[1, 0.3, 0.3]`; channels above 1 glow). `toColor('#FF4757')` converts a CSS color to numbers.
272
+
273
+ ## Performance
274
+
275
+ The engine is built so that a game written the obvious way stays smooth on mid-range Android phones.
276
+
277
+ - **Display clock and render thread (iOS and Android).** The display's vsync (`CADisplayLink` on iOS, `Choreographer` on Android) asks for each frame at 60 Hz. `onUpdate` steps the game and hands a copy of the frame to a native render thread, which culls, submits and presents it. The JS thread never waits on the GPU, and a frame that is still waiting when a newer one arrives is replaced, not queued. The clock stops while the app is in the background or the view is off screen. On the web, `requestAnimationFrame` drives the same loop.
278
+ - **Culling in Rust.** Everything outside the camera's view (and outside the light's box for shadows) is skipped every frame, so open levels and long tracks only pay for what is on screen. Don't cull scenery in JS.
279
+ - **Automatic quality.** `quality="auto"` (the default) starts at `high` and steps down (shadow detail first, then anti-aliasing, resolution last) only while the device can't keep up. `onStats` reports fps, update and render time, the quality chosen and the graphics backend.
280
+ - **Background loading.** `loadTexture`, `loadModel`, `loadFont` and `audio.load` decode on native threads in parallel, and meshes are registered with the renderer inside Rust.
281
+ - **Work between frames.** `afterNextFrame(fn)` runs `fn` right after the next frame leaves, so you can spread long setup work (map generation, mesh building) over several frames behind a loading screen.
282
+ - **Cached procedural assets.** `ProceduralCache` saves built geometry on disk keyed by a recipe, so later launches restore it instead of rebuilding; `peek(key)` returns a saved value without building anything.
283
+
284
+ Keep these habits:
285
+
286
+ - Build meshes once, at module scope or behind a cache, and spawn new entities from them. Mesh ids are app-wide and never freed (`getMeshIdLimit()` tells you how many there are), so never create a mesh per spawn or per frame.
287
+ - Move many things each frame through `world.transforms` and the `out` arguments of queries, which allocate nothing.
288
+ - Keep long one-off jobs out of gameplay frames: a single slow frame stalls Android's Hermes far longer than desktop JavaScript suggests.
289
+
280
290
  ## Troubleshooting
281
291
 
282
292
  | Symptom | Fix |
@@ -287,6 +297,14 @@ Drawings save as SVG paths and export transparent PNG snapshots.
287
297
  | An Android screenshot of the game view is blank | The engine draws into a `SurfaceView`: use `captureScreen(..., { handleGLSurfaceViewOnAndroid: true })`. |
288
298
  | Out of mesh ids | A mesh is being created per spawn or per frame: build it once and reuse it (`getMeshIdLimit()`). |
289
299
 
300
+ ## Prebuilt games
301
+
302
+ [@nayan-ui/games](https://www.nayanui.com/games) offers 53 ready-made 3D games built on this engine. Each is one component with the same props.
303
+
304
+ `@nayan-ui/games` is a **private package**. To use it in your application or website, reach out to [niranjan.devasani@gmail.com](mailto:niranjan.devasani@gmail.com).
305
+
306
+ **[Play them in your browser](https://www.nayanui.com/games)**
307
+
290
308
  ## Learn more
291
309
 
292
310
  | Topic | Guide |
@@ -302,7 +320,7 @@ Drawings save as SVG paths and export transparent PNG snapshots.
302
320
 
303
321
  ## License
304
322
 
305
- MIT © [Niranjan Devasani](https://github.com/ursnj)
323
+ MIT
306
324
 
307
325
  ---
308
326
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nayan-ui/engine",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "Fast 3D game engine for React and React Native: Rust and wgpu rendering, Rapier physics, animation, particle effects, audio, haptics and drawing.",
5
5
  "keywords": [
6
6
  "3d",