@series-inc/rundot-3d-engine 0.6.19 → 0.6.21
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.
- package/SKILL.md +252 -8
- package/dist/{chunk-4BJMBHQA.js → chunk-YMH4TFOA.js} +2762 -265
- package/dist/chunk-YMH4TFOA.js.map +1 -0
- package/dist/index.js +6 -406
- package/dist/index.js.map +1 -1
- package/dist/systems/index.d.ts +235 -56
- package/dist/systems/index.js +644 -1
- package/dist/systems/index.js.map +1 -1
- package/docs/systems/InputManager.md +106 -7
- package/docs/systems/PoolingSystem.md +346 -0
- package/docs/systems/ShaderSystem.md +222 -0
- package/package.json +2 -1
- package/dist/chunk-4BJMBHQA.js.map +0 -1
package/SKILL.md
CHANGED
|
@@ -13,10 +13,18 @@ class MyGame extends VenusGame {
|
|
|
13
13
|
// Load assets
|
|
14
14
|
const stowkit = StowKitSystem.getInstance()
|
|
15
15
|
const buildJson = (await import("../prefabs/build.json")).default
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
|
|
17
|
+
// Step 1: Parse build.json (pure, no network)
|
|
18
|
+
const { prefabs, mounts } = stowkit.parseBuildJson(buildJson, {
|
|
19
|
+
materialConverter: (mat) => mat, // optional
|
|
18
20
|
})
|
|
19
21
|
|
|
22
|
+
// Step 2: Download and register each pack
|
|
23
|
+
await Promise.all(mounts.map(async (mount) => {
|
|
24
|
+
const blob = await fetch(mount.path).then(r => r.blob())
|
|
25
|
+
await stowkit.registerAssetsFromBlob(mount.alias, blob)
|
|
26
|
+
}))
|
|
27
|
+
|
|
20
28
|
// Create a player
|
|
21
29
|
const player = new GameObject("Player")
|
|
22
30
|
player.position.set(0, 1, 0)
|
|
@@ -310,18 +318,32 @@ animGraph.getCurrentState()
|
|
|
310
318
|
|
|
311
319
|
### StowKitSystem — Asset Loading
|
|
312
320
|
|
|
321
|
+
Two-step loading: parse build.json first (pure, no network), then download and register each pack.
|
|
322
|
+
|
|
313
323
|
```typescript
|
|
314
324
|
import { StowKitSystem } from "@series-inc/rundot-3d-engine/systems"
|
|
315
325
|
|
|
316
326
|
const stowkit = StowKitSystem.getInstance()
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
327
|
+
const buildJson = (await import("../prefabs/build.json")).default
|
|
328
|
+
|
|
329
|
+
// Step 1: Parse build.json — returns prefab collection and mount list (no I/O)
|
|
330
|
+
const { prefabs, mounts } = stowkit.parseBuildJson(buildJson, {
|
|
331
|
+
materialConverter: (mat) => mat, // optional — transform materials at load time
|
|
332
|
+
decoderPaths: { // optional — override decoder locations
|
|
333
|
+
basis: "basis/",
|
|
334
|
+
draco: "stowkit/draco/",
|
|
335
|
+
wasm: "stowkit_reader.wasm",
|
|
336
|
+
},
|
|
321
337
|
})
|
|
322
338
|
|
|
339
|
+
// Step 2: Download and register each .stow pack (WASM decode, no further network)
|
|
340
|
+
await Promise.all(mounts.map(async (mount) => {
|
|
341
|
+
const blob = await fetch(mount.path).then(r => r.blob())
|
|
342
|
+
await stowkit.registerAssetsFromBlob(mount.alias, blob)
|
|
343
|
+
}))
|
|
344
|
+
|
|
323
345
|
// Access assets
|
|
324
|
-
const mesh = await stowkit.getMesh("name") // async
|
|
346
|
+
const mesh = await stowkit.getMesh("name") // async, on-demand load & cache
|
|
325
347
|
const mesh = stowkit.getMeshSync("name") // sync (null if not loaded)
|
|
326
348
|
const tex = await stowkit.getTexture("name")
|
|
327
349
|
const clip = await stowkit.getAnimation("walk", "character_mesh")
|
|
@@ -333,12 +355,234 @@ const clone = await stowkit.cloneMesh("name", castShadow, receiveShadow)
|
|
|
333
355
|
|
|
334
356
|
// GPU instancing
|
|
335
357
|
await stowkit.registerMeshForInstancing("coin_batch", "coin_mesh", true, true, 500)
|
|
358
|
+
await stowkit.registerBatchFromPrefab("burger", true, true, 100) // from prefab's stow_mesh component
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Preloading** — pre-decode assets during load screen to avoid pop-in:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
await stowkit.preloadAll({
|
|
365
|
+
meshes: ["level_geometry", "coin_mesh"],
|
|
366
|
+
skinnedMeshes: { names: ["hero_model"], scale: 1.0 },
|
|
367
|
+
textures: ["hero_diffuse"],
|
|
368
|
+
audio: ["bgm_main", "sfx_click"],
|
|
369
|
+
animations: [{ name: "hero_idle", meshName: "hero_model" }],
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
// Or individually:
|
|
373
|
+
await stowkit.preloadMeshes(["level_geometry", "coin_mesh"])
|
|
374
|
+
await stowkit.preloadSkinnedMeshes(["hero_model"], 1.0)
|
|
375
|
+
await stowkit.preloadAudio(["bgm_main"])
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### InputManager — Keyboard, Pointer, Touch & Custom Actions
|
|
379
|
+
|
|
380
|
+
The `InputManager` is a singleton that tracks keyboard, pointer, and touch state. Use the `Input` convenience object for quick access.
|
|
381
|
+
|
|
382
|
+
**Built-in action polling (keyboard):**
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import { Input, InputAction } from "@series-inc/rundot-3d-engine/systems"
|
|
386
|
+
|
|
387
|
+
// Check built-in movement actions
|
|
388
|
+
if (Input.isPressed(InputAction.MOVE_FORWARD)) { /* W key */ }
|
|
389
|
+
if (Input.isKeyPressed("Space")) { /* raw key check */ }
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Pointer & touch state:**
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
Input.isPointerDown() // true while mouse/pen is held
|
|
396
|
+
Input.getPointerPosition() // { x, y } in client coords
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Custom action system — register named actions with multiple trigger types:**
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { Input } from "@series-inc/rundot-3d-engine/systems"
|
|
403
|
+
import type { ActionTrigger } from "@series-inc/rundot-3d-engine/systems"
|
|
404
|
+
|
|
405
|
+
// Register an action with multiple triggers
|
|
406
|
+
Input.registerAction("jump", [
|
|
407
|
+
{ type: "key", code: "Space" },
|
|
408
|
+
{ type: "pointer" },
|
|
409
|
+
{ type: "touch" },
|
|
410
|
+
])
|
|
411
|
+
|
|
412
|
+
// Subscribe to action (fires on press) — returns unsubscribe function
|
|
413
|
+
const unsub = Input.onAction("jump", () => {
|
|
414
|
+
player.flap()
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
// Polling API
|
|
418
|
+
if (Input.isCustomActionActive("jump")) { /* any trigger held */ }
|
|
419
|
+
|
|
420
|
+
// Cleanup
|
|
421
|
+
unsub()
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**ActionTrigger types:**
|
|
425
|
+
- `{ type: "key", code: string }` — keyboard key (uses `event.code`, e.g. `"Space"`, `"KeyW"`)
|
|
426
|
+
- `{ type: "pointer" }` — mouse or pen pointer down (excludes touch — no double-firing)
|
|
427
|
+
- `{ type: "touch" }` — touch screen tap
|
|
428
|
+
|
|
429
|
+
**Notes:**
|
|
430
|
+
- On touch devices, pointer events from touch input are automatically skipped to prevent double-firing when an action has both `pointer` and `touch` triggers.
|
|
431
|
+
- `Input.setEnabled(false)` disables all input processing and clears all state.
|
|
432
|
+
- Custom actions registered with `registerAction` also get `preventDefault` on their key triggers.
|
|
433
|
+
|
|
434
|
+
### ShaderRegistry & ShaderComponent — Shader Library
|
|
435
|
+
|
|
436
|
+
A registry of named shader presets with a component that applies them to meshes. Shared `time` and `deltaTime` uniforms are updated automatically once per frame.
|
|
437
|
+
|
|
438
|
+
**Built-in presets:**
|
|
439
|
+
- `"fresnel"` — Fresnel rim lighting, wrap diffuse, specular, under-lighting, directional light. Good for stylized characters and objects.
|
|
440
|
+
- `"wind"` — Simplex noise-based vertex displacement for vegetation animation. Includes depth shader pair for correct shadow casting with alpha clip.
|
|
441
|
+
|
|
442
|
+
**Usage — apply a shader preset to a GameObject:**
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { ShaderComponent } from "@series-inc/rundot-3d-engine/systems"
|
|
446
|
+
|
|
447
|
+
// Apply fresnel shader with default settings
|
|
448
|
+
gameObject.addComponent(new ShaderComponent("fresnel"))
|
|
449
|
+
|
|
450
|
+
// Apply with uniform overrides
|
|
451
|
+
gameObject.addComponent(new ShaderComponent("fresnel", {
|
|
452
|
+
fresnelIntensity: { value: 0.3 },
|
|
453
|
+
underLightIntensity: { value: 0.2 },
|
|
454
|
+
}))
|
|
455
|
+
|
|
456
|
+
// Apply wind shader (vegetation)
|
|
457
|
+
seaweedObject.addComponent(new ShaderComponent("wind"))
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**ShaderComponent behavior:**
|
|
461
|
+
- Waits for all `MeshRenderer` components in children to load before applying
|
|
462
|
+
- Traverses all child meshes and replaces materials with ShaderMaterial from the preset
|
|
463
|
+
- Automatically includes fog uniforms and shared time uniforms
|
|
464
|
+
- Sets `preventAutoInstancing = true` (custom materials can't be auto-batched)
|
|
465
|
+
- On cleanup, restores original materials and disposes created shader materials
|
|
466
|
+
- For presets with depth shaders (e.g. `"wind"`), creates `customDepthMaterial` and `customDistanceMaterial` for correct shadow casting
|
|
467
|
+
|
|
468
|
+
**Registering custom presets:**
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { ShaderRegistry } from "@series-inc/rundot-3d-engine/systems"
|
|
472
|
+
import type { ShaderPreset } from "@series-inc/rundot-3d-engine/systems"
|
|
473
|
+
|
|
474
|
+
ShaderRegistry.register({
|
|
475
|
+
name: "myShader",
|
|
476
|
+
vertexShader: `...`,
|
|
477
|
+
fragmentShader: `...`,
|
|
478
|
+
depthVertexShader: `...`, // optional — for shadow casting
|
|
479
|
+
depthFragmentShader: `...`, // optional
|
|
480
|
+
side: THREE.FrontSide, // optional (default: FrontSide)
|
|
481
|
+
transparent: false, // optional (default: false)
|
|
482
|
+
fog: true, // optional (default: true)
|
|
483
|
+
toneMapped: false, // optional (default: false)
|
|
484
|
+
defaultUniforms: {
|
|
485
|
+
myColor: { value: new THREE.Color(1, 0, 0) },
|
|
486
|
+
myFloat: { value: 1.0 },
|
|
487
|
+
},
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
// Then use it
|
|
491
|
+
gameObject.addComponent(new ShaderComponent("myShader"))
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Shared uniforms (available in all shader presets):**
|
|
495
|
+
- `time` (float) — `performance.now() * 0.001` (seconds since page load)
|
|
496
|
+
- `deltaTime` (float) — frame delta time
|
|
497
|
+
- `baseMap` (sampler2D) — automatically set from the mesh's existing texture map
|
|
498
|
+
|
|
499
|
+
**Fresnel preset uniforms:** `sunDir`, `sunColor`, `sunIntensity`, `fresnelColor`, `fresnelPower`, `fresnelIntensity`, `ambientColor`, `ambientIntensity`, `specColor`, `specShininess`, `specIntensity`, `underLightColor`, `underLightIntensity`, `underLightPower`, `rimLightDir`, `rimLightColor`, `rimLightIntensity`
|
|
500
|
+
|
|
501
|
+
**Wind preset uniforms:** `windStrength`, `windSpeed`, `rootWorldOrigin`, `alphaClip`, `tintColor`, `lightDir`, `ambientColor`, `ambientStrength`
|
|
502
|
+
|
|
503
|
+
### ObjectPool, GameObjectPool & ScrollingPool — Object Pooling
|
|
504
|
+
|
|
505
|
+
Generic and specialized pools for reusing objects instead of creating/destroying them each frame.
|
|
506
|
+
|
|
507
|
+
**ObjectPool — generic pool:**
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
import { ObjectPool } from "@series-inc/rundot-3d-engine/systems"
|
|
511
|
+
|
|
512
|
+
const pool = new ObjectPool({
|
|
513
|
+
create: () => new Bullet(),
|
|
514
|
+
onAcquire: (b) => b.activate(),
|
|
515
|
+
onRelease: (b) => b.deactivate(),
|
|
516
|
+
onDestroy: (b) => b.dispose(),
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
pool.warm(20) // pre-allocate 20 objects
|
|
520
|
+
const bullet = pool.acquire() // get from pool (or create new)
|
|
521
|
+
pool.release(bullet) // return to pool
|
|
522
|
+
pool.releaseAll() // return all active objects
|
|
523
|
+
pool.dispose() // destroy everything
|
|
524
|
+
|
|
525
|
+
pool.activeCount // currently in use
|
|
526
|
+
pool.availableCount // ready to acquire
|
|
527
|
+
pool.totalCount // active + available
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**GameObjectPool — pool for GameObjects with visibility management:**
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
import { GameObjectPool } from "@series-inc/rundot-3d-engine/systems"
|
|
534
|
+
|
|
535
|
+
const pipePool = new GameObjectPool(() => {
|
|
536
|
+
const go = new GameObject("Pipe")
|
|
537
|
+
go.addComponent(new MeshRenderer("pipe_mesh"))
|
|
538
|
+
return go
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
pipePool.warm(10)
|
|
542
|
+
const pipe = pipePool.acquire() // visible = true
|
|
543
|
+
pipe.position.set(0, 0, 50)
|
|
544
|
+
|
|
545
|
+
pipePool.release(pipe) // visible = false, moved offscreen (y = -9999)
|
|
546
|
+
pipePool.releaseAll()
|
|
547
|
+
pipePool.dispose() // calls gameObject.dispose() on all
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**ScrollingPool — infinite tile recycler for endless/runner games:**
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import { ScrollingPool, GameObjectPool } from "@series-inc/rundot-3d-engine/systems"
|
|
554
|
+
|
|
555
|
+
const tilePool = new GameObjectPool(() => {
|
|
556
|
+
const tile = Prefabs.instantiate("Background").gameObject
|
|
557
|
+
return tile
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
const scroller = new ScrollingPool({
|
|
561
|
+
pool: tilePool,
|
|
562
|
+
tileSize: 250,
|
|
563
|
+
bufferCount: 3,
|
|
564
|
+
axis: "z", // "x" | "y" | "z" (default: "z")
|
|
565
|
+
onSpawn: (tile, index) => {
|
|
566
|
+
// customize each spawned tile
|
|
567
|
+
tile.position.x = 25
|
|
568
|
+
},
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
scroller.initialize() // spawn initial buffer
|
|
572
|
+
|
|
573
|
+
// In update loop:
|
|
574
|
+
scroller.update(playerZ) // spawns ahead, recycles behind
|
|
575
|
+
|
|
576
|
+
// On restart:
|
|
577
|
+
scroller.reset()
|
|
578
|
+
scroller.initialize()
|
|
579
|
+
|
|
580
|
+
scroller.dispose()
|
|
336
581
|
```
|
|
337
582
|
|
|
338
583
|
### Other Systems
|
|
339
584
|
|
|
340
585
|
- **AudioSystem** — 2D/3D positional audio with AudioListener management
|
|
341
|
-
- **InputManager** — keyboard, mouse, touch, and gamepad input
|
|
342
586
|
- **LightingSystem** — directional, point, and spot lights with shadow management
|
|
343
587
|
- **NavigationSystem** — A* pathfinding on navigation meshes
|
|
344
588
|
- **ParticleSystem** — GPU particle effects
|