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

using-methods.mddocs/guides/

Guide: Using Methods

This guide covers how to add methods to entities using the ECOS factory system.

Overview

Methods are functions attached to entities that provide behavior. Methods are:

  • Registered once globally
  • Referenced by name in factory configuration
  • Shared across all entities from that factory
  • Executed with entity as this context

Basic Method Usage

Step 1: Register a Method

const { factory } = require('ecos');

factory.registerMethod('greet', function() {
    console.log(`Hello, I'm ${this.name}!`);
});

Step 2: Add Method to Factory

const personFactory = factory.create({
    name: 'person',
    props: ['name'],
    methods: ['greet']
});

Step 3: Use the Method

const person = personFactory.create({ name: 'Alice' });
person.greet(); // "Hello, I'm Alice!"

Method Features

Methods with Parameters

factory.registerMethod('takeDamage', function(amount) {
    this.health = Math.max(0, this.health - amount);
    console.log(`${this.name} took ${amount} damage. Health: ${this.health}`);
});

const combatFactory = factory.create({
    name: 'combatant',
    props: ['name', 'health'],
    default: { health: 100 },
    methods: ['takeDamage']
});

const warrior = combatFactory.create({ name: 'Warrior' });
warrior.takeDamage(30); // "Warrior took 30 damage. Health: 70"

Methods with Return Values

factory.registerMethod('calculateDamage', function(targetDefense) {
    const baseDamage = this.attack;
    const actualDamage = Math.max(1, baseDamage - targetDefense);
    return actualDamage;
});

const attackerFactory = factory.create({
    name: 'attacker',
    props: ['name', 'attack'],
    default: { attack: 20 },
    methods: ['calculateDamage']
});

const attacker = attackerFactory.create({ name: 'Orc', attack: 50 });
const damage = attacker.calculateDamage(10); // Returns 40
console.log(`Damage dealt: ${damage}`);

Methods Calling Other Methods

factory.registerMethod('heal', function(amount) {
    this.health = Math.min(this.maxHealth, this.health + amount);
    return this.health;
});

factory.registerMethod('fullHeal', function() {
    return this.heal(this.maxHealth);
});

factory.registerMethod('isAlive', function() {
    return this.health > 0;
});

const lifeFactory = factory.create({
    name: 'living',
    props: ['health', 'maxHealth'],
    default: { health: 50, maxHealth: 100 },
    methods: ['heal', 'fullHeal', 'isAlive']
});

const entity = lifeFactory.create();
entity.fullHeal(); // Calls heal() internally
console.log(entity.health); // 100
console.log(entity.isAlive()); // true

Common Method Patterns

Pattern: State Modification

factory.registerMethod('levelUp', function() {
    this.level += 1;
    this.experience = 0;
    this.maxHealth += 10;
    this.health = this.maxHealth;
    console.log(`Level up! Now level ${this.level}`);
});

factory.registerMethod('gainExperience', function(amount) {
    this.experience += amount;
    const expNeeded = this.level * 100;

    if (this.experience >= expNeeded) {
        this.levelUp();
    }
});

Pattern: Validation

factory.registerMethod('canEquip', function(item) {
    if (item.requiredLevel > this.level) {
        return false;
    }
    if (item.requiredClass && item.requiredClass !== this.class) {
        return false;
    }
    return true;
});

factory.registerMethod('equip', function(item) {
    if (!this.canEquip(item)) {
        console.log(`Cannot equip ${item.name}`);
        return false;
    }

    this.equipment[item.slot] = item;
    console.log(`Equipped ${item.name}`);
    return true;
});

Pattern: Computed Values

factory.registerMethod('getTotalAttack', function() {
    let total = this.baseAttack;

    if (this.equipment.weapon) {
        total += this.equipment.weapon.attackBonus;
    }

    return total;
});

factory.registerMethod('getTotalDefense', function() {
    let total = this.baseDefense;

    Object.values(this.equipment).forEach(item => {
        if (item && item.defenseBonus) {
            total += item.defenseBonus;
        }
    });

    return total;
});

Pattern: Entity Interaction

const { entities } = require('ecos');

factory.registerMethod('attack', function(targetId) {
    const target = entities.get(targetId);
    if (!target) {
        console.log('Target not found');
        return;
    }

    const damage = this.getTotalAttack();
    target.takeDamage(damage);
    console.log(`${this.name} attacked ${target.name} for ${damage} damage`);
});

