Entities are containers for components. Use spawn() to create entities with initial components:
// Create entity with components
const entity = world.spawn({
position: { x: 10, y: 20 },
health: { value: 100 }
});
// Look up an entity by ID
const found = world.getEntity(entity.id); // Entity | undefined
// Add components later
world.addComponent(entity.id, 'velocity', { x: 5, y: 0 });
// Get component data (returns undefined if not found)
const position = world.getComponent(entity.id, 'position');
// Remove components or entities
world.removeComponent(entity.id, 'velocity');
world.removeEntity(entity.id);
React to component additions and removals. Both methods return an unsubscribe function:
const unsubAdd = world.onComponentAdded('health', ({ value, entity }) => {
console.log(`Health added to entity ${entity.id}:`, value);
});
const unsubRemove = world.onComponentRemoved('health', ({ value, entity }) => {
console.log(`Health removed from entity ${entity.id}:`, value);
});
// Unsubscribe when done
unsubAdd();
unsubRemove();
Also available on world.entityManager.onComponentAdded() / onComponentRemoved().
Systems process entities that match specific component patterns:
world.addSystem('combat')
.addQuery('fighters', {
with: ['position', 'health'],
without: ['dead']
})
.addQuery('projectiles', {
with: ['position', 'damage']
})
.setProcess(({ queries }) => {
for (const fighter of queries.fighters) {
for (const projectile of queries.projectiles) {
// Combat logic here
}
}
});
For more on systems, see Systems.
Resources provide global state accessible to all systems.
interface Resources {
score: { value: number };
settings: { difficulty: 'easy' | 'hard' };
}
const world = ECSpresso.create()
.withComponentTypes<Components>()
.withResourceTypes<Resources>()
.withResource('score', { value: 0 })
.build();
// Sync or async factories (lazy initialization)
world.addResource('config', () => ({ difficulty: 'normal', soundEnabled: true }));
world.addResource('database', async () => await connectToDatabase());
// Factory with dependencies (initialized after dependencies are ready)
world.addResource('cache', {
dependsOn: ['database'],
factory: (ecs) => ({ db: ecs.getResource('database') })
});
// Initialize all resources (respects dependency order, detects circular deps)
await world.initializeResources();
// Use in systems
world.addSystem('scoring')
.setProcess(({ ecs }) => {
const score = ecs.getResource('score');
score.value += 10;
});
// Get a resource (throws if not found)
const score = world.getResource('score');
// Get a resource (returns undefined if not found)
const score = world.tryGetResource('score');
// Set a resource to a plain value
world.setResource('score', { value: 42 });
// Update a resource using a function (when new value depends on old)
world.updateResource('score', (prev) => ({ value: prev.value + 1 }));
Subscribe to resource changes for reactive integrations (e.g., React's useSyncExternalStore). Returns an unsubscribe function. Notifications only fire when the value actually changes (Object.is comparison).
const unsubscribe = world.onResourceChange('score', (newValue, oldValue) => {
console.log(`Score changed from ${oldValue.value} to ${newValue.value}`);
});
// Stop listening
unsubscribe();
Resources also chain naturally with plugins in the builder:
const world = ECSpresso.create()
.withPlugin(physicsPlugin)
.withResource('config', { debug: true, maxEntities: 1000 })
.withResource('score', () => ({ value: 0 }))
.withResource('cache', {
dependsOn: ['database'],
factory: (ecs) => createCache(ecs.getResource('database'))
})
.build();
Resources can define cleanup logic with onDispose callbacks:
world.addResource('keyboard', {
factory: () => {
const handler = (e: KeyboardEvent) => { /* ... */ };
window.addEventListener('keydown', handler);
return { handler };
},
onDispose: (resource) => {
window.removeEventListener('keydown', resource.handler);
}
});
await world.disposeResource('keyboard'); // Dispose a single resource
await world.disposeResources(); // All, in reverse dependency order
onDispose receives the resource value and the ECSpresso instance. Supports sync and async callbacks. Only initialized resources have their onDispose called. removeResource() still exists for removal without disposal.