9
\$\begingroup\$

Note: I'm programming this in Javascript, but it should be language agnostic in the most part.

I am thinking about converting my engine to an ECS based one.

I get the basic idea (note: this is wrong, see my answer):

Entities are game objects.
Components are bits of functionality (reactToInput()) or state (position) which can get "glued" to entities.
Systems have a list of entities they manage and update.

But, I'm not quite sure that I get the implementation and some details...

Question: can a system operate on different kinds of entities? I usually give the example of a class called Scene in my engine, and it will serve this purpose now, as well. A scene is a container of all objects that can be rendered, updated, affect the rendering (lights), and maybe, in future, even 2DSoundEmitter objects. It has a high-level interface so the user doesn't need to worry about the type of the object he's scene.add()ing, and all that kind of stuff.

I realize that the Scene could be a system. It takes in entities, stores them, and then it can call their update methods, and maybe even do some state changes. But, there is a problem: as I described above, the Scene can be fed different types of objects! What should I do in, say, a situation where a scene has both renderable objects ("drawables") and lights in it? Should I make it type-check entities before interacting? Or, should I solve it at an even lower level: make a LightSource component which can be added to any object, and the light would just be an entity with LightSource and Position components. Is that acceptable?

Also, is it a good practice to still use conventional inheritance and traditional classes? For example, I just can't figure out what would my Renderer be! It's not a system, as its only function is to take in a camera and a scene, render everything and apply effects (such as shadows). It also manages the context, the width and the height of the game, makes translations... But it's still not a system!

Edit: could you maybe link any resources you found on the ECS? I'm having trouble finding good ones.

\$\endgroup\$
11
  • 2
    \$\begingroup\$ Rather than repost the answer on this page, I'll just give this link: gamedev.stackexchange.com/questions/23533/… Entity should not be derived from, any differences between entities should be accomplished through components. Generally you will need an interface for each major system (Rendering, Physics, Networking, Input, Audio, etc...). The way I've setup my renderer is to query the scene for renderable entities, and the scene manager than asks each entity that has a render component on it for its render information. \$\endgroup\$
    – Nic Foster
    Jul 18, 2012 at 23:21
  • 1
    \$\begingroup\$ Component design on the T=Machine blog (since you asked for a good one) \$\endgroup\$ Jul 18, 2012 at 23:32
  • \$\begingroup\$ Code and discussion of an entity framework: gamadu.com/artemis \$\endgroup\$ Jul 19, 2012 at 4:03
  • \$\begingroup\$ @JohnMcDonald, I wrote a comment on that article, although it's awaiting moderation. You can see it here: t-machine.org/index.php/2007/12/22/…. I'm "Yannbane". \$\endgroup\$
    – jcora
    Jul 19, 2012 at 15:37
  • \$\begingroup\$ Also, @NicFoster, the article that John linked to on T=Machine describes something kinda different than your answer... To that Dave, entities don't have a list of components, they are just a name. Like "flsjn304" - that's an entity. It is stored "somewhere". And I must re-read the thing to understand if he actually keeps components inside systems, which seems very strange to me! \$\endgroup\$
    – jcora
    Jul 19, 2012 at 17:10

3 Answers 3

6
\$\begingroup\$

Let me see if by trying to understand as a web/UI JS dev, I can be of help. Also, don't go too far in language agnosticism. A lot of patterns established in other languages are worth studying but can be applied very differently in JS due to its flexibility or really aren't necessary due to the malleable nature of the language. You could blow some opportunities if you write your code thinking of JS as having the same set of boundaries that a more classical OOP-oriented language does.

First of all, on the "don't use OOP" factor, remember that JavaScript objects are like playdough compared to other languages and you actually have to go out of your way to build a cascading-inheritance scheme nightmare since JS isn't class-based and compositing comes much more naturally to it. If you are implementing some silly class or prototype hand-me-down system in your JS, consider ditching it. In JS we use closures, prototypes, and we pass functions around like candy. It's disgusting and filthy and wrong but also powerful, concise and that's the way we like it.