factory.registerMethod('heal', function(targetId, amount) {
    const target = entities.get(targetId);
    if (!target) {
        console.log('Target not found');
        return;
    }

    target.health = Math.min(target.maxHealth, target.health + amount);
    console.log(`${this.name} healed ${target.name} for ${amount} HP`);
});

Multiple Methods

Register multiple related methods and use them together:

// Register movement methods
factory.registerMethod('move', function(dx, dy) {
    this.x += dx;
    this.y += dy;
});

factory.registerMethod('moveTo', function(x, y) {
    this.x = x;
    this.y = y;
});

factory.registerMethod('getPosition', function() {
    return { x: this.x, y: this.y };
});

factory.registerMethod('getDistanceTo', function(otherEntity) {
    const dx = this.x - otherEntity.x;
    const dy = this.y - otherEntity.y;
    return Math.sqrt(dx * dx + dy * dy);
});

// Register combat methods
factory.registerMethod('takeDamage', function(amount) {
    this.health = Math.max(0, this.health - amount);
});

factory.registerMethod('isAlive', function() {
    return this.health > 0;
});

factory.registerMethod('isDead', function() {
    return this.health <= 0;
});

// Use all methods in factory
const gameEntityFactory = factory.create({
    name: 'gameEntity',
    props: ['name', 'x', 'y', 'health', 'maxHealth'],
    default: { x: 0, y: 0, health: 100, maxHealth: 100 },
    methods: [
        'move',
        'moveTo',
        'getPosition',
        'getDistanceTo',
        'takeDamage',
        'isAlive',
        'isDead'
    ]
});

const entity1 = gameEntityFactory.create({ name: 'Entity1' });
const entity2 = gameEntityFactory.create({ name: 'Entity2', x: 10, y: 10 });

entity1.move(5, 5);
const distance = entity1.getDistanceTo(entity2);
console.log(`Distance: ${distance}`);

Method Registration Best Practices

Organize Methods by Category

// Combat methods
factory.registerMethod('attack', function(target) { /* ... */ });
factory.registerMethod('defend', function() { /* ... */ });
factory.registerMethod('takeDamage', function(amount) { /* ... */ });

// Movement methods
factory.registerMethod('move', function(dx, dy) { /* ... */ });
factory.registerMethod('teleport', function(x, y) { /* ... */ });

// Inventory methods
factory.registerMethod('addItem', function(item) { /* ... */ });
factory.registerMethod('removeItem', function(itemId) { /* ... */ });
factory.registerMethod('hasItem', function(itemId) { /* ... */ });

Name Methods Clearly

// ✅ Good - clear intent
factory.registerMethod('isAlive', function() { /* ... */ });
factory.registerMethod('canMove', function() { /* ... */ });
factory.registerMethod('getTotalDamage', function() { /* ... */ });

// ❌ Bad - unclear intent
factory.registerMethod('check', function() { /* ... */ });
factory.registerMethod('do', function() { /* ... */ });
factory.registerMethod('calc', function() { /* ... */ });

Keep Methods Focused

// ✅ Good - single responsibility
factory.registerMethod('move', function(dx, dy) {
    this.x += dx;
    this.y += dy;
});

factory.registerMethod('checkBounds', function(minX, minY, maxX, maxY) {
    return this.x >= minX && this.x <= maxX &&
           this.y >= minY && this.y <= maxY;
});

// ❌ Bad - doing too much
factory.registerMethod('moveAndCheck', function(dx, dy, bounds) {
    this.x += dx;
    this.y += dy;
    if (this.x < bounds.minX) this.x = bounds.minX;
    if (this.x > bounds.maxX) this.x = bounds.maxX;
    if (this.y < bounds.minY) this.y = bounds.minY;
    if (this.y > bounds.maxY) this.y = bounds.maxY;
    this.updateSprite();
    this.playSound('move');
    this.checkCollisions();
});

Complete Examples

Example: Character Management System

const { factory, entities } = require('ecos');

// Register character methods
factory.registerMethod('gainExperience', function(amount) {
    this.experience += amount;
    const expNeeded = this.level * 100;

    if (this.experience >= expNeeded) {
        this.levelUp();
    }
});

factory.registerMethod('levelUp', function() {
    this.level += 1;
    this.experience = 0;
    this.maxHealth += 10;
    this.health = this.maxHealth;
    this.statPoints += 5;
    console.log(`${this.name} leveled up to ${this.level}!`);
});

