cubeforge 0.1.8 → 0.2.0

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 (3) hide show
  1. package/dist/index.d.mts +315 -190
  2. package/dist/index.js +2382 -565
  3. package/package.json +1 -1
package/dist/index.d.mts CHANGED
@@ -1,14 +1,17 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default, { CSSProperties, ReactNode } from 'react';
4
- import { Plugin, EntityId, System, ECSWorld, ScriptUpdateFn, EventBus, AssetManager, GameLoop, WorldSnapshot } from '@cubeforge/core';
5
- export { Component, ECSWorld, Ease, EntityId, Plugin, ScriptUpdateFn, TransformComponent, TweenHandle, WorldSnapshot, createTag, createTransform, definePlugin, findByTag, tween } from '@cubeforge/core';
6
- import { InputManager, ActionBindings } from '@cubeforge/input';
7
- export { ActionBindings, InputManager, InputMap, createInputMap } from '@cubeforge/input';
8
- import { Canvas2DRenderer, RenderSystem } from '@cubeforge/renderer';
9
- export { AnimationStateComponent, ParallaxLayerComponent, Particle, ParticlePoolComponent, SpriteComponent, SquashStretchComponent, TextComponent, createSprite } from '@cubeforge/renderer';
10
- import { PhysicsSystem } from '@cubeforge/physics';
11
- export { BoxColliderComponent, CircleColliderComponent, RaycastHit, RigidBodyComponent, overlapBox, raycast } from '@cubeforge/physics';
4
+ import { Plugin, EntityId, System, ECSWorld, ScriptUpdateFn, NavGrid, WorldSnapshot, EventBus } from '@cubeforge/core';
5
+ export { AssetProgress, Component, ECSWorld, Ease, EntityId, GameTimer, NavGrid, Plugin, PreloadManifest, ScriptUpdateFn, TransformComponent, TweenHandle, Vec2Like, WorldSnapshot, arrive, createTag, createTimer, createTransform, definePlugin, findByTag, flee, patrol, preloadManifest, seek, tween, wander } from '@cubeforge/core';
6
+ import { InputManager, ActionBindings, InputContextName, PlayerInput, InputRecorderControls } from '@cubeforge/input';
7
+ export { ActionBindings, AxisBinding, InputContextName, InputManager, InputMap, InputRecorderControls, InputRecording, InputRecording as InputRecordingData, PlayerInput, createInputMap, createInputRecorder, createPlayerInput, globalInputContext } from '@cubeforge/input';
8
+ import { EngineState } from '@cubeforge/context';
9
+ export { EngineState, useCircleEnter, useCircleExit, useCircleStay, useCollisionEnter, useCollisionExit, useCollisionStay, useTriggerEnter, useTriggerExit, useTriggerStay } from '@cubeforge/context';
10
+ export { AISteering, AnimationClip, AnimationControllerResult, BindingControls, GameState as GameStateDefinition, GameStateMachineResult, HealthControls, HealthOptions, KinematicBodyControls, LevelTransitionControls, PathfindingControls, PlatformerControllerOptions, RestartControls, SaveControls, SaveOptions, TopDownMovementOptions, TransitionOptions, TransitionType, useAISteering, useAnimationController, useDamageZone, useDropThrough, useGameStateMachine, useHealth, useKinematicBody, useLevelTransition, usePathfinding, usePersistedBindings, usePlatformerController, useRestart, useSave, useTopDownMovement } from '@cubeforge/gameplay';
11
+ export { AudioGroup, SoundControls, duck, getGroupVolume, setGroupMute, setGroupVolume, setMasterVolume, stopGroup, useSound } from '@cubeforge/audio';
12
+ export { DevToolsHandle } from '@cubeforge/devtools';
13
+ export { BoxColliderComponent, CapsuleColliderComponent, CircleColliderComponent, RaycastHit, RigidBodyComponent, overlapBox, overlapCircle, raycast, raycastAll, sweepBox } from '@cubeforge/physics';
14
+ export { AnimationStateComponent, ParallaxLayerComponent, Particle, ParticlePoolComponent, SpriteComponent, SquashStretchComponent, TextComponent, TrailComponent, createSprite } from '@cubeforge/renderer';
12
15
 
