@zylem/behaviors 0.4.1 → 0.5.2
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/README.md +13 -1
- package/dist/{behavior-system-y8SGrGWy.d.ts → behavior-system-CldqNUgr.d.ts} +334 -19
- package/dist/{chunk-GFARAJV2.js → chunk-2PAHQZZD.js} +44 -73
- package/dist/chunk-2PAHQZZD.js.map +1 -0
- package/dist/chunk-4CEW64HO.js +210 -0
- package/dist/chunk-4CEW64HO.js.map +1 -0
- package/dist/chunk-4LLK2FJI.js +344 -0
- package/dist/chunk-4LLK2FJI.js.map +1 -0
- package/dist/chunk-4MZ57HPD.js +323 -0
- package/dist/chunk-4MZ57HPD.js.map +1 -0
- package/dist/chunk-52FJIDGL.js +30 -0
- package/dist/chunk-52FJIDGL.js.map +1 -0
- package/dist/{chunk-UYFOLI2X.js → chunk-5A5AYP2C.js} +1 -1
- package/dist/{chunk-UYFOLI2X.js.map → chunk-5A5AYP2C.js.map} +1 -1
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/{chunk-ZRQWSURL.js → chunk-5XPPLTC7.js} +2 -2
- package/dist/chunk-5XPPLTC7.js.map +1 -0
- package/dist/{chunk-VMQHNJJI.js → chunk-7DGXUAPV.js} +17 -23
- package/dist/chunk-7DGXUAPV.js.map +1 -0
- package/dist/{chunk-5SM5SH5O.js → chunk-7JLWZXPA.js} +2 -2
- package/dist/{chunk-QDCJLJUV.js → chunk-7W3KY55G.js} +22 -13
- package/dist/chunk-7W3KY55G.js.map +1 -0
- package/dist/chunk-AS3HR7AS.js +188 -0
- package/dist/chunk-AS3HR7AS.js.map +1 -0
- package/dist/chunk-BAXJ7XHJ.js +155 -0
- package/dist/chunk-BAXJ7XHJ.js.map +1 -0
- package/dist/chunk-DZ4LLQON.js +867 -0
- package/dist/chunk-DZ4LLQON.js.map +1 -0
- package/dist/{chunk-4L36OPY2.js → chunk-G2KUXVU6.js} +81 -135
- package/dist/chunk-G2KUXVU6.js.map +1 -0
- package/dist/{chunk-BXP4K7RF.js → chunk-JVADRLWX.js} +57 -186
- package/dist/chunk-JVADRLWX.js.map +1 -0
- package/dist/{chunk-ZA43LPRM.js → chunk-KIYUUSPK.js} +2 -2
- package/dist/chunk-LZIHSJZR.js +191 -0
- package/dist/chunk-LZIHSJZR.js.map +1 -0
- package/dist/{chunk-WWVD5DR5.js → chunk-M3QDKKDF.js} +2 -8
- package/dist/chunk-M3QDKKDF.js.map +1 -0
- package/dist/{chunk-FTDZAKZW.js → chunk-NQPYMUDF.js} +7 -5
- package/dist/{chunk-FTDZAKZW.js.map → chunk-NQPYMUDF.js.map} +1 -1
- package/dist/{chunk-XP3ASVGJ.js → chunk-NW4SE5XW.js} +2 -2
- package/dist/{chunk-KDY5N6J4.js → chunk-OPZ2LVS2.js} +1 -22
- package/dist/{chunk-KDY5N6J4.js.map → chunk-OPZ2LVS2.js.map} +1 -1
- package/dist/{chunk-5XZWTFGF.js → chunk-PLIXVF7B.js} +3 -3
- package/dist/chunk-PLIXVF7B.js.map +1 -0
- package/dist/{chunk-27UPCP5B.js → chunk-QS3VXWVO.js} +22 -13
- package/dist/chunk-QS3VXWVO.js.map +1 -0
- package/dist/{chunk-Y7YIAWPW.js → chunk-SGHIGTVZ.js} +1 -11
- package/dist/{chunk-Y7YIAWPW.js.map → chunk-SGHIGTVZ.js.map} +1 -1
- package/dist/{chunk-UL6Q5HDP.js → chunk-TBUKJKY6.js} +3 -25
- package/dist/chunk-TBUKJKY6.js.map +1 -0
- package/dist/{chunk-Z72F2ENM.js → chunk-WU6XXL3D.js} +17 -26
- package/dist/chunk-WU6XXL3D.js.map +1 -0
- package/dist/{chunk-A4CA35DQ.js → chunk-Y4PHT2HG.js} +25 -45
- package/dist/chunk-Y4PHT2HG.js.map +1 -0
- package/dist/components-BmievOxF.d.ts +30 -0
- package/dist/cooldown.d.ts +2 -2
- package/dist/cooldown.js +2 -1
- package/dist/coordinators/boundary-ricochet-3d.d.ts +4 -4
- package/dist/coordinators/boundary-ricochet-3d.js +1 -0
- package/dist/coordinators/boundary-ricochet.d.ts +4 -4
- package/dist/coordinators/boundary-ricochet.js +3 -2
- package/dist/coordinators/first-person-shooter.js +1 -0
- package/dist/coordinators/multidirectional-space-shooter.d.ts +3 -3
- package/dist/coordinators/multidirectional-space-shooter.js +5 -3
- package/dist/coordinators/top-down-shooter.d.ts +3 -3
- package/dist/coordinators/top-down-shooter.js +5 -3
- package/dist/core.d.ts +298 -8
- package/dist/core.js +502 -14
- package/dist/core.js.map +1 -1
- package/dist/destructible-3d.d.ts +32 -4
- package/dist/destructible-3d.js +2 -1
- package/dist/destructible-prebake.js +1 -0
- package/dist/{entity-types-CmtQ-IBp.d.ts → entity-types-DhfsZbSK.d.ts} +5 -3
- package/dist/first-person.d.ts +27 -38
- package/dist/first-person.js +8 -8
- package/dist/index.d.ts +14 -15
- package/dist/index.js +38 -53
- package/dist/jumper-2d.d.ts +3 -17
- package/dist/jumper-2d.js +4 -9
- package/dist/jumper-3d.d.ts +5 -39
- package/dist/jumper-3d.js +5 -10
- package/dist/{moveable-Cl9h-_w7.d.ts → moveable-rdIvWiak.d.ts} +21 -4
- package/dist/particle-emitter/presets.js +1 -0
- package/dist/particle-emitter.d.ts +3 -3
- package/dist/particle-emitter.js +3 -2
- package/dist/platformer-2d.d.ts +21 -82
- package/dist/platformer-2d.js +4 -5
- package/dist/platformer-3d.d.ts +21 -78
- package/dist/platformer-3d.js +4 -5
- package/dist/ricochet-2d.d.ts +3 -3
- package/dist/ricochet-2d.js +4 -3
- package/dist/ricochet-3d.d.ts +3 -3
- package/dist/ricochet-3d.js +4 -3
- package/dist/runtime-2d.d.ts +2 -2
- package/dist/runtime-2d.js +1 -0
- package/dist/runtime-2d.js.map +1 -1
- package/dist/screen-visibility.d.ts +2 -2
- package/dist/screen-visibility.js +1 -0
- package/dist/screen-wrap.d.ts +2 -2
- package/dist/screen-wrap.js +4 -3
- package/dist/shooter-2d.d.ts +3 -3
- package/dist/shooter-2d.js +4 -3
- package/dist/thruster.d.ts +5 -34
- package/dist/thruster.js +4 -6
- package/dist/top-down-movement.d.ts +9 -27
- package/dist/top-down-movement.js +6 -6
- package/dist/{vector-Bm65-FOG.d.ts → vector-DjhLUrak.d.ts} +2 -4
- package/dist/world-boundary-2d.d.ts +2 -2
- package/dist/world-boundary-2d.js +5 -4
- package/dist/world-boundary-3d.d.ts +2 -2
- package/dist/world-boundary-3d.js +4 -3
- package/package.json +4 -5
- package/dist/chunk-27UPCP5B.js.map +0 -1
- package/dist/chunk-4L36OPY2.js.map +0 -1
- package/dist/chunk-4URF65BR.js +0 -44
- package/dist/chunk-4URF65BR.js.map +0 -1
- package/dist/chunk-5XZWTFGF.js.map +0 -1
- package/dist/chunk-67XLJ26X.js +0 -536
- package/dist/chunk-67XLJ26X.js.map +0 -1
- package/dist/chunk-75FJIUIG.js +0 -240
- package/dist/chunk-75FJIUIG.js.map +0 -1
- package/dist/chunk-A4CA35DQ.js.map +0 -1
- package/dist/chunk-BXP4K7RF.js.map +0 -1
- package/dist/chunk-F72WX4MH.js +0 -422
- package/dist/chunk-F72WX4MH.js.map +0 -1
- package/dist/chunk-GFARAJV2.js.map +0 -1
- package/dist/chunk-JXMF4NUI.js +0 -309
- package/dist/chunk-JXMF4NUI.js.map +0 -1
- package/dist/chunk-OTLSUYQQ.js +0 -29
- package/dist/chunk-OTLSUYQQ.js.map +0 -1
- package/dist/chunk-Q75LKHDF.js +0 -56
- package/dist/chunk-Q75LKHDF.js.map +0 -1
- package/dist/chunk-QDCJLJUV.js.map +0 -1
- package/dist/chunk-RDHSHEZM.js +0 -479
- package/dist/chunk-RDHSHEZM.js.map +0 -1
- package/dist/chunk-UIG7MWS2.js +0 -518
- package/dist/chunk-UIG7MWS2.js.map +0 -1
- package/dist/chunk-UL6Q5HDP.js.map +0 -1
- package/dist/chunk-VMQHNJJI.js.map +0 -1
- package/dist/chunk-WWVD5DR5.js.map +0 -1
- package/dist/chunk-Z72F2ENM.js.map +0 -1
- package/dist/chunk-ZRQWSURL.js.map +0 -1
- package/dist/components-BWG1GkZA.d.ts +0 -20
- package/dist/use-behavior-B85d5u7-.d.ts +0 -13
- /package/dist/{chunk-5SM5SH5O.js.map → chunk-7JLWZXPA.js.map} +0 -0
- /package/dist/{chunk-ZA43LPRM.js.map → chunk-KIYUUSPK.js.map} +0 -0
- /package/dist/{chunk-XP3ASVGJ.js.map → chunk-NW4SE5XW.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/platformer-3d/components.ts","../src/lib/behaviors/platformer-3d/platformer-3d-fsm.ts","../src/lib/behaviors/platformer-3d/platformer-3d.descriptor.ts"],"sourcesContent":["/**\n * Platformer 3D ECS Components\n * \n * Components for 3D platformer movement system including walking, running,\n * jumping with multi-jump support, and falling/landing states.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DMovementComponent (capability / configuration)\n// Defines the movement capabilities and physics parameters\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DMovementComponent {\n\t/** Base walking speed */\n\twalkSpeed: number;\n\t/** Sprint/run speed */\n\trunSpeed: number;\n\t/** Initial jump force/velocity */\n\tjumpForce: number;\n\t/** Maximum number of jumps (1 = single jump, 2 = double jump, etc.) */\n\tmaxJumps: number;\n\t/** Gravity force (optional override) */\n\tgravity: number;\n\t/** Coyote time in seconds (grace period after leaving ground) */\n\tcoyoteTime: number;\n\t/** Jump buffer time in seconds (queue jump input before landing) */\n\tjumpBufferTime: number;\n\t/** Velocity multiplier when releasing jump button early (0-1) */\n\tjumpCutMultiplier: number;\n\t/** Time in seconds before multi-jump becomes available (0 = after button release) */\n\tmultiJumpWindowTime: number;\n\t/** Maximum slope angle (degrees) the character can walk up */\n\tslopeClimbAngle: number;\n\t/** Slope angle (degrees) past which the character slides down */\n\tslopeSlideAngle: number;\n\t/** Autostep configuration for small ledges, or false to disable */\n\tautostep: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance (keeps the character glued going downhill) */\n\tsnapToGroundDistance: number;\n\t/** Character controller offset (small gap kept between collider and environment) */\n\tcharacterOffset: number;\n}\n\nexport function createPlatformer3DMovementComponent(\n\toptions: Partial<Platformer3DMovementComponent> = {}\n): Platformer3DMovementComponent {\n\treturn {\n\t\twalkSpeed: options.walkSpeed ?? 12,\n\t\trunSpeed: options.runSpeed ?? 24,\n\t\tjumpForce: options.jumpForce ?? 12,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tgravity: options.gravity ?? 9.82,\n\t\tcoyoteTime: options.coyoteTime ?? 0.1,\n\t\tjumpBufferTime: options.jumpBufferTime ?? 0.1,\n\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,\n\t\tmultiJumpWindowTime: options.multiJumpWindowTime ?? 0.15, // 150ms default\n\t\tslopeClimbAngle: options.slopeClimbAngle ?? 50,\n\t\tslopeSlideAngle: options.slopeSlideAngle ?? 40,\n\t\tautostep: options.autostep ?? { maxHeight: 0.3, minWidth: 0.1 },\n\t\tsnapToGroundDistance: options.snapToGroundDistance ?? 0.3,\n\t\tcharacterOffset: options.characterOffset ?? 0.05,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DInputComponent (intent)\n// Written by: Player controller, AI controller\n// Read by: Platformer3DBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DInputComponent {\n\t/** Horizontal movement input (-1 to 1) */\n\tmoveX: number;\n\t/** Forward/backward movement input (-1 to 1) */\n\tmoveZ: number;\n\t/** Jump button pressed */\n\tjump: boolean;\n\t/** Run/sprint button held */\n\trun: boolean;\n}\n\nexport function createPlatformer3DInputComponent(): Platformer3DInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tmoveZ: 0,\n\t\tjump: false,\n\t\trun: false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DStateComponent (runtime state)\n// Tracks the current state of the platformer entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DStateComponent {\n\t/** Is the entity on the ground */\n\tgrounded: boolean;\n\t/** Is currently in a jump arc */\n\tjumping: boolean;\n\t/** Is falling (not jumping) */\n\tfalling: boolean;\n\t/** Current jump count (for multi-jump) */\n\tjumpCount: number;\n\t/** Y position when jump started */\n\tjumpStartHeight: number;\n\t/** Current movement speed being applied */\n\tcurrentSpeed: number;\n\t/** Last known ground Y position */\n\tlastGroundedY: number;\n\t/** Was jump button pressed last frame (for edge detection) */\n\tjumpPressedLastFrame: boolean;\n\t\n\t// -- New fields --\n\t/** Is currently colliding with ground (from physics collision callback) */\n\tcollisionGrounded: boolean;\n\t/** Timestamp of last ground collision */\n\tgroundedCollisionTime: number;\n\t/** Time in seconds since last grounded */\n\ttimeSinceGrounded: number;\n\t/** Is a jump allowed due to buffer? */\n\tjumpBuffered: boolean;\n\t/** Time remaining in jump buffer */\n\tjumpBufferTimer: number;\n\t/** Is jump button currently held */\n\tjumpHeld: boolean;\n\t/** Has the jump cut been applied for this jump? */\n\tjumpCutApplied: boolean;\n\t/** Has jump button been released since last jump (required for multi-jump) */\n\tjumpReleasedSinceLastJump: boolean;\n\t/** Time since current jump started (for multi-jump window) */\n\ttimeSinceJump: number;\n\t/** True for the single frame on which a jump impulse was applied; prevents gravity from clobbering jump velocity. */\n\tjumpedThisFrame: boolean;\n\t/** Remaining seconds of jump-cut velocity ramp; while > 0, verticalVelocity lerps toward verticalVelocity * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/**\n\t * Controller-owned vertical velocity (units/second).\n\t * The body is kinematic, so `linvel()` is meaningless — this field is the\n\t * single source of truth for jump/gravity motion on the Y axis.\n\t */\n\tverticalVelocity: number;\n}\n\nexport function createPlatformer3DStateComponent(): Platformer3DStateComponent {\n\treturn {\n\t\tgrounded: false,\n\t\tjumping: false,\n\t\tfalling: false,\n\t\tjumpCount: 0,\n\t\tjumpStartHeight: 0,\n\t\tcurrentSpeed: 0,\n\t\tlastGroundedY: 0,\n\t\tjumpPressedLastFrame: false,\n\t\tcollisionGrounded: false,\n\t\tgroundedCollisionTime: 0,\n\t\ttimeSinceGrounded: 0,\n\t\tjumpBuffered: false,\n\t\tjumpBufferTimer: 0,\n\t\tjumpHeld: false,\n\t\tjumpCutApplied: false,\n\t\tjumpReleasedSinceLastJump: true,\n\t\ttimeSinceJump: 0,\n\t\tjumpedThisFrame: false,\n\t\tjumpCutRampTimer: 0,\n\t\tverticalVelocity: 0,\n\t};\n}\n","/**\n * Platformer 3D Finite State Machine\n * \n * Manages state transitions for 3D platformer movement:\n * - Idle: Standing still on ground\n * - Walking: Moving on ground (slow)\n * - Running: Moving on ground (fast)\n * - Jumping: Ascending from jump\n * - Falling: Descending (gravity)\n * - Landing: Just touched ground\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { Platformer3DInputComponent, Platformer3DStateComponent } from './components';\n\n/**\n * Simplified Collision Context interface\n */\nexport interface PlatformerCollisionContext {\n\tcontact: {\n\t\tnormal: { x: number; y: number; z?: number };\n\t};\n}\n\n/**\n * Platformer states\n */\nexport enum Platformer3DState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n\tLanding = 'landing',\n}\n\n/**\n * Events that trigger state transitions\n */\nexport enum Platformer3DEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n\tStop = 'stop',\n}\n\n/**\n * Context for the FSM\n */\nexport interface Platformer3DContext {\n\tinput: Platformer3DInputComponent;\n\tstate: Platformer3DStateComponent;\n}\n\n/**\n * Platformer 3D FSM\n */\nexport class Platformer3DFSM {\n\tmachine: SyncStateMachine<Platformer3DState, Platformer3DEvent, never>;\n\n\tconstructor(private ctx: Platformer3DContext) {\n\t\tthis.machine = new SyncStateMachine<Platformer3DState, Platformer3DEvent, never>(\n\t\t\tPlatformer3DState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Walking transitions\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Running transitions\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Jumping transitions\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Jump, Platformer3DState.Jumping), // Multi-jump\n\t\t\t\t\n\t\t\t\t// Falling transitions\n\t\t\t\tt(Platformer3DState.Falling, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\t\n\t\t\t\t// Landing transitions\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\t\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get the current state\n\t */\n\tgetState(): Platformer3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to the FSM\n\t */\n\tdispatch(event: Platformer3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Check if grounded\n\t */\n\tisGrounded(): boolean {\n\t\tconst state = this.getState();\n\t\treturn state === Platformer3DState.Idle || \n\t\t state === Platformer3DState.Walking || \n\t\t state === Platformer3DState.Running || \n\t\t state === Platformer3DState.Landing;\n\t}\n\n\t/**\n\t * Get current jump count from context\n\t */\n\tgetJumpCount(): number {\n\t\treturn this.ctx.state.jumpCount;\n\t}\n\n\t/**\n\t * Handle collision event to update ground state\n\t */\n\thandleCollision(ctx: PlatformerCollisionContext): void {\n\t\t// Check if collision is ground (normal pointing up)\n\t\t// Y normal > 0.5 means roughly within 60 degrees of up\n\t\tif (ctx.contact.normal.y > 0.5) {\n\t\t\tthis.ctx.state.collisionGrounded = true;\n\t\t\tthis.ctx.state.groundedCollisionTime = performance.now();\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM based on current state\n\t */\n\tupdate(input: Platformer3DInputComponent, state: Platformer3DStateComponent): void {\n\t\t// Update context\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\t// Get current FSM state\n\t\tconst currentState = this.getState();\n\n\t\t// Determine movement state\n\t\tconst hasInput = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\t\tconst isRunning = input.run;\n\n\t\t// Handle landing: if we're in falling state and now grounded, land\n\t\tif (currentState === Platformer3DState.Falling && state.grounded) {\n\t\t\tthis.dispatch(Platformer3DEvent.Land);\n\t\t\t// Don't return - allow immediate transition to movement state\n\t\t}\n\n\t\t// Send appropriate events based on state\n\t\tif (state.grounded) {\n\t\t\tif (hasInput) {\n\t\t\t\tif (isRunning) {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Run);\n\t\t\t\t} else {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Walk);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Stop);\n\t\t\t}\n\t\t} else {\n\t\t\t// Not grounded - check if we should be falling\n\t\t\t// Transition to falling if: We're in jumping state and now falling, \n\t\t\t// OR we walked off a ledge (idle/walking/running and now falling)\n\t\t\tif (state.falling) {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Fall);\n\t\t\t}\n\t\t}\n\n\t\t// Handle jump input (edge detection handled in behavior)\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tthis.dispatch(Platformer3DEvent.Jump);\n\t\t}\n\t}\n}\n","/**\n * Platformer 3D Behavior Descriptor\n * \n * Type-safe descriptor for the platformer 3D behavior system.\n * Provides entity.use() API for 3D platformer movement.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Platformer3DState, PlatformerCollisionContext } from './platformer-3d-fsm';\nimport {\n\tcreatePlatformer3DMovementComponent,\n\tcreatePlatformer3DInputComponent,\n\tcreatePlatformer3DStateComponent,\n\ttype Platformer3DMovementComponent,\n\ttype Platformer3DInputComponent,\n\ttype Platformer3DStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_JUMP,\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\tStagePlatformerShapeSource,\n\ttype BehaviorRuntime,\n\ttype StagePlatformer3DOptions,\n} from '../../runtime/wasm-stage-runtime';\n\n/** Rust `Platformer3DFsmState` index → TS state (see `platformer_3d/fsm.rs`). */\nconst FSM_STATE_BY_INDEX: readonly Platformer3DState[] = [\n\tPlatformer3DState.Idle,\n\tPlatformer3DState.Walking,\n\tPlatformer3DState.Running,\n\tPlatformer3DState.Jumping,\n\tPlatformer3DState.Falling,\n\tPlatformer3DState.Landing,\n];\n\n/**\n * Platformer behavior options (typed for entity.use() autocomplete)\n */\nexport interface Platformer3DBehaviorOptions {\n\t/** Base walking speed (default: 12) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 24) */\n\trunSpeed?: number;\n\t/** Initial jump force (default: 12) */\n\tjumpForce?: number;\n\t/** Maximum number of jumps (default: 1) */\n\tmaxJumps?: number;\n\t/** Gravity force (default: 9.82) */\n\tgravity?: number;\n\t/** Maximum slope angle in degrees the character can walk up (default: 50) */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default: 40) */\n\tslopeSlideAngle?: number;\n\t/** Autostep over small ledges, or false to disable (default: { maxHeight: 0.3, minWidth: 0.1 }) */\n\tautostep?: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance for downhill/step-down stability (default: 0.3) */\n\tsnapToGroundDistance?: number;\n\t/** Character controller collision offset (default: 0.05) */\n\tcharacterOffset?: number;\n\t/** Explicit KCC capsule override (requires `useAttachedCollider: false`). */\n\tshape?: {\n\t\thalfHeight: number;\n\t\tradius: number;\n\t\toffset?: [number, number, number];\n\t};\n\t/** When true (default), KCC reads shape + offset from the attached Rapier collider. */\n\tuseAttachedCollider?: boolean;\n}\n\nconst defaultOptions: Platformer3DBehaviorOptions = {\n\twalkSpeed: 12,\n\trunSpeed: 24,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 9.82,\n\tslopeClimbAngle: 50,\n\tslopeSlideAngle: 40,\n\tautostep: { maxHeight: 0.3, minWidth: 0.1 },\n\tsnapToGroundDistance: 0.3,\n\tcharacterOffset: 0.05,\n};\n\nconst PLATFORMER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-3d');\n\nfunction resolvePlatformerAttachShape(\n\toptions: Platformer3DBehaviorOptions,\n\tgameEntity?: {\n\t\tcolliderDesc?: {\n\t\t\tshape?: { type?: string; halfHeight?: number; radius?: number };\n\t\t\toffset?: [number, number, number];\n\t\t};\n\t},\n): Pick<StagePlatformer3DOptions, 'halfHeight' | 'radius' | 'offsetX' | 'offsetY' | 'offsetZ' | 'shapeSource'> {\n\tconst fallback = {\n\t\thalfHeight: 0.9,\n\t\tradius: 0.4,\n\t\toffsetX: 0,\n\t\toffsetY: 0,\n\t\toffsetZ: 0,\n\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t};\n\n\tif (options.useAttachedCollider === false && options.shape) {\n\t\tconst offset = options.shape.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: options.shape.halfHeight,\n\t\t\tradius: options.shape.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Override,\n\t\t};\n\t}\n\n\tconst collider = gameEntity?.colliderDesc;\n\tif (collider?.shape?.type === 'capsule') {\n\t\tconst offset = collider.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: collider.shape.halfHeight ?? fallback.halfHeight,\n\t\t\tradius: collider.shape.radius ?? fallback.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t\t};\n\t}\n\n\treturn fallback;\n}\n\n/**\n * Entity with platformer components (input written by the game; the wasm\n * runtime owns the kinematic character controller and FSM).\n */\nexport interface Platformer3DEntity {\n\tuuid: string;\n\tplatformer: Platformer3DMovementComponent;\n\t$platformer: Platformer3DInputComponent;\n\tplatformerState: Platformer3DStateComponent;\n}\n\n/**\n * Forwards platformer input into the wasm runtime. The Rust `platformer_3d`\n * Stage behavior (KCC + jump FSM) is the single simulation path; handles\n * read its state back via the snapshot buffer.\n */\nclass Platformer3DBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PLATFORMER_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst platformerRef = link.ref as any;\n\n\t\t\tconst options = platformerRef.options as Platformer3DBehaviorOptions;\n\n\t\t\tif (!gameEntity.platformer) {\n\t\t\t\tgameEntity.platformer = createPlatformer3DMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$platformer) {\n\t\t\t\tgameEntity.$platformer = createPlatformer3DInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.platformerState) {\n\t\t\t\tgameEntity.platformerState = createPlatformer3DStateComponent();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options, gameEntity);\n\t\t\tplatformerRef.__wasmStage = this.wasmStage;\n\t\t\tplatformerRef.__entity = gameEntity;\n\t\t\tconst input = gameEntity.$platformer;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(\n\t\thandle: number,\n\t\toptions: Platformer3DBehaviorOptions,\n\t\tgameEntity?: { colliderDesc?: { shape?: { type?: string; halfHeight?: number; radius?: number }; offset?: [number, number, number] } },\n\t): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst autostep = options.autostep ?? defaultOptions.autostep!;\n\t\tconst shape = resolvePlatformerAttachShape(options, gameEntity);\n\t\tthis.wasmStage.attachPlatformer3D(handle, {\n\t\t\t...shape,\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\tjumpForce: options.jumpForce ?? defaultOptions.jumpForce!,\n\t\t\tmaxJumps: options.maxJumps ?? defaultOptions.maxJumps!,\n\t\t\tgravity: options.gravity ?? defaultOptions.gravity!,\n\t\t\tslopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle!,\n\t\t\tslopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle!,\n\t\t\tautostepHeight: autostep ? autostep.maxHeight : 0,\n\t\t\tautostepMinWidth: autostep ? autostep.minWidth : 0.1,\n\t\t\tsnapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance!,\n\t\t\tcharacterOffset: options.characterOffset ?? defaultOptions.characterOffset!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Platformer3DBehavior - typed descriptor for 3D platformer movement\n * \n * Provides complete 3D platformer physics including:\n * - Walking and running\n * - Jumping with multi-jump support\n * - Gravity and falling\n * - Ground detection\n * \n * @example\n * ```typescript\n * import { Platformer3DBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const player = await playgroundActor('player');\n * const platformer = player.use(Platformer3DBehavior, {\n * walkSpeed: 12,\n * runSpeed: 24,\n * jumpForce: 12,\n * maxJumps: 2, // Double jump!\n * });\n * \n * // In update loop\n * player.onUpdate(({ inputs }) => {\n * player.$platformer.moveX = inputs.p1.axes.Horizontal.value;\n * player.$platformer.moveZ = inputs.p1.axes.Vertical.value;\n * player.$platformer.jump = inputs.p1.buttons.A.held > 0;\n * player.$platformer.run = inputs.p1.shoulders.LTrigger.held > 0;\n * \n * const state = platformer.getState();\n * const grounded = platformer.isGrounded();\n * });\n * ```\n */\nexport const Platformer3DBehavior = defineBehavior<\n\tPlatformer3DBehaviorOptions,\n\t{\n\t\tgetState: () => Platformer3DState;\n\t\tisGrounded: () => boolean;\n\t\tgetJumpCount: () => number;\n\t\tonPlatformCollision: (ctx: PlatformerCollisionContext) => void;\n\t},\n\tPlatformer3DEntity\n>({\n\tname: 'platformer-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Platformer3DBehaviorSystem(\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\t// Handles read the runtime snapshot buffer (values are from the end of\n\t// the previous step — one-frame lag). The Rust FSM is the single source\n\t// of truth.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? Platformer3DState.Idle;\n\t\t\treturn Platformer3DState.Idle;\n\t\t},\n\t\tisGrounded: () => readBehaviorSnapshot(ref)?.grounded ?? false,\n\t\tgetJumpCount: () => readBehaviorSnapshot(ref)?.jumpCount ?? 0,\n\t\tonPlatformCollision: (_ctx) => {},\n\t}),\n});\n"],"mappings":"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","names":["Platformer3DState","Platformer3DEvent"]}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SyncStateMachine,
|
|
3
|
+
import_typescript_fsm
|
|
4
|
+
} from "./chunk-AS3HR7AS.js";
|
|
5
|
+
import {
|
|
6
|
+
STAGE_INPUT_BUTTON_JUMP,
|
|
7
|
+
STAGE_INPUT_BUTTON_RUN,
|
|
8
|
+
StagePlatformerShapeSource,
|
|
9
|
+
readBehaviorSnapshot
|
|
10
|
+
} from "./chunk-DZ4LLQON.js";
|
|
11
|
+
import {
|
|
12
|
+
defineBehavior
|
|
13
|
+
} from "./chunk-EZNCCO4L.js";
|
|
14
|
+
|
|
15
|
+
// src/lib/behaviors/platformer-2d/components.ts
|
|
16
|
+
function createPlatformer2DMovementComponent(options = {}) {
|
|
17
|
+
return {
|
|
18
|
+
walkSpeed: options.walkSpeed ?? 8,
|
|
19
|
+
runSpeed: options.runSpeed ?? 14,
|
|
20
|
+
jumpForce: options.jumpForce ?? 12,
|
|
21
|
+
maxJumps: options.maxJumps ?? 1,
|
|
22
|
+
gravity: options.gravity ?? 20,
|
|
23
|
+
coyoteTime: options.coyoteTime ?? 0.1,
|
|
24
|
+
jumpBufferTime: options.jumpBufferTime ?? 0.1,
|
|
25
|
+
jumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,
|
|
26
|
+
multiJumpWindowTime: options.multiJumpWindowTime ?? 0.15,
|
|
27
|
+
slopeClimbAngle: options.slopeClimbAngle ?? 50,
|
|
28
|
+
slopeSlideAngle: options.slopeSlideAngle ?? 40,
|
|
29
|
+
autostep: options.autostep ?? { maxHeight: 0.25, minWidth: 0.05 },
|
|
30
|
+
snapToGroundDistance: options.snapToGroundDistance ?? 0.2,
|
|
31
|
+
characterOffset: options.characterOffset ?? 0.05
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createPlatformer2DInputComponent() {
|
|
35
|
+
return {
|
|
36
|
+
moveX: 0,
|
|
37
|
+
jump: false,
|
|
38
|
+
run: false
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function createPlatformer2DStateComponent() {
|
|
42
|
+
return {
|
|
43
|
+
grounded: false,
|
|
44
|
+
jumping: false,
|
|
45
|
+
falling: false,
|
|
46
|
+
jumpCount: 0,
|
|
47
|
+
jumpStartHeight: 0,
|
|
48
|
+
currentSpeed: 0,
|
|
49
|
+
lastGroundedY: 0,
|
|
50
|
+
jumpPressedLastFrame: false,
|
|
51
|
+
timeSinceGrounded: 0,
|
|
52
|
+
jumpBuffered: false,
|
|
53
|
+
jumpBufferTimer: 0,
|
|
54
|
+
jumpHeld: false,
|
|
55
|
+
jumpCutApplied: false,
|
|
56
|
+
jumpReleasedSinceLastJump: true,
|
|
57
|
+
timeSinceJump: 0,
|
|
58
|
+
jumpedThisFrame: false,
|
|
59
|
+
jumpCutRampTimer: 0,
|
|
60
|
+
verticalVelocity: 0
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/lib/behaviors/platformer-2d/platformer-2d-fsm.ts
|
|
65
|
+
var Platformer2DState = /* @__PURE__ */ ((Platformer2DState2) => {
|
|
66
|
+
Platformer2DState2["Idle"] = "idle";
|
|
67
|
+
Platformer2DState2["Walking"] = "walking";
|
|
68
|
+
Platformer2DState2["Running"] = "running";
|
|
69
|
+
Platformer2DState2["Jumping"] = "jumping";
|
|
70
|
+
Platformer2DState2["Falling"] = "falling";
|
|
71
|
+
Platformer2DState2["Landing"] = "landing";
|
|
72
|
+
return Platformer2DState2;
|
|
73
|
+
})(Platformer2DState || {});
|
|
74
|
+
var Platformer2DEvent = /* @__PURE__ */ ((Platformer2DEvent2) => {
|
|
75
|
+
Platformer2DEvent2["Walk"] = "walk";
|
|
76
|
+
Platformer2DEvent2["Run"] = "run";
|
|
77
|
+
Platformer2DEvent2["Jump"] = "jump";
|
|
78
|
+
Platformer2DEvent2["Fall"] = "fall";
|
|
79
|
+
Platformer2DEvent2["Land"] = "land";
|
|
80
|
+
Platformer2DEvent2["Stop"] = "stop";
|
|
81
|
+
return Platformer2DEvent2;
|
|
82
|
+
})(Platformer2DEvent || {});
|
|
83
|
+
var Platformer2DFSM = class {
|
|
84
|
+
constructor(ctx) {
|
|
85
|
+
this.ctx = ctx;
|
|
86
|
+
this.machine = new SyncStateMachine(
|
|
87
|
+
"idle" /* Idle */,
|
|
88
|
+
[
|
|
89
|
+
// Idle transitions
|
|
90
|
+
(0, import_typescript_fsm.t)("idle" /* Idle */, "walk" /* Walk */, "walking" /* Walking */),
|
|
91
|
+
(0, import_typescript_fsm.t)("idle" /* Idle */, "run" /* Run */, "running" /* Running */),
|
|
92
|
+
(0, import_typescript_fsm.t)("idle" /* Idle */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
93
|
+
(0, import_typescript_fsm.t)("idle" /* Idle */, "fall" /* Fall */, "falling" /* Falling */),
|
|
94
|
+
// Walking transitions
|
|
95
|
+
(0, import_typescript_fsm.t)("walking" /* Walking */, "run" /* Run */, "running" /* Running */),
|
|
96
|
+
(0, import_typescript_fsm.t)("walking" /* Walking */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
97
|
+
(0, import_typescript_fsm.t)("walking" /* Walking */, "stop" /* Stop */, "idle" /* Idle */),
|
|
98
|
+
(0, import_typescript_fsm.t)("walking" /* Walking */, "fall" /* Fall */, "falling" /* Falling */),
|
|
99
|
+
// Running transitions
|
|
100
|
+
(0, import_typescript_fsm.t)("running" /* Running */, "walk" /* Walk */, "walking" /* Walking */),
|
|
101
|
+
(0, import_typescript_fsm.t)("running" /* Running */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
102
|
+
(0, import_typescript_fsm.t)("running" /* Running */, "stop" /* Stop */, "idle" /* Idle */),
|
|
103
|
+
(0, import_typescript_fsm.t)("running" /* Running */, "fall" /* Fall */, "falling" /* Falling */),
|
|
104
|
+
// Jumping transitions
|
|
105
|
+
(0, import_typescript_fsm.t)("jumping" /* Jumping */, "fall" /* Fall */, "falling" /* Falling */),
|
|
106
|
+
(0, import_typescript_fsm.t)("jumping" /* Jumping */, "land" /* Land */, "landing" /* Landing */),
|
|
107
|
+
(0, import_typescript_fsm.t)("jumping" /* Jumping */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
108
|
+
// Multi-jump
|
|
109
|
+
// Falling transitions
|
|
110
|
+
(0, import_typescript_fsm.t)("falling" /* Falling */, "land" /* Land */, "landing" /* Landing */),
|
|
111
|
+
(0, import_typescript_fsm.t)("falling" /* Falling */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
112
|
+
// Air jump
|
|
113
|
+
// Landing transitions
|
|
114
|
+
(0, import_typescript_fsm.t)("landing" /* Landing */, "walk" /* Walk */, "walking" /* Walking */),
|
|
115
|
+
(0, import_typescript_fsm.t)("landing" /* Landing */, "run" /* Run */, "running" /* Running */),
|
|
116
|
+
(0, import_typescript_fsm.t)("landing" /* Landing */, "stop" /* Stop */, "idle" /* Idle */),
|
|
117
|
+
// Self-transitions (no-ops)
|
|
118
|
+
(0, import_typescript_fsm.t)("idle" /* Idle */, "stop" /* Stop */, "idle" /* Idle */)
|
|
119
|
+
]
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
ctx;
|
|
123
|
+
machine;
|
|
124
|
+
getState() {
|
|
125
|
+
return this.machine.getState();
|
|
126
|
+
}
|
|
127
|
+
dispatch(event) {
|
|
128
|
+
if (this.machine.can(event)) {
|
|
129
|
+
this.machine.syncDispatch(event);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
isGrounded() {
|
|
133
|
+
const state = this.getState();
|
|
134
|
+
return state === "idle" /* Idle */ || state === "walking" /* Walking */ || state === "running" /* Running */ || state === "landing" /* Landing */;
|
|
135
|
+
}
|
|
136
|
+
getJumpCount() {
|
|
137
|
+
return this.ctx.state.jumpCount;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Update FSM based on current component state.
|
|
141
|
+
*/
|
|
142
|
+
update(input, state) {
|
|
143
|
+
this.ctx.input = input;
|
|
144
|
+
this.ctx.state = state;
|
|
145
|
+
const currentState = this.getState();
|
|
146
|
+
const hasInput = Math.abs(input.moveX) > 0.1;
|
|
147
|
+
const isRunning = input.run;
|
|
148
|
+
if (currentState === "falling" /* Falling */ && state.grounded) {
|
|
149
|
+
this.dispatch("land" /* Land */);
|
|
150
|
+
}
|
|
151
|
+
if (state.grounded) {
|
|
152
|
+
if (hasInput) {
|
|
153
|
+
if (isRunning) {
|
|
154
|
+
this.dispatch("run" /* Run */);
|
|
155
|
+
} else {
|
|
156
|
+
this.dispatch("walk" /* Walk */);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
this.dispatch("stop" /* Stop */);
|
|
160
|
+
}
|
|
161
|
+
} else if (state.falling) {
|
|
162
|
+
this.dispatch("fall" /* Fall */);
|
|
163
|
+
}
|
|
164
|
+
if (input.jump && !state.jumpPressedLastFrame) {
|
|
165
|
+
this.dispatch("jump" /* Jump */);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/lib/behaviors/platformer-2d/platformer-2d.descriptor.ts
|
|
171
|
+
var FSM_STATE_BY_INDEX = [
|
|
172
|
+
"idle" /* Idle */,
|
|
173
|
+
"walking" /* Walking */,
|
|
174
|
+
"running" /* Running */,
|
|
175
|
+
"jumping" /* Jumping */,
|
|
176
|
+
"falling" /* Falling */,
|
|
177
|
+
"landing" /* Landing */
|
|
178
|
+
];
|
|
179
|
+
var defaultOptions = {
|
|
180
|
+
walkSpeed: 8,
|
|
181
|
+
runSpeed: 14,
|
|
182
|
+
jumpForce: 12,
|
|
183
|
+
maxJumps: 1,
|
|
184
|
+
gravity: 20,
|
|
185
|
+
coyoteTime: 0.1,
|
|
186
|
+
jumpBufferTime: 0.1,
|
|
187
|
+
jumpCutMultiplier: 0.5,
|
|
188
|
+
slopeClimbAngle: 50,
|
|
189
|
+
slopeSlideAngle: 40,
|
|
190
|
+
autostep: { maxHeight: 0.25, minWidth: 0.05 },
|
|
191
|
+
snapToGroundDistance: 0.2,
|
|
192
|
+
characterOffset: 0.05
|
|
193
|
+
};
|
|
194
|
+
var PLATFORMER_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:platformer-2d");
|
|
195
|
+
function resolvePlatformer2DAttachShape(options, gameEntity) {
|
|
196
|
+
const fallback = {
|
|
197
|
+
halfHeight: 0.9,
|
|
198
|
+
radius: 0.4,
|
|
199
|
+
offsetX: 0,
|
|
200
|
+
offsetY: 0,
|
|
201
|
+
offsetZ: 0,
|
|
202
|
+
shapeSource: StagePlatformerShapeSource.Auto
|
|
203
|
+
};
|
|
204
|
+
if (options.useAttachedCollider === false && options.shape) {
|
|
205
|
+
const offset = options.shape.offset ?? [0, 0, 0];
|
|
206
|
+
return {
|
|
207
|
+
halfHeight: options.shape.halfHeight,
|
|
208
|
+
radius: options.shape.radius,
|
|
209
|
+
offsetX: offset[0],
|
|
210
|
+
offsetY: offset[1],
|
|
211
|
+
offsetZ: offset[2],
|
|
212
|
+
shapeSource: StagePlatformerShapeSource.Override
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const collider = gameEntity?.colliderDesc;
|
|
216
|
+
if (collider?.shape?.type === "capsule") {
|
|
217
|
+
const offset = collider.offset ?? [0, 0, 0];
|
|
218
|
+
return {
|
|
219
|
+
halfHeight: collider.shape.halfHeight ?? fallback.halfHeight,
|
|
220
|
+
radius: collider.shape.radius ?? fallback.radius,
|
|
221
|
+
offsetX: offset[0],
|
|
222
|
+
offsetY: offset[1],
|
|
223
|
+
offsetZ: offset[2],
|
|
224
|
+
shapeSource: StagePlatformerShapeSource.Auto
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return fallback;
|
|
228
|
+
}
|
|
229
|
+
var Platformer2DBehaviorSystem = class {
|
|
230
|
+
constructor(wasmStage, getBehaviorLinks) {
|
|
231
|
+
this.wasmStage = wasmStage;
|
|
232
|
+
this.getBehaviorLinks = getBehaviorLinks;
|
|
233
|
+
}
|
|
234
|
+
wasmStage;
|
|
235
|
+
getBehaviorLinks;
|
|
236
|
+
attachedRuntimeSlots = /* @__PURE__ */ new Set();
|
|
237
|
+
update(_ecs, _delta) {
|
|
238
|
+
const links = this.getBehaviorLinks?.(PLATFORMER_2D_BEHAVIOR_KEY);
|
|
239
|
+
if (!links) return;
|
|
240
|
+
for (const link of links) {
|
|
241
|
+
const gameEntity = link.entity;
|
|
242
|
+
const platformerRef = link.ref;
|
|
243
|
+
const options = platformerRef.options;
|
|
244
|
+
if (!gameEntity.platformer2D) {
|
|
245
|
+
gameEntity.platformer2D = createPlatformer2DMovementComponent(options);
|
|
246
|
+
}
|
|
247
|
+
if (!gameEntity.$platformer2D) {
|
|
248
|
+
gameEntity.$platformer2D = createPlatformer2DInputComponent();
|
|
249
|
+
}
|
|
250
|
+
if (!gameEntity.platformer2DState) {
|
|
251
|
+
gameEntity.platformer2DState = createPlatformer2DStateComponent();
|
|
252
|
+
}
|
|
253
|
+
const handle = gameEntity.runtimeHandle ?? -1;
|
|
254
|
+
if (!this.wasmStage || handle < 0) continue;
|
|
255
|
+
this.ensureWasmAttached(handle, options, gameEntity);
|
|
256
|
+
platformerRef.__wasmStage = this.wasmStage;
|
|
257
|
+
platformerRef.__entity = gameEntity;
|
|
258
|
+
const input = gameEntity.$platformer2D;
|
|
259
|
+
if (input) {
|
|
260
|
+
this.wasmStage.writeInputAxes(handle, input.moveX ?? 0, 0);
|
|
261
|
+
this.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);
|
|
262
|
+
this.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
ensureWasmAttached(handle, options, gameEntity) {
|
|
267
|
+
if (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;
|
|
268
|
+
const autostep = options.autostep ?? defaultOptions.autostep;
|
|
269
|
+
const shape = resolvePlatformer2DAttachShape(options, gameEntity);
|
|
270
|
+
this.wasmStage.attachPlatformer2D(handle, {
|
|
271
|
+
...shape,
|
|
272
|
+
walkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed,
|
|
273
|
+
runSpeed: options.runSpeed ?? defaultOptions.runSpeed,
|
|
274
|
+
jumpForce: options.jumpForce ?? defaultOptions.jumpForce,
|
|
275
|
+
maxJumps: options.maxJumps ?? defaultOptions.maxJumps,
|
|
276
|
+
gravity: options.gravity ?? defaultOptions.gravity,
|
|
277
|
+
coyoteTime: options.coyoteTime ?? defaultOptions.coyoteTime,
|
|
278
|
+
jumpBufferTime: options.jumpBufferTime ?? defaultOptions.jumpBufferTime,
|
|
279
|
+
jumpCutMultiplier: options.jumpCutMultiplier ?? defaultOptions.jumpCutMultiplier,
|
|
280
|
+
slopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle,
|
|
281
|
+
slopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle,
|
|
282
|
+
autostepHeight: autostep ? autostep.maxHeight : 0,
|
|
283
|
+
autostepMinWidth: autostep ? autostep.minWidth : 0.05,
|
|
284
|
+
snapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance,
|
|
285
|
+
characterOffset: options.characterOffset ?? defaultOptions.characterOffset
|
|
286
|
+
});
|
|
287
|
+
this.attachedRuntimeSlots.add(handle);
|
|
288
|
+
}
|
|
289
|
+
destroy(_ecs) {
|
|
290
|
+
this.attachedRuntimeSlots.clear();
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
var Platformer2DBehavior = defineBehavior({
|
|
294
|
+
name: "platformer-2d",
|
|
295
|
+
defaultOptions,
|
|
296
|
+
systemFactory: (ctx) => new Platformer2DBehaviorSystem(
|
|
297
|
+
ctx.wasmStage ?? null,
|
|
298
|
+
ctx.getBehaviorLinks
|
|
299
|
+
),
|
|
300
|
+
// Handles read the runtime snapshot buffer (values are from the end of
|
|
301
|
+
// the previous step — one-frame lag). The Rust FSM is the single source
|
|
302
|
+
// of truth.
|
|
303
|
+
createHandle: (ref) => ({
|
|
304
|
+
getState: () => {
|
|
305
|
+
const snapshot = readBehaviorSnapshot(ref);
|
|
306
|
+
if (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? "idle" /* Idle */;
|
|
307
|
+
return "idle" /* Idle */;
|
|
308
|
+
},
|
|
309
|
+
isGrounded: () => readBehaviorSnapshot(ref)?.grounded ?? false,
|
|
310
|
+
getJumpCount: () => readBehaviorSnapshot(ref)?.jumpCount ?? 0
|
|
311
|
+
})
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
export {
|
|
315
|
+
createPlatformer2DMovementComponent,
|
|
316
|
+
createPlatformer2DInputComponent,
|
|
317
|
+
createPlatformer2DStateComponent,
|
|
318
|
+
Platformer2DState,
|
|
319
|
+
Platformer2DEvent,
|
|
320
|
+
Platformer2DFSM,
|
|
321
|
+
Platformer2DBehavior
|
|
322
|
+
};
|
|
323
|
+
//# sourceMappingURL=chunk-4MZ57HPD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/platformer-2d/components.ts","../src/lib/behaviors/platformer-2d/platformer-2d-fsm.ts","../src/lib/behaviors/platformer-2d/platformer-2d.descriptor.ts"],"sourcesContent":["/**\n * Platformer 2D Components\n *\n * Components for 2D platformer movement on the X/Y plane: walking/running on\n * X, jumping with multi-jump support, and falling/landing states.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DMovementComponent (capability / configuration)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DMovementComponent {\n\t/** Base walking speed */\n\twalkSpeed: number;\n\t/** Sprint/run speed */\n\trunSpeed: number;\n\t/** Initial jump force/velocity */\n\tjumpForce: number;\n\t/** Maximum number of jumps (1 = single jump, 2 = double jump, etc.) */\n\tmaxJumps: number;\n\t/** Gravity force */\n\tgravity: number;\n\t/** Coyote time in seconds (grace period after leaving ground) */\n\tcoyoteTime: number;\n\t/** Jump buffer time in seconds (queue jump input before landing) */\n\tjumpBufferTime: number;\n\t/** Velocity multiplier when releasing jump button early (0-1) */\n\tjumpCutMultiplier: number;\n\t/** Time in seconds before multi-jump becomes available (0 = after button release) */\n\tmultiJumpWindowTime: number;\n\t/** Maximum slope angle (degrees) the character can walk up */\n\tslopeClimbAngle: number;\n\t/** Slope angle (degrees) past which the character slides down */\n\tslopeSlideAngle: number;\n\t/** Autostep configuration for SNES-style step-ups, or false to disable */\n\tautostep: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance (keeps the character glued going downhill) */\n\tsnapToGroundDistance: number;\n\t/** Character controller offset (small gap kept between collider and environment) */\n\tcharacterOffset: number;\n}\n\nexport function createPlatformer2DMovementComponent(\n\toptions: Partial<Platformer2DMovementComponent> = {},\n): Platformer2DMovementComponent {\n\treturn {\n\t\twalkSpeed: options.walkSpeed ?? 8,\n\t\trunSpeed: options.runSpeed ?? 14,\n\t\tjumpForce: options.jumpForce ?? 12,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tgravity: options.gravity ?? 20,\n\t\tcoyoteTime: options.coyoteTime ?? 0.1,\n\t\tjumpBufferTime: options.jumpBufferTime ?? 0.1,\n\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,\n\t\tmultiJumpWindowTime: options.multiJumpWindowTime ?? 0.15,\n\t\tslopeClimbAngle: options.slopeClimbAngle ?? 50,\n\t\tslopeSlideAngle: options.slopeSlideAngle ?? 40,\n\t\tautostep: options.autostep ?? { maxHeight: 0.25, minWidth: 0.05 },\n\t\tsnapToGroundDistance: options.snapToGroundDistance ?? 0.2,\n\t\tcharacterOffset: options.characterOffset ?? 0.05,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DInputComponent (intent)\n// Written by: Player controller, AI controller\n// Read by: Platformer2DBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DInputComponent {\n\t/** Horizontal movement input (-1 to 1) */\n\tmoveX: number;\n\t/** Jump button pressed */\n\tjump: boolean;\n\t/** Run/sprint button held */\n\trun: boolean;\n}\n\nexport function createPlatformer2DInputComponent(): Platformer2DInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tjump: false,\n\t\trun: false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DStateComponent (runtime state)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DStateComponent {\n\t/** Is the entity on the ground */\n\tgrounded: boolean;\n\t/** Is currently in a jump arc */\n\tjumping: boolean;\n\t/** Is falling (not jumping) */\n\tfalling: boolean;\n\t/** Current jump count (for multi-jump) */\n\tjumpCount: number;\n\t/** Y position when jump started */\n\tjumpStartHeight: number;\n\t/** Current movement speed being applied */\n\tcurrentSpeed: number;\n\t/** Last known ground Y position */\n\tlastGroundedY: number;\n\t/** Was jump button pressed last frame (for edge detection) */\n\tjumpPressedLastFrame: boolean;\n\t/** Time in seconds since last grounded */\n\ttimeSinceGrounded: number;\n\t/** Is a jump allowed due to buffer? */\n\tjumpBuffered: boolean;\n\t/** Time remaining in jump buffer */\n\tjumpBufferTimer: number;\n\t/** Is jump button currently held */\n\tjumpHeld: boolean;\n\t/** Has the jump cut been applied for this jump? */\n\tjumpCutApplied: boolean;\n\t/** Has jump button been released since last jump (required for multi-jump) */\n\tjumpReleasedSinceLastJump: boolean;\n\t/** Time since current jump started (for multi-jump window) */\n\ttimeSinceJump: number;\n\t/** True for the single frame on which a jump impulse was applied; prevents gravity from clobbering jump velocity. */\n\tjumpedThisFrame: boolean;\n\t/** Remaining seconds of jump-cut velocity ramp; while > 0, verticalVelocity lerps toward verticalVelocity * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/**\n\t * Controller-owned vertical velocity (units/second).\n\t * The body is kinematic, so `linvel()` is meaningless — this field is the\n\t * single source of truth for jump/gravity motion on the Y axis.\n\t */\n\tverticalVelocity: number;\n}\n\nexport function createPlatformer2DStateComponent(): Platformer2DStateComponent {\n\treturn {\n\t\tgrounded: false,\n\t\tjumping: false,\n\t\tfalling: false,\n\t\tjumpCount: 0,\n\t\tjumpStartHeight: 0,\n\t\tcurrentSpeed: 0,\n\t\tlastGroundedY: 0,\n\t\tjumpPressedLastFrame: false,\n\t\ttimeSinceGrounded: 0,\n\t\tjumpBuffered: false,\n\t\tjumpBufferTimer: 0,\n\t\tjumpHeld: false,\n\t\tjumpCutApplied: false,\n\t\tjumpReleasedSinceLastJump: true,\n\t\ttimeSinceJump: 0,\n\t\tjumpedThisFrame: false,\n\t\tjumpCutRampTimer: 0,\n\t\tverticalVelocity: 0,\n\t};\n}\n","/**\n * Platformer 2D Finite State Machine\n *\n * Manages state transitions for 2D platformer movement:\n * - Idle: Standing still on ground\n * - Walking: Moving on ground (slow)\n * - Running: Moving on ground (fast)\n * - Jumping: Ascending from jump\n * - Falling: Descending (gravity)\n * - Landing: Just touched ground\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { Platformer2DInputComponent, Platformer2DStateComponent } from './components';\n\n/**\n * Simplified collision context (normal in the X/Y plane).\n */\nexport interface Platformer2DCollisionContext {\n\tcontact: {\n\t\tnormal: { x: number; y: number };\n\t};\n}\n\nexport enum Platformer2DState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n\tLanding = 'landing',\n}\n\nexport enum Platformer2DEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n\tStop = 'stop',\n}\n\nexport interface Platformer2DContext {\n\tinput: Platformer2DInputComponent;\n\tstate: Platformer2DStateComponent;\n}\n\nexport class Platformer2DFSM {\n\tmachine: SyncStateMachine<Platformer2DState, Platformer2DEvent, never>;\n\n\tconstructor(private ctx: Platformer2DContext) {\n\t\tthis.machine = new SyncStateMachine<Platformer2DState, Platformer2DEvent, never>(\n\t\t\tPlatformer2DState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Walking transitions\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Running transitions\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Jumping transitions\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Land, Platformer2DState.Landing),\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Jump, Platformer2DState.Jumping), // Multi-jump\n\n\t\t\t\t// Falling transitions\n\t\t\t\tt(Platformer2DState.Falling, Platformer2DEvent.Land, Platformer2DState.Landing),\n\t\t\t\tt(Platformer2DState.Falling, Platformer2DEvent.Jump, Platformer2DState.Jumping), // Air jump\n\n\t\t\t\t// Landing transitions\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): Platformer2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: Platformer2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\tisGrounded(): boolean {\n\t\tconst state = this.getState();\n\t\treturn (\n\t\t\tstate === Platformer2DState.Idle ||\n\t\t\tstate === Platformer2DState.Walking ||\n\t\t\tstate === Platformer2DState.Running ||\n\t\t\tstate === Platformer2DState.Landing\n\t\t);\n\t}\n\n\tgetJumpCount(): number {\n\t\treturn this.ctx.state.jumpCount;\n\t}\n\n\t/**\n\t * Update FSM based on current component state.\n\t */\n\tupdate(input: Platformer2DInputComponent, state: Platformer2DStateComponent): void {\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\tconst currentState = this.getState();\n\t\tconst hasInput = Math.abs(input.moveX) > 0.1;\n\t\tconst isRunning = input.run;\n\n\t\t// Handle landing: if we're in falling state and now grounded, land\n\t\tif (currentState === Platformer2DState.Falling && state.grounded) {\n\t\t\tthis.dispatch(Platformer2DEvent.Land);\n\t\t\t// Don't return - allow immediate transition to movement state\n\t\t}\n\n\t\tif (state.grounded) {\n\t\t\tif (hasInput) {\n\t\t\t\tif (isRunning) {\n\t\t\t\t\tthis.dispatch(Platformer2DEvent.Run);\n\t\t\t\t} else {\n\t\t\t\t\tthis.dispatch(Platformer2DEvent.Walk);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.dispatch(Platformer2DEvent.Stop);\n\t\t\t}\n\t\t} else if (state.falling) {\n\t\t\tthis.dispatch(Platformer2DEvent.Fall);\n\t\t}\n\n\t\t// Handle jump input (edge detection handled in the system)\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tthis.dispatch(Platformer2DEvent.Jump);\n\t\t}\n\t}\n}\n","/**\n * Platformer 2D Behavior Descriptor\n *\n * Type-safe descriptor for the platformer 2D behavior system.\n * Provides entity.use() API for 2D platformer movement on the X/Y plane.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Platformer2DState } from './platformer-2d-fsm';\nimport {\n\tcreatePlatformer2DMovementComponent,\n\tcreatePlatformer2DInputComponent,\n\tcreatePlatformer2DStateComponent,\n\ttype Platformer2DMovementComponent,\n\ttype Platformer2DInputComponent,\n\ttype Platformer2DStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_JUMP,\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\tStagePlatformerShapeSource,\n\ttype BehaviorRuntime,\n\ttype StagePlatformer2DOptions,\n} from '../../runtime/wasm-stage-runtime';\n\n/** Rust FSM index → TS state (shared `platformer_3d/fsm.rs` ordering). */\nconst FSM_STATE_BY_INDEX: readonly Platformer2DState[] = [\n\tPlatformer2DState.Idle,\n\tPlatformer2DState.Walking,\n\tPlatformer2DState.Running,\n\tPlatformer2DState.Jumping,\n\tPlatformer2DState.Falling,\n\tPlatformer2DState.Landing,\n];\n\n/**\n * Platformer 2D behavior options (typed for entity.use() autocomplete)\n */\nexport interface Platformer2DBehaviorOptions {\n\t/** Base walking speed (default: 8) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 14) */\n\trunSpeed?: number;\n\t/** Initial jump force (default: 12) */\n\tjumpForce?: number;\n\t/** Maximum number of jumps (default: 1) */\n\tmaxJumps?: number;\n\t/** Gravity force (default: 20) */\n\tgravity?: number;\n\t/** Coyote time in seconds (default: 0.1) */\n\tcoyoteTime?: number;\n\t/** Jump buffer time in seconds (default: 0.1) */\n\tjumpBufferTime?: number;\n\t/** Velocity multiplier when releasing jump early (default: 0.5) */\n\tjumpCutMultiplier?: number;\n\t/** Maximum slope angle in degrees the character can walk up (default: 50) */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default: 40) */\n\tslopeSlideAngle?: number;\n\t/** Autostep over small step-ups, or false to disable (default: { maxHeight: 0.25, minWidth: 0.05 }) */\n\tautostep?: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance for downhill/step-down stability (default: 0.2) */\n\tsnapToGroundDistance?: number;\n\t/** Character controller collision offset (default: 0.05) */\n\tcharacterOffset?: number;\n\t/** Explicit KCC capsule override (requires `useAttachedCollider: false`). */\n\tshape?: {\n\t\thalfHeight: number;\n\t\tradius: number;\n\t\toffset?: [number, number, number];\n\t};\n\t/** When true (default), KCC reads shape + offset from the attached Rapier collider. */\n\tuseAttachedCollider?: boolean;\n}\n\nconst defaultOptions: Platformer2DBehaviorOptions = {\n\twalkSpeed: 8,\n\trunSpeed: 14,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 20,\n\tcoyoteTime: 0.1,\n\tjumpBufferTime: 0.1,\n\tjumpCutMultiplier: 0.5,\n\tslopeClimbAngle: 50,\n\tslopeSlideAngle: 40,\n\tautostep: { maxHeight: 0.25, minWidth: 0.05 },\n\tsnapToGroundDistance: 0.2,\n\tcharacterOffset: 0.05,\n};\n\nconst PLATFORMER_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-2d');\n\nfunction resolvePlatformer2DAttachShape(\n\toptions: Platformer2DBehaviorOptions,\n\tgameEntity?: {\n\t\tcolliderDesc?: {\n\t\t\tshape?: { type?: string; halfHeight?: number; radius?: number };\n\t\t\toffset?: [number, number, number];\n\t\t};\n\t},\n): Pick<StagePlatformer2DOptions, 'halfHeight' | 'radius' | 'offsetX' | 'offsetY' | 'offsetZ' | 'shapeSource'> {\n\tconst fallback = {\n\t\thalfHeight: 0.9,\n\t\tradius: 0.4,\n\t\toffsetX: 0,\n\t\toffsetY: 0,\n\t\toffsetZ: 0,\n\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t};\n\n\tif (options.useAttachedCollider === false && options.shape) {\n\t\tconst offset = options.shape.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: options.shape.halfHeight,\n\t\t\tradius: options.shape.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Override,\n\t\t};\n\t}\n\n\tconst collider = gameEntity?.colliderDesc;\n\tif (collider?.shape?.type === 'capsule') {\n\t\tconst offset = collider.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: collider.shape.halfHeight ?? fallback.halfHeight,\n\t\t\tradius: collider.shape.radius ?? fallback.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t\t};\n\t}\n\n\treturn fallback;\n}\n\n/**\n * Entity with 2D platformer components (input written by the game; the wasm\n * runtime owns the kinematic character controller and FSM).\n */\nexport interface Platformer2DEntity {\n\tuuid: string;\n\tplatformer2D: Platformer2DMovementComponent;\n\t$platformer2D: Platformer2DInputComponent;\n\tplatformer2DState: Platformer2DStateComponent;\n}\n\n/**\n * Forwards 2D platformer input into the wasm runtime. Entities with a\n * `runtimeHandle` are attached once to the Stage runtime and receive input\n * via the slot-indexed input buffer each frame; the Rust `platformer_2d` system\n * is the single simulation path.\n */\nclass Platformer2DBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PLATFORMER_2D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst platformerRef = link.ref as any;\n\n\t\t\tconst options = platformerRef.options as Platformer2DBehaviorOptions;\n\n\t\t\tif (!gameEntity.platformer2D) {\n\t\t\t\tgameEntity.platformer2D = createPlatformer2DMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$platformer2D) {\n\t\t\t\tgameEntity.$platformer2D = createPlatformer2DInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.platformer2DState) {\n\t\t\t\tgameEntity.platformer2DState = createPlatformer2DStateComponent();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options, gameEntity);\n\t\t\tplatformerRef.__wasmStage = this.wasmStage;\n\t\t\tplatformerRef.__entity = gameEntity;\n\t\t\tconst input = gameEntity.$platformer2D;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, 0);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(\n\t\thandle: number,\n\t\toptions: Platformer2DBehaviorOptions,\n\t\tgameEntity?: { colliderDesc?: { shape?: { type?: string; halfHeight?: number; radius?: number }; offset?: [number, number, number] } },\n\t): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst autostep = options.autostep ?? defaultOptions.autostep!;\n\t\tconst shape = resolvePlatformer2DAttachShape(options, gameEntity);\n\t\tthis.wasmStage.attachPlatformer2D(handle, {\n\t\t\t...shape,\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\tjumpForce: options.jumpForce ?? defaultOptions.jumpForce!,\n\t\t\tmaxJumps: options.maxJumps ?? defaultOptions.maxJumps!,\n\t\t\tgravity: options.gravity ?? defaultOptions.gravity!,\n\t\t\tcoyoteTime: options.coyoteTime ?? defaultOptions.coyoteTime!,\n\t\t\tjumpBufferTime: options.jumpBufferTime ?? defaultOptions.jumpBufferTime!,\n\t\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? defaultOptions.jumpCutMultiplier!,\n\t\t\tslopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle!,\n\t\t\tslopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle!,\n\t\t\tautostepHeight: autostep ? autostep.maxHeight : 0,\n\t\t\tautostepMinWidth: autostep ? autostep.minWidth : 0.05,\n\t\t\tsnapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance!,\n\t\t\tcharacterOffset: options.characterOffset ?? defaultOptions.characterOffset!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Platformer2DBehavior - typed descriptor for 2D platformer movement\n *\n * Provides complete 2D platformer physics on the X/Y plane including:\n * - Walking and running on X\n * - Jumping with buffer, coyote time, multi-jump, and jump-cut\n * - Gravity ownership and falling\n * - Kinematic character controller (collide-and-slide, autostep, snap-to-ground)\n *\n * @example\n * ```typescript\n * import { Platformer2DBehavior } from \"@zylem/behaviors/platformer-2d\";\n *\n * const platformer = player.use(Platformer2DBehavior, {\n * walkSpeed: 8,\n * runSpeed: 14,\n * jumpForce: 12,\n * maxJumps: 2, // Double jump!\n * });\n *\n * // In update loop\n * player.onUpdate(({ inputs }) => {\n * player.$platformer2D.moveX = inputs.p1.axes.Horizontal.value;\n * player.$platformer2D.jump = inputs.p1.buttons.A.held > 0;\n * player.$platformer2D.run = inputs.p1.shoulders.LTrigger.held > 0;\n *\n * const state = platformer.getState();\n * const grounded = platformer.isGrounded();\n * });\n * ```\n */\nexport const Platformer2DBehavior = defineBehavior<\n\tPlatformer2DBehaviorOptions,\n\t{\n\t\tgetState: () => Platformer2DState;\n\t\tisGrounded: () => boolean;\n\t\tgetJumpCount: () => number;\n\t},\n\tPlatformer2DEntity\n>({\n\tname: 'platformer-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Platformer2DBehaviorSystem(\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\t// Handles read the runtime snapshot buffer (values are from the end of\n\t// the previous step — one-frame lag). The Rust FSM is the single source\n\t// of truth.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? Platformer2DState.Idle;\n\t\t\treturn Platformer2DState.Idle;\n\t\t},\n\t\tisGrounded: () => readBehaviorSnapshot(ref)?.grounded ?? false,\n\t\tgetJumpCount: () => readBehaviorSnapshot(ref)?.jumpCount ?? 0,\n\t}),\n});\n"],"mappings":"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","names":["Platformer2DState","Platformer2DEvent"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getEntityFacingAngle2D,
|
|
3
|
+
resolveAim2D
|
|
4
|
+
} from "./chunk-TBUKJKY6.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/coordinators/shared/shooter-fire.ts
|
|
7
|
+
function fireShooter2DIfRequested(entity, shooter, stage, input, aim) {
|
|
8
|
+
if (!input.shootPressed && !input.shootHeld) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const sourcePosition = entity.body?.translation();
|
|
12
|
+
if (!sourcePosition) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const resolved = aim ?? resolveAim2D(input.aimX, input.aimY, getEntityFacingAngle2D(entity));
|
|
16
|
+
void shooter.fire({
|
|
17
|
+
source: entity,
|
|
18
|
+
stage,
|
|
19
|
+
target: {
|
|
20
|
+
x: sourcePosition.x + resolved.direction.x,
|
|
21
|
+
y: sourcePosition.y + resolved.direction.y
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
fireShooter2DIfRequested
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=chunk-52FJIDGL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/coordinators/shared/shooter-fire.ts"],"sourcesContent":["import type {\n\tShooter2DHandle,\n\tShooter2DSourceEntity,\n\tShooter2DStageLike,\n} from '../../behaviors/shooter-2d/shooter-2d.descriptor';\nimport {\n\tgetEntityFacingAngle2D,\n\tresolveAim2D,\n\ttype ResolvedAim2D,\n} from './top-down-aim';\n\n/**\n * Aim + fire input fields shared by the 2D shooter coordinators\n * (top-down-shooter, multidirectional-space-shooter).\n */\nexport interface Shooter2DAimFireInput {\n\taimX: number;\n\taimY: number;\n\tshootPressed: boolean;\n\tshootHeld: boolean;\n}\n\n/**\n * Fire the entity's Shooter2D behavior when the input requests it.\n *\n * Resolves the 2D aim (explicit aim axes, falling back to the entity's\n * current facing) and fires toward a point one unit along the aim direction.\n * Pass a pre-resolved `aim` to avoid recomputing when the coordinator already\n * resolved it for rotation.\n *\n * @returns true when a fire attempt was issued\n */\nexport function fireShooter2DIfRequested(\n\tentity: Shooter2DSourceEntity,\n\tshooter: Shooter2DHandle,\n\tstage: Shooter2DStageLike,\n\tinput: Shooter2DAimFireInput,\n\taim?: ResolvedAim2D,\n): boolean {\n\tif (!input.shootPressed && !input.shootHeld) {\n\t\treturn false;\n\t}\n\n\tconst sourcePosition = entity.body?.translation();\n\tif (!sourcePosition) {\n\t\treturn false;\n\t}\n\n\tconst resolved =\n\t\taim ?? resolveAim2D(input.aimX, input.aimY, getEntityFacingAngle2D(entity));\n\n\tvoid shooter.fire({\n\t\tsource: entity,\n\t\tstage,\n\t\ttarget: {\n\t\t\tx: sourcePosition.x + resolved.direction.x,\n\t\t\ty: sourcePosition.y + resolved.direction.y,\n\t\t},\n\t});\n\treturn true;\n}\n"],"mappings":";;;;;;AAgCO,SAAS,yBACf,QACA,SACA,OACA,OACA,KACU;AACV,MAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAW;AAC5C,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,OAAO,MAAM,YAAY;AAChD,MAAI,CAAC,gBAAgB;AACpB,WAAO;AAAA,EACR;AAEA,QAAM,WACL,OAAO,aAAa,MAAM,MAAM,MAAM,MAAM,uBAAuB,MAAM,CAAC;AAE3E,OAAK,QAAQ,KAAK;AAAA,IACjB,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,MACP,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,MACzC,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,IAC1C;AAAA,EACD,CAAC;AACD,SAAO;AACR;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/use-behavior.ts","../src/lib/behaviors/components.ts"],"sourcesContent":["import { GameEntity } from '../entities/entity';\nimport { BehaviorDescriptor } from './behavior-descriptor';\n\n/**\n * Type-safe helper to apply a behavior to an entity and return the entity cast to the behavior's interface.\n * \n * @param entity The entity to apply the behavior to\n * @param descriptor The behavior descriptor\n * @param options Behavior options\n * @returns The entity, cast to E & I (where I is the behavior's interface)\n */\nexport function useBehavior<\n E extends GameEntity<any>,\n O extends Record<string, any>,\n H extends Record<string, any>,\n I // Entity Interface\n>(\n entity: E,\n descriptor: BehaviorDescriptor<O, H, I>,\n options?: Partial<O>\n): E & I {\n entity.use(descriptor, options);\n return entity as unknown as E & I;\n}\n","/**\n * Core behavior helper types\n *\n * Plain data interfaces shared by movement behaviors and coordinators.\n */\n\nimport
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/use-behavior.ts","../src/lib/behaviors/components.ts"],"sourcesContent":["import { GameEntity } from '../entities/entity';\nimport { BehaviorDescriptor } from './behavior-descriptor';\n\n/**\n * Type-safe helper to apply a behavior to an entity and return the entity cast to the behavior's interface.\n * \n * @param entity The entity to apply the behavior to\n * @param descriptor The behavior descriptor\n * @param options Behavior options\n * @returns The entity, cast to E & I (where I is the behavior's interface)\n */\nexport function useBehavior<\n E extends GameEntity<any>,\n O extends Record<string, any>,\n H extends Record<string, any>,\n I // Entity Interface\n>(\n entity: E,\n descriptor: BehaviorDescriptor<O, H, I>,\n options?: Partial<O>\n): E & I {\n entity.use(descriptor, options);\n return entity as unknown as E & I;\n}\n","/**\n * Core behavior helper types\n *\n * Plain data interfaces shared by movement behaviors and coordinators.\n */\n\nimport { Vector3, Quaternion } from 'three';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TransformComponent (render-facing)\n// Written only by PhysicsSyncBehavior, read by rendering/animation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface TransformComponent {\n\tposition: Vector3;\n\trotation: Quaternion;\n}\n\nexport function createTransformComponent(): TransformComponent {\n\treturn {\n\t\tposition: new Vector3(),\n\t\trotation: new Quaternion(),\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PhysicsBodyComponent (bridge to the host physics reference, not state)\n// ECS does not store velocity, mass, etc. — the wasm runtime owns all\n// physical truth; `body` is an opaque host-owned reference.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PhysicsBodyComponent {\n\tbody: unknown;\n}\n\nexport function createPhysicsBodyComponent(body: unknown): PhysicsBodyComponent {\n\treturn { body };\n}\n"],"mappings":";AAWO,SAAS,YAMZ,QACA,YACA,SACK;AACL,SAAO,IAAI,YAAY,OAAO;AAC9B,SAAO;AACX;;;ACjBA,SAAS,SAAS,kBAAkB;AAY7B,SAAS,2BAA+C;AAC9D,SAAO;AAAA,IACN,UAAU,IAAI,QAAQ;AAAA,IACtB,UAAU,IAAI,WAAW;AAAA,EAC1B;AACD;AAYO,SAAS,2BAA2B,MAAqC;AAC/E,SAAO,EAAE,KAAK;AACf;","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
+
mod
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
__commonJS,
|
|
29
|
+
__toESM
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=chunk-5WRI5ZAA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-EZNCCO4L.js";
|
|
4
4
|
|
|
5
5
|
// src/lib/behaviors/cooldown/cooldown-store.ts
|
|
6
|
-
import { proxy } from "valtio";
|
|
6
|
+
import { proxy } from "valtio/vanilla";
|
|
7
7
|
var cooldownStore = proxy({});
|
|
8
8
|
function registerCooldown(name, duration, immediate = true) {
|
|
9
9
|
cooldownStore[name] = {
|
|
@@ -115,4 +115,4 @@ export {
|
|
|
115
115
|
getCooldownStore,
|
|
116
116
|
CooldownBehavior
|
|
117
117
|
};
|
|
118
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-5XPPLTC7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/cooldown/cooldown-store.ts","../src/lib/behaviors/cooldown/cooldown.descriptor.ts"],"sourcesContent":["import { proxy } from 'valtio/vanilla';\n\n/**\n * A single cooldown entry in the global store.\n */\nexport interface CooldownEntry {\n\t/** Identifier for this cooldown */\n\tname: string;\n\t/** Total cooldown time in seconds */\n\tduration: number;\n\t/** Time accumulated since last fire */\n\telapsed: number;\n\t/** Whether the cooldown is ready (elapsed >= duration) */\n\tready: boolean;\n\t/** Normalized progress 0..1 (0 = just fired, 1 = ready) */\n\tprogress: number;\n\t/** Whether this cooldown is currently counting down */\n\tactive: boolean;\n}\n\n/**\n * Global reactive cooldown registry (valtio proxy).\n * Accessible from entities, behaviors, and UI components.\n */\nconst cooldownStore = proxy<Record<string, CooldownEntry>>({});\n\n/**\n * Register a named cooldown in the global store.\n * If a cooldown with the same name already exists, it is overwritten.\n *\n * @param name Unique cooldown identifier\n * @param duration Cooldown duration in seconds\n * @param immediate If true, the cooldown starts ready (default: true)\n */\nexport function registerCooldown(\n\tname: string,\n\tduration: number,\n\timmediate: boolean = true,\n): void {\n\tcooldownStore[name] = {\n\t\tname,\n\t\tduration,\n\t\telapsed: immediate ? duration : 0,\n\t\tready: immediate,\n\t\tprogress: immediate ? 1 : 0,\n\t\tactive: !immediate,\n\t};\n}\n\n/**\n * Get a cooldown entry by name.\n * Returns undefined if the cooldown has not been registered.\n */\nexport function getCooldown(name: string): CooldownEntry | undefined {\n\treturn cooldownStore[name];\n}\n\n/**\n * Fire (trigger) a cooldown, resetting its timer.\n * Sets the cooldown to not-ready and starts the countdown.\n */\nexport function fireCooldown(name: string): void {\n\tconst entry = cooldownStore[name];\n\tif (!entry) return;\n\tentry.elapsed = 0;\n\tentry.ready = false;\n\tentry.progress = 0;\n\tentry.active = true;\n}\n\n/**\n * Force a cooldown to ready immediately.\n */\nexport function resetCooldown(name: string): void {\n\tconst entry = cooldownStore[name];\n\tif (!entry) return;\n\tentry.elapsed = entry.duration;\n\tentry.ready = true;\n\tentry.progress = 1;\n\tentry.active = false;\n}\n\n/**\n * Advance all active cooldowns by delta seconds.\n * Called once per frame by the CooldownBehavior system.\n */\nexport function tickCooldowns(delta: number): void {\n\tfor (const name in cooldownStore) {\n\t\tconst entry = cooldownStore[name];\n\t\tif (!entry.active) continue;\n\n\t\tentry.elapsed += delta;\n\t\tif (entry.elapsed >= entry.duration) {\n\t\t\tentry.elapsed = entry.duration;\n\t\t\tentry.ready = true;\n\t\t\tentry.progress = 1;\n\t\t\tentry.active = false;\n\t\t} else {\n\t\t\tentry.progress = entry.duration > 0 ? entry.elapsed / entry.duration : 1;\n\t\t}\n\t}\n}\n\n/**\n * Get the raw cooldown store (for advanced/debugging use).\n */\nexport function getCooldownStore(): Record<string, CooldownEntry> {\n\treturn cooldownStore;\n}\n","/**\n * CooldownBehavior\n *\n * A stage-scoped behavior that manages named cooldowns via the global CooldownStore.\n * Entities use this to register, check, and fire cooldowns.\n * UI components (CooldownIcon) can read from the same store.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tregisterCooldown,\n\tgetCooldown,\n\tfireCooldown,\n\tresetCooldown,\n\ttickCooldowns,\n} from './cooldown-store';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Options\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface CooldownConfig {\n\t/** Duration in seconds */\n\tduration: number;\n\t/** Whether the cooldown starts ready (default: true) */\n\timmediate?: boolean;\n}\n\nexport interface CooldownOptions {\n\t/**\n\t * Named cooldowns to register.\n\t * Keys are the cooldown names, values are their config.\n\t */\n\tcooldowns: Record<string, CooldownConfig>;\n}\n\ninterface CompiledCooldownEntry {\n\tname: string;\n\tduration: number;\n\timmediate: boolean;\n}\n\nconst COOLDOWN_BEHAVIOR_KEY = Symbol.for('zylem:behavior:cooldown');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Handle methods returned by entity.use(CooldownBehavior, ...).\n */\nexport interface CooldownHandle {\n\t/** Check if a named cooldown is ready */\n\tisReady(name: string): boolean;\n\t/** Fire (trigger) a named cooldown, resetting its timer */\n\tfire(name: string): void;\n\t/** Force a cooldown to ready immediately */\n\treset(name: string): void;\n\t/** Get cooldown progress 0..1 (0 = just fired, 1 = ready) */\n\tgetProgress(name: string): number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// System\n// ─────────────────────────────────────────────────────────────────────────────\n\nclass CooldownSystem implements BehaviorSystem {\n\tconstructor(\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) { }\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(COOLDOWN_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\tensureCooldownEntriesRegistered(link.ref as BehaviorRef<CooldownOptions>);\n\t\t\t}\n\t\t}\n\n\t\ttickCooldowns(delta);\n\t}\n\n\tdestroy(_ecs: unknown): void { }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle factory\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction createCooldownHandle(ref: BehaviorRef<CooldownOptions>): CooldownHandle {\n\tensureCooldownEntriesRegistered(ref);\n\n\treturn {\n\t\tisReady: (name: string) => getCooldown(name)?.ready ?? false,\n\t\tfire: (name: string) => fireCooldown(name),\n\t\treset: (name: string) => resetCooldown(name),\n\t\tgetProgress: (name: string) => getCooldown(name)?.progress ?? 1,\n\t};\n}\n\nfunction getCompiledCooldownEntries(\n\tref: BehaviorRef<CooldownOptions>,\n): CompiledCooldownEntry[] {\n\tconst compiled = (ref as any).__compiledCooldownEntries as CompiledCooldownEntry[] | undefined;\n\tif (compiled) return compiled;\n\n\tconst entries = Object.entries(ref.options.cooldowns).map(([name, config]) => ({\n\t\tname,\n\t\tduration: config.duration,\n\t\timmediate: config.immediate ?? true,\n\t}));\n\t(ref as any).__compiledCooldownEntries = entries;\n\treturn entries;\n}\n\nfunction ensureCooldownEntriesRegistered(ref: BehaviorRef<CooldownOptions>): void {\n\tif ((ref as any).__cooldownsRegistered) return;\n\n\tfor (const entry of getCompiledCooldownEntries(ref)) {\n\t\tregisterCooldown(entry.name, entry.duration, entry.immediate);\n\t}\n\n\t(ref as any).__cooldownsRegistered = true;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Behavior Definition\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * CooldownBehavior\n *\n * @example\n * ```ts\n * import { CooldownBehavior } from '@zylem/game-lib/behavior';\n * import { moveBy } from '@zylem/game-lib/actions';\n *\n * const hero = createBox({ ... });\n * const cd = hero.use(CooldownBehavior, {\n * cooldowns: {\n * attack: { duration: 5 },\n * dash: { duration: 2, immediate: false },\n * },\n * });\n *\n * hero.onUpdate(({ me, inputs }) => {\n * if (cd.isReady('attack') && inputs.p1.buttons.A.pressed) {\n * cd.fire('attack');\n * // perform attack\n * }\n * if (cd.isReady('dash') && inputs.p1.buttons.B.pressed) {\n * cd.fire('dash');\n * me.runAction(moveBy({ x: 10, duration: 0.3 }));\n * }\n * });\n * ```\n */\nexport const CooldownBehavior = defineBehavior<CooldownOptions, CooldownHandle>({\n\tname: 'cooldown',\n\tdefaultOptions: { cooldowns: {} },\n\tsystemFactory: (ctx) =>\n\t\tnew CooldownSystem(ctx.getBehaviorLinks),\n\tcreateHandle: createCooldownHandle,\n});\n"],"mappings":"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","names":[]}
|