Giter Club home page Giter Club logo

entity-system-js's Introduction

ensy: Entity System for JavaScript

ensy is a JavaScript implementation of the Entity System model as described by Adam Martin in his blog post series Entity Systems are the future of MMOs.

Component/Entity Systems are an architectural pattern used mostly in game development. A CES follows the Composition over Inheritance principle to allow for greater flexibility when defining entities (anything that's part of a game's scene: enemies, doors, bullets) by building out of individual parts that can be mixed-and-matched. This eliminates the ambiguity problems of long inheritance chains and promotes clean design. However, CES systems do incur a small cost to performance.

โ€” From the Entity Systems Wiki

Installation

This module is available on npm as ensy. It has no dependencies.

npm install --save ensy

Usage

import EntityManager from 'ensy';

let manager = new EntityManager();

// Create a component and add it to the manager.
const PlayerComponent = {
    name: 'Player',
    description: "The player's state",
    state: {
        life: 100,
        strength: 18,
        charisma: 3,
    }
};
manager.addComponent(PlayerComponent.name, PlayerComponent);

// Create a new entity.
const playerId = manager.createEntity(['Player']);

// Update the player's state:
let playerData = manager.getComponentDataForEntity('Player', playerId);
playerData.life = 80;

// Which is equivalent to:
manager.updateComponentDataForEntity('Player', playerId, {
    life: 80,
});

// Which can also be done when creating the entity:
const playerId = manager.createEntity(['Player'], null, {
    Player: {
        life: 80,
    },
});

console.log(playerData);
// { life: 80, strength: 18, charisma: 3 }
}

Documentation

The documentation is available on Read the docs. All methods are well documented and parameters are described.

For an overall explanation of ensy, you can read my blog post ensy - Entity System Reloaded.

Examples

There are examples in the examples directory:

For developers

Install the dependencies with npm install. The source files are in src/. The code uses es6 features, and is compiled to es5 using babel and rollup.

Building the code

$ npm run build

We use rollup and babel to compile the code from es6 to es5, and uglify to minify the source code.

Running tests

$ npm test

To have tests watch your files and re-run when they change:

$ npm run test-w

To run the tests in your browser:

$ npm run test-browser

Building the API documentation

$ npm run build_doc

entity-system-js's People

Contributors

adngdb avatar batiste avatar brianpeiris avatar dependabot[bot] avatar fenomas avatar lotholf avatar maxailloud avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

entity-system-js's Issues

1.4 API change not usable?

Not sure if you still care to maintain this library. I've noticed that the API change introduced in v1.4.0 makes it so you can't easily get the id of entities unless you access entityComponentData directly, which I'm not sure is the intended use. For example the "concentration" example made heavy use of entity ids retrieved from the previous version of getComponentsData.

What was the reason for the change? What is the recommended method of querying entities and their ids?

Proposal; `addComponents()`

I was mildly annoyed at typing manager.addComponent(...) over and over again at the start of my game to register all the Components, so I wrote a function to do it for me. It takes a single parameter, an Array of Components. All Components in the Array are registered to the manager.
I propose this become part of the library as a standard function. Name is only one character away from the original function name, so it might be ideal to change the function name.

    function addComponents (array) {
        for (var item in array) {
            manager.addComponent(array[item].name, array[item]);
        }
    };

    addComponents([
        Position,
        Controlled,
        Model
    ]);
// Manager now recognizes Position, Controlled, and Model components now.

Collect use cases and difficulties from users to improve documentation

In order to make a great documentation, I think we need a lot of very precise and concrete examples. For that, I would need to know what people struggle to understand or achieve using ensy. That can be something high level related to the concept of Entity Systems or things related to the use of the lib itself.

So, if you read this and have anything to say, please comment! :)

Why are component props put behind getter/setters?

Hi -- This library looks really nice and well-designed, I'm trying it out in a game!

One question: why are component properties always wrapped in getter/setter functions, even though the manager isn't an event emitter?

If the intent is that the user can optionally add a manager.emit function, wouldn't it make sense to make getter/setter wrapping optional to avoid overhead in the default case?

E.g.:

