or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/ecos@0.2.x

docs

index.md
tile.json

tessl/npm-ecos

tessl install tessl/npm-ecos@0.2.0

Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern

custom-constructors.mddocs/guides/

Custom Constructors

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.

Overview

Custom constructors:

  • Create entities with predefined property values
  • Add type suffixes (e.g., unit.warrior instead of just unit)
  • Provide convenient, named methods on factory instances
  • Reduce repetitive code when creating similar entities

Getting Started

Step 1: Define a Factory with Custom Constructors

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 }
    }
});

Step 2: Use Custom Constructor Methods

// 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 }

Understanding Type Suffixes

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.

Common Patterns

Character Classes

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();

Item Rarities

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)'

Enemy Types

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();

UI Components

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>

Combining with Other Features

Custom Constructors with Methods

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 = 65

Custom Constructors with Extenders

Custom 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); // Timestamp

Custom Constructors with Presets

Combine 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, 200

Understanding Custom Constructor Implementation

Custom 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 suffix

Accessing Custom Constructor Configuration

The 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 }
// }

Best Practices

Use Descriptive Names

Choose clear names that describe the entity variant:

// Good
custom: {
    warrior: { ... },
    mage: { ... },
    tank: { ... }
}

// Avoid
custom: {
    type1: { ... },
    variant2: { ... },
    v3: { ... }
}

Group Related Variants

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: { ... }
    }
});

Provide Meaningful Defaults

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
    }
});

Don't Overuse Custom Constructors

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 });

Document Custom Constructors

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 }
    }
});

Type Checking Custom Entities

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

See Also

  • Using Presets - Combine custom constructors with presets
  • Working with Extenders - Add initialization logic to custom entities
  • Patterns - Design patterns using custom constructors