Inheritance heavy approaches are actually spelled out as an anti-pattern in Design Patterns and for good reason as anyone who has walked down 15+ levels worth of class or class-like structures to try and figure out where the heck the busted version of a method was coming in from can tell you.

I don't know why so many programmers love doing this (especially java guys writing JavaScript for some reason), but it's awful, illegible, and completely unmaintainable when used to excess. Inheritance is okay here and there, but not really necessary in JS. In languages where it's a more enticing shortcut, it should really be reserved for more abstract architecture concerns rather than more literal modeling schemes like frankensteining a zombie implementation through an inheritance chain that included a BunnyRabbit because it happened to work. That's not good code reuse. It's a maintenance nightmare.

As a JS dev Entity/Component/System based engines strike me as a system/pattern for decoupling design concerns and then compositing objects for implementation on a highly granular level. In other words, child's play in a language like JavaScript. But let me see if I'm grokking this correctly first.

  • Entity - The specific thing you are designing. We're talking more in the direction of proper nouns (but not actually, of course). Not 'Scene', but 'IntroAreaLevelOne'. IntroAreaLevelOne might sit inside a sceneEntity box of some kind but we're focusing on something specific that varies from other related things. In the code, an entity is really just a name (or ID) tied to a bunch of stuff that it needs to have implemented or established (the components) in order to be useful.

  • Components - types of things an entity needs. These are general nouns. Like WalkingAnimation. Within WalkingAnimation we can get more specific, like "Shambling" (good choice for zombies and plant monsters), or "ChickenWalker" (great for reverse-joint ed-209ish robot-types). Note: Not sure how that could decouple from the rendering of a 3D model like that - so maybe a crap example but I'm more of a JS pro than an experienced game dev. In JS I would put the mapping mechanism in the same box with the components. Components in their own right are likely to be light on logic and more of a roadmap telling your systems what to implement if systems are even needed (in my attempt at ECS some components are just collections of property sets). Once a component is established, it's easy enough to tweak one component and assign the altered version to a new name if necessary but having sets of properties that can referred to for branching of behavior established by Systems strikes me as a better way to go.

  • Systems - The real programmey meat is here. AI systems are built and linked, Rendering is achieved, animations sequences established, etc... I'm copping out and leaving these mostly to the imagination but in the example System.AI takes a bunch of properties in and spits out a function which is used to add event handlers to the object that ultimately gets used in implementation. The key thing about System.AI is that it covers multiple component types. You could sort out all the AI stuff with one component but to do so is to misunderstand the point of making things granular.

Mind the Goals: We want to make it easy to plug in some kind of GUI interface for non-designers to easily tweak different kinds of stuff by maxing and matching components within a paradigm that makes sense to them, and we want to get away from popular arbitrary code schemes that are a lot easier to write than they are to modify or maintain.

So in JS, maybe something like this. Game devs please tell me if I've got it horribly wrong:

//I'm going with simple objects of flags over arrays of component names
//easier to read and can provide an opt-out default
//Assume a genre-bending stealth assassin game

//new (function etc... is a lazy way to define a constructor and auto-instantiate
var npcEntities = new (function NpcEntities(){

    //note: {} in JS is an object literal, a simple obj namespace (a dictionary)
    //plain ol' internal var in JS is akin to a private member
    var default={ //most NPCs are humanoids and critters - why repeat things?
        speedAttributes:true,
        maneuverAttributes:true,
        combatAttributes:true,
        walkingAnimation:true,
        runningAnimation:true,
        combatAnimation:true,
        aiOblivious:true,
        aiAggro:true,
        aiWary:true, //"I heard something!"
        aiFearful:true
    };

    //this. exposes as public

    this.zombie={ //zombies are slow, but keep on coming so don't need these
        runningAnimation:false,
        aiFearful:false
    };

    this.laserTurret={ //most defaults are pointless so ignore 'em
        ignoreDefault:true,
        combatAttributes:true,
        maneuverAttrubtes:true, //turning speed only
    };
    //also this.nerd, this.lawyer and on and on...

    //loop runs on instantiation which we're forcing on the spot

    //note: it would be silly to repeat this loop in other entity collections
    //but I'm spelling it out to keep things straight-forward.
    //Probably a good example of a place where one-level inheritance from
    //a more general entity class might make sense with hurting the pattern.
    //In JS, of course, that would be completely unnecessary. I'd just build a
    //constructor factory with a looping function new objects could access via
    //closure.

    for(var x in npcEntities){

        var thisEntity = npcEntities[x];

        if(!thisEntity.ignoreDefaults){

            thisEntity = someObjectXCopyFunction(defaults,thisEntity);
            //copies entity properties over defaults

        }
        else {
            //remove nonComponent property since we loop again later
            delete thisEntity.ignoreDefaults;
        }
    }
})() //end of entity instantiation

