Understanding Entity Component System?

Started by
15 comments, last by Uttam Kushwah 5 years, 1 month ago

doubt.jpg.3607c6f6ccb3633e9cab2d1e9e110d58.jpg

This article is on wikipedia says that we can change the behavior of the game entity by adding or removing the component at runtime how it goes 

i don't know.

 

What i am doing is, I have a gameObject class which is having four components 

 class gameObject
 {

     public:
      #define MAX_COMPONENT 4
      unsigned int components[MAX_COMPONENT];

      gameObject()
       {
        for(int i=0;i<MAX_COMPONENT;i++)
         {
           components=0;
         }
            //parent_id=-1;
      }

};

i don't use inheritance, Whenever i make the new game entity i add an gameObject in the gameObjectManger class using the game entities constructor 

with all of its components filled at the time of game entity creation like index for rigid body , mesh and etc.

Then i use these gameObjects at individual systems to run the game like below 

// For renderer 

for(unsigned int i=0;i<manager->gameObjects.size();i++)
     {
        unsigned int meshIndex = manager->gameObjects.components[MY_MESH]; //mesh data
        mat4 trans=(*transforms)[bodyIndex];// The transformation matrix extracted and spitted out by the the Physics engine 

        mesh_Entries[meshIndex].init();

        GLuint lMM  = glGetUniformLocation(programHandle,"Model");
        glUniformMatrix4fv(lMM, 1, GL_FALSE,&trans[0][0]);

        mesh_Entries[meshIndex].bindTexture();
        glBindBuffer(GL_ARRAY_BUFFER, mesh_Entries[meshIndex].getVertexBuffer());
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh_Entries[meshIndex].getIndexBuffer());

        pickColor=vec4(1.0f);
        pickScale=mat4(1.0f);
        lMM  = glGetUniformLocation(programHandle,"pick");
        glUniform4fv(lMM,1,&pickColor[0]);
        lMM  = glGetUniformLocation(programHandle,"pickScale");
        glUniformMatrix4fv(lMM,1,GL_FALSE,&pickScale[0][0]);
        // This is very basic rendering since all object's indices are treated as
        // indexed based. Stored in element buffer with indices and vbo with vertices
        // for each single gameObject having their own VAO and IO. Optimization need to be done later on.
        glDrawElements(
                        GL_TRIANGLES,                                // mode
                        mesh_Entries[meshIndex].getIndicesSize(),    // count
                        GL_UNSIGNED_SHORT,                           // type
                        (void*)0                                     // element array buffer offset
                      );
     }

but i am not getting it how to add a new component like LogicalData to the gameObject not about how to point from gameObject but how to build one 

is this should be my approach 

struct LogicalData 

{

 float running_speed;

vec3 seek_position;

vec3 attack_point;

};

Character : public CharacterController

{

 private:

gameObject* me;

public:

// methods to manipulate gameObject content using the component id for each component in their container

// i.e is to update component[LOGICAL_DATA] in the gameLogicContainer 

};

and then a global container which hold this logical data and every entity has a id to it using gameobjects  

or i should not be doing this at all. i can just put all logical data  into the any game entity like this and push the physics and rendering data back to gameobject

Character : public CharacterController

{

 private:

   float running_speed;

   vec3 seek_position;

   vec3 attack_point;

public:

// methods to manipulate above

};

Any comments will be greatly appreciated.

 

 

Advertisement

The easiest way to experience it is to go look over the Unity or Unreal documentation, and to play with either one of those tools a little bit.

In general terms, each component may have update functions, may have physics response functions, and may have other properties that systems know about.  When the game object is updated all of the components are also updated.  When the physics events happen the physics response functions are called.

The game object entity is ultimately a container for zero or more components.  Each component is updated, or collided, or probed, or otherwise processed.  Each component does it's own tasks a specified in their own code. 

An auto-aiming component update function could twist or turn the object toward the target.  An auto-firing component's update function could detect if the aim is close enough and then fire.  A sentry component's update function could probe for nearby targets.  Add all three components and you've got a turret.  Another component could attempt to route toward a target, add that to the other three and suddenly you've got a mobile enemy that fires at a target. 

 

Another ECS design has components as pure data and has "Systems" that do the updates in large batches on those components.  And Entities are just an ID and are only used to reference (poll/search) Systems.

So, say you have an entity with an ID of 101 and decide that it now needs a TrackTargetComponent.  Using the above ECS I described you would likely call TrackTargetSystem->CreateComponent(entityID, targetID, ...); where entityID is 101 and targetID is the entity ID of the target and if you have other information you want to pass on.  This, of course, is a completely contrived example.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

As @CrazyCdn already mentioned, the primeval ECS approach was to keep the logic apart from the classes and instead use Systems to process a single or bunch of Entities and this is what they mean by "changing behavior". Systems scan for Entities with certain Component pattern on them (that may be specified by a flag in AAA games). Those components match into the processing of that System, for example you want to perform a UI action and the UIActorSystem seeks for Entities that have the Position, UIElement and UIAction Component attached to them. Regardless of what Components are attached to certain Entity additionally, the System always accepts Entities that match the pattern

you might want to check out the source of https://github.com/miguelmartin75/anax
it's a minimal ECS in c++, you could find some inspiration there.


 
i don't know it is the right way
for an example 

i have different sets of operation can be performed on each single character(id) like seeking to the position,attacking to the enemy by pursuing him etc.

for instance let say 

character have a have static pointer to the StrategicSystem and it will add its related physicalBody and id , seek_point

as we do in pure ECS 

