Using Archetypes
This guide shows you how to use archetypes to create entities from predefined templates, ensuring consistent component sets for common entity types.
Prerequisites
Before starting, you should:
Overview
We'll cover:
- Defining an archetype
- Creating entities from archetypes
- Common archetype patterns
Step 1: Define an Archetype
An archetype is a predefined combination of components. Define it once, then use it to create entities that always have those components.
import { createECS, defineComponent } from '@motioneffector/ecs'
import { createDatabase } from '@motioneffector/sql'
const Position = defineComponent('Position', { x: 'number', y: 'number' })
const Health = defineComponent('Health', { current: 'number', max: 'number' })
const Velocity = defineComponent('Velocity', { vx: 'number', vy: 'number' })
const db = await createDatabase()
const ecs = createECS(db, [Position, Health, Velocity])
await ecs.initialize()
// Define an archetype for moving characters
const CharacterArchetype = ecs.defineArchetype([Position, Health, Velocity])
The archetype ensures all its components are registered with the ECS.
Step 2: Create Entities from Archetypes
Use createFromArchetype to create an entity with all archetype components in one call:
// Create a player using the archetype
const player = ecs.createFromArchetype(CharacterArchetype, {
Position: { x: 0, y: 0 },
Health: { current: 100, max: 100 },
Velocity: { vx: 0, vy: 0 }
})
// The entity now has all three components
console.log(ecs.hasComponent(player, Position)) // true
console.log(ecs.hasComponent(player, Health)) // true
console.log(ecs.hasComponent(player, Velocity)) // true
You must provide data for every component in the archetype.
Step 3: Common Archetype Patterns
Player Archetype
const PlayerControlled = defineComponent('PlayerControlled', {
inputEnabled: 'boolean'
})
const PlayerArchetype = ecs.defineArchetype([
Position,
Health,
Velocity,
PlayerControlled
])
const player = ecs.createFromArchetype(PlayerArchetype, {
Position: { x: 100, y: 100 },
Health: { current: 100, max: 100 },
Velocity: { vx: 0, vy: 0 },
PlayerControlled: { inputEnabled: true }
})
Enemy Archetype
const AIControlled = defineComponent('AIControlled', {
behavior: 'string',
aggroRange: 'number'
})
const EnemyArchetype = ecs.defineArchetype([
Position,
Health,
Velocity,
AIControlled
])
function spawnEnemy(x: number, y: number, behavior: string) {
return ecs.createFromArchetype(EnemyArchetype, {
Position: { x, y },
Health: { current: 30, max: 30 },
Velocity: { vx: 0, vy: 0 },
AIControlled: { behavior, aggroRange: 100 }
})
}
const goblin = spawnEnemy(200, 150, 'patrol')
const boss = spawnEnemy(500, 500, 'guard')
Item Archetype
const Pickupable = defineComponent('Pickupable', {
itemId: 'string',
quantity: 'number'
})
const Sprite = defineComponent('Sprite', {
texture: 'string',
width: 'number',
height: 'number'
})
const ItemArchetype = ecs.defineArchetype([
Position,
Sprite,
Pickupable
])
function spawnItem(x: number, y: number, itemId: string, quantity: number) {
return ecs.createFromArchetype(ItemArchetype, {
Position: { x, y },
Sprite: { texture: `items/${itemId}.png`, width: 32, height: 32 },
Pickupable: { itemId, quantity }
})
}
const healthPotion = spawnItem(100, 100, 'health-potion', 1)
const goldPile = spawnItem(150, 100, 'gold', 50)
Complete Example
import { createECS, defineComponent } from '@motioneffector/ecs'
import { createDatabase } from '@motioneffector/sql'
// Define components
const Position = defineComponent('Position', { x: 'number', y: 'number' })
const Health = defineComponent('Health', { current: 'number', max: 'number' })
const Velocity = defineComponent('Velocity', { vx: 'number', vy: 'number' })
const Team = defineComponent('Team', { name: 'string' })
const Damage = defineComponent('Damage', { amount: 'number' })
// Initialize ECS
const db = await createDatabase()
const ecs = createECS(db, [Position, Health, Velocity, Team, Damage])
await ecs.initialize()
// Define archetypes
const PlayerArchetype = ecs.defineArchetype([Position, Health, Velocity, Team])
const EnemyArchetype = ecs.defineArchetype([Position, Health, Velocity, Team, Damage])
const ProjectileArchetype = ecs.defineArchetype([Position, Velocity, Damage, Team])
// Create entities from archetypes
const player = ecs.createFromArchetype(PlayerArchetype, {
Position: { x: 100, y: 100 },
Health: { current: 100, max: 100 },
Velocity: { vx: 0, vy: 0 },
Team: { name: 'player' }
})
const enemy = ecs.createFromArchetype(EnemyArchetype, {
Position: { x: 300, y: 100 },
Health: { current: 50, max: 50 },
Velocity: { vx: -1, vy: 0 },
Team: { name: 'enemy' },
Damage: { amount: 10 }
})
const fireball = ecs.createFromArchetype(ProjectileArchetype, {
Position: { x: 100, y: 100 },
Velocity: { vx: 5, vy: 0 },
Damage: { amount: 25 },
Team: { name: 'player' }
})
// Query by components (archetypes don't affect queries)
const movingEntities = ecs.query([Position, Velocity])
console.log(movingEntities.length) // 3
const damageDealing = ecs.query([Damage])
console.log(damageDealing.length) // 2 (enemy and fireball)
Variations
Factory Functions with Archetypes
const EnemyArchetype = ecs.defineArchetype([Position, Health, Velocity])
// Wrap archetype usage in a factory for convenience
function createEnemy(config: {
x: number
y: number
hp: number
speed: number
}) {
return ecs.createFromArchetype(EnemyArchetype, {
Position: { x: config.x, y: config.y },
Health: { current: config.hp, max: config.hp },
Velocity: { vx: -config.speed, vy: 0 }
})
}
// Clean API for game code
const enemy1 = createEnemy({ x: 100, y: 50, hp: 30, speed: 1 })
const enemy2 = createEnemy({ x: 200, y: 50, hp: 50, speed: 0.5 })
Multiple Archetypes per Entity Type
// Base archetype
const BaseCharacter = ecs.defineArchetype([Position, Health])
// Extended archetypes for variants
const MeleeEnemy = ecs.defineArchetype([Position, Health, MeleeDamage])
const RangedEnemy = ecs.defineArchetype([Position, Health, RangedAttack])
const FlyingEnemy = ecs.defineArchetype([Position, Health, Flying])
Troubleshooting
Missing Component Data
Symptom: Error about missing data for component.
Cause: You didn't provide data for all components in the archetype.
Solution: Ensure the data object has a key for every component in the archetype.
Component Not Registered
Symptom: Error about component not registered with ECS.
Cause: The component used in the archetype wasn't passed to createECS().
Solution: Include all components in the array passed to createECS().
See Also
- Components Concept - Understanding components
- Advanced API - defineArchetype(), createFromArchetype() reference
- Your First ECS - Basic entity creation