13
16
  interface GameControls {
14
17
  pause(): void;
@@ -177,6 +180,17 @@ interface CircleColliderProps {
177
180
  }
178
181
  declare function CircleCollider({ radius, offsetX, offsetY, isTrigger, layer, mask, }: CircleColliderProps): null;
179
182
 
183
+ interface CapsuleColliderProps {
184
+ width: number;
185
+ height: number;
186
+ offsetX?: number;
187
+ offsetY?: number;
188
+ isTrigger?: boolean;
189
+ layer?: string;
190
+ mask?: string | string[];
191
+ }
192
+ declare function CapsuleCollider({ width, height, offsetX, offsetY, isTrigger, layer, mask, }: CapsuleColliderProps): null;
193
+
180
194
  interface ScriptProps {
181
195
  /** Called once when the entity is mounted — use to attach extra components */
182
196
  init?: (entityId: EntityId, world: ECSWorld) => void;
@@ -223,8 +237,16 @@ interface AnimationProps {
223
237
  playing?: boolean;
224
238
  /** Called once when a non-looping animation finishes playing */
225
239
  onComplete?: () => void;
240
+ /**
241
+ * Callbacks fired when the animation advances to specific frame indices.
242
+ * Key = 0-based position in the `frames` array.
243
+ *
244
+ * @example
245
+ * frameEvents={{ 2: () => playFootstep(), 5: () => playFootstep() }}
246
+ */
247
+ frameEvents?: Record<number, () => void>;
226
248
  }
227
- declare function Animation({ frames, fps, loop, playing, onComplete }: AnimationProps): null;
249
+ declare function Animation({ frames, fps, loop, playing, onComplete, frameEvents }: AnimationProps): null;
228
250
 
229
251
  interface SquashStretchProps {
230
252
  /** How much to squash/stretch (default 0.2) */
@@ -261,6 +283,40 @@ interface ParticleEmitterProps {
261
283
  }
262
284
  declare function ParticleEmitter({ active, preset, rate, speed, spread, angle, particleLife, particleSize, color, gravity, maxParticles, }: ParticleEmitterProps): null;
263
285
 
286
+ interface VirtualJoystickProps {
287
+ /** Diameter of the joystick base in pixels (default 120) */
288
+ size?: number;
289
+ /** Screen corner to anchor to (default 'left') */
290
+ position?: 'left' | 'right';
291
+ /** Extra CSS applied to the outer container */
292
+ style?: React__default.CSSProperties;
293
+ /** Show an action button (e.g. jump) alongside the joystick (default false) */
294
+ actionButton?: boolean;
295
+ /** Label shown on the action button (default 'A') */
296
+ actionLabel?: string;
297
+ /** Name of the virtual button to set (default 'action') */
298
+ actionName?: string;
299
+ }
300
+ /**
301
+ * On-screen virtual joystick for touch / mobile. Place it as a sibling of the
302
+ * `<Game>` canvas (inside a `position: relative` container).
303
+ *
304
+ * Read the joystick state with `useVirtualInput()`.
305
+ *
306
+ * @example
307
+ * <div style={{ position: 'relative' }}>
308
+ * <Game ...>...</Game>
309
+ * <VirtualJoystick position="left" actionButton />
310
+ * </div>
311
+ *
312
+ * // Inside an entity:
313
+ * function MobilePlayer() {
314
+ * const virt = useVirtualInput()
315
+ * // virt.axisX, virt.axisY, virt.button('action')
316
+ * }
317
+ */
318
+ declare function VirtualJoystick({ size, position, style, actionButton, actionLabel, actionName, }: VirtualJoystickProps): react_jsx_runtime.JSX.Element;
319
+
264
320
  interface MovingPlatformProps {
265
321
  /** Start position */
266
322
  x1: number;
@@ -353,8 +409,21 @@ interface TilemapProps {
353
409
  * Receives the global tile ID, the property map, and the tile's world position.
354
410
  */
355
411
  onTileProperty?: (tileId: number, properties: Record<string, unknown>, x: number, y: number) => void;
412
+ /**
413
+ * If provided, collision-layer tiles automatically mark the corresponding
414
+ * NavGrid cells as non-walkable. The grid must already be created with the
415
+ * correct dimensions (map.width × map.height) and cellSize (map.tilewidth).
416
+ */
417
+ navGrid?: NavGrid;
418
+ /**
419
+ * Name of the object layer that contains spawn markers.
420
+ * Objects in this layer are forwarded to `onSpawnObject` just like any
421
+ * other object layer, but they are also identified as spawn points so you
422
+ * can filter them by `layer.name === spawnLayer` inside the callback.
423
+ */
424
+ spawnLayer?: string;
356
425
  }
357
- declare function Tilemap({ src, onSpawnObject, layerFilter, zIndex, collisionLayer, triggerLayer: triggerLayerName, onTileProperty, }: TilemapProps): React__default.ReactElement | null;
426
+ declare function Tilemap({ src, onSpawnObject, layerFilter, zIndex, collisionLayer, triggerLayer: triggerLayerName, onTileProperty, navGrid, }: TilemapProps): React__default.ReactElement | null;
358
427
 
359
428
  interface ParallaxLayerProps {
360
429
  /** Image URL to use as the background layer */
@@ -388,21 +457,94 @@ interface ScreenFlashHandle {
388
457
  }
389
458
  declare const ScreenFlash: React.ForwardRefExoticComponent<React.RefAttributes<ScreenFlashHandle>>;
390
459
 
391
- interface EngineState {
392
- ecs: ECSWorld;
393
- input: InputManager;
394
- /** Canvas2D renderer. Undefined when a custom WebGL renderer is used via the `renderer` Game prop. */
395
- renderer?: Canvas2DRenderer;
396
- /** The active render system. Undefined when a custom renderer is used. */
397
- renderSystem?: RenderSystem;
398
- physics: PhysicsSystem;
399
- events: EventBus;
400
- assets: AssetManager;
401
- loop: GameLoop;
402
- canvas: HTMLCanvasElement;
403
- /** Maps string entity IDs (e.g. "player") to numeric ECS EntityIds */
404
- entityIds: Map<string, EntityId>;
460
+ interface CameraZoneProps {
461
+ /** World-space center X of the trigger zone */
462
+ x: number;
463
+ /** World-space center Y of the trigger zone */
464
+ y: number;
465
+ /** Half-width of the trigger zone */
466
+ width: number;
467
+ /** Half-height of the trigger zone */
468
+ height: number;
469
+ /**
470
+ * When an entity with this tag enters the zone, the camera stops following
471
+ * its entity and locks to the fixed position (targetX, targetY).
472
+ */
473
+ watchTag?: string;
474
+ /** Fixed world-space X the camera moves to when activated (defaults to zone center) */
475
+ targetX?: number;
476
+ /** Fixed world-space Y the camera moves to when activated (defaults to zone center) */
477
+ targetY?: number;
478
+ children?: React__default.ReactNode;
479
+ }
480
+ /**
481
+ * Invisible trigger area. When the player (or another tagged entity) enters
482
+ * the zone, the camera follow entity is cleared and the camera locks to the
483
+ * zone's center (or a custom target). On exit, the camera resumes following.
484
+ *
485
+ * Zone detection runs inside the ScriptSystem tick — it respects pause and
486
+ * deterministic stepping.
487
+ *
488
+ * @example
489
+ * ```tsx
490
+ * <CameraZone x={500} y={300} width={200} height={150} watchTag="player" />
491
+ * ```
492
+ */
493
+ declare function CameraZone({ x, y, width, height, watchTag, targetX, targetY, children, }: CameraZoneProps): react_jsx_runtime.JSX.Element;
494
+
495
+ interface TrailProps {
496
+ /** Maximum number of trail points (default 20) */
497
+ length?: number;
498
+ /** CSS color string (default '#ffffff') */
499
+ color?: string;
500
+ /** Trail width in pixels (default 3) */
501
+ width?: number;
405
502
  }
503
+ /**
504
+ * Renders a fading polyline that follows the entity's position.
505
+ *
506
+ * The trail is drawn in the render pass — it uses the entity's Transform
507
+ * history collected each frame.
508
+ *
509
+ * Must be used inside an `<Entity>` with a `<Transform>`.
510
+ *
511
+ * @example
512
+ * ```tsx
513
+ * <Entity id="bullet">
514
+ * <Transform x={x} y={y} />
515
+ * <Sprite width={6} height={6} color="#ff0" />
516
+ * <Trail length={15} color="#ff0" width={2} />
517
+ * </Entity>
518
+ * ```
519
+ */
520
+ declare function Trail({ length, color, width }: TrailProps): null;
521
+
522
+ interface AssetLoaderProps {
523
+ /** List of asset URLs to preload */
524
+ assets: string[];
525
+ /** Shown while assets are loading */
526
+ fallback?: React__default.ReactNode;
527
+ /** Called if any asset fails to load */
528
+ onError?: (err: Error) => void;
529
+ children: React__default.ReactNode;
530
+ }
531
+ /**
532
+ * Suspense-style asset loading boundary.
533
+ *
534
+ * Shows `fallback` until all assets in the list are loaded (or errored).
535
+ * Once loaded, renders `children`.
536
+ *
537
+ * @example
538
+ * ```tsx
539
+ * <AssetLoader
540
+ * assets={['/hero.png', '/tiles.png', '/jump.wav']}
541
+ * fallback={<div>Loading…</div>}
542
+ * >
543
+ * <GameScene />
544
+ * </AssetLoader>
545
+ * ```
546
+ */
547
+ declare function AssetLoader({ assets, fallback, onError, children }: AssetLoaderProps): react_jsx_runtime.JSX.Element;
406
548
 
407
549
  declare function useGame(): EngineState;
408
550
 
@@ -507,6 +649,35 @@ declare function useEntity(): EntityId;
507
649
  */
508
650
  declare function useDestroyEntity(): () => void;
509
651
 
652
+ interface VirtualInputState {
653
+ /** Left–right axis in [−1, 1]. Positive = right. */
654
+ readonly axisX: number;
655
+ /** Up–down axis in [−1, 1]. Positive = down. */
656
+ readonly axisY: number;
657
+ /** Whether a named virtual button is currently pressed. */
658
+ button(name: string): boolean;
659
+ }
660
+ /**
661
+ * Returns the current state of the on-screen virtual joystick and buttons.
662
+ * Reads synchronously from the module-level store — safe to call in a Script
663
+ * update function every frame without triggering React re-renders.
664
+ *
665
+ * Combine with keyboard or gamepad input for multi-input support:
666
+ *
667
+ * @example
668
+ * function MobilePlayer() {
669
+ * const input = useInput()
670
+ * const virt = useVirtualInput()
671
+ *
672
+ * // Script update:
673
+ * const moveX = input.isHeld('ArrowRight') ? 1
674
+ * : input.isHeld('ArrowLeft') ? -1
675
+ * : virt.axisX
676
+ * const jump = input.isPressed('Space') || virt.button('action')
677
+ * }
678
+ */
679
+ declare function useVirtualInput(): VirtualInputState;
680
+
510
681
  declare function useInput(): InputManager;
511
682
 
512
683
  interface BoundInputMap {
@@ -516,6 +687,16 @@ interface BoundInputMap {
516
687
  isActionPressed(action: string): boolean;
517
688
  /** True only on the frame any bound key was released. */
518
689
  isActionReleased(action: string): boolean;
690
+ /**
691
+ * Returns -1..1 for axis bindings. For key bindings, 1 if down, 0 otherwise.
692
+ * @see {@link AxisBinding}
693
+ */
694
+ getAxis(action: string): number;
695
+ /**
696
+ * Alias for `getAxis`. Matches the `PlayerInput` naming convention — use this
697
+ * when writing code that needs to work with both `useInputMap` and `usePlayerInput`.
698
+ */
699
+ getActionAxis(action: string): number;
519
700
  }
520
701
  /**
521
702
  * React hook that returns a pre-bound action map for use inside `<Game>`.
@@ -537,141 +718,146 @@ declare function useInputMap(bindings: ActionBindings): BoundInputMap;
537
718
  declare function useEvents(): EventBus;
538
719
  declare function useEvent<T>(event: string, handler: (data: T) => void): void;
539
720
 
540
- interface PlatformerControllerOptions {
541
- /** Horizontal move speed in px/s (default 200) */
542
- speed?: number;
543
- /** Upward impulse on jump (default -500) */
544
- jumpForce?: number;
545
- /** Max consecutive jumps, e.g. 2 for double jump (default 1) */
546
- maxJumps?: number;
547
- /** Seconds after leaving ground the player can still jump (default 0.08) */
548
- coyoteTime?: number;
549
- /** Seconds to buffer a jump input before landing (default 0.08) */
550
- jumpBuffer?: number;
721
+ interface CoordinateHelpers {
551
722
  /**
552
- * Minimum seconds between jumps prevents multi-jump spam when holding the
553
- * jump key with double-jump enabled (default 0.18)
723
+ * Convert a world-space position to canvas pixel coordinates.
724
+ * Takes current camera position and zoom into account.
554
725
  */
555
- jumpCooldown?: number;
726
+ worldToScreen(wx: number, wy: number): {
727
+ x: number;
728
+ y: number;
729
+ };
556
730
  /**
557
- * Override the default key bindings. Each action accepts a single key code
558
- * or an array of key codes.
559
- *
560
- * Defaults:
561
- * left: ['ArrowLeft', 'KeyA', 'a']
562
- * right: ['ArrowRight', 'KeyD', 'd']
563
- * jump: ['Space', 'ArrowUp', 'KeyW', 'w']
731
+ * Convert canvas pixel coordinates to world-space position.
732
+ * Takes current camera position and zoom into account.
564
733
  */
565
- bindings?: {
566
- left?: string | string[];
567
- right?: string | string[];
568
- jump?: string | string[];
734
+ screenToWorld(sx: number, sy: number): {
735
+ x: number;
736
+ y: number;
569
737
  };
570
738
  }
571
739
  /**
572
- * Attaches platformer movement (customisable keys + Space/Up to jump) to an entity.
573
- * The entity must already have a RigidBody component.
740
+ * Returns helpers for converting between world-space and screen (canvas pixel) coordinates.
574
741
  *
575
742
  * @example
576
- * function Player() {
577
- * const id = useEntity()
578
- * usePlatformerController(id, { speed: 220, maxJumps: 2 })
579
- * return (
580
- * <Entity id="player">
581
- * <Transform x={100} y={300} />
582
- * <Sprite width={28} height={40} color="#4fc3f7" />
583
- * <RigidBody />
584
- * <BoxCollider width={26} height={40} />
585
- * </Entity>
586
- * )
743
+ * ```tsx
744
+ * function Minimap() {
745
+ * const { worldToScreen, screenToWorld } = useCoordinates()
746
+ * // worldToScreen(player.x, player.y) → canvas pixel position of player
587
747
  * }
748
+ * ```
588
749
  */
589
- declare function usePlatformerController(entityId: EntityId, opts?: PlatformerControllerOptions): void;
750
+ declare function useCoordinates(): CoordinateHelpers;
590
751
 
591
- interface TopDownMovementOptions {
592
- /** Movement speed in px/s (default 200) */
593
- speed?: number;
594
- /** Normalize diagonal movement to avoid faster diagonal movement (default true) */
595
- normalizeDiagonal?: boolean;
752
+ interface PreloadState {
753
+ /** 0–1 loading progress */
754
+ progress: number;
755
+ /** True when all assets have finished loading (or errored) */
756
+ loaded: boolean;
757
+ /** First error encountered, if any */
758
+ error: Error | null;
596
759
  }
597
760
  /**
598
- * Attaches 4-directional top-down movement (WASD/Arrows) to an entity.
599
- * The entity must have a RigidBody with gravityScale=0 for top-down games.
761
+ * Preloads a list of asset URLs using the engine's AssetManager.
762
+ * Returns loading progress and a `loaded` flag.
763
+ *
764
+ * Works with images (any extension) — audio goes through the engine's audio API.
600
765
  *
601
766
  * @example
602
- * <Entity id="player">
603
- * <Transform x={100} y={100} />
604
- * <Sprite width={24} height={24} color="#4fc3f7" />
605
- * <RigidBody gravityScale={0} />
606
- * <BoxCollider width={24} height={24} />
607
- * </Entity>
608
- * // In a Script or parent component:
609
- * useTopDownMovement(playerId, { speed: 180 })
767
+ * ```tsx
768
+ * function Level() {
769
+ * const { progress, loaded } = usePreload(['/hero.png', '/tiles.png'])
770
+ * if (!loaded) return <LoadingBar progress={progress} />
771
+ * return <GameScene />
772
+ * }
773
+ * ```
610
774
  */
611
- declare function useTopDownMovement(entityId: EntityId, opts?: TopDownMovementOptions): void;
775
+ declare function usePreload(assets: string[]): PreloadState;
612
776
 
613
- interface SoundControls {
614
- /** Start playing. If already playing and loop=false, restarts from the beginning. */
615
- play(): void;
616
- /** Stop the current playback. */
617
- stop(): void;
618
- /** Change the volume (0–1). */
619
- setVolume(v: number): void;
777
+ interface InputContextControls {
778
+ push(ctx: InputContextName): void;
779
+ pop(ctx: InputContextName): void;
780
+ readonly active: InputContextName;
620
781
  }
621
782
  /**
622
- * Loads and plays an audio file via the Web Audio API.
783
+ * Access and manipulate the global input context stack.
784
+ *
785
+ * If you pass a `ctx` argument, that context is automatically pushed on mount
786
+ * and popped on unmount — ideal for pause menus, cutscenes, etc.
787
+ *
788
+ * @example
789
+ * ```tsx
790
+ * function PauseMenu() {
791
+ * useInputContext('pause') // auto-push/pop
792
+ * return <div>Paused</div>
793
+ * }
794
+ * ```
795
+ */
796
+ declare function useInputContext(ctx?: InputContextName): InputContextControls;
797
+
798
+ /**
799
+ * Returns a `PlayerInput` bound to the shared InputManager for the given player ID.
623
800
  *
624
- * The AudioContext is created lazily — the first call to `play()` resumes it
625
- * if the browser suspended it before a user gesture.
801
+ * @param playerId - Player number (1-based).
802
+ * @param bindings - Action bindings for this player.
626
803
  *
627
804
  * @example
628
- * function JumpSfx() {
629
- * const { play } = useSound('/jump.wav')
630
- * // call play() on jump event
805
+ * ```tsx
806
+ * function P1({ x, y }: Props) {
807
+ * const p1 = usePlayerInput(1, { jump: 'Space', left: 'ArrowLeft', right: 'ArrowRight' })
808
+ * return (
809
+ * <Entity id="p1">
810
+ * <Script update={(id, world, input, dt) => {
811
+ * if (p1.isActionPressed('jump')) rb.vy = -400
812
+ * }} />
813
+ * </Entity>
814
+ * )
631
815
  * }
816
+ * ```
632
817
  */
633
- declare function useSound(src: string, opts?: {
634
- volume?: number;
635
- loop?: boolean;
636
- }): SoundControls;
818
+ declare function usePlayerInput(playerId: number, bindings: ActionBindings): PlayerInput;
819
+
820
+ /**
821
+ * Returns an array of `PlayerInput` objects for local multiplayer.
822
+ * Each player gets their own action bindings.
823
+ *
824
+ * @param bindingsPerPlayer - Array of binding objects, one per player.
825
+ *
826
+ * @example
827
+ * ```tsx
828
+ * const [p1, p2] = useLocalMultiplayer([
829
+ * { jump: 'Space', left: 'ArrowLeft', right: 'ArrowRight' },
830
+ * { jump: 'KeyW', left: 'KeyA', right: 'KeyD' },
831
+ * ])
832
+ * ```
833
+ */
834
+ declare function useLocalMultiplayer(bindingsPerPlayer: ActionBindings[]): PlayerInput[];
637
835
 
638
836
  /**
639
- * A lightweight game-loop timer for use inside Script update functions.
837
+ * Returns an `InputRecorderControls` instance that persists across renders.
838
+ *
839
+ * Use inside a `<Script update>` to drive recording/playback:
640
840
  *
641
841
  * @example
642
- * // Create once outside the update function (e.g. in playerInit):
643
- * const invincibleTimer = createTimer(2.0, () => { state.isInvincible = false })
644
- *
645
- * // Call update(dt) inside the Script update function each frame:
646
- * function playerUpdate(id, world, input, dt) {
647
- * invincibleTimer.update(dt)
648
- * if (someHitCondition) {
649
- * state.isInvincible = true
650
- * invincibleTimer.restart()
651
- * }
842
+ * ```tsx
843
+ * function DemoPlayer() {
844
+ * const recorder = useInputRecorder()
845
+ *
846
+ * return (
847
+ * <Script update={(id, world, input, dt) => {
848
+ * if (recorder.isRecording) {
849
+ * recorder.captureFrame([...downKeys])
850
+ * }
851
+ * if (recorder.isPlaying) {
852
+ * const frame = recorder.advancePlayback()
853
+ * // apply frame.pressedKeys to player controller
854
+ * }
855
+ * }} />
856
+ * )
652
857
  * }
858
+ * ```
653
859
  */
654
- interface GameTimer {
655
- /** Advance the timer by dt seconds. Calls onComplete when it reaches zero. */
656
- update(dt: number): void;
657
- /** Start (or resume) counting down. */
658
- start(): void;
659
- /** Pause counting without resetting. */
660
- stop(): void;
661
- /** Reset elapsed time to 0 and stop. Optionally change the duration. */
662
- reset(newDuration?: number): void;
663
- /** Reset elapsed time to 0 and immediately start. */
664
- restart(): void;
665
- /** Whether the timer is currently counting. */
666
- readonly running: boolean;
667
- /** Elapsed seconds since last reset/restart. */
668
- readonly elapsed: number;
669
- /** Remaining seconds (clamped to 0). */
670
- readonly remaining: number;
671
- /** Progress from 0 (just started) to 1 (complete). */
672
- readonly progress: number;
673
- }
674
- declare function createTimer(duration: number, onComplete?: () => void, autoStart?: boolean): GameTimer;
860
+ declare function useInputRecorder(): InputRecorderControls;
675
861
 
676
862
  interface GamepadState {
677
863
  /** Whether a gamepad is connected at this player index. */
@@ -720,65 +906,4 @@ interface PauseControls {
720
906
  */
721
907
  declare function usePause(): PauseControls;
722
908
 
723
- interface ContactOpts {
724
- /** Only fire if the other entity has this tag */
725
- tag?: string;
726
- /** Only fire if the other entity's BoxCollider is on this layer */
727
- layer?: string;
728
- }
729
- /**
730
- * Fires once when another entity's collider first overlaps this entity's trigger.
731
- * Must be used inside an `<Entity>`.
732
- *
733
- * @example
734
- * function CoinPickup() {
735
- * useTriggerEnter((other) => collectCoin(), { tag: 'player' })
736
- * return null
737
- * }
738
- */
739
- declare function useTriggerEnter(handler: (other: EntityId) => void, opts?: ContactOpts): void;
740
- /**
741
- * Fires once when an overlapping entity's collider leaves this entity's trigger.
742
- * Must be used inside an `<Entity>`.
743
- */
744
- declare function useTriggerExit(handler: (other: EntityId) => void, opts?: ContactOpts): void;
745
- /**
746
- * Fires once on the first frame two solid dynamic bodies touch.
747
- * Must be used inside an `<Entity>`.
748
- *
749
- * @example
750
- * function Enemy() {
751
- * useCollisionEnter((other) => takeDamage(), { tag: 'player' })
752
- * return null
753
- * }
754
- */
755
- declare function useCollisionEnter(handler: (other: EntityId) => void, opts?: ContactOpts): void;
756
- /**
757
- * Fires once when two solid dynamic bodies separate.
758
- * Must be used inside an `<Entity>`.
759
- */
760
- declare function useCollisionExit(handler: (other: EntityId) => void, opts?: ContactOpts): void;
761
- /**
762
- * Fires once when another entity's CircleCollider first overlaps this entity's CircleCollider.
763
- * Also fires when a CircleCollider overlaps a BoxCollider.
764
- * Must be used inside an `<Entity>`.
765
- *
766
- * @example
767
- * function Asteroid() {
768
- * useCircleEnter((other) => onHit(other), { tag: 'bullet' })
769
- * return null
770
- * }
771
- */
772
- declare function useCircleEnter(handler: (other: EntityId) => void, opts?: ContactOpts): void;
773
- /**
774
- * Fires once when two CircleCollider entities stop overlapping.
775
- * Must be used inside an `<Entity>`.
776
- */
777
- declare function useCircleExit(handler: (other: EntityId) => void, opts?: ContactOpts): void;
778
-
779
- interface DevToolsHandle {
780
- buffer: WorldSnapshot[];
781
- onFrame?: () => void;
782
- }
783
-
784
- export { Animation, type BoundInputMap, BoxCollider, Camera2D, type CameraControls, Checkpoint, CircleCollider, type DevToolsHandle, type EngineState, Entity, Game, type GameControls, type GameTimer, type GamepadState, MovingPlatform, ParallaxLayer, ParticleEmitter, type ParticlePreset, type PauseControls, type PlatformerControllerOptions, RigidBody, ScreenFlash, type ScreenFlashHandle, Script, type SnapshotControls, type SoundControls, Sprite, type SpriteAtlas, SquashStretch, Text, type TiledLayer, type TiledObject, Tilemap, type TopDownMovementOptions, Transform, World, createAtlas, createTimer, useCamera, useCircleEnter, useCircleExit, useCollisionEnter, useCollisionExit, useDestroyEntity, useEntity, useEvent, useEvents, useGame, useGamepad, useInput, useInputMap, usePause, usePlatformerController, useSnapshot, useSound, useTopDownMovement, useTriggerEnter, useTriggerExit };
909
+ export { Animation, AssetLoader, type BoundInputMap, BoxCollider, Camera2D, type CameraControls, CameraZone, CapsuleCollider, Checkpoint, CircleCollider, type CoordinateHelpers, Entity, Game, type GameControls, type GamepadState, type InputContextControls, MovingPlatform, ParallaxLayer, ParticleEmitter, type ParticlePreset, type PauseControls, type PreloadState, RigidBody, ScreenFlash, type ScreenFlashHandle, Script, type SnapshotControls, Sprite, type SpriteAtlas, SquashStretch, Text, type TiledLayer, type TiledObject, Tilemap, Trail, Transform, type VirtualInputState, VirtualJoystick, type VirtualJoystickProps, World, createAtlas, useCamera, useCoordinates, useDestroyEntity, useEntity, useEvent, useEvents, useGame, useGamepad, useInput, useInputContext, useInputMap, useInputRecorder, useLocalMultiplayer, usePause, usePlayerInput, usePreload, useSnapshot, useVirtualInput };