var npcComponents = {
    //all components should have public entityMap properties

    //No systems in use here. Just bundles of related attributes
    speedAttributes: new (function SpeedAttributes(){
        var shamblingBiped = {
            walkingAcceleration:1,
            topWalking:3
        },
        averageMan = {
            walkingAcceleration:3,
            runningAcceleration:4,
            topWalking: 4,
            topRunning: 6
        },
        programmer = {
            walkingAcceleration:1,
            runningAcceleration:100,
            topWalking:2
            topRunning:2000
        }; //end local/private vars

        //left is entity names | right is the component subcategory
        this.entityMap={
            zombie:shamblingBiped,
            lawyer:averageMan,
            nerd:programmer,
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(), //end speedAttributes

    //Now an example of an AI component - maps to function used to set eventHandlers
    //functions which, because JS is awesome we can pass around like candy
    //I'll just use some imaginary systems on this one

    aiFearful: new (function AiFearful(){
        var averageMan = Systems.AI({ //builds and returns eventSetting function
            fearThreshold:70, //%hitpoints remaining
            fleeFrom:'lastAttacker',
            tactic:'avoidIntercept',
            hazardAwareness:'distracted'
        }),
        programmer = Systems.AI({
            fearThreshold:95,
            fleeFrom:'anythingMoving',
            tactic:'beeline',
            hazardAwareness:'pantsCrappingPanic'
        });//end local vars/private members


         this.entityMap={
            lawyer:averageMan,
            nerd:averageMan, //nerds can run like programmers but are less cowardly
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(),//and more components...

    //Systems.AI is general and would get called for all the AI components.
    //It basically spits out functions used to set events on NPC objects that
    //determine their behavior. You could do it all in one shot but
    //the idea is to keep it granular enough for designers to actually tweak stuff
    //easily without tugging on developer pantlegs constantly.
    //e.g. SuperZombies, zombies, but slightly tougher, faster, smarter
}//end npcComponents

function createNPCConstructor(npcType){

    var components = npcEntities[npcType],

    //objConstructor is returned but components is still accessible via closure.

    objConstructor = function(){
        for(var x in components){
            //object iteration <property> in <object>

            var thisComponent = components[x];

            if(typeof thisComponent === 'function'){
                thisComponent.apply(this);
                //fires function as if it were a property of instance
                //would allow the function to add additional properties and set
                //event handlers via the 'this' keyword
            }
            else {
                objConstructor.prototype[x] = thisComponent;
                //public property accessed via reference to constructor prototype
                //good for low memory footprint among other things
            }
        }
    }
    return objConstructor;
}

var npcBuilders= {}; //empty object literal
for (var x in npcEntities){
    npcConstructors[x] = createNPCConstructor(x);
}

Now any time you need an NPC, you could build with npcBuilders.<npcName>();

A GUI could plug into the npcEntities and components objects and allow designers to tweak old entities or create new entities by simply mixing and matching components (although there's no mechanism in there for non-default components but special components could be added on the fly in the code as long as there was a defined component for it.

\$\endgroup\$
1
  • \$\begingroup\$ Looking at this six years later, I'm not sure I understand my own answer. Could this be improved? \$\endgroup\$ Apr 13, 2018 at 2:54
1
\$\begingroup\$

I have read up on Entity Systems in the articles that kind people provided in the comments, but I still had some doubts, so I asked another question.

First off, my definitions were wrong. Entities and Components are just dumb data holders, while systems provide all functionality.

I learned enough to cover most of my question here, so I'll answer it.

The Scene class I was talking about shouldn't be a system. It should, though, be a central manager that can hold all entities, facilitate messages, and maybe even manage systems. It can also function as a sort-of factory for entities, and I've decided to use it like that. It can take any type of an entity, but then it must feed that entity to an appropriate system (which, for performance reasons, shouldn't perform any type-checks, unless they're bitwise).

I shouldn't use any OOP while implementing an ES, suggests Adam, but I find no reason not to have objects with methods for both entities and components, not just dumb data holders.

The Renderer can simply be implemented as a system. It would maintain a list of drawable objects and call their render component's draw() method each 16ms.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ "Entities and Components are just dumb data holders, while systems provide all functionality" "call their render component's draw() method" you're still confused, besides a "draw" method defeats the purpouse of the Rendering system altogether. Also i don't understand why can't your Scene graph be a part of the Renderer, it's just a convenient tool, you can always implement your "drawable" component as a node. Making the scene graph be responsible for more than the scene is just unnecessary and i'm sure it's going to be a mess to debug. \$\endgroup\$
    – dreta
    Jul 19, 2012 at 22:29
  • \$\begingroup\$ @dreta, the renderer currently (non-ES implementation of the engine) does transformations, camera changes, alpha stuff, and in the future it will draw various effects, the GUI and shadows. It just seemed natural to group that stuff. Shouldn't the scene be responsible for creating an storing entities? Or should something else store them? The creation part is probably just a few lines of aggregating user-provided components together, it's not really "creating" anything at all, just instancing. \$\endgroup\$
    – jcora
    Jul 19, 2012 at 22:49
  • \$\begingroup\$ not every object is renderable, not every object can be collided with or emits a sound, with your scene object you're doing extreme coupling, why? this is just going to be a pain to write and debug. Entities are used to identify an object, components hold data and systems operate on the data. Why would you mash all of that together instead of having proper systems like RenderingSystem and SoundSystem and only bother those systems if an entity has all the required components. \$\endgroup\$
    – dreta
    Jul 19, 2012 at 23:02
  • 1
    \$\begingroup\$ shadow casting is usualy attached to light sources, though you can just create a component "CastsShadow" and look for it when rendering dynamic objects. if you're doing 2D then this is just a basic issue of ordering, a simple painter's algorithm will solve this issue for you. TBH you're worryng too early. you'll figure this out when it's time to do it and you have only that on your mind, right now you're just confusing yourself. you can't hope to get everything right the first time, it's just not going to happen. you'll cross that bridge when you get to it. \$\endgroup\$
    – dreta
    Jul 19, 2012 at 23:20
  • 1
    \$\begingroup\$ "Entities and Components are just dumb data holders, while systems provide all functionality." Not necessarily. They are in some people's approaches. But not others. Look at the Unity engine - all the behaviour is in the components. \$\endgroup\$
    – Kylotan
    Jul 21, 2012 at 1:24
-2
\$\begingroup\$

Introduction to dependency management 101.

This course assumes you have basic knowledge of dependency injection and repository design.

Dependency injection is just a fancy way for objects to talk each other (via messages/signals/delegates/whatever) without being directly coupled.

It goes by the phrase: "new is glue."

I will be demonstrating this in C#.

public interface IEntity
{
    int[] Position { get; }
    int[] Size { get; }
    bool Update();
    void Render();
}

public interface IRenderSystem
{
    void Draw(IEntity entity);
}

public interface IMovementSystem
{
    bool CanMoveLeft();
    void MoveLeft();
    bool CanMoveRight();
    void MoveRight();
    bool CanMoveUp();
    void MoveUp();
    bool CanMoveDown();
    void MoveDown();
    bool Moved();
    int[] Position { get; set; }
}

public interface IInputSystem
{
    string Direction { get; set; }
}

public class Player : IEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public bool Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();

        return _movementSystem.Moved();
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

This is a basic system for Input, Movement, and Rendering. The Player class is the entity in this case and the components are the Interfaces. The Player class knows nothing about the internals of how a concrete IRenderSystem, IMovementSystem, or IInputSystem work. However, the interfaces provide a way for the Player to send signals (e.g. invoke Draw on the IRenderSystem) without depending on how the end result is accomplished.

For example take my implementation of IMovementSystem:

public interface IGameMap
{
    string LeftOf(int[] currentPosition);
    string RightOf(int[] currentPosition);
    string UpOf(int[] currentPosition);
    string DownOf(int[] currentPosition);
}

public class MovementSystem : IMovementSystem
{
    private readonly IGameMap _gameMap;
    private int[] _previousPosition;
    private readonly int[] _currentPosition;
    public MovementSystem(IGameMap gameMap, int[] initialPosition)
    {
        _gameMap = gameMap;
        _currentPosition = initialPosition;
        _previousPosition = initialPosition;
    }

    public bool CanMoveLeft()
    {
        return _gameMap.LeftOf(_currentPosition) == "Unoccupied";
    }

    public void MoveLeft()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]--;
    }

    public bool CanMoveRight()
    {
        return _gameMap.RightOf(_currentPosition) == "Unoccupied";
    }

    public void MoveRight()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]++;
    }

    public bool CanMoveUp()
    {
        return _gameMap.UpOf(_currentPosition) == "Unoccupied";
    }

    public void MoveUp()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]--;
    }

    public bool CanMoveDown()
    {
        return _gameMap.DownOf(_currentPosition) == "Unoccupied";
    }

    public void MoveDown()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]++;
    }

    public bool Moved()
    {
        return _previousPosition == _currentPosition;
    }

    public int[] Position
    {
        get { return _currentPosition; }
    }
}

