3
\$\begingroup\$

I'm trying to get a better feel for how an entity-component-system system works. I'm most comfortable in JavaScript, so that's what I wrote it in. I'm mostly looking for ways my code violates ECS principles, but am also looking for ways to make my code more idiomatic.

class Component {
    get type() { return this.constructor.name; }

    constructor(data) {
        this.data = data;
    }
}

class Entity {
    get type() { return this.constructor.name; }

    constructor(id) {
        this.id = id;
        this.components = [];
    }

    get(type) { return this.components.find(c => c.type === type); }

    assign(component) {
        const included = this.get(component.type);
        if (included === undefined) this.components.push(component);
        else this.components[this.components.indexOf(included)] = component;
    }
}

class System {
    get type() { return this.constructor.name; }

    constructor(types, callback) {
        this.types = types;
        this.callback = callback;
    }

    run(entities) {
        entities.forEach(e => 
            this.types.every(t => e.get(t) !== undefined) &&
            this.callback(e)
        );
    }
}

Also, here's a basic example of how the system gets extended.

class ErrorComponent extends Component {
    constructor(error) {
        super(error);
    }
}

class ErrorSystem extends System {
    constructor() {
        super(
            ["ErrorComponent"],
            e => console.error(e.get("ErrorComponent").data)
        );
    }
}
const testComponent = new ErrorComponent("test");
const testComponent2 = new ErrorComponent("test2");

const testEntity = new Entity("test");
testEntity.assign(testComponent);
testEntity.assign(testComponent2);

const entities = [testEntity];
const testSystem = new ErrorSystem();
testSystem.run(entities);

One other thing that I feel like I don't understand about ECS is why I should extend Component and System, but not Entity. It feels asymmetrical. For example, could I say

class ErrorEntity extends Entity {
    constructor(id, error) {
        super(id);
        this.assign(new ErrorComponent(error));
    }
}

? Or does this violate some principle of ECS?

\$\endgroup\$
1
\$\begingroup\$

I didn't like the strings in the ErrorSystem call, so I factored them out. Comments and additional answers are still welcome.

class Component {
    constructor(data) {
        this.data = data;
    }
} 

class Entity {
    constructor(id) {
        this.id = id;
        this.components = [];
    }

    type(component) {
        return component.prototype === undefined?
            component.constructor.name:
            component.prototype.constructor.name;
    }

    get(component) { return this.components.find(c => this.type(c) === this.type(component)); }

    set(component) {
        const included = this.get(component);
        if (included === undefined) this.components.push(component);
        else this.components[this.components.indexOf(included)] = component;
    }
}

class System {
    constructor(components, callback) {
        this.components = components;
        this.callback = callback;
    }

    run(entities) {
        entities.forEach(e => 
            this.components.every(c => e.get(c) !== undefined) &&
            this.callback(e)
        );
    }
}
class ErrorComponent extends Component {
    constructor(error) {
        super(error);
    }
}

class ErrorSystem extends System {
    constructor() {
        super(
            [ErrorComponent],
            e => console.error(e.get(ErrorComponent).data)
        );
    }
}

const testComponent = new ErrorComponent("test");

const testEntity = new Entity("test");
testEntity.set(testComponent);

const entities = [testEntity];
const testSystem = new ErrorSystem();
testSystem.run(entities);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.