Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
77186f6
feat: started adding new ECS system
stormmuller Jan 6, 2026
ad58529
feat: migrate animation components and systems to new ECS architectur…
Copilot Jan 6, 2026
0debc0b
refactor: streamline animation component and system implementations
stormmuller Jan 6, 2026
6b26982
feat: refactor audio component and system to ECS architecture
stormmuller Jan 6, 2026
5f5e78c
test: add comprehensive test suite for audio system (#428)
Copilot Jan 6, 2026
9b0af14
feat: added new ecs implementation and removed the old one, migrating…
stormmuller Jan 10, 2026
30e3924
fix: restore test compatibility after ECS migration (#431)
Copilot Jan 10, 2026
493e7bd
Update utility functions to use new ECS implementation (#430)
Copilot Jan 10, 2026
f2be4bf
fix: refactor physics and particle test files to use proper types and…
Copilot Jan 11, 2026
4eb50fd
Merge branch 'dev' into create-new-ecs-system
stormmuller Jan 11, 2026
4ab598b
feat(demo): migrate demo to new ECS implementation (#435)
Copilot Jan 11, 2026
69b6595
refactor: remove createBatch function from demo
stormmuller Jan 11, 2026
04917a6
refactor: reorder imports and improve formatting in various test file…
stormmuller Jan 11, 2026
5e0c939
refactor: update audio system tests to use Howl for sound playback an…
stormmuller Jan 11, 2026
4b97d17
fix: update ECS module paths to new directory structure
stormmuller Jan 11, 2026
6e51749
refactor: remove pooling module from package exports
stormmuller Jan 11, 2026
e2653be
docs: updated space shooter demo to use new ECS
stormmuller Jan 12, 2026
d3b4dbf
refactor: update ECS system interfaces to include tags and improve qu…
stormmuller Jan 13, 2026
f23f00f
fix: correct bullet and player movement direction in ECS systems
stormmuller Jan 17, 2026
4ed8139
docs: update ECS documentation for components, entities, systems, and…
stormmuller Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .vscode/forge.code-snippets
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"Forge: New ECS Component": {
"prefix": "fcomp",
"body": [
"import { createComponentId } from '../../new-ecs/ecs-component.js';",
"",
"/**",
" * ECS-style component interface for ${1:ComponentDescription}.",
" */",
"export interface ${2:ComponentName}EcsComponent {",
" ${3:field}: ${4:type};",
"}",
"",
"export const ${2}Id = createComponentId<${2}EcsComponent>('${5:${2}}');",
],
"description": "Create a new ECS component.",
"scope": "typescript",
},
"Forge: New ECS System": {
"prefix": "fsys",
"body": [
"import { EcsSystem } from '../../new-ecs/ecs-system.js';",
"",
"/**",
" * Creates an ECS system to handle ${1:description}.",
" */",
"export const create${2:systemName}EcsSystem = (): EcsSystem<[${3:CompA}]> => ({",
" query: [${4:compAId}],",
" run: (result) => {",
" const [${5:compA}] = result.components;",
" // TODO: implement system logic",
" },",
"});",
],
"description": "Create a new ECS system.",
"scope": "typescript",
},
}
124 changes: 85 additions & 39 deletions demo/src/animationDemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,47 @@ import {
Axis2dAction,
buttonMoments,
createAnimation,
FlipComponent,
createResetInputsEcsSystem,
createSpriteAnimationEcsSystem,
createUpdateInputEcsSystem,
flipId,
Game,
InputManager,
inputsId,
KeyboardAxis1dBinding,
KeyboardAxis2dBinding,
KeyboardInputSource,
KeyboardTriggerBinding,
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,
adventurerSprite: Sprite,
): ReturnType<typeof setupInputs> {
const inputs = setupInputs(world, game, time);

// Add animation system
world.addSystem(createSpriteAnimationEcsSystem(time));

const ShipController = createShipAnimationController();
buildShipEntities(world, shipSprite, ShipController);

Expand All @@ -58,7 +65,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);
Expand All @@ -78,22 +85,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(
Expand Down Expand Up @@ -234,32 +247,65 @@ function createAdventurerControllableInputs() {
}

function buildShipEntities(
world: World,
world: EcsWorld,
shipSprite: Sprite,
stateMachine: FiniteStateMachine<AnimationInputs, AnimationClip>,
) {
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, AnimationClip>,
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.addTag(entity, controlAdventurerId);
world.addComponent(entity, flipId, {
flipX: false,
flipY: false,
});
}
4 changes: 2 additions & 2 deletions demo/src/control-adventurer-component.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { Component } from '../../src';
import { createTagId } from '../../src/new-ecs/ecs-component';

export class ControlAdventurerComponent extends Component {}
export const controlAdventurerId = createTagId('control-adventurer');
80 changes: 29 additions & 51 deletions demo/src/control-adventurer-system.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,47 @@
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 { controlAdventurerId } from './control-adventurer-component';

export const createControlAdventurerEcsSystem = (
attackTriggerInput: TriggerAction,
runRTriggerInput: TriggerAction,
runLTriggerInput: TriggerAction,
jumpTriggerInput: TriggerAction,
takeDamageTriggerInput: TriggerAction,
): EcsSystem<
[SpriteAnimationEcsComponent, FlipEcsComponent, PositionEcsComponent]
> => ({
query: [spriteAnimationId, flipId, positionId],
tags: [controlAdventurerId],
run: (result) => {
const [spriteAnimationComponent, flipComponent] = result.components;

const animationInputs = spriteAnimationComponent.animationInputs;

if (this._jumpTriggerInput.isTriggered) {
if (jumpTriggerInput.isTriggered) {
console.log('Jumping!');

animationInputs.setTrigger('jump');

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;

Expand All @@ -72,13 +50,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) {
Expand All @@ -87,5 +65,5 @@ export class ControlAdventurerSystem extends System {

health.value = Math.max(0, health.value - 50);
}
}
}
},
});
38 changes: 0 additions & 38 deletions demo/src/create-batch.ts

This file was deleted.

Loading
Loading