Is my understanding of ECS correct?

Started by
28 comments, last by Acosix 4 years, 10 months ago

I'm currently playing around with ECS but was wondering if my understanding of ECS was correct. Based on what I've understood so far:

  • Entities: Are structs and only hold an id that identifies it.
  • Components: Also are structs, but holds properties that describe said component. Example: position (vec3), hp (float), etc. ***Also stores a reference to the entity it represents?
  • Systems: Functions that act upon components. If I need to update my Position Component, a system would do that.

My questions so far:

  1. Should components also store a reference or id to the entity it represents? I'm asking this after reading an article where the author stored an array of components within the component struct. I was under the understanding that entities and components should be treated independently.
  2. Should I keep an array for each component and loop through them in my main loop. I'm very confused about this part. In oop I would just put all gameobjects in an array and loop through them.

I also wrote some pseudo code showing what I think the whole thing should look like. It's very Rust...but the code is straight forward. Thanks for reading btw:


MainLoop

    UpdateTime()

    FixedUpdate
        UpdateSystems
            UpdatePositions() // System: Loops through all position components and updates them
    EndFixedUpdate

    RenderUpdate
        UpdateSystems
            UpdateRenderables() // System: Loops through all Render Components and draws
            UpdatePostprocessingSystems()
    EndRenderUpdate   

Loop MainLoop


// Components
struct Entity {
    id: u16, // unsigned integer 
}

struct TransformComponent {
    entityID:   u16,  // Reference to entity
    position:   Vector3,
}

struct RenderComponent {
    entityID: u16,  // Reference to entity
    meshID: GLuint, // Reference to mesh
}

// Should I keep arrays of each kind of component seperatly? 

// Systems
fn update_tranforms () {
    // Loop array of TransformComponents
    // Apply physics to position property 
    // Input logic: aka if (Input.rightArrow) blah.blah.blah
}

fn update_render_components () {
    // Loop array of render components 
    // Draw using meshID in RenderComponent
}

 

Advertisement

I think you've missed something, considering you have no (demonstrated) way to query whether a given component type is attached to an entity, no way to get a given component attached to an entity, and no way to add or remove components from entities.

If you can't get to the components from the entities, what is the point of having entities? Without that, you just have numerous components loose in separate semantic spaces, which have no way to communicate with eachother (e.g. how does each TransformComponent inform its entity's RenderComponent where the object is in place?).

RIP GameDev.net: launched 2 unusably-broken forum engines in as many years, and now has ceased operating as a forum at all, happy to remain naught but an advertising platform with an attached social media presense, headed by a staff who by their own admission have no idea what their userbase wants or expects.Here's to the good times; shame they exist in the past.

That's mostly right but the position is usually part of the entity.  Not always, but usually.

I am an indie game developer who enjoys pixel art games.

An Entity Component System is something that is open to interpretation and everyone implements it in a different way. What I like to use looks like this:

  • Entity: just a number
  • Component: a simple struct holding data. It doesn't know about entity
  • ComponentManager: Holds an array of entities and an array of components of a type. Also holds a lookup table (hash map) that maps entity to arrayindex (arrayindex can index a component and the corresponding entity). Using this, you can find out if an entity has a component of the specific type or not by performing a map lookup. You can also iterate all components or all entities as linear arrays with very fast data access pattern. It also has some smart features, like removing elements by swapping in the last element, ordering elements, etc.
  • System: simple function, that operates on arrays of components/entities (by using component manager for example).

With this, basically I only needed to implement the ComponentManager and I have an entity component system. My implementation is simple but I used it everywhere in my engine and confident that it can be used as a fully featured entity component system: https://github.com/turanszkij/WickedEngine/blob/master/WickedEngine/wiECS.h

 

 

Relational database view of ECS --
a component type is a table. A component instance is a row in a table. Columns in tables are the component data members. Every component table has a single primary key / index, which is the Entity ID that owns that component instance.


Table (Hitpoint Component)
| Entity Id | Hitpoints |
| 1         | 42        |
| 2         | 42        |

OOP Inheritance view of ECS -- (n.b. don't ever do this :( )


class IComponent {};
class Entity { vector<IComponent> components; }

class HitpointComponent : public IComponent { int hp; }

Entity e;
e.components.push_back(new HitpointComponent());

OOP composition view of ECS -- 
Components are array elements. They can contain IDs / array indices to reference each other / or be group into "entities".


template<class T>
struct Table
{
  array<T> components;
};

struct HitpointComponent { int entity; int hp };
Table<HitpointComponent> hpComponents;

 

On 6/27/2019 at 5:11 AM, Hashbrown said:
  • Should components also store a reference or id to the entity it represents? I'm asking this after reading an article where the author stored an array of components within the component struct. I was under the understanding that entities and components should be treated independently.
  • Should I keep an array for each component and loop through them in my main loop. I'm very confused about this part. In oop I would just put all gameobjects in an array and loop through them.

It's cleaner if you can keep relationships just one way. Either entities point to components, or components point to entities.
e.g. If you do "for each entity, update each child component", then you can pass the current entity to the child component when calling it's update method, so there's no need to store it.
Also, in many ECS frameworks, there's no need for an "entity" to actually exist at all, beyond just being an ID (see the relational viewpoint, above).

As for point #2. Looping through an abstract list of gameobjects is a common pattern that's easy to write in OOP languages, but it's a complete violation of most of OO's actual rules on how to design code... "Prefer composition over inheritance", "write algorithms against base types, not derived types", etc... It's also possible in an OOP solution to have one loop per component / one loop per system.

Let me try to explain:

Try to use classes instead of structs, and encapsulate data.

So all data should be private, and all functions should be public.

A class can be used as an entity.

An object is an important part of the Object Oriented Programming.

You make an object or object common type by defining a class.

While OOP doesn't magically solve all problems, it is one of the most important parts of C++.


#include <string>

class LivingBeing		//or entity/creature...whatever generalizes your ECS
{
	private:
		bool open_source_appliable{true};
		std::string type;
	public:
		LivingBeing(std::string type, bool open_source=true)
  		{
  			this->type=type;
  			open_source_appliable=open_source;
  		}
};

From this class you then derive your living beings of different types for different commands(behavior), components(biological tree position) and systems(like organic parts of the body...).

You could start with boring objects, use composition instead of inheritance, and then introduce gradually forms of indexing, management and memory layout optimization as the need arises.

In other words, don't do unusual things like putting entity IDs inside components if it isn't clearly worthwhile, just because you think you should, and don't think about being "right" before you actually need and implement an advanced architecture: at that point, either your game has a good performance and a good organization, in which case what you are doing is right, or you know enough to learn from what others are doing..
 

Omae Wa Mou Shindeiru

4 hours ago, Acosix said:

Try to use classes instead of structs, and encapsulate data.

This could maybe use some clarification. I haven't seen a language specified in the thread, although there are some examples (including yours) that appear to be C++. When you say to use classes instead of structs, what language do you have in mind, and what would be the reason for preferring classes? (I'm not saying you're wrong, but in languages that include both classes and structs, the differences between the two depend on the language, so some clarification might be helpful here.)

Quote

So all data should be private, and all functions should be public.

I'm not sure about that as an absolute. Even in traditional OOP it's common for there to be non-public functions (and even non-private data in some cases).

I see some possible minor issues in your example code, but I don't want to nitpick. What I think may be relevant though is that I think your example may demonstrate something other than the type of data-driven or database-oriented system under discussion.

5 hours ago, Zakwayda said:

This could maybe use some clarification. I haven't seen a language specified in the thread, although there are some examples (including yours) that appear to be C++. When you say to use classes instead of structs, what language do you have in mind, and what would be the reason for preferring classes? (I'm not saying you're wrong, but in languages that include both classes and structs, the differences between the two depend on the language, so some clarification might be helpful here.)

I'm not sure about that as an absolute. Even in traditional OOP it's common for there to be non-public functions (and even non-private data in some cases).

I see some possible minor issues in your example code, but I don't want to nitpick. What I think may be relevant though is that I think your example may demonstrate something other than the type of data-driven or database-oriented system under discussion.

I use C++.

Well a class is made  for making an object. A struct can not be an object.

Well whole idea is to learn the standardized way of making objects, the way it was intended to be used when C++ was designed?

Thanks everybody for all your great answers. I feel a lot more familiarized with the concept now! I'll share some links I found as well, just in case somebody is looking for info and want to add more to the great answers:

Thanks again!

This topic is closed to new replies.

Advertisement