EntityManager.prototype.addComponentsToEntity = function (
              componentIds, 
              entityId, 
              wrapWithGetterSetters=false) {

or the like?

Thanks!

Add a tutorial

If I want people to use this tool, I need to give them ways to understand it. The best things would be to add a tutorial to creating a simple game with the entity-system and to provide an exhaustive documentation about all the concepts used here.

Internal representation question/suggestion

Hi,

I've been further looking into ensy and I have a question/suggestion about its internal representation.

Currently, component data is stored like

this.entityComponentData["comp_name"] = {
    "5": { __id:5,  ... },
    "8": { __id:8,  ... }
}

Since entityComponentData and its values are hashes, I guess a typical System will look like:

function updateMeshes() {
    var entHash = entityManager.getComponentsData('has-mesh')
    var ids = Object.keys(entHash)
    for (var i=0; i<ids.length; ++i) {
        var entID = ids[i]
        var data = entList[entID]
        var otherData = entityManager.getComponentDataForEntity('other', entID)
        /* ... */
    }
}

Consider, however, if the list of component data was a plain array, accompanied by a hash of IDs:

this.entityComponentData["comp_name"] =  {
    ids: {          // hash
        "5": 0,
        "8": 1
    },
    data: [         // array
        { __id:5,  ... },
        { __id:8,  ... }
    ]
}

Doing this ought (I haven't tested!) to allow Systems to run a lot faster, at least in v8 (i.e. Chrome and Node). The reason is, Systems only need the data array, and in v8 dealing with plain arrays is much faster than hash tables, provided certain rules are followed (not sparse, starts from 0, etc.). For plain arrays,accessing an element is just a memory read from an offset, but for hash tables it means doing a dictionary lookup of each property name. As a result, looping through an array is much faster than doing for in on a hash.

The only user change, I think, is that the user cannot use object keys as entity IDs - they must check the __id value instead. But also I think it would be more convenient to get an array, since the System can use map, filter, etc. E.g.:

function updateMeshes() {
    var entArr = entityManager.getComponentsData('has-mesh')
    entArr.map(function(data) {
        var otherData = entityManager.getComponentDataForEntity('other', data.__id)
        /* ... */
    })
}

Again, this may be a premature optimization - I have not tried it! But I think that for large projects it would be a noticeable benefit (in Chrome and Node, at least).

Have you considered this, or do you have any thoughts?

Again thanks for the very very nice library!

Send events to Processors

Based on @CodestarGames' proposition in #21.

This is a proposal. I am not sure yet I want to include that. I am concerned that it could change the nature of processors from a simple "function" to something event-based. There are definitely places where I could see abusing the event thing, notably in node.js applications where running a loop is less practical.


The manager will emit events to processors on various occasions. Processors which implement the on method will receive each event emitted by the manager, and then do whatever they want with it. Processors which do not implement the on method will simply be skipped.

The on method accepts 2 arguments, the first one is a string identifying the event, and the second is an object containing data associated with the event. For example:

class MyProcessor {
    on(type, data) {
        switch (type) {
            // ...
        }
    }
}

For a start, events will be:

Type Data
COMPONENT_CREATED entity, component, state
COMPONENT_REMOVED entity, component
COMPONENT_STATE_UPDATED entity, component, state

The data sent along with each event will be:

{
    entity: 'entity ID', 
    component: 'component name', 
    state: {
        /* component state */
    }
}

Here's what a Processor using that could look like:

class RenderingProcessor {
    constructor(manager) {
        this.manager = manager;
        this._sprites = {};
    }

    on(type, data) {
        switch (type) {
            case 'COMPONENT_CREATED':
                if (data.component === 'Display') {
                    let display = data.state;
                    let position = this.manager.getComponentDataForEntity('Position', data.entity);
                    let newSprite = this.game.add.sprite(position.x, position.y, display.spriteImagePath);
                    this._sprites[data.entity] = newSprite;
                }
                break;
            case 'COMPONENT_REMOVED':
                if (data.component === 'Display' && this._sprites[data.entity]) {
                    this._sprites[entity].kill();
                    delete this._sprites[entity];
                }
                break;
            default:
                break;
        }
    }

    update(dt) {
        let displays = this.manager.getComponentsData('Display');
        for (let entity in displays) {
            let display = displays[entity];
            let position = this.manager.getComponentDataForEntity('Position', entity);

            this._sprites[entity].x = position.x;
            this._sprites[entity].y = position.y;
        }
    }
}

Add examples and documentation

I need to add:

  • examples of what Components can be
  • documentation about the API (though it is in-code, maybe find a way to export it to a documentation file? )
  • examples of games using this system

Implement export and import to JSON

One of the biggest advantages of using the Entity System is that it makes saving and loading very easy, since all the state is in components.

Eventually there should be ways to save to and load from databases like MySQL of Postgres directly, but as a first step, having a way to export all the data of a manager into a JSON structure would be nice.

Features:

  • function export() - returns a JSON document containing all the states of all components, and the list of all entities
  • function import(source) - takes a JSON document like the one created by export and imports it into a manager

JSON format to be decided.

entityHasComponent method should return falseinstead of throwing an Error object when unknown component is found.

see below example:

EntityManager.prototype.entityHasComponent = function (entityId, componentId) {
        if (!(componentId in this.components)) {
  // comment out below line.  Users  shall decide when to throw ERROR if the component is not found.     
//   throw new Error('Trying to use unknown component: ' + componentId);

           return false; // return false when not found.
        }

        return (
            this.entityComponentData.hasOwnProperty(componentId) &&
            this.entityComponentData[componentId].hasOwnProperty(entityId)
        );
    };
'''

Proposals; `addAssemblages` and `addProcessors`

Just like #31 I ended up creating functions to do these using Arrays instead of repeating the individual calls.

    // Adds processes to manager from Array
    function addProcessors (array) {
        for (var item in array) {
            manager.addProcessor(new array[item](manager));
        }
    };
    // Adds Assemblages to manager from Array
    function addAssemblages (array) {
        for (var item in array) {
            manager.addAssemblage(array[item].name, array[item]);
        }
    };
    // In use
    addProcessors([
        UpdateRenderPosition
    ]);
    addAssemblages([
        PlayerAssembly,
        Floor,
        Wall
    ]);

Integrate Processors

This model is called Entity System, but there is currently nothing about Systems in the code. There could be a class with some basic methods that systems need.

Note: Adam said he renamed Systems to Processors in a recent blog post. It would probably be wise to use the same name.

Add performance tests around ensy

Games need high performance, and as a center piece of games using it, the entity system must be critically fast and optimized. But it's a mistake to optimize blindly, so the first step is to collect metrics about the speed and memory usage of ensy, so that we can then find the bottlenecks and optimize them.

I'm very very noob in this domain so any recommendation or link is appreciated!

[Idea] Having a way of creating an entity with an initial state

What do you think of a way to create an entity with an initial state?
By with an initial state I mean something like :
let entity = manager.createEntity(['MyComponent'], initialState);

I know the createEntity has the entityId as second parameter so it could be a third optional parameter. Or maybe a new method that will use the intialState value as default value for the entity's components.

My use case is that I am working on #24 and being able to create an entity with an initial state will allow, using the create event, to manage more clearly the creation of the an framework related objects (Phaser in my case) when one is linked to an other.

My use case is with Phaser when you deal with ParticleEmitter you need a ParticleEmitterManager to actually create your emitter. So for that I need to create my manager and give it's entityId to my ParticleEmitter. I did it and it works https://github.com/maxailloud/phangular/blob/ensy-events/src/app/scenes/main.scene.ts#L58. However in my rendering processor I need to plan for the case when my emitter doesn't have a manager attached to it when i create it, something that cannot exist as in Phaser the emitter is created by a manager.

So for my case having an initial state for my entity will make the code for my rendering processor way cleaner as I won't have to deal with 2 different cases, one with and one without the manager entityId I need to create my emitter.

Same thing when an object follows (in any way) an other object, having an initial value makes you able to make it directly.

I hope I was clear enough so you can understand what I mean.
Of course if you think that could be a good addition to ensy I will see to make it happen.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.