MovementSystem can have its own dependencies and the Player wouldn't even care. By using interfaces, a game state machine can be created:

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IEntity>();
        foreach (var entity in _entities)
        {
            if(entity.Update())
                _renderQueue.Add(entity);
        }
        // Linq version for those interested
        //_renderQueue.AddRange(_entities.Where(e => e.Update()));
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

And that is the start of a lovely game (that is also unit testable).

And with a few additions and some polymorphism:

public interface IEntity
{
}

public interface IRenderableEntity : IEntity
{
    void Render();        
}

public interface IUpdateableEntity : IEntity
{
    void Update();
    bool Updated { get; }
}

public interface IRenderSystem
{
    void Draw(IRenderableEntity entity);
}

// new player class
public class Player : IRenderableEntity, IUpdateableEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public void Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();
    }

    public bool Updated
    {
        get { return _movementSystem.Moved(); }
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IRenderableEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IRenderableEntity>();
        foreach (var entity in _entities)
        {
            if (entity is IUpdateableEntity)
            {
                var updateEntity = entity as IUpdateableEntity;
                updateEntity.Update();
            }

            if (entity is IRenderableEntity)
            {
                var renderEntity = entity as IRenderableEntity;
                _renderQueue.Add(renderEntity);
            }
        }
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

We now have a primitive entity/component system based on aggregating interfaces and loose inheritance.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ This is so against component design :) What would you do if you want one player to make sounds and other not? \$\endgroup\$
    – Kikaimaru
    Jul 25, 2012 at 10:13
  • \$\begingroup\$ @Kikaimaru Pass in an ISoundSystem that doesn't play sound. i.e. Does nothing. \$\endgroup\$ Jul 25, 2012 at 10:57
  • 3
    \$\begingroup\$ -1, not because it's bad code, but because it's not relevant at all to the component-based architecture - in fact it's the proliferation of interfaces like this that the components try to avoid. \$\endgroup\$
    – Kylotan
    Jul 25, 2012 at 12:27
  • \$\begingroup\$ @Kylotan I guess my understanding must be wrong. \$\endgroup\$ Jul 25, 2012 at 14:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .