• fullscreen
  • Collision.pde
  • EntityManager.pde
  • FECS.pde
  • Render.pde
  • System.pde
  • Velocity.pde
  • class CollisionSystem extends System <Float> {
    
      // This is a simple collision system that -only- checks for collision with the screen edge.
      // Find out on how to include other objects here:
      // http://processing.org/learning/topics/circlecollision.html
    
      void process(int entityID) {
    
        Float radius = (Float)objects.get(entityID);
        PVector pos = position.getDataFor(entityID);
        PVector vel = velocity.getDataFor(entityID);
    
        if (radius != null && pos != null && vel != null) {
              
          // Check for collision with the screen edge (left and right)
          boolean collision = false;
          
          if (pos.x + radius >= width) {
            collision = true;
            pos.x = width - radius;
          }
          else {
            if (pos.x - radius < 0) {
              collision = true;
              pos.x = radius;
            }
          }
          
          // Update position and velocity if there was a collision
          if (collision) {
            position.updateDataFor(entityID, pos);
            vel.x *= -1;
            velocity.updateDataFor(entityID, vel);
          }
          
          // Check for collision with the screen edge (top and bottom)
          collision = false;
          
          if (pos.y + radius > height) {
            collision = true;
            pos.y = height - radius;
          }
          else {
            if (pos.y - radius < 0) {
              collision = true;
              pos.y = radius;
            }
          }
          
          // Update position and velocity if there was a collision
          if (collision) {
            position.updateDataFor(entityID, pos);
            vel.y *= -1;
            velocity.updateDataFor(entityID, vel);
          }            
          
        }
      }
    }
    
    
    class EntityManager {
      
      Vector activeEntityIDs;
      
      EntityManager() {
        activeEntityIDs = new Vector();
      }
      
      int getNextAvailableID() {
    
        // See if there is an unused id in the activeEntityIDs list
        for (int i = 0; i < activeEntityIDs.size(); i++) {
          // If so, set that id to "true" (meaning it's in use now) and return it
          if (!(Boolean)activeEntityIDs.get(i)) {
            activeEntityIDs.set(i, true);
            return i;
          }
        }
    
        // Otherwise, add a new entity id to the list and return its index
        activeEntityIDs.add(true);
        return (activeEntityIDs.size() - 1);
      }
      
      void removeEntity (int entityID) {
        if (entityID < activeEntityIDs.size()) {
          position.removeDataFor(entityID);
          render.removeDataFor(entityID);
          velocity.removeDataFor(entityID);
          collision.removeDataFor(entityID);
          activeEntityIDs.set(entityID, false);
        }
      }
    }
    
    
    // Fast Entity Component System
    // Ted Brown, Jan. 21, 2011
    // roguedreamerdev.blogspot.com
    
    // This is a "Fast" system that uses direct lookups via integer id's, instead of paging through arrays at each request.
    // Please note, there is no "entity" class, per se, just an integer.  How you manage a list of numbers is up to you.
    // This has no tradeoffs for this ultra-simple application of the concept.
    // But as more components are added, and entities become collections of different components, the number of "null" values in each component system will grow.
    
    // Good things you can expand upon:
    // - Create a "diameter" system so that render and collision are locked in sync.
    // - Create a flyweight "motion" class that stores velocity, drag, etc. and externalize PVector methods
    // - Create "group" classes that manage their own list of entity ID's, while using entity manager to keep id's unique.
    //   Assuming members of a group have the same components, this would ensure component systems only process entity id's that are in use.
    
    // Uploaded to:
    // - Open Processing: http://www.openprocessing.org/visuals/?visualID=18023
    // - Entity Systems Wiki: http://entity-systems.wikidot.com/fast-entity-component-system
    
    // Further reading on Entity Component models:
    // - http://gamasutra.com/blogs/MeganFox/20101208/6590/Game_Engines_101_The_EntityComponent_Model.php
    // - http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
    // - http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/
    
    EntityManager em = new EntityManager();
    PositionSystem position = new PositionSystem();
    RenderSystem render = new RenderSystem();
    VelocitySystem velocity = new VelocitySystem();
    CollisionSystem collision = new CollisionSystem();
    
    PFont font;
    boolean show_help = true;
    
    void setup () {
    /////////////////////////////////////////
      size(200, 200);
      imageMode(CENTER);
      fill(128);
      stroke(0);
      font = createFont("Verdana", 12);
      textFont(font);
      textAlign(CENTER);
      addEntity(32f);
    }
    
    void draw () {
    /////////////////////////////////////////
      velocity.process();
      collision.process();
      background(255);
      render.process();
    
      if (show_help) {
        text("Hit SPACEBAR to add an entity", width/2, height - 20);
      }
    }
    
    void addEntity (float diameter) {
    /////////////////////////////////////////
      int newEntityID = em.getNextAvailableID();
      float posX = random(diameter, width - diameter);
      float posY = random(diameter, height - diameter);
      float theta = random(360);
      position.addDataFor(newEntityID, new PVector(posX, posY, 0));
      velocity.addDataFor(newEntityID, radians(theta), 3);
      collision.addDataFor(newEntityID, diameter * 0.5);
      render.addDataFor(newEntityID, diameter);
    }  
    
    void keyReleased() {
    /////////////////////////////////////////
      if (key == ' ') {
        addEntity(random(1, 64));
        show_help = false;
      }
    }
    
    // Ultra-basic system to store position as a vector
    class PositionSystem extends System <PVector> {
    }
    
    
    class RenderSystem extends System <Float> {
    
      // This system currently stores a Float to dynamically generate a sphere.
      // This could naturally store an image reference as well.
    
      void process(int entityID) {
        
        Float diameter = (Float)objects.get(entityID);
        PVector pos = position.getDataFor(entityID);
    
        if (diameter != null && pos != null) {
          ellipse(pos.x, pos.y, diameter, diameter);
        }
        
      }
        
    }
    
    
    class System <Type> {
    
      // Instantiate this system with <Type> to determine which class to handle
      // http://en.wikipedia.org/wiki/Generics_in_Java
      
      Vector objects;
    
      System() {
        objects = new Vector();
      }
      
      // This method allows for the engine to access data for every entity ID, without knowing if it exists.
      void process () {
        for (int i = 0; i < objects.size(); i++) {
          if ((Type)objects.get(i) != null) {
            process(i);
          }
        }
      }
      
      // Use this method if you are managing entity ID's in outside arrays, such as "enemies" or "projectiles"
      void process (int entityID) {}
    
      void addDataFor (int entityID) {
        addDataFor(entityID, null);
      }
    
      void addDataFor (int entityID, Type object) {
        // If the id is out of bounds of the objects array, expand it to meet the requirements of the entity.
        if (entityID >= objects.size()) {
          objects.setSize(entityID);
          objects.add(entityID, object);
        }
        else {
          // If the id is within the bounds of the current array, simply update it with the new object
          if (entityID < objects.size()) {
            objects.set(entityID, object);
          }
        }
      }
    
      void updateDataFor (int entityID, Type object) {
        objects.set(entityID, object);
      }
      
      void removeDataFor (int entityID) {
        if (entityID < objects.size()) {
          objects.set(entityID, null);
        }
      }
      
      Type getDataFor (int entityID) {
        if (entityID < objects.size()) return (Type)objects.elementAt(entityID);
        else return null;
      }
    
    }
    
    
    class VelocitySystem extends System <PVector> {
    
      // For future versions, it would make sense to have a custom class with inertia, drag, etc.
    
      void process (int entityID) {
        
        PVector vel = (PVector)objects.get(entityID);
        PVector pos = position.getDataFor(entityID);
        
        if (vel != null && pos != null) {
          pos.add(vel);
          position.updateDataFor(entityID, pos);
        }
        
      }
    
      void addDataFor (int entityID, float theta, float speed) {
        addDataFor(entityID, new PVector(cos(theta) * speed, sin(theta) * speed));
      }  
    
      void updateThetaFor (int entityID, float theta) {
        PVector velocity = getDataFor(entityID);
        float speed = velocity.mag();
        velocity.x = cos(theta) * speed;
        velocity.y = sin(theta) * speed;
        updateDataFor(entityID, velocity);
      }
    
      void updateSpeedFor (int entityID, float speed) {
        PVector velocity = getDataFor(entityID);
        float theta = atan2(velocity.y, velocity.x);
        velocity.x = cos(theta) * speed;
        velocity.y = sin(theta) * speed;
        updateDataFor(entityID, velocity);
      }
    
    }
    
    
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Ted Brown

    Fast Entity Component System

    Add to Faves Me Likey@! 3
    You must login/register to add this sketch to your favorites.

    Requires Processing 1.2.1 due to use of Generics.

    This is a "milestone" tech foundation for a personal game project called Rogue Dreamer. It implements a paradigm for entities and components that is data driven, instead of hierarchical: entities are handles for components, but don't "know" which components they have. This makes entities totally flexible, without having to weigh down a "super" class. URLs for further reading are commented in the code, or you can search for "Entity Component" in your favorite search engine.

    Ted Brown
    20 Jan 2011
    Updated on Jan 20 2011:
    * Fixed issues with the System class.
    * No longer uses a "flyweight" entity class, just a straight integer.
    * Removed mouse control, since that complicates an otherwise simple design.
    * Removed LOL guy image in favor of basic ellipse.
    * You can add multiple entities with the spacebar.
    Arni Arent
    21 Jan 2011
    This is very interesting. I've been doing some research on this technique lately and your approach is quite nice.

    But, it's not entirely perfect. Because you're using arrays for it, and using the indexes as entityId, whenever you delete an entity a spot in the array becomes unused. And unless you fill it up, it's still there.
    E.g. if you were to create 1000 entities, then delete all but the last, you'd still have those 999 entities to iterate through, since you always start at 0.

    But still, one of the best impl. I've seen so far (although there aren't many examples).
    Ted Brown
    21 Jan 2011
    Thank you, Arni, both for the compliments and taking time to discover a weak spot in the implementation.

    I updated the generic System template so that individual entity ID's can be processed, while keeping the "do 'em all" method for projects that are small in scope.

    This allows the creation of "group" classes (enemies, projectiles, etc), which would control entities that share certain components. Thus, they can ask systems to only process id's in use, instead of forcing the system to go through everything it has.

    Because there are many ways to implement such a class, I will leave that out of this example. But I plan to finish a simple "software toy" as I explore this concept. That toy will use groups, and I will upload it here when it's ready.

    Thanks again!
    Arni Arent
    21 Jan 2011
    You've made the same updates to your system as I would have, an abstract process(entity) method to be.

    There's one lingering doubt in my mind about the Entity System design (not yours, just general). It is how systems are set up to process aspects of the entities, e.g. like in your example all entities having radius, position and velocity. What if you added "mass" to the equation, or some other attribute? You'd end up with many if-checks, starting to check if all attributes are set, then checking if all but mass are set, then all but mass and velocity, etc.
    This smells like a bottom-up polymorphism, brute-forcing it with if-checks.

    I'm sure there must be a better way.

    Currently I'm thinking about using polymorphism by allowing each system to have an array of "Processors". So, the systems would not contain any processing logic, but only to delegate it to the Processors, which specify an abstract "process()" method to be implemented by extending classes. I haven't thought it through entirely, it might work under some circumstances, but might break down if you do frequent changes to an entity, e.g. adding/removing components.

    Please keep up the good work. As I'm working on this project right now I'm sure we could share our insights, so keep up the dialogue :)

    Thanks.
    Arni Arent
    22 Jan 2011
    Here's a question.
    Every component seems to require a system. What if you wish to add a component to an entity that doesn't require a system?

    Another point.
    Although this may be fast, it does requires a lot of processing "misses" in each system, as not all entities have all components, and therefor there will be empty gaps in the systems internal component vectors.
    Ted Brown
    25 Jan 2011
    All good points! I took some time to think about the issues you raised, and here are some potential answers.

    Issue: Lots of if-checks in the system's process function

    My Possible Resolution: Overload the process function so that one version requires all of its necessary components to be passed as parameters. Again, I can see handling this with each "group" manager calling the process function. Since each "group" most likely has the same components, it can risk the blind assertion that the necessary data will be in place.

    I think that's what you were saying as well, perhaps in part. I think it's a good time to point out that I am a designer by profession, not a programmer, so some terms might go "whoosh" (waves hand over hair).

    Question: What if there was a component that didn't have a system?

    Answer: Probably not a big deal, but I can't see any downside to having a standard system template that formalizes data access. Is there an example we can us?

    Issue: Processing "misses" as the number of entities grow, while not having identical components.

    My Possible Resolution: If the raw "process()" function is called on the system, it does indeed go through every possible entity "just to make sure." But an intermediate class between Entity and System, which I call a Group, can know which entities it owns, and then only sends those id's to be processed. This would cut down (or entirely prevent) the "misses" issue.

    I'll try to e-mail you when I post a new sketch with an example of the Group concept. I think that's a good next step.

    Thanks for the thought-provoking comments!
    Arni Arent
    25 Jan 2011
    Hey again.

    I'm working with another guy implementing (and designing) this Entity System in Java. So far we've implemented your design, to begin with, and it seems very fast and efficient. However, it has limits, and we're designing ourselves away from those limits:

    1. There should not be a 1:1 relationship between a System and Component type. I know adam said that there is "usually" a 1:1 relationship, but later he retracts that and says it's really a compromise to keep things simple to begin with.
    So, we decided not to begin simple and do a full solution, because moving to a full solution later might be more difficult than anticipated. For example, your design requires components to be stored in each system. If there is no 1:1 relationship between systems and components, that design doesn't make sense.

    2. In your design I solved the problem with "wasted cycles" by having an array in each system called "actives". This array contains only the entities that are processed by that system. It's an vector, and the value it contains is the entityId, which it uses to look up the component in the components array. The actives vector can be of any size, and the keys are not the entityIds. This works pretty well. Every time a component is added to the system, it's entity is added to the actives array.

    3. In our design, we moved all vectors for the components to the EntityManager (away from the systems). In addition, we created a EntityTypes vector which stores in bits the type of components associated with that entity (each Component type must have it's own bit in this design).

    Every time a component is added to the EntityManager, it let's all systems know of a change to that entity. Each system checks the type bits for that entity and sees if it matches it's own type bits of entities it is interested in for processing. If it is, it adds that entity to an "actives" vector (like I explained before) inside that system.
    This is very efficient, but requires mapping of component types to a certain bit.

    The way we're doing it is very low level, but this can be accomplished using EnumSet as well, or BitSet. But using int[] array (or long[] to support 128 component types) is 5x faster. We might move to EnumSet if we wish to support more than 128 component types.



    I'll give you the source code for this implementation when it's done.
    Ted Brown
    25 Jan 2011
    Wow! I think I made a major error: I should not have called it the Fast Entity Component System! =D Instead, what I wrote is really a Simple Entity Component System, because all it does, really, is establish the tenets of the concept. (It has a far better sounding acronym, to boot.... hahah) And, to be frank, its simplicity is more a result of my limitations than a frank expression of what is "right."

    Thanks again for posting these detailed comments! I am learning a lot.

    Now I understand your point about 1:1 relationships between Systems and Components. When I answered before, I couldn't see a reason to have a Component without a System that uses it. But it makes sense to have Components re-used in multiple Systems. (of course, that might not be what you are talking about!) The good thing about game development (which I focus on) is that they are projects which come to an end. Experienced developers can map these "exceptions" out far ahead of time, and break the "rules," where expedient, without abandoning the philosophy entirely.

    Your new Entity Manager system seems extremely tight and focused, which I prefer to "super systems" that try to handle everything. I'm definitely looking forward to taking a look at the code when it's done. It also looks like you've moved past the HashMap solution that Adam first used, which I saw as a bit of a weakness. Is that correct?

    At any rate, you guys deserve to open this discussion up to more serious engineers. I would humbly suggest adding it to Adam's ES wiki at http://entity-systems.wikidot.com/ . My own "entry" is going to get some serious revisions and pared-down promises. =)

    Great stuff, and thanks again!
    Ted Brown
    25 Jan 2011
    (Using the adjective "frank" twice in a comment? Good. Using it twice in a sentence? Not good. No way to edit the comment once written? D'oh!)
    ale plus+
    23 Jul 2012
    This code is quite interesting, thanks for sharing!
    You need to login/register to comment.