Phase 2: ECS foundation — world, components, systems, bridge
- bitECS world with time tracking (delta, elapsed, tick) - 5 components: Position, Velocity, SpriteRef, Health, ChemicalComposition - Movement system (velocity * delta) + bounce system (boundary reflection) - Health system with damage, healing, death detection - Entity factory (createGameEntity/removeGameEntity) - Phaser bridge: polling sync creates/destroys/updates circle sprites - GameScene: 20 colored circles bouncing at 60fps - BootScene: click-to-start transition, version bump to v0.2.0 - 39 ECS unit tests passing (74 total) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,29 +30,33 @@ export class BootScene extends Phaser.Scene {
|
||||
|
||||
// Version
|
||||
this.add
|
||||
.text(cx, cy + 80, 'v0.1.0 — Phase 0: Project Setup', {
|
||||
.text(cx, cy + 80, 'v0.2.0 — Phase 2: ECS Foundation', {
|
||||
fontSize: '12px',
|
||||
color: '#333333',
|
||||
fontFamily: 'monospace',
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
|
||||
// Pulsing indicator
|
||||
const dot = this.add
|
||||
.text(cx, cy + 120, '◉', {
|
||||
fontSize: '24px',
|
||||
// Click to start
|
||||
const startText = this.add
|
||||
.text(cx, cy + 120, '[ Click to start ]', {
|
||||
fontSize: '16px',
|
||||
color: '#00ff88',
|
||||
fontFamily: 'monospace',
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
|
||||
this.tweens.add({
|
||||
targets: dot,
|
||||
alpha: 0.2,
|
||||
targets: startText,
|
||||
alpha: 0.3,
|
||||
duration: 1500,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut',
|
||||
});
|
||||
|
||||
this.input.once('pointerdown', () => {
|
||||
this.scene.start('GameScene');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
91
src/scenes/GameScene.ts
Normal file
91
src/scenes/GameScene.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import Phaser from 'phaser';
|
||||
import { createGameWorld, updateTime, type GameWorld } from '../ecs/world';
|
||||
import { movementSystem, bounceSystem } from '../ecs/systems/movement';
|
||||
import { healthSystem } from '../ecs/systems/health';
|
||||
import { createGameEntity, removeGameEntity } from '../ecs/factory';
|
||||
import { PhaserBridge } from '../ecs/bridge';
|
||||
import { GAME_WIDTH, GAME_HEIGHT } from '../config';
|
||||
|
||||
const ENTITY_COUNT = 20;
|
||||
const COLORS = [
|
||||
0x00ff88, 0xff0044, 0x44aaff, 0xffaa00,
|
||||
0xff44ff, 0x44ffaa, 0xffff44, 0xaa44ff,
|
||||
];
|
||||
|
||||
export class GameScene extends Phaser.Scene {
|
||||
private gameWorld!: GameWorld;
|
||||
private bridge!: PhaserBridge;
|
||||
private statsText!: Phaser.GameObjects.Text;
|
||||
|
||||
constructor() {
|
||||
super({ key: 'GameScene' });
|
||||
}
|
||||
|
||||
create(): void {
|
||||
// 1. Initialize ECS
|
||||
this.gameWorld = createGameWorld();
|
||||
this.bridge = new PhaserBridge(this);
|
||||
|
||||
// 2. Spawn bouncing circles with random properties
|
||||
for (let i = 0; i < ENTITY_COUNT; i++) {
|
||||
const speed = 50 + Math.random() * 150;
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
|
||||
createGameEntity(this.gameWorld.world, {
|
||||
position: {
|
||||
x: 100 + Math.random() * (GAME_WIDTH - 200),
|
||||
y: 100 + Math.random() * (GAME_HEIGHT - 200),
|
||||
},
|
||||
velocity: {
|
||||
vx: Math.cos(angle) * speed,
|
||||
vy: Math.sin(angle) * speed,
|
||||
},
|
||||
sprite: {
|
||||
color: COLORS[i % COLORS.length],
|
||||
radius: 6 + Math.random() * 14,
|
||||
},
|
||||
health: {
|
||||
current: 100,
|
||||
max: 100,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 3. UI labels
|
||||
this.add.text(10, 10, 'Phase 2: ECS Foundation', {
|
||||
fontSize: '14px',
|
||||
color: '#00ff88',
|
||||
fontFamily: 'monospace',
|
||||
});
|
||||
|
||||
this.statsText = this.add.text(10, 30, '', {
|
||||
fontSize: '12px',
|
||||
color: '#557755',
|
||||
fontFamily: 'monospace',
|
||||
});
|
||||
}
|
||||
|
||||
update(_time: number, delta: number): void {
|
||||
// 1. Update world time
|
||||
updateTime(this.gameWorld, delta);
|
||||
|
||||
// 2. Run systems
|
||||
movementSystem(this.gameWorld.world, delta);
|
||||
bounceSystem(this.gameWorld.world, GAME_WIDTH, GAME_HEIGHT);
|
||||
|
||||
// 3. Health check + cleanup
|
||||
const dead = healthSystem(this.gameWorld.world);
|
||||
for (const eid of dead) {
|
||||
removeGameEntity(this.gameWorld.world, eid);
|
||||
}
|
||||
|
||||
// 4. Sync to Phaser
|
||||
this.bridge.sync(this.gameWorld.world);
|
||||
|
||||
// 5. Update stats
|
||||
const fps = delta > 0 ? Math.round(1000 / delta) : 0;
|
||||
this.statsText.setText(
|
||||
`${this.bridge.entityCount} entities | tick ${this.gameWorld.time.tick} | ${fps} fps`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user