From 91b1aab376598f8e1f37dd40dd4e72dd599cccc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:42:02 +0000 Subject: [PATCH 1/4] Initial plan From 93276bd6e1334e18effeefa26f04965aac15d1cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:48:37 +0000 Subject: [PATCH 2/4] feat(demo): update demo to use new ECS system Co-authored-by: stormmuller <17644200+stormmuller@users.noreply.github.com> --- demo/src/animationDemo.ts | 125 ++++++++++++++++------- demo/src/control-adventurer-component.ts | 9 +- demo/src/control-adventurer-system.ts | 92 ++++++++--------- demo/src/fire-system.ts | 29 +++--- demo/src/game.ts | 18 +++- 5 files changed, 161 insertions(+), 112 deletions(-) diff --git a/demo/src/animationDemo.ts b/demo/src/animationDemo.ts index 25afe225..ad455f25 100644 --- a/demo/src/animationDemo.ts +++ b/demo/src/animationDemo.ts @@ -9,8 +9,14 @@ import { Axis2dAction, buttonMoments, createAnimation, - FlipComponent, + createResetInputsEcsSystem, + createSpriteAnimationEcsSystem, + createUpdateInputEcsSystem, + FlipEcsComponent, + flipId, Game, + InputManager, + inputsId, KeyboardAxis1dBinding, KeyboardAxis2dBinding, KeyboardInputSource, @@ -18,24 +24,23 @@ import { keyCodes, MouseAxis2dBinding, MouseInputSource, - PositionComponent, - registerInputs, - ScaleComponent, + positionId, + scaleId, Sprite, - SpriteAnimationComponent, - SpriteComponent, + spriteAnimationId, + spriteId, Time, TriggerAction, Vector2, - World, } from '../../src'; +import { EcsWorld } from '../../src/new-ecs'; import { FiniteStateMachine } from '../../src/finite-state-machine/finite-state-machine'; import { Transition } from '../../src/finite-state-machine/transition'; import { ADVENTURER_ANIMATIONS, SHIP_ANIMATIONS } from './animationEnums'; -import { ControlAdventurerComponent } from './control-adventurer-component'; +import { controlAdventurerId } from './control-adventurer-component'; export function setupAnimationsDemo( - world: World, + world: EcsWorld, game: Game, time: Time, shipSprite: Sprite, @@ -43,6 +48,9 @@ export function setupAnimationsDemo( ): ReturnType { const inputs = setupInputs(world, game, time); + // Add animation system + world.addSystem(createSpriteAnimationEcsSystem(time)); + const ShipController = createShipAnimationController(); buildShipEntities(world, shipSprite, ShipController); @@ -58,7 +66,7 @@ export function setupAnimationsDemo( return inputs; } -function setupInputs(world: World, game: Game, time: Time) { +function setupInputs(world: EcsWorld, game: Game, time: Time) { const gameInputGroup = 'game'; const attackInput = new TriggerAction('attack', gameInputGroup); @@ -78,22 +86,28 @@ function setupInputs(world: World, game: Game, time: Time) { actionResetTypes.noReset, ); - const { inputsManager } = registerInputs(world, time, { - triggerActions: [ - attackInput, - runRInput, - runLInput, - jumpInput, - takeDamageInput, - ], - axis2dActions: [axis2dInput], - axis1dActions: [axis1dInput], - }); + // Create input manager and register actions + const inputsManager = new InputManager(gameInputGroup); + inputsManager.addTriggerActions( + attackInput, + runRInput, + runLInput, + jumpInput, + takeDamageInput, + ); + inputsManager.addAxis2dActions(axis2dInput); + inputsManager.addAxis1dActions(axis1dInput); + + // Create an entity with inputs component + const inputEntity = world.createEntity(); + world.addComponent(inputEntity, inputsId, { inputManager: inputsManager }); - inputsManager.setActiveGroup(gameInputGroup); + // Add input systems + world.addSystem(createUpdateInputEcsSystem(time)); + world.addSystem(createResetInputsEcsSystem()); const keyboardInputSource = new KeyboardInputSource(inputsManager); - const mouseInputSource = new MouseInputSource(inputsManager, game); + const mouseInputSource = new MouseInputSource(inputsManager, game.container); keyboardInputSource.axis2dBindings.add( new KeyboardAxis2dBinding( @@ -234,32 +248,65 @@ function createAdventurerControllableInputs() { } function buildShipEntities( - world: World, + world: EcsWorld, shipSprite: Sprite, stateMachine: FiniteStateMachine, ) { const animationInputs = new AnimationInputs(); - world.buildAndAddEntity([ - new PositionComponent(-500, -150), - new SpriteComponent(shipSprite), - new ScaleComponent(0.5, 0.5), - new SpriteAnimationComponent(stateMachine, animationInputs), - ]); + const entity = world.createEntity(); + world.addComponent(entity, positionId, { + local: new Vector2(-500, -150), + world: new Vector2(-500, -150), + }); + world.addComponent(entity, spriteId, { + sprite: shipSprite, + enabled: true, + }); + world.addComponent(entity, scaleId, { + local: new Vector2(0.5, 0.5), + world: new Vector2(0.5, 0.5), + }); + world.addComponent(entity, spriteAnimationId, { + animationFrameIndex: 0, + playbackSpeed: 1, + frameDurationMilliseconds: 33.3333, + lastFrameChangeTimeInSeconds: 0, + animationInputs, + stateMachine, + }); } function buildAdventurerControllableEntities( - world: World, + world: EcsWorld, adventurerSprite: Sprite, stateMachine: FiniteStateMachine, animationInputs: AnimationInputs, ) { - world.buildAndAddEntity([ - new PositionComponent(400, 0), - new SpriteComponent(adventurerSprite), - new ScaleComponent(0.3, 0.6), - new SpriteAnimationComponent(stateMachine, animationInputs, 33.3333, 0.3), - new ControlAdventurerComponent(), - new FlipComponent(), - ]); + const entity = world.createEntity(); + world.addComponent(entity, positionId, { + local: new Vector2(400, 0), + world: new Vector2(400, 0), + }); + world.addComponent(entity, spriteId, { + sprite: adventurerSprite, + enabled: true, + }); + world.addComponent(entity, scaleId, { + local: new Vector2(0.3, 0.6), + world: new Vector2(0.3, 0.6), + }); + world.addComponent(entity, spriteAnimationId, { + animationFrameIndex: 0, + playbackSpeed: 0.3, + frameDurationMilliseconds: 33.3333, + lastFrameChangeTimeInSeconds: 0, + animationInputs, + stateMachine, + }); + world.addComponent(entity, controlAdventurerId, {}); + world.addComponent(entity, flipId, { + flipX: false, + flipY: false, + }); } diff --git a/demo/src/control-adventurer-component.ts b/demo/src/control-adventurer-component.ts index a6366398..95933465 100644 --- a/demo/src/control-adventurer-component.ts +++ b/demo/src/control-adventurer-component.ts @@ -1,3 +1,8 @@ -import { Component } from '../../src'; +import { createComponentId } from '../../src/new-ecs/ecs-component'; -export class ControlAdventurerComponent extends Component {} +export interface ControlAdventurerEcsComponent { + // Marker component - no data needed +} + +export const controlAdventurerId = + createComponentId('control-adventurer'); diff --git a/demo/src/control-adventurer-system.ts b/demo/src/control-adventurer-system.ts index 3422c15c..336b8ffd 100644 --- a/demo/src/control-adventurer-system.ts +++ b/demo/src/control-adventurer-system.ts @@ -1,54 +1,44 @@ import { - Entity, - FlipComponent, - PositionComponent, - SpriteAnimationComponent, - System, + FlipEcsComponent, + flipId, + PositionEcsComponent, + positionId, + SpriteAnimationEcsComponent, + spriteAnimationId, TriggerAction, } from '../../src'; -import { ControlAdventurerComponent } from './control-adventurer-component'; - -export class ControlAdventurerSystem extends System { - private readonly _attackTriggerInput: TriggerAction; - private readonly _runRTriggerInput: TriggerAction; - private readonly _runLTriggerInput: TriggerAction; - private readonly _jumpTriggerInput: TriggerAction; - private readonly _takeDamageTriggerInput: TriggerAction; - - constructor( - attackTriggerInput: TriggerAction, - runRTriggerInput: TriggerAction, - runLTriggerInput: TriggerAction, - jumpTriggerInput: TriggerAction, - takeDamageTriggerInput: TriggerAction, - ) { - super( - [ - ControlAdventurerComponent, - SpriteAnimationComponent, - FlipComponent, - PositionComponent, - ], - 'ControlAdventurerSystem', - ); - - this._attackTriggerInput = attackTriggerInput; - this._runRTriggerInput = runRTriggerInput; - this._runLTriggerInput = runLTriggerInput; - this._jumpTriggerInput = jumpTriggerInput; - this._takeDamageTriggerInput = takeDamageTriggerInput; - } - - public run(entity: Entity): void { - const spriteAnimationComponent = entity.getComponentRequired( - SpriteAnimationComponent, - ); - - const flipComponent = entity.getComponentRequired(FlipComponent); +import { EcsSystem } from '../../src/new-ecs'; +import { + ControlAdventurerEcsComponent, + controlAdventurerId, +} from './control-adventurer-component'; + +export const createControlAdventurerEcsSystem = ( + attackTriggerInput: TriggerAction, + runRTriggerInput: TriggerAction, + runLTriggerInput: TriggerAction, + jumpTriggerInput: TriggerAction, + takeDamageTriggerInput: TriggerAction, +): EcsSystem< + [ + ControlAdventurerEcsComponent, + SpriteAnimationEcsComponent, + FlipEcsComponent, + PositionEcsComponent, + ] +> => ({ + query: [controlAdventurerId, spriteAnimationId, flipId, positionId], + run: (result) => { + const [ + _controlAdventurerComponent, + spriteAnimationComponent, + flipComponent, + _positionComponent, + ] = result.components; const animationInputs = spriteAnimationComponent.animationInputs; - if (this._jumpTriggerInput.isTriggered) { + if (jumpTriggerInput.isTriggered) { console.log('Jumping!'); animationInputs.setTrigger('jump'); @@ -56,14 +46,14 @@ export class ControlAdventurerSystem extends System { return; } - if (this._runLTriggerInput.isTriggered) { + if (runLTriggerInput.isTriggered) { animationInputs.setToggle('run', true); flipComponent.flipX = true; return; } - if (this._runRTriggerInput.isTriggered) { + if (runRTriggerInput.isTriggered) { animationInputs.setToggle('run', true); flipComponent.flipX = false; @@ -72,13 +62,13 @@ export class ControlAdventurerSystem extends System { animationInputs.setToggle('run', false); - if (this._attackTriggerInput.isTriggered) { + if (attackTriggerInput.isTriggered) { animationInputs.setText('attack', 'attack is being set'); return; } - if (this._takeDamageTriggerInput.isTriggered) { + if (takeDamageTriggerInput.isTriggered) { const health = animationInputs.getNumber('health'); if (!health) { @@ -87,5 +77,5 @@ export class ControlAdventurerSystem extends System { health.value = Math.max(0, health.value - 50); } - } -} + }, +}); diff --git a/demo/src/fire-system.ts b/demo/src/fire-system.ts index 203eac96..ad75ce06 100644 --- a/demo/src/fire-system.ts +++ b/demo/src/fire-system.ts @@ -1,23 +1,18 @@ -import { HoldAction, InputsComponent, System, TriggerAction } from '../../src'; +import { HoldAction, InputsEcsComponent, inputsId, TriggerAction } from '../../src'; +import { EcsSystem } from '../../src/new-ecs'; -export class FireSystem extends System { - private readonly _fireAction: TriggerAction; - private readonly _runAction: HoldAction; - - constructor(fireAction: TriggerAction, runAction: HoldAction) { - super([InputsComponent], 'FireSystem'); - - this._fireAction = fireAction; - this._runAction = runAction; - } - - public run(): void { - if (this._fireAction.isTriggered) { +export const createFireEcsSystem = ( + fireAction: TriggerAction, + runAction: HoldAction, +): EcsSystem<[InputsEcsComponent]> => ({ + query: [inputsId], + run: () => { + if (fireAction.isTriggered) { console.log(`Fire action triggered`); } - if (this._runAction.isHeld) { + if (runAction.isHeld) { console.log(`Run action is being held`); } - } -} + }, +}); diff --git a/demo/src/game.ts b/demo/src/game.ts index 843c6f42..3ea8e5cf 100644 --- a/demo/src/game.ts +++ b/demo/src/game.ts @@ -1,11 +1,11 @@ import { + cameraId, createGame, createImageSprite, createRenderEcsSystem, positionId, Random, Rect, - registerCamera, spriteId, Vector2, } from '../../src'; @@ -20,9 +20,21 @@ enum RenderLayer { const { game, world, renderContext, time } = createGame('demo-container'); -registerCamera(world, time, { - scissorRect: new Rect(Vector2.zero, new Vector2(0.5, 1)), +// Create camera entity +const cameraEntity = world.createEntity(); +world.addComponent(cameraEntity, positionId, { + world: Vector2.zero, + local: Vector2.zero, +}); +world.addComponent(cameraEntity, cameraId, { + zoom: 1, + zoomSensitivity: 0.1, + panSensitivity: 1, + minZoom: 0.1, + maxZoom: 10, + isStatic: false, layerMask: RenderLayer.default | RenderLayer.foreground, + scissorRect: new Rect(Vector2.zero, new Vector2(0.5, 1)), }); const planetSprite = await createImageSprite( From 489f95210fd92a143a8c121ff1e4dd6273f05e42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:49:58 +0000 Subject: [PATCH 3/4] feat(demo): add camera system to demo Co-authored-by: stormmuller <17644200+stormmuller@users.noreply.github.com> --- demo/src/game.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demo/src/game.ts b/demo/src/game.ts index 3ea8e5cf..bfa6aa9c 100644 --- a/demo/src/game.ts +++ b/demo/src/game.ts @@ -1,5 +1,6 @@ import { cameraId, + createCameraEcsSystem, createGame, createImageSprite, createRenderEcsSystem, @@ -98,6 +99,7 @@ setInterval(() => { }, 1000); world.addSystem(createMoveEcsSystem(time)); +world.addSystem(createCameraEcsSystem(time)); world.addSystem(createRenderEcsSystem(renderContext)); game.run(); From a8080ec071b996f40e08e283a44bc59bc1557083 Mon Sep 17 00:00:00 2001 From: Storm Date: Sun, 11 Jan 2026 13:06:16 +0000 Subject: [PATCH 4/4] refactor: replace ControlAdventurerEcsComponent with tag in control-adventurer system --- demo/src/animationDemo.ts | 3 +-- demo/src/control-adventurer-component.ts | 9 ++------- demo/src/control-adventurer-system.ts | 19 +++---------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/demo/src/animationDemo.ts b/demo/src/animationDemo.ts index ad455f25..24a66d94 100644 --- a/demo/src/animationDemo.ts +++ b/demo/src/animationDemo.ts @@ -12,7 +12,6 @@ import { createResetInputsEcsSystem, createSpriteAnimationEcsSystem, createUpdateInputEcsSystem, - FlipEcsComponent, flipId, Game, InputManager, @@ -304,7 +303,7 @@ function buildAdventurerControllableEntities( animationInputs, stateMachine, }); - world.addComponent(entity, controlAdventurerId, {}); + world.addTag(entity, controlAdventurerId); world.addComponent(entity, flipId, { flipX: false, flipY: false, diff --git a/demo/src/control-adventurer-component.ts b/demo/src/control-adventurer-component.ts index 95933465..93c94b6e 100644 --- a/demo/src/control-adventurer-component.ts +++ b/demo/src/control-adventurer-component.ts @@ -1,8 +1,3 @@ -import { createComponentId } from '../../src/new-ecs/ecs-component'; +import { createTagId } from '../../src/new-ecs/ecs-component'; -export interface ControlAdventurerEcsComponent { - // Marker component - no data needed -} - -export const controlAdventurerId = - createComponentId('control-adventurer'); +export const controlAdventurerId = createTagId('control-adventurer'); diff --git a/demo/src/control-adventurer-system.ts b/demo/src/control-adventurer-system.ts index 336b8ffd..2dadc7c3 100644 --- a/demo/src/control-adventurer-system.ts +++ b/demo/src/control-adventurer-system.ts @@ -8,10 +8,7 @@ import { TriggerAction, } from '../../src'; import { EcsSystem } from '../../src/new-ecs'; -import { - ControlAdventurerEcsComponent, - controlAdventurerId, -} from './control-adventurer-component'; +import { controlAdventurerId } from './control-adventurer-component'; export const createControlAdventurerEcsSystem = ( attackTriggerInput: TriggerAction, @@ -20,21 +17,11 @@ export const createControlAdventurerEcsSystem = ( jumpTriggerInput: TriggerAction, takeDamageTriggerInput: TriggerAction, ): EcsSystem< - [ - ControlAdventurerEcsComponent, - SpriteAnimationEcsComponent, - FlipEcsComponent, - PositionEcsComponent, - ] + [SpriteAnimationEcsComponent, FlipEcsComponent, PositionEcsComponent] > => ({ query: [controlAdventurerId, spriteAnimationId, flipId, positionId], run: (result) => { - const [ - _controlAdventurerComponent, - spriteAnimationComponent, - flipComponent, - _positionComponent, - ] = result.components; + const [spriteAnimationComponent, flipComponent] = result.components; const animationInputs = spriteAnimationComponent.animationInputs;