class StrategyManger
 {
     struct SeekerObject
      {
        btVector3 seek_point;
        btRigidBody* body;
      };
   /*
    and many more
   */  

    std::map<unsigned int,SeekerObject> seekers;
    static physicsSystem*   game_world_physics;
    static objects::Camera* camera;
   public:
     StrategyManger(physicsSystem* game_physics,objects::Camera* cam)
      {
        game_world_physics = game_physics;
        camera = cam;
      }
     void seekUpdate();// will iterate over each seeker object and change them accordingly.
     void addSeekerObject(const unsigned int id,btRigidBody* seeker,btVector3 seek_point);// called by the character which needs to be updated 
      {
        SeekerObject t;
        t.seek_point=seek_point;
        t.body= body;
        seekers.insert(pairs<id,t>);
      }
};

all character needs to do is 

class character : EventHandler
 {
    private:
        btRigidBody* body;
        static StrategManager* strategic_manager;
        unsigned int id;
    public:
    void handler(const Event* e);
    void setSeekPoint(const Event* e);
 };

void character::handler(const Event* e)
 {
        switch(e->type)
         {
           case MOVE_COMMAND:
               {
                  this->setSeekPoint(e);
               }break;
         }
 }

void character::setSeekPoint(const Event* e)
 {
  pickingSystem* p =(pickingSystem*)e->args2;
  btVector3* seek_position =(btVector3*)e->args1;//make a cop
  btVector3 seek_point=*seek_position;
  delete seek_position;
  for(unsigned int i =0;i<p->num_picked_objects;i++)
  if(p->picked_objects==my_id)
   {
     strategy_manager->addSeekerObject(my_id,body,seek_point);
   }
 }

So above the strategyManager is a system id is entity and seekPoint , body are component for the id.

am going right.

@Uttam Kushwah please wrap your code in code blocks, it's the <> and is very easy to do and makes reading your code a whole lot easier for us.  You can even edit your posts and add in the blocks if I recall correctly.

And sadly for you, there is no right way of doing ECS.  Everyone and their dog/cat has an opinion on the matter.  Look at different pieces of existing example code.  Tutorials you can find online.  See which one best fits or suits your needs/preferences.

But in your above example, I would say you have issues.  Why does your seeker need access to a rigid body?  That should be handled by the physics system and it's component for this entity.  I honestly think "StrategyManger" is a poor choice in naming but hope this is merely a made up example.  But I would have StrategyManger have access to the PositionSystem and if really needed the PhysicsSystem to query information based on the entityID it's currently processing.

I honestly feel, after a few years now using of ECS that in 90% of cases they're way over engineered solutions that typically have much simpler and cleaner traditional solutions.  Do you really need to dynamically add features to your game objects?  Will a table suddenly grow a new leg?  Will a cat really need a second tail?

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

Sorry for the inconvenience i am newbie here.

Actually i am really going to use that code which is shown above.

should i decouple the rigid body from the logic. I am using it in the seek behavior to move object directly  because i need it here for getting  its current velocity and act accordingly. But if i just remove it from here then i have to make a new system and component as you mentioned up there for positionSystem and associate each entity that component(which needed it) and in the positionSystem it will update velocity,position for all rigidBodies in single go.

Current Approach:

1909335588_approachone.thumb.jpg.78eaaa9ae06a8985012a7a611115d570.jpg

Second approach:Untitled.thumb.jpg.fcd72893ef7a4583722a2edf59c356ff.jpg

That is pretty all i understand from your explanation.

12 hours ago, CrazyCdn said:

Do you really need to dynamically add features to your game objects?  Will a table suddenly grow a new leg?  Will a cat really need a second tail?

No, i am doing it for optimization purpose , in my current approach i can only able to render 25 character at 40fps but i want at least 50 character. Yes i know this is too inefficient even for a small rts having only 100 character but i am using compound shape instead of capsule collider which make it pretty hard to survive in such tightly coupled system.  

if anyone can give me some simple system architecture diagram like component system which unity uses but don't show how? that would be really helpful.(sorry if i am asking for something really specific)

Note: i didn't mean unity uses some simple component architecture i mean i want simplified understanding of that thing.

but as everyone can see i really am not getting how to organize  the physical data and position data and all other stuff in nutshell so i am stuck and can't do any other work on this project just yet And if do something i just have to redo the thing.?(And giving up is not my thing so i am trying every single direction in which i can go)

And at the very specific end i really want some thing which can run the simulation no matter what it takes, i read a lot and found that ECS can do this so i jumped into it. 

Thanks everyone , i really didn't mean to west anyone's time.?

Unfortunately you are mixing different update steps and so systems. In a strict ECS case you won't have a Rigidbody Component, you just have

  • Velocity Component
  • Transform Component
  • Seeker Component??

And do this in two update steps depending which system is updating first.

Your Startegy System takes components Velocity, Transform and Seeker (because I think you do a lookup in the StrategySystem based on an Entity position right?) and does it's stragety calculation for each Entity that contains exactly those combination of components (inclusive not exclusive ;) ). At the end, it sets Velocity based on its calculations.

Your Physics System is unaffected by that. It takes Velocity and Transform and does the position updates.

Both systems may work at the same list of components in memory and this may also be asynchronously but they keep track of a subset of those in their own list of indices to identify valid Entities. Writing operations should lock an Entity's component while reading operations could run along with other reading operations

And can you for sure say nothing else will ever need access to an entities velocity?  Or it's position?  If it might, does it make sense to go through your Strategy System?  Or a Position & Velocity system?

But to be honest with you, I don't see ECS doubling your speed.  Maybe 5-10% upgrade with good threading.

What are your system specs?

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

This topic is closed to new replies.

Advertisement