factory.registerMethod('allocateStat', function(stat, amount) {
    if (this.statPoints < amount) {
        console.log('Not enough stat points');
        return false;
    }

    this[stat] = (this[stat] || 0) + amount;
    this.statPoints -= amount;
    return true;
});

factory.registerMethod('getStats', function() {
    return {
        level: this.level,
        health: this.health,
        maxHealth: this.maxHealth,
        strength: this.strength,
        intelligence: this.intelligence,
        agility: this.agility
    };
});

// Create factory
const characterFactory = factory.create({
    name: 'character',
    props: ['name', 'level', 'experience', 'health', 'maxHealth',
            'statPoints', 'strength', 'intelligence', 'agility'],
    default: {
        level: 1,
        experience: 0,
        health: 100,
        maxHealth: 100,
        statPoints: 0,
        strength: 10,
        intelligence: 10,
        agility: 10
    },
    methods: ['gainExperience', 'levelUp', 'allocateStat', 'getStats']
});

// Use the system
const hero = characterFactory.create({ name: 'Hero' });

hero.gainExperience(50);  // No level up yet
hero.gainExperience(60);  // Level up! (110 total exp >= 100 needed)

hero.allocateStat('strength', 3);
hero.allocateStat('agility', 2);

console.log(hero.getStats());
// {
//   level: 2,
//   health: 110,
//   maxHealth: 110,
//   strength: 13,
//   intelligence: 10,
//   agility: 12
// }

Example: Inventory System

const { factory, entities } = require('ecos');

// Register inventory methods
factory.registerMethod('addItem', function(itemId) {
    if (this.inventory.length >= this.maxInventorySize) {
        console.log('Inventory full');
        return false;
    }

    this.inventory.push(itemId);
    console.log(`Added item ${itemId} to inventory`);
    return true;
});

factory.registerMethod('removeItem', function(itemId) {
    const index = this.inventory.indexOf(itemId);
    if (index === -1) {
        console.log('Item not found in inventory');
        return false;
    }

    this.inventory.splice(index, 1);
    console.log(`Removed item ${itemId} from inventory`);
    return true;
});

factory.registerMethod('hasItem', function(itemId) {
    return this.inventory.includes(itemId);
});

factory.registerMethod('getInventoryItems', function() {
    return this.inventory.map(itemId => entities.get(itemId)).filter(Boolean);
});

factory.registerMethod('getInventoryValue', function() {
    return this.getInventoryItems().reduce((total, item) => {
        return total + (item.value || 0);
    }, 0);
});

// Create factories
const playerFactory = factory.create({
    name: 'player',
    props: ['name', 'inventory', 'maxInventorySize'],
    default: { inventory: [], maxInventorySize: 10 },
    methods: ['addItem', 'removeItem', 'hasItem', 'getInventoryItems', 'getInventoryValue']
});

const itemFactory = factory.create({
    name: 'item',
    props: ['name', 'type', 'value']
});

// Use the system
const player = playerFactory.create({ name: 'Player1' });
const sword = itemFactory.create({ name: 'Sword', type: 'weapon', value: 100 });
const potion = itemFactory.create({ name: 'Potion', type: 'consumable', value: 25 });

player.addItem(sword.id);
player.addItem(potion.id);

console.log(`Has sword: ${player.hasItem(sword.id)}`); // true
console.log(`Inventory value: ${player.getInventoryValue()}`); // 125

Error Handling

Methods should handle errors gracefully:

factory.registerMethod('attack', function(targetId) {
    // Validate target exists
    const target = entities.get(targetId);
    if (!target) {
        console.error('Attack failed: target not found');
        return { success: false, reason: 'target_not_found' };
    }

    // Validate target is alive
    if (!target.isAlive || !target.isAlive()) {
        console.error('Attack failed: target is dead');
        return { success: false, reason: 'target_dead' };
    }

    // Validate attacker can attack
    if (this.stamina < 10) {
        console.error('Attack failed: not enough stamina');
        return { success: false, reason: 'insufficient_stamina' };
    }

    // Perform attack
    this.stamina -= 10;
    const damage = this.calculateDamage();
    target.takeDamage(damage);

    return {
        success: true,
        damage: damage,
        targetHealth: target.health
    };
});

Next Steps

  • Working with Extenders - Customize entity creation
  • Using Presets - Bundle methods in reusable presets
  • Creating Entities - Entity creation basics

See Also