Jump to content
  • Advertisement
  • 03/19/14 02:42 PM
    Sign in to follow this  

    Entities-Parts I: Game Objects

    General and Gameplay Programming

    dave19067

    Background

    I. Game Objects (current) II. Interactions III. Serialization This article is the first in a series of articles about the practical application of the entity-component pattern to game programming. This article, in particular, describes what an entity and a component are, how they interact with eachother, and what the advantages of the entity-component pattern are over some traditional methods of creating gameobjects. Also, it will describe the Entities-Parts framework which is simply a collection of reusable classes and concepts for entity-component interaction. Since I started implementing the entity-component model several years ago, it has evolved based off of experience using it in my games and studying various articles and other implementations. It has not been tested in large scale games such as RPGs, so by no means is it perfect. The implementation and the articles will keep evolving as my knowledge increases and I develop more games. As part of the framework, I will also provide an implementation of the entity and component and an example rpg-battle system showing interactions between entities and components. The example will be built on in the next articles. The implementation is provided in Java and C++ w/boost. It is generic enough that you can reuse your components in multiple games and decouple your game objects, i.e. entities, from high-level game logic and systems. I prefer to use the word 'part' instead of 'component' because it is shorter.

    The Problem

    Some approaches to creating game objects require you to cram all functionality into one GameObject class, or create several game object classes that have duplicate code. With the entity-component pattern, you can reuse code and make your game objects dynamic by thinking of them as a bunch of interconnected parts. Deep Inheritance Hierarchy Approach: Let's say you have a Monster class. The class contains a few variables, such as those for health, damage, and position. If you want a new type of Monster that flies, you derive a FlyingMonster class from the Monster class. If you want a spellcasting Monster, you derive a SpellMonster class from the Monster class. The problem arises when you want a spellcasting flying monster. You now need to decide whether you want to derive SpellFlyingMonster from FlyingMonster or SpellMonster. Furthermore, you will need to copy and paste code from the FlyingMonster or SpellMonster class to provide the functionality that the SpellFlyingMonster is missing from its parent class. Monolithic Class Approach: Another approach is creating a single class that represents and contains all the functionality of your game objects, i.e. the GameObject. While this solves the issue of having duplicate code that exists in deep inheritance hierarchies, this class can quickly get out of hand as it becomes responsible for flying, spells, health, position, etc. Maintaining it will be near impossible and gameobjects will be required to hold data for functionality they don't need. For example, a monster that only flies will still need to keep a collection of spells. Solution - Entity-Component Approach: With the entity-component pattern, you think of a monster as an entity made up of several parts. You do not need separate classes for Monster, FlyingMonster, SpellMonster, and SpellFlyingMonster. For example, to create a FlyingMonster, you create an entity and add a health part, a damage part, and a flying part. If later on, you want it to cast spells, you add the spell part with one line of code. You can create dozens of monster types by mixing and matching parts.

    Explaining the Concept

    Note: The approach I present is fundamentally different to ECS (Entity Component Systems). In ECS, components only store the data, but do not provide the functionality. Instead, the functionality is provided by external systems. See http://en.wikipedia.org/wiki/Entity_component_system. It is natural to categorize gameobjects into monsters, items, and walls. Notice that these objects have similar attributes and functionality. They all need to be drawn, hold a physical space in the world, and collide with the player in some way. The line between different categories of gameobjects tend to blur as the gameobjects become more complex. For example, adding movement and damaging spikes to a wall, basically turns it into an enemy, e.g. Thwomp in Mario. In the entity-component pattern, game objects are described as entities that contain several attributes and functionality. The components/parts provide an abstraction for the attributes and functionality. Parts can be functional, e.g. control the behavior the entity, or just hold attributes that other systems and parts can reference, e.g. a health stat. The entity is responsible for managing parts as well as their lifetimes, i.e. initializing, updating, and cleaning up parts. There are many benefits of this approach: code reusability, addition/removal of attributes in a game object during runtime, and ease in generating new game object types in complex games such as MMORPGs.

    Example: Roleplaying Game (RPG) Battle

    It's best to illustrate how the entity-part framework works using the following example game/simulation. The example project is attached to this article. The example is in Java, but the C++ code for the Entity and Part classes is also attached. The Main class contains the logic to initialize and run our game. It creates a monster and a helpless villager. It then uses a basic game loop and updates the entities. While running the application, the state of the game will be printed to the console by the monster entity such as the monster's health, villager's health, and monster's height as the result from flying. As you can see, it is very easy to create new types of monsters once you write the code for the parts. For example, we can create a nonflying monster by removing the line of code that attaches the flying part. The MonsterControllerPart is the AI for the monster entity and the target passed into its constructor is the entity that will be attacked. We can make a friendly monster by passing in an enemy Entity into the MonsterControllerPart constructor instead of the helpless villager. public class Main { // main entry to the game application public static void main(String[] args) throws InterruptedException { Entity villager = createVillager(); Entity monster = createMonster(villager); // very basic game loop while (true) { villager.update(1); monster.update(1); Thread.sleep(1000); } } // factory method for creating a monster public static Entity createMonster(Entity target) { Entity monster = new Entity(); monster.attach(new StatsPart(100, 2)); // If we don't want our monster to fly, simply uncomment this line. monster.attach(new FlyingPart(20)); // If we don't want our monster to cast spells, simply uncomment this line. monster.attach(new SpellsPart(5)); monster.attach(new MonsterControllerPart(target)); monster.initialize(); return monster; } // factor method for creating an innocent villager public static Entity createVillager() { Entity villager = new Entity(); villager.attach(new StatsPart(50, 0)); villager.initialize(); return villager; } } MonsterControllerPart code, which serves as the AI and behavior for the monster, includes attacking its target, saying stuff, and attempting to use spells. All of your parts must derive from the Part class. Optionally, parts such as the MonsterControllerPart can override the initialize, cleanup, and update methods to provide additional functionality. These methods are called when its parent entity gets respectively initialized, cleaned up, or updated. Notice that parts can access other parts of its parent entity, e.g., entity.get(StatsPart.class). public class MonsterControllerPart extends Part { private Entity target; public MonsterControllerPart(Entity target) { this.target = target; } @Override public void initialize() { System.out.println("I am alive!"); } @Override public void cleanup() { System.out.println("Nooo I am dead!"); } @Override public void update(float delta) { StatsPart myStatsPart = entity.get(StatsPart.class); // if target has stats part, damage him if (target.has(StatsPart.class)) { StatsPart targetStatsPart = target.get(StatsPart.class); target.get(StatsPart.class).setHealth(targetStatsPart.getHealth() - myStatsPart.getDamage()); System.out.println("Whomp! Target's health is " + targetStatsPart.getHealth()); } // if i have spells, heal myself using my spells if (entity.has(SpellsPart.class)) { entity.get(SpellsPart.class).castHeal(); System.out.println("Healed myself! Now my health is " + myStatsPart.getHealth()); } } } General-purpose StatsPart keeps track of important RPG stats such as health and damage. This is used by both the Monster and the Villager entity. public class StatsPart extends Part { private float health; private float damage; public StatsPart(float health, float damage) { this.health = health; this.damage = damage; } public float getHealth() { return health; } public void setHealth(float health) { this.health = health; } public float getDamage() { return damage; } } SpellsPart class gives the Monster a healing spell to cast. public class SpellsPart extends Part { private float healRate; public SpellsPart(float healAmount) { this.healRate = healAmount; } public void castHeal() { StatsPart statsPart = entity.get(StatsPart.class); statsPart.setHealth(statsPart.getHealth() + healRate); } } FlyingPart code allows the Monster to fly to new heights. public class FlyingPart extends Part { private float speed; // in more sophisticated games, the height could be used to tell if an entity can be attacked by a grounded opponent. private float height = 0; public FlyingPart(float speed) { this.speed = speed; } @Override public void update(float delta) { height += speed * delta; System.out.println("Goin up! Current height is " + height); } }

    The Entity-Part Code

    The following code blocks are for the Entity class and the Part class. These two classes are the base classes you need for the entity-part framework. Entity class: /** * Made up of parts that provide functionality and state for the entity. * There can only be one of each part type attached. * @author David Chen * */ public class Entity { private boolean isInitialized = false; private boolean isActive = false; private Map, Part> parts = new HashMap, Part>(); private List partsToAdd = new ArrayList(); private List> partsToRemove = new ArrayList>(); /** * @return If the entity will be updated. */ public boolean isActive() { return isActive; } /** * Sets the entity to be active or inactive. * @param isActive True to make the entity active. False to make it inactive. */ public void setActive(boolean isActive) { this.isActive = isActive; } /** * @param partClass The class of the part to check. * @return If there is a part of type T attached to the entity. */ public boolean has(Class partClass) { return parts.containsKey(partClass); } /** * @param partClass The class of the part to get. * @return The part attached to the entity of type T. * @throws IllegalArgumentException If there is no part of type T attached to the entity. */ @SuppressWarnings("unchecked") public T get(Class partClass) { if (!has(partClass)) { throw new IllegalArgumentException("Part of type " + partClass.getName() + " could not be found."); } return (T)parts.get(partClass); } /** * Adds a part. * @param part The part. */ public void attach(Part part) { if (has(part.getClass())) { throw new IllegalArgumentException("Part of type " + part.getClass().getName() + " already exists."); } parts.put(part.getClass(), part); part.setEntity(this); if (isInitialized) { part.initialize(); } } /** * If a part of the same type already exists, removes the existing part. Adds the passed in part. * @param part The part. */ public void replace(Part part) { if (has(part.getClass())) { detach(part.getClass()); } if (isInitialized) { partsToAdd.add(part); } else { attach(part); } } /** * Removes a part of type T if it exists. * @param partClass The class of the part to remove. */ public void detach(Class partClass) { if (has(partClass) && !partsToRemove.contains(partClass)) { partsToRemove.add(partClass); } } /** * Makes the entity active. Initializes attached parts. */ public void initialize() { isInitialized = true; isActive = true; for (Part part : parts.values()) { part.initialize(); } } /** * Makes the entity inactive. Cleans up attached parts. */ public void cleanup() { isActive = false; for (Part part : parts.values()) { part.cleanup(); } } /** * Updates attached parts. Removes detached parts and adds newly attached parts. * @param delta Time passed since the last update. */ public void update(float delta) { for (Part part : parts.values()) { if (part.isActive()) { part.update(delta); } } while (!partsToRemove.isEmpty()) { remove(partsToRemove.remove(0)); } while (!partsToAdd.isEmpty()) { attach(partsToAdd.remove(0)); } } private void remove(Class partClass) { if (!has(partClass)) { throw new IllegalArgumentException("Part of type " + partClass.getName() + " could not be found."); } parts.get(partClass).cleanup(); parts.remove(partClass); } } Part class: /** * Provides partial functionality and state for an entity. * @author David Chen * */ public abstract class Part { private boolean isActive = true; protected Entity entity; /** * @return If the part will be updated. */ public final boolean isActive() { return isActive; } /** * @return The entity the part is attached to. */ public final Entity getEntity() { return entity; } /** * Sets the entity the part is attached to. * @param entity The entity. */ public final void setEntity(Entity entity) { this.entity = entity; } /** * Initialization logic. */ public void initialize() { } /** * Cleanup logic. */ public void cleanup() { } /** * Update logic. * @param delta Time since last update. */ public void update(float delta) { } }

    Conclusion

    This concludes the basics of the entity-component pattern and the implementation. Also, you got to see how to use the implementation's API in the example. As you can see, entities and components are powerful tools for creating game objects, but they can't stand on their own without higher level systems. In the example project, notice that the villager's health went below zero but wasn't removed from the game. Also, the villager Entity had to be passed into the MonsterControllerPart constructor which would be an issue if the monster Entity had to be created first. These issues are outside the scope of this article but will be addressed by the next article, which describes how to implement high-level game logic to manage entity interactions with the help of these classes: Event manager - Provides a centralized hub for event subscription and publication between systems, entities, and parts. For example, this will allow the villager to tell the entity manager that it has died. Entity manager - Manages entities by adding newly created ones, removing dead entities, and updating them. For example, this will remove the dead villager from the gameworld. AI System - Manages AI behavior of an Entity that is dependent on the presence of other entities. For example, this will provide the monster entity knowledge of available targets. Battle System - Provides the rules of the game, e.g., calculating how much damage is dealt, knowing when an Entity is dead, etc. The example will be expanded so that instead of simulating a 1 on 1 fight, a team of monsters will go up against a team of villagers. The entities will have varying classes (melee specialist, ranger, flying ranger, summoner, support mage, etc). The next article will deal with entity management and interactions: Interactions.

    Article Update Log

    5 Apr 2014: Shortened title. 22 Mar 2014: Added C++ w/boost version of code and fixed a memory leak bug in the C++ version. 22 Mar 2014: Changed title from Basics to Game Objects to be more inline with the content of the article 12 Mar 2014: Added C++ version of code 9 Mar 2014: Added additional code samples


      Report Article
    Sign in to follow this  


    User Feedback


    It is probably worth mentioning in C++ you can avoid the issue with multiple inheritance coupled with virtual inheritance.

    While true, I would stay away from multiple inheritance. Entity based frameworks provide a solid architecture through componentization without having to deal with this language specific feature. Multiple inheritance is highly discouraged in general.

    Share this comment


    Link to comment
    Share on other sites

    Thanks for the feedback guys,

     

    I have added the C++ version in the 'article resource'.  I took the C++ code from one of my games and modified it to remove any game specific code and uses of the boost library.  Let me know if there are any bugs with it since I haven't had a chance to test it.

     

    Madhed - Yes, the entities own and manage components.  I'll add a couple sentences about the relational database inspired model in the Usage Notes.  I'm not completely sure what the alternate implementation is, but I assume that it's getting entities and parts by integer IDs instead of pointers.

    Share this comment


    Link to comment
    Share on other sites

    @dave exactly. In the other model, entities are just an id, components contain only data, and 'systems'  contain the logic. It's another solution to the mentioned OOP inheritance dilemma. I like it from a conecptional point of view, but it can be harder to implement and you certainly need some kind of caching/indexing for the entity-component binding. Having the components stored as contiguous blobs opens some opportunities to improve performance though.

     

    I only mentioned it because there is some confusion when talking about Entity Component Systems. I came in contact with your approach first and later learned there is another model with the same name.

    Share this comment


    Link to comment
    Share on other sites

    This *appears* to be an initial part of a multi-part article, but nevertheless could really use a conclusion that demonstrates a more interesting, complete example. You sort of build up towards something but never quite get to anything interesting.

    Share this comment


    Link to comment
    Share on other sites

     From "Examining the Concept" section:

     

    object-oriented programming requires you to cram all functionality into one GameObject class, or create several game object classes that have duplicate code

     

     

     

    First, that "Examining.." section starts out with "OO isn't the concept." See below for suggestions to improve.

     

    A concept is an abstract idea, not details of a specific implementation. It would greatly enhance your article if you can provide a good explanation for your concept of an entity-component model.

     

    If you provide a good idea of your concept, then you can have a section titled "Examining the Concept." You have a lot of information detailing how the concept can be implemented, but it's out of the context of what you're implementing.

     

    I would suggest you change the emphasis of the article from "OO is bad! Entity-component is good!" to something a bit more toned-down, perhaps along the line of "An entity-component approach to object construction provides the ability to.." That expands on the concept a bit better, IMHO.

     

    As currently written, your article implies that the entity-component model can't stand on its own, but should be considered only as an alternative to object-oriented programming.

     

    If you really want the emphasis of the article to be "entity-component versus OO," change the title and, after a more moderated introduction, you can down-play the emphasis on "Good vs. Evil" by briefly discussing a feature of the entity-component model, continuing with "This feature avoids possible complications when using an OO approach.."

     

     

    make your game objects more dynamic

     

     

    In the statement above, you use the word "more," still in the mode of comparison with OO. Why not just "make your game objects dynamic" and discuss ways to do that?

     

    You may even want to scrap all the OO discussion, and provide a discussion at the end of the article comparing your model to other methods of providing attributes to game objects, and the particular advantages and limitations of your approach.

     

    You've obviously put a lot of effort into the code, providing examples in a couple languages. Good job!

     

    EDIT: One of the features I find useful using an entity-component model: the emphasis is on run-time vs. compile-time decisions. I think that's part of what you imply by using the word "dynamic," but it could be emphasized more - if you want.

    Share this comment


    Link to comment
    Share on other sites

    Its a good article and describes an interesting architectural design.

    A lot of entity/component system try to solve the problems that are created from improper use of inheritance. Take a look at this wiki for 'Composition over inheritance' (http://en.wikipedia.org/wiki/Composition_over_inheritance). It should give you a good idea of why many people prefer entity/component systems.

     

    I would also recommend watching videos from Robert C. Martin. He explains a lot of good design concepts when it comes to code architecture (https://www.youtube.com/watch?v=WpkDN78P884).

     

    Edit - I didn't notice it, but madhed already mentioned the relation of entity/components and the inheritance issue. Nonetheless, check out the links its good info.

     

    A side note - There are component systems that are OOP and some procedural (data-driven). OOP competes with procedural. 

     

    "Procedural code (code using data structures) makes it easy to add new functions without changing the existing data structures. OO code, on the other hand, makes it easy to add new classes without changing existing functions. " - Clean Code: A Handbook of Agile Software Craftsmanship

     

    So if you do decide to use one over there other than It's a good idea to stick to that method and not mix the two. (http://en.wikipedia.org/wiki/BaseBean). To that point, I really like how you made all of your variables private :)

     

    Share this comment


    Link to comment
    Share on other sites

    Good feedback...

     

    This is a part of a series of articles.  This first article describes the implementation of the entity and component class I use which I tried to make as simple and flexible as possible, and serves as a practical introduction to anyone who is new to the entity-component pattern.

     

    I also moved the sidenote about the more datadriven ECS approach in the "Example" section.

     

    Josh -  I fleshed out the conclusion some more and made it more clear this is a part of an article series.  I agree the example is very basic, but I think it describes the base functionality of the entity and part class.  I will elaborate on the example in the next article when I talk about entity management and interaction, which would be too much to put in this article.

     

    Buckeye - You're right.  The comparison shouldn't be in the "Explaining the Concept" section and should be more about the entity-component pattern.  I created a new section called "The Problem" which talks about traditional approaches to gameobject creation and renamed some things so it wouldn't seem like an attack on OO which I didn't intend it to be.  I added some things in "Explaining the Concept" that detail the entity-component pattern specifically.

    Share this comment


    Link to comment
    Share on other sites

    Good rewrite! Toning it down a bit makes a lot of difference (to me, in any case). Will be back to give it another read-through.

     

    Nice job overall.

    Share this comment


    Link to comment
    Share on other sites

    Sanka - Glad you like the article smile.png.  I can't say for sure when it will be out because I'm busy with work and school, but I hope by the end of this month.  If not, I'll add more content to the example.

    Share this comment


    Link to comment
    Share on other sites

    It's a great article, you presented the basic very well without getting caught up on tangents.  When do you plan on posting the next article?

    Share this comment


    Link to comment
    Share on other sites

    SpiderJack - Thanks SpiderJack!  Like I said, I don't have a fixed timeframe for the next article because I'm carefully planning out what I want to talk about next.  I did make a lot of progress on the example code today so things are moving along smoothly.  I'm shooting to complete it by the end of the month.

    Share this comment


    Link to comment
    Share on other sites

    It is really a great article. The Idea that components are owned by entities, awesome. For me completly new approach to use entities.

     

    I'm looking forward for your next article :D

    Share this comment


    Link to comment
    Share on other sites

    I have a small question. In class Entity you use a HashMap to store the Parts. If I create a Part responsible for drawing the Entity, as I do so that part is always executed last? In my view, I think this can generate some inconsistencies or bugs when, for example, a DrawingPart is "updated" before any PhysicsPart.

     

    Anyway, I liked your explanation of the subject and the fact of having something concrete (codes, codes everywhere :D).

    Share this comment


    Link to comment
    Share on other sites

    Sorry for the late response.  I haven't dealt with a situation like that where I would need parts to be updated in a certain order.  In that case, you can probably modify the Entity class to use SortedMap instead of HashMap.  Another solution is to add another method to your physics part called preUpdate(), which can be called before the main update stage.

    Share this comment


    Link to comment
    Share on other sites


    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!