feat: add game loop with NPC spawning and system execution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { TICK_RATE, BROADCAST_EVERY_N_TICKS } from '@dflike/shared';
|
||||
import { World } from '../ecs/World.js';
|
||||
import { GameMap } from '../map/GameMap.js';
|
||||
import { needsDecaySystem } from '../systems/needsDecaySystem.js';
|
||||
import { npcBrainSystem } from '../systems/npcBrainSystem.js';
|
||||
import { movementSystem } from '../systems/movementSystem.js';
|
||||
import { spawnNPC } from './spawner.js';
|
||||
|
||||
export class GameLoop {
|
||||
readonly world: World;
|
||||
readonly map: GameMap;
|
||||
private tick = 0;
|
||||
private interval: ReturnType<typeof setInterval> | null = null;
|
||||
private onBroadcast: (() => void) | null = null;
|
||||
|
||||
constructor() {
|
||||
this.world = new World();
|
||||
this.map = new GameMap();
|
||||
this.setupMap();
|
||||
this.spawnInitialNPCs(8);
|
||||
}
|
||||
|
||||
private setupMap(): void {
|
||||
// Add some obstacle clusters for pathfinding interest
|
||||
for (let x = 10; x <= 14; x++) for (let y = 10; y <= 12; y++) this.map.setObstacle(x, y);
|
||||
for (let x = 30; x <= 33; x++) for (let y = 25; y <= 28; y++) this.map.setObstacle(x, y);
|
||||
for (let x = 50; x <= 53; x++) for (let y = 40; y <= 43; y++) this.map.setObstacle(x, y);
|
||||
|
||||
// Points of interest
|
||||
this.map.addPointOfInterest({ type: 'food', position: { x: 15, y: 15 } });
|
||||
this.map.addPointOfInterest({ type: 'food', position: { x: 45, y: 30 } });
|
||||
this.map.addPointOfInterest({ type: 'rest', position: { x: 8, y: 8 } });
|
||||
this.map.addPointOfInterest({ type: 'rest', position: { x: 55, y: 50 } });
|
||||
}
|
||||
|
||||
private spawnInitialNPCs(count: number): void {
|
||||
for (let i = 0; i < count; i++) {
|
||||
spawnNPC(this.world, this.map);
|
||||
}
|
||||
}
|
||||
|
||||
setBroadcastHandler(handler: () => void): void {
|
||||
this.onBroadcast = handler;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
const tickInterval = 1000 / TICK_RATE;
|
||||
this.interval = setInterval(() => this.update(), tickInterval);
|
||||
console.log(`Game loop started at ${TICK_RATE} ticks/sec`);
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
this.tick++;
|
||||
|
||||
// Run systems in order
|
||||
needsDecaySystem(this.world);
|
||||
npcBrainSystem(this.world, this.map);
|
||||
movementSystem(this.world);
|
||||
|
||||
// Broadcast state periodically
|
||||
if (this.tick % BROADCAST_EVERY_N_TICKS === 0 && this.onBroadcast) {
|
||||
this.onBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
getTick(): number {
|
||||
return this.tick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { World } from '../ecs/World.js';
|
||||
import type { GameMap } from '../map/GameMap.js';
|
||||
import { generateRandomAppearance } from '../spawner/appearanceGenerator.js';
|
||||
import type { EntityId, Position, Needs, Movement, NPCBrain, Appearance } from '@dflike/shared';
|
||||
|
||||
export function spawnNPC(world: World, map: GameMap): EntityId {
|
||||
const entity = world.createEntity();
|
||||
const pos = map.getRandomWalkable();
|
||||
|
||||
world.addComponent<Position>(entity, 'position', pos);
|
||||
world.addComponent<Needs>(entity, 'needs', {
|
||||
hunger: 40 + Math.random() * 40, // 40-80
|
||||
energy: 40 + Math.random() * 40,
|
||||
});
|
||||
world.addComponent<Movement>(entity, 'movement', {
|
||||
state: 'idle',
|
||||
target: null,
|
||||
path: [],
|
||||
direction: 0,
|
||||
});
|
||||
world.addComponent<NPCBrain>(entity, 'npcBrain', {
|
||||
currentGoal: null,
|
||||
goalQueue: [],
|
||||
});
|
||||
world.addComponent<Appearance>(entity, 'appearance', generateRandomAppearance());
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
|
||||
Reference in New Issue
Block a user