Is this a good example of an Entity Component System?

Started by
5 comments, last by Tutorial Doctor 9 years, 4 months ago
Would this be a good example of an Entity Component System?

I am trying to understand how one works, so I made a simple python script to demonstrate what I suppose an
ECS would look like v.s Object Oriented Programming.


# COMPONENT ENTITY SYSTEM

class Human:
    def __init__(self):
        self.height = 2
        self.position = (0,0)
        self.velocity = (0,0)
        self.sprite = '' #get_some_car_image()
        self.moveable = False
        self.rendered = False 

class Renderer:
    def render(self,obj,screen):
        print 'rendering %s to %s' % (obj,screen)
        obj.rendered = True

class Mover:
    def Move(self,obj):
        print 'Moving %s' % (obj)
        obj.moveable = True 
        
man = Human()

renderer = Renderer()
renderer.render(man,'monitor 1')

mover = Mover()
mover.Move(man)

print man.moveable
print man.rendered

They call me the Tutorial Doctor.

Advertisement

The entity (Human) should have the components (Renderer and/or Mover), in your example the components do something with the Human and just set some flags in it, and you're able to call "render(someobject)" with any entity, even if the entity is note supposed to be rendered.

You should be able to get the "Renderer" component from 2 different Humans and those renderers shuldn't be the same object, components can have a state, so you want each entity to have different instances of a component class.

When you want to render all objects with "renderer" component you iterate over the entities, get the Renderer component (it it has any) and call the rendering method with the entity's state needed (if you're rendering you probably want the position of the entity, and the Renderer component should already have the sprite).

Also, the Human shouldn't have the state of the components, if you rendered an entity and want to keep a "rendered" flag, put that flag in the component. If an entity must be renderable, put the Sprite in the Render component.

EDIT: In this article there's also a "System" that has the methods related to each component (instead of having the methods in the components). This way when the system is supposed to draw, it gets the state of the entity (position) and the state of the Render component (a sprite), and makes the Draw call. Maybe that's the best approach, I've never implemented one of this patterns, I just used them.

EDIT: In this article there's also a "System" that has the methods related to each component (instead of having the methods in the components). This way when the system is supposed to draw, it gets the state of the entity (position) and the state of the Render component (a sprite), and makes the Draw call. Maybe that's the best approach, I've never implemented one of this patterns, I just used them.


That's kind of the key part of an ECS. Without that, it'd just be an EC, now, wouldn't it? tongue.png

An ECS has merit when it comes to good data-oriented design*. Since data-oriented programming is about three steps short of utterly impossible with a language like Python** (there's a reason serious games aren't written it), you're probably far better off with a simpler component model.

You can use simple aggregation very easily in Python. Since you can dynamic add and remove properties, there's no reason to worry about add a ton of boilerplate. Adding a mesh renderable is as easy as:

object.renderable = Renderable(object)
You can easily test if object has renderable set, you can set renderable to different kinds of components (sprites vs meshes vs whatever) and so on.

Keep it simple.

That said, your code isn't a bad approach altogether. It's definitely not an ECS and it's not even component based. It's really just a straight up old-school "fat game object" design. Which works just fine. There are weaknesses with such a design, but so too with every single component architecture. Design for what you actually need and not for what some random blog article online told you is a cool idea.

* you don't in any way need an ECS to apply data-oriented design principles. It's the easiest way for Java programmers to do it - and C programmers tend to favor it due to the language's nature - but in multi-paradigm languages like C++ you have far more options.

** in Python, you'd need to follow similar constraints to those of Java. The problem is that Python is a garbage-collected (sorta, also with reference counting) language that doesn't let the user control allocation patterns or placement of user-defined types. The way to really use data-oriented approaches on Python is to keep all of your data in arrays of primitive data types, just like in Java. Unfortunately, the nature of Python means you'd need to be cautious and stick to specialized types and not Python's native number types to really get the benefit.

Sean Middleditch – Game Systems Engineer – Join my team!

In this article there's also a "System"

No wonder why its called Entity Component System EDIT: Ninja'd by sean tongue.png

The "pure" ECS approach has 3 parts:

  • Entities, which are an ID (integer, long, UUID, whatever you prefer).
  • Components, which are data, and only data (no behavior, no methods, no logic).
  • Systems, which are the logic and manipulate components and entities.

For setting up these, you need a way to track what components each entity has (many ways to do this) and a way for the systems to specify what kind of entities they can process, that means, a way to specify what components a system needs in an entity for processing it (again, many ways to do this), for example, a PhysicsSystem won't process an entity if it doesn't has a "RigidBody" component.

That's the general idea, you put components in an entity, and let the systems resolve what entities they process, run all the systems in the game loop.

Ideally the systems aren't very coupled, ie, only few of them need to be run in a specific order, and ideally, when a system processes an entity, it doesn't has side effects that go beyond the components of said entity. This allows for running decoupled systems in parallel and for running the entity processing itself of each system in parallel too.

