Entity Component System example in Javascript - Rectangle Eater



  • Hey guys, I wrote a post about building an Entity Component System based game using Javascript. I had a hard time finding a simple Javascript implementation of a game when I was learning about ECS. So, I tried to build a very simple (but working) game in a day to illustrate the concepts as simply as I could. The game I built for the post is Rectangle Eater

    I thought I’d share it if anyone else finds it useful. The code is just to demonstrate the concepts, but you can access it on github.


  • LDG

    Nice article! I actually saw this on Hacker News before I checked the forum today. There is definitely a lack of well-written, simple ECS articles. I like your approach as it’s very similar to how I’ve coded ECS before.

    Lately, my preference has been to reduce the entities to simple JavaScript objects. I found that I had an Entity constructor with on a few, redundent methods, such as addComponent and removeComponent. Here’s a quick example of how I’ve been constructing entities:

    var hero = {
      position: {
        x: 0,
        y: 0
      },
      size: {
        width: 100,
        height: 100
      }
      // etc
    };
    
    // Add a component
    hero.collision = {
      group: "player"
      // etc
    };
    
    // Remove a component
    delete hero.collision;
    

    You can still create default component values by merging an entity’s data with default values (also specified in plain JavaScript objects) at creation time as well as assigning an ID.

    Thanks for dropping by and welcome to the forum! :)



  • Thanks! I’ve been a long time listener of the podcast, I love it!

    I like that approach, it reduces complexity a quite a bit. Good point about the redundant methods - even though there’s just one function in memory with a prototype property, what value does it provide other than sugar of calling entity.addComponent(component) vs. entity[component] = component.
    Do you handle components in a similar way then, just objects? To give them a default value do you need to explicitly extend the properties with a ‘defaults’ object?

    One thing I’m curious of is if you’ve ever run into cases where you might want some side effects when modifying the entity state (e.g., logging a message when a component is modified). How do your systems take in entities - do they look at all entities, or is there some layer that adds entities to a list by component or something? (This is one potential benefit I see to having a add / remove component method, as a list of IDs could be created for each component and a system could just look at those entities without having to explicitly handle it). Sorry if this are silly questions, it’s super helpful to hear how professionals do it!

    thanks!


  • LDG

    @Enoex said:

    Do you handle components in a similar way then, just objects? To give them a default value do you need to explicitly extend the properties with a ‘defaults’ object?

    Components are handled in a similar way, yes. I “merge” the defaults of components with an entity’s own values during creation. Additionally, we have the concept of “prefabs”, or prefabricated entities. Prefabs represent the various types of entities that can be created. Here’s an example:

    var components = {
      position: {
        x: 0,
        y: 0
      },
      size: {
        width: 100,
        height: 100
      }
    };
    
    var prefabs = {
      hero: {
        position: {},
        size: {}
      },
      monster: {
        position: {},
        size: {}
      }
    };
    
    var createEntity = function (type, conf) {
      var prefab = prefabs[type];  
      var entity = merge(conf, prefab); // merge combines two objects, resulting in a new object
      // Merge default component values into components attached to this entity
      for (var key in entity) {
        entity[key] = merge(entity[key], components[key]);
      }
      return entity;
    };
    
    // Create a hero entity while overriding the position
    var hero = createEntity("hero", {
      position: {
        x: 100,
        y: 200
      }
    });
    

    One thing I’m curious of is if you’ve ever run into cases where you might want some side effects when modifying the entity state (e.g., logging a message when a component is modified).

    Definitely, although this is a tricky thing in JavaScript. You can use getters/setters to modify properties on an object, but having to write those methods is tedious and defeats the purpose of such simple entities. I’ve been interested in experimenting with Object.observe which may solve this nicely, but it’s not well supported yet.

    My current approach is to fire events from the systems when important data changes occur. For example, if you have a simple physics system:

    var physics = {
      updateEntity: function (entity, dt) {
        var pos = entity.position;
        var vel = entity.velocity;
    
        pos.x += vel.x * dt;
        pos.y += vel.y * dt;
     
        // In my design, the methods of "systems" are executed in the same scope as the world/simulation.
        // In this case, observers interested in the positionUpdate event would subscribe to the world:
        // e.g. world.on("positionUpdate", handlePositionUpdate);
        this.fireEvent("positionUpdate", entity);
      }
    };
    

    How do your systems take in entities - do they look at all entities, or is there some layer that adds entities to a list by component or something? (This is one potential benefit I see to having a add / remove component method, as a list of IDs could be created for each component and a system could just look at those entities without having to explicitly handle it).

    Our systems currently look at all entities. While this isn’t as efficient as it could be, it’s rarely (read: never in my experience) the bottleneck. The rendering and collision detection will far outstrip the time it takes for each system to handle each entity. We provide an early exit for entities which do not have the required components. In the physics example above, I would add the following line to the top of the updateEntity function:

    if (!entity.position || !entity.velocity) { return; }
    

    That said, it would still be possible to create a shortlist of entities matching various components. I wonder if that wouldn’t be the same or worse in terms of performance, though. Many systems operate on several components and some components are optional. Continuing the physics example, position and velocity are required, but you can optionally take into account, gravity, friction, and bounce. Trying to manage which entities are passed to which systems sounds like a lot more complication than simply performing an early exit from a system if a given entity doesn’t meet the requirements. Basically, follow the performance golden rule and don’t optimize prematurely. Measure your game’s performance and focus on the parts which are actually slow, not theoretically slow.

    Sorry if this are silly questions, it’s super helpful to hear how professionals do it!

    Not silly at all! Since you listen to Lostcast, you’re probably aware that I love talking about ECS. :) Also, take my opinions with a grain of salt. In JavaScript, and gamedev, there are many ways to skin the cat, so YMMV!



  • @geoffb Great! Thanks for taking the time to write up that detailed response. Man, what a great response. I really like that approach of just using objects. Just data. With that method it’d be simple to fetch then JSON.parse the response. Bam, done!

    Your prefab approach is clean, I like it a lot more than the assemblage approach I took (e.g., a simple example https://github.com/enoex/RectangleEater/blob/master/scripts/Assemblages.js ). Now what I did feels heavy-handed and inexpressive in comparison. There are some benefits, but if I’m building a game it seems the trade offs probably aren’t worth it, especially given your perspective. I only have a fraction of my free time to work on games, and like you mentioned a couple podcasts ago, productivity of full time vs. part is night and day. So, I’m gonna hijack your experience.

    The performance question was another thing I was curious about. I built (or building, but haven’t touched it over a year) a simulation engine using an ECS-like approach that involved tens of thousands of “entities” and a ton of smaller “systems” / “processors”. The more fleshed out object / prototype based behavior worked well for me (too many entities was a bottleneck for me in that case) but added more complexity. I haven’t built any “real” browser based games yet (but I do have a ton of shitty demos), so your perspective has been very helpful to me. For the game I’m trying to find time to work on, I’m definitely going to be building something more inspired by your feedback than by the previous approach I took.

    Thanks a ton, I owe you a beer!



  • Here’s another quite nice approach which is very easy to implement:

    http://invrse.co/entities-and-components-system/


  • Tiger Hat

    Here’s the ECS/CES that I’m using: ces.js I’ve fused it together with three.js to create a 3D engine in our HTML5 3D MMO Ironbane (open source WIP)

    Keeping the components as “just data” is the key. Do whatever you can to make that happen (unlike the examples from ces.js); I don’t think components should have any mutators or anything, only properties. Systems should handle everything else. Of course, at some point you have to go with what works, CES/ECS is a hard belief system to live by (at least for me, coming from OOP land).



  • Hey geoffb - I love your idea for the the ECS - so simple, and so “JavaScript”! I’m just wondering how you handle user input vs AI input: are they both separate systems, and how do you know to apply it to the player, vs enemies? Maybe with a component that is just a flag? Or… how?!

    Thanks!


  • LDG

    @MrSpeaker AI is such an interesting and complicated subject. I’m still fumbling around with the best way to approach that stuff, but I’ll try to give you an idea of where my head’s been at.

    In general, the AI portions of the code are extra components tacked on to entities. As of late, we’ve been trying to isolate specific behaviors even more so we can achieve higher reuse. For example: you could have a component that instructs the entity to pick a random direction on init and then change direction every time it hits a wall. We’d call that a “bouncer”. You can then add the bouncer component to any entity that you wanted to have that behavior. Depending on your game this can be a great way to compose consistent enemies of fundamental building blocks.

    The player’s entity would simply lack any of the “behavior” components and its movement would be dictated by polling the keyboard or gamepad. I don’t typically create a component to handle this, instead I’ll just grab a reference to the player entity in the input code and modify its movement properties directly.

    To go into a bit more detail: All movable entities have a component which holds their x/y velocity. The difference between player and AI is that the player’s velocity is set by the input handling code while the enemy velocities are set by components attached to them. There’s a little more to it than that when we’re talking about handing things like knockback or stun states, but that’s the gist.

    Something else we’ve learned is that “enemies” shouldn’t really ever look directly for the player entity itself. I like to have some data on the entities which categories them into “teams”. Player units are team 1 and enemies are team 2. This allows you to create AI controlled beneficial entities such as familiars or just helpful NPCs. Another advantage is that if you had multiple players, enemy entities could poll the sim for all entities of the opposing team and sort them based on distance to determine their target.

    Not sure if that helps, but if you have follow-ups I’ll be happy to try and elaborate more. Seems like a good topic for Lostcast, too.



  • I love your solution: simple, pragmatic, with nothing “architect astronaut” about it. And, WOW, it’s so much fun! I started testing it out, and 12 hours later I’m still giggling as I add components everywhere: hard to imagine how I made games before!

    I’m not sure how best to manage tile-based grids with this approach: I feed my map into the physics system to do map collisions (and made a “bouncer” enemy, then added the component to the bullets and bouncing bullets was super fun… I love this system!) - but I don’t really know how to do the interaction between special tiles (like “ice” makes things slippery) yet.

    So, questions: do you run any automagical per-system init function when you add a component to an entity, or just do it ad-hoc? So far my systems all only have one method “update” that I call in the main loop on every entity. Do your systems have any other methods or properties?

    Even in my small game I’ve already got a stack of components: do you namespace them, or anything like that? How many components do you have in a “large-ish” game? Can you give us examples of some components? I’m just experimenting and finding fun ones like “sine” and “jiggle” for moving things around - I’m sure there are some really cool and common ones you use over and over again.

    Likewise, how many systems do your games have (roughly)? Do you have many things that are basically just “1 component, 1 system”?

    Ok, gotta get back to playing. And yes - please do a Lostcast on it! Thanks again!


  • LDG

    @MrSpeaker said:

    I love your solution: simple, pragmatic, with nothing “architect astronaut” about it. And, WOW, it’s so much fun! I started testing it out, and 12 hours later I’m still giggling as I add components everywhere: hard to imagine how I made games before!

    Yeah! It really changes the way you think about game dev.

    I’m not sure how best to manage tile-based grids with this approach: I feed my map into the physics system to do map collisions (and made a “bouncer” enemy, then added the component to the bullets and bouncing bullets was super fun… I love this system!) - but I don’t really know how to do the interaction between special tiles (like “ice” makes things slippery) yet.

    It seems like your physics system will need to take the ice into account when updating an entity’s position based on x/y velocity. As far as detecting if an entity is on ice, if the “ice” tiles are baked into your tilemap you can check the values of the tiles below and entity just like you would for checking collision. Alternatively, you could create “ice” entities and apply some kind of slippery state to entities which collide with them. Although, in the latter approach the physics system will still need to look at the slippery state when updating x/y position.

    So, questions: do you run any automagical per-system init function when you add a component to an entity, or just do it ad-hoc? So far my systems all only have one method “update” that I call in the main loop on every entity. Do your systems have any other methods or properties?

    Our systems have a number of “hooks” which allow them to modify entities. There’s an event for when an entity is spawned into the world, when it’s despawned, when it collides with another entity, when it collides with a wall, etc, etc. This allows systems to respond to things happening in other systems. To give you a more concrete example, the collision system is responsible for detecting collisions and then throwing a “system event” when one occurs. The physics system then responds to the collision event by looking at various properties like if the entity is solid or not and responding accordingly. So, there are some events, like update, which are triggered from the core simulation loop, and others which are triggered from within the various systems.

    Even in my small game I’ve already got a stack of components: do you namespace them, or anything like that? How many components do you have in a “large-ish” game? Can you give us examples of some components? I’m just experimenting and finding fun ones like “sine” and “jiggle” for moving things around - I’m sure there are some really cool and common ones you use over and over again.

    We don’t generally namespace the components, we haven’t run into any collisions so far. I think that comes down to your particular setup and taste, though. For AWL, I think we have dozens of components. Again, it depends on how you’re breaking down the data structures. In AWL, physics is a single component with velocity and other properties whereas in other games velocity, gravity, friction, etc are all separate components. I’m leaning more towards the latter approach, separating the data into more components.

    Some common components we use: position, size, velocity, gravity, bounce, collision, control mortal, hazard. Most are pretty obvious, control is a component that holds data about how an entity wants to move, while velocity holds data about how it is moving. The distinction is important because there are many cases where entities can lose control of themselves and still be moving. E.g. stun, knockback, ice, etc.

    Likewise, how many systems do your games have (roughly)? Do you have many things that are basically just “1 component, 1 system”?

    Again, it depends, but usually not more than 10-12 I’d say. In general there isn’t a 1:1 ratio of systems to components, either. In most cases a single systems will deal with a number of components. The physics system will touch position, size, velocity, gravity, etc and the combat system might touch mortal (hitpoints), hazard (damage dealt, etc), and loot tables.

    Ok, gotta get back to playing. And yes - please do a Lostcast on it! Thanks again!

    Will do!



  • Having a ball with this thing! I listened to your ECS LostCast and that cleared up some questions I had… and raised a bunch more ;)

    Can you explain your behavior system a bit more? Does each entity have an array of them that’s called each loop? Could you give a concrete example of one or two behaviours? And, what is a “behavior”? Is it all of the actual game logic parts, or does that go somewhere else (for example, in my test game, there is a “movedEntity” event and my behavior system subtracts some fuel from the player… if the fuel becomes 0 then I want the player to be removed and make an explosion: does the “remove and trigger an explosion” code belong in the behavior system, or in some game specific file? I guess I mean, should the behavior system be more general or for specific “business logic”?

    Phew.

    Ok, also… tile-based grids. My level is made of tiles, and each tile has a “health” attribute - when it gets hit by a bullet the tile fades until it’s completely destroyed. This sounds like it would fit nicely into my ECS - buuut, do you make every tile an entity?! If it’s a big level, that’s a LOT of entities! Or do you treat them as a completely separate thing and just write it’s own logic?

    Thanks again for the LostCast - fantastic stuff!


Log in to reply