tessl install tessl/npm-ecos@0.2.0Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern
Custom constructors allow you to create specialized entity types with predefined property values. This guide shows you how to define and use custom constructors for convenient entity creation.
Custom constructors:
unit.warrior instead of just unit)const { factory } = require('ecos');
const unitFactory = factory.create({
name: 'unit',
props: ['health', 'attack', 'defense'],
default: { health: 100, attack: 10, defense: 5 },
custom: {
warrior: { health: 150, attack: 20, defense: 15 },
mage: { health: 80, attack: 30, defense: 5 },
tank: { health: 200, attack: 10, defense: 30 }
}
});// Create entities using custom constructors
const warrior = unitFactory.warrior();
const mage = unitFactory.mage();
const tank = unitFactory.tank();
console.log(warrior);
// { type: 'unit.warrior', health: 150, attack: 20, defense: 15, id: 0 }
console.log(mage);
// { type: 'unit.mage', health: 80, attack: 30, defense: 5, id: 1 }
console.log(tank);
// { type: 'unit.tank', health: 200, attack: 10, defense: 30, id: 2 }Custom constructors append the custom name to the entity type:
// Standard create method
const unit = unitFactory.create();
console.log(unit.type); // 'unit'
// Custom constructor
const warrior = unitFactory.warrior();
console.log(warrior.type); // 'unit.warrior'This makes it easy to identify specific entity variants by their type.
Define different character classes with specialized stats:
const characterFactory = factory.create({
name: 'character',
props: ['health', 'mana', 'strength', 'intelligence', 'agility'],
default: {
health: 100,
mana: 50,
strength: 10,
intelligence: 10,
agility: 10
},
custom: {
warrior: {
health: 150,
mana: 30,
strength: 20,
intelligence: 5,
agility: 10
},
mage: {
health: 80,
mana: 150,
strength: 5,
intelligence: 25,
agility: 8
},
rogue: {
health: 100,
mana: 50,
strength: 12,
intelligence: 10,
agility: 20
}
}
});
const warrior = characterFactory.warrior();
const mage = characterFactory.mage();
const rogue = characterFactory.rogue();Create items with different rarity levels:
factory.registerMethod('getDescription', function() {
return `${this.name} (${this.rarity})`;
});
const itemFactory = factory.create({
name: 'item',
props: ['name', 'value', 'rarity', 'dropChance'],
default: {
name: 'Item',
value: 10,
rarity: 'common',
dropChance: 0.5
},
methods: ['getDescription'],
custom: {
common: {
value: 10,
rarity: 'common',
dropChance: 0.5
},
uncommon: {
value: 50,
rarity: 'uncommon',
dropChance: 0.25
},
rare: {
value: 200,
rarity: 'rare',
dropChance: 0.1
},
legendary: {
value: 1000,
rarity: 'legendary',
dropChance: 0.01
}
}
});
const commonItem = itemFactory.common();
commonItem.name = 'Iron Sword';
const legendaryItem = itemFactory.legendary();
legendaryItem.name = 'Excalibur';
console.log(commonItem.getDescription()); // 'Iron Sword (common)'
console.log(legendaryItem.getDescription()); // 'Excalibur (legendary)'Define different enemy variants:
const enemyFactory = factory.create({
name: 'enemy',
props: ['health', 'damage', 'speed', 'xpReward'],
default: {
health: 50,
damage: 5,
speed: 1,
xpReward: 10
},
custom: {
goblin: {
health: 30,
damage: 5,
speed: 1.2,
xpReward: 10
},
orc: {
health: 100,
damage: 15,
speed: 0.8,
xpReward: 50
},
dragon: {
health: 500,
damage: 50,
speed: 1.5,
xpReward: 1000
}
}
});
const goblin = enemyFactory.goblin();
const orc = enemyFactory.orc();
const dragon = enemyFactory.dragon();Create different button types:
factory.registerMethod('render', function() {
console.log(`<button class="${this.className}">${this.text}</button>`);
});
const buttonFactory = factory.create({
name: 'button',
props: ['text', 'className', 'width', 'height'],
default: {
text: 'Button',
className: 'btn',
width: 100,
height: 30
},
methods: ['render'],
custom: {
primary: {
className: 'btn btn-primary',
width: 120,
height: 40
},
secondary: {
className: 'btn btn-secondary',
width: 120,
height: 40
},
danger: {
className: 'btn btn-danger',
width: 100,
height: 35
}
}
});
const primaryBtn = buttonFactory.primary();
primaryBtn.text = 'Submit';
primaryBtn.render(); // <button class="btn btn-primary">Submit</button>Custom constructors work seamlessly with registered methods:
factory.registerMethod('attack', function(target) {
const damage = this.attack - target.defense;
target.health -= Math.max(1, damage);
});
const unitFactory = factory.create({
name: 'unit',
props: ['health', 'attack', 'defense'],
default: { health: 100, attack: 10, defense: 5 },
methods: ['attack'],
custom: {
warrior: { health: 150, attack: 20, defense: 15 },
mage: { health: 80, attack: 30, defense: 5 }
}
});
const warrior = unitFactory.warrior();
const mage = unitFactory.mage();
warrior.attack(mage);
console.log(mage.health); // 80 - 15 = 65Custom constructors apply extenders like any entity:
const { factory, extenders } = require('ecos');
factory.registerExtender('timestamps', {
type: extenders.FUNCTION,
handler: function(entity) {
entity.createdAt = Date.now();
}
});
const entityFactory = factory.create({
name: 'entity',
props: ['value'],
default: { value: 0 },
extend: ['timestamps'],
custom: {
typeA: { value: 100 },
typeB: { value: 200 }
}
});
const a = entityFactory.typeA();
const b = entityFactory.typeB();
console.log(a.createdAt); // Timestamp
console.log(b.createdAt); // TimestampCombine custom constructors with presets for powerful configurations:
factory.registerPreset('positioned', {
props: ['x', 'y'],
methods: ['move']
});
const spriteFactory = factory.create({
name: 'sprite',
presets: ['positioned'],
default: { x: 0, y: 0 },
custom: {
player: { x: 50, y: 50 },
enemy: { x: 200, y: 200 }
}
});
const player = spriteFactory.player();
const enemy = spriteFactory.enemy();
console.log(player.x, player.y); // 50, 50
console.log(enemy.x, enemy.y); // 200, 200Custom constructors are methods added to the factory instance that internally call factoryInstance.create():
// This custom constructor definition:
const factory = factory.create({
name: 'unit',
custom: {
warrior: { health: 150, attack: 20 }
}
});
// Creates this method on the factory instance:
factoryInstance.warrior = function() {
return factoryInstance.create({ health: 150, attack: 20 }, 'warrior');
};
// The second parameter 'warrior' creates the type suffixThe custom constructor definitions are stored on the factory instance:
const unitFactory = factory.create({
name: 'unit',
props: ['health', 'attack'],
default: { health: 100, attack: 10 },
custom: {
warrior: { health: 150, attack: 20 },
mage: { health: 80, attack: 30 }
}
});
console.log(unitFactory.custom);
// {
// warrior: { health: 150, attack: 20 },
// mage: { health: 80, attack: 30 }
// }Choose clear names that describe the entity variant:
// Good
custom: {
warrior: { ... },
mage: { ... },
tank: { ... }
}
// Avoid
custom: {
type1: { ... },
variant2: { ... },
v3: { ... }
}Keep related entity types in the same factory:
// Good - character classes together
const characterFactory = factory.create({
name: 'character',
custom: {
warrior: { ... },
mage: { ... },
rogue: { ... }
}
});
// Avoid - mixing unrelated types
const factory = factory.create({
name: 'entity',
custom: {
warrior: { ... },
button: { ... },
document: { ... }
}
});Set defaults that make sense for the base entity type:
const unitFactory = factory.create({
name: 'unit',
props: ['health', 'attack', 'defense'],
default: { health: 100, attack: 10, defense: 5 }, // Balanced base stats
custom: {
warrior: { health: 150, attack: 20, defense: 15 }, // High health/defense
mage: { health: 80, attack: 30, defense: 5 } // High attack, low defense
}
});Use custom constructors for true variants, not for every property combination:
// Good - distinct entity types
custom: {
warrior: { health: 150, attack: 20, defense: 15 },
mage: { health: 80, attack: 30, defense: 5 }
}
// Avoid - too many similar variants
custom: {
warrior1: { health: 150, attack: 20, defense: 15 },
warrior2: { health: 151, attack: 21, defense: 15 },
warrior3: { health: 152, attack: 22, defense: 15 },
// ... 20 more warriors
}
// Better - use create() with options for minor variations
const warrior1 = warriorFactory.create({ health: 150 });
const warrior2 = warriorFactory.create({ health: 151 });Add comments describing what each custom constructor represents:
const unitFactory = factory.create({
name: 'unit',
props: ['health', 'attack', 'defense'],
default: { health: 100, attack: 10, defense: 5 },
custom: {
// Tank unit: High health and defense, low attack
warrior: { health: 150, attack: 20, defense: 15 },
// Glass cannon: High attack, low defense
mage: { health: 80, attack: 30, defense: 5 },
// Defensive unit: Very high defense, moderate health
tank: { health: 200, attack: 10, defense: 30 }
}
});Use the type property to identify custom entity types:
const warrior = unitFactory.warrior();
const mage = unitFactory.mage();
console.log(warrior.type === 'unit.warrior'); // true
console.log(mage.type === 'unit.mage'); // true
// Check if entity is a unit (any type)
function isUnit(entity) {
return entity.type.startsWith('unit');
}
console.log(isUnit(warrior)); // true
console.log(isUnit(mage)); // true
// Check specific unit type
function isWarrior(entity) {
return entity.type === 'unit.warrior';
}
console.log(isWarrior(warrior)); // true
console.log(isWarrior(mage)); // false