I said "ideally" because coming up with such decoupling can be a challenge in itself (just like designing class hierarchies).

This is the most "pure" approach I'd say. Of course you can go off tangent by having Entities being more than pure unique IDs, components having some logic, and using inheritance here and there when it merits it. As with everything, nothing is set in stone and no one says doing ONLY components and ONLY systems and ONLY entities will fix all of your problems.

EDIT: On the "data oriented" part. The idea is that components are stored contiguously in memory, so that when a system iterates over entities, it can fetch contiguous components from an array and make the best use of CPU cache. How you do this part depends on your game.

For example, if you put every kind of component in their own array, and use the Entity ID to index into said arrays, your data essentially becomes a struct of arrays.

If a system that processes most of the entities in the game uses 5 or more different types of components at the same time, it might be more worthwhile placing those 5 components into an array of structures instead, so the 5 components are near each other in memory and can be prefetched by the CPU more efficiently.

This part depends a lot on how your game is set up (remember we're making a game after all), you might want some components grouped in memory rather than isolated by type, and some others might be better isolated by type rather than grouped.

That is something you'd only know when you have all the components and systems set up, and can profile everything. You can't make these kind of decisions as you go (or maybe you can if you really, really know what you're doing). Ideally, the layout in memory should be abstracted in such a way that you can do these sort of changes (grouping some components, isolating others) without having to refactor everything. As always, YMMV.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator


http://python-utilities.readthedocs.org/en/latest/ebs.html

From the original site

Imagine a car game class in traditional OOP, which might look like


class Car:
def __init__(self):
self.color = "red"
self.position = 0, 0
self.velocity = 0, 0
self.sprite = get_some_car_image()
...
def drive(self, timedelta):
self.position[0] = self.velocity[0] * timedelta
self.position[1] = self.velocity[1] * timedelta
...
def stop(self):
self.velocity = 0, 0
...
def render(self, screen):
screen.display(self.sprite)

mycar = new Car()
mycar.color = "green"
mycar.velocity = 10, 0

The car features information stored in attributes (color, position, ...) and behaviour (application logic, drive(), stop() ...).

A component-based approach aims to split and reduce the car to a set of information and external systems providing the application logic.


class Car:
def __init__(self):
self.color = "red"
self.position = 0, 0
self.velocity = 0, 0
self.sprite = get_some_car_image()

class CarMovement:
def drive(self, car, timedelta):
car.position[0] = car.velocity[0] * timedelta
car.position[1] = car.velocity[1] * timedelta
...
def stop(self):
car.velocity = 0, 0

class CarRenderer:
def render(self, car, screen):
screen.display(car.sprite)

They call me the Tutorial Doctor.


http://python-utilities.readthedocs.org/en/latest/ebs.html

From the original site

Imagine a car game class in traditional OOP, which might look like


class Car:
def __init__(self):
self.color = "red"
self.position = 0, 0
self.velocity = 0, 0
self.sprite = get_some_car_image()
...
def drive(self, timedelta):
self.position[0] = self.velocity[0] * timedelta
self.position[1] = self.velocity[1] * timedelta
...
def stop(self):
self.velocity = 0, 0
...
def render(self, screen):
screen.display(self.sprite)

mycar = new Car()
mycar.color = "green"
mycar.velocity = 10, 0

The car features information stored in attributes (color, position, ...) and behaviour (application logic, drive(), stop() ...).

A component-based approach aims to split and reduce the car to a set of information and external systems providing the application logic.


class Car:
def __init__(self):
self.color = "red"
self.position = 0, 0
self.velocity = 0, 0
self.sprite = get_some_car_image()

class CarMovement:
def drive(self, car, timedelta):
car.position[0] = car.velocity[0] * timedelta
car.position[1] = car.velocity[1] * timedelta
...
def stop(self):
car.velocity = 0, 0

class CarRenderer:
def render(self, car, screen):
screen.display(car.sprite)

I don't know why they put that example, that's not ECS and it's confusing. Keep reading and you'll find the definition of Entity, Component and System that you'll find everywhere else:

"Component provides information (data bag)

Entity In-application instance that consists of component items

System Application logic for working with Entity items and their component data"

That code doesn't follow the definitions:

- The entity (Car) has data and doesn't "have" the components, the Car doesn't even know they exists

- The components (CarMovement, CarRenderer) have no data and define the behaviour

- The system doesn't exists.

Thanks Diego. I am still digging in trying to make sense of it, and I am glad that I checked first, before I went ahead with this approach. I actually do have a method I want to use, but I figure it has already been discovered before, so that is how I came across ECS. A new term I have learned from here, and elsewhere is "Data Driven Design."

Question, how would you turn this example into an actual ECS program?

They call me the Tutorial Doctor.

This topic is closed to new replies.

Advertisement