ECS Game Architecture Implementation

Source: Internet
Author: User

Reprinted from: http://blog.csdn.net/i_dovelemon/article/details/27230719
Implementing components-entities-systems-Blog channel

This article is carried out on the basis of my previous article, Understanding Components-entity-system. If you haven't read this article, it is recommended that you take a look, so you will not be so unfamiliar with what is to be achieved here.

First to summarize, the previous article said something:

    • Component represents a portion of the data that a game object can have
    • An entity is used to represent a game object, which is an aggregation of multiple components
    • The system provides the operations that are performed on these components

This article will describe how to implement an ECS system, and will solve some of the existing problems. All the instance code, I use C language to write.

Component

As I said in the previous article, the component is actually a C struct, with just plain, simple data, so I'll use the struct to implement the component. The following component, from the name, will be able to understand exactly what its role is. In the following I will implement three kinds of components:

    1. Displacement (x, y)
    2. Velocity (x, y)
    3. Appearance (name)

The following code demonstrates how to define a displacement component. It's just a simple structure with two components:

    1. typedef struct
    2. {
    3. float x;
    4. float y;
    5. } displacement;
typedef struct{        float x;        float y;} Displacement;

The velocity component is also defined in the same way, and the display contains only a single string member.

In addition to the specific component types defined above, we also need a component identifier that is used to label the component. Each component and system will have a component identifier, and how to use it will be explained in detail below.

    1. typedef ENUM
    2. {
    3. Component_none = 0,
    4. component_displacement = 1 << 0,
    5. component_velocity = 1 << 1,
    6. Component_appearance = 1 << 2
    7. } Component;
typedef enum{      component_none = 0,      component_displacement = 1 << 0,      component_velocity = 1 << 1,      component_appearance = 1 << 2} Component;

Defining a component identifier is a simple matter. In the context of an entity, we use the component identifiers to indicate which components the entity owns. If this entity has displacement and appearance components, then the component identifier for this entity will be component_displacement | Component_appearance.

Entity

The entity itself is not explicitly defined as a specific data type. We do not use an object-oriented approach to define a class for an entity, and then let it have a series of member properties. Therefore, we will add the component to memory and create an array of structures. This improves buffering efficiency and can help with iterations. So, to achieve this, we use the subscript of these array of structures to represent the entity. This subscript represents a component of the entity.

I call this structure an array of world. This structure not only preserves all the components, but also preserves the component identifiers for each entity.

    1. typedef struct
    2. {
    3. int Mask[entity_count];
    4. Displacement Displacement[entity_count];
    5. Velocity Velocity[entity_count];
    6. Appearance Appearance[entity_count];
    7. } World;
typedef struct{int MASK[ENTITY_COUNT];D isplacement Displacement[entity_count]; Velocity Velocity[entity_count]; Appearance Appearance[entity_count];} World;

Entity_count is defined in my test program as 100, but in a real game, this value should be much larger. In this implementation, the maximum value is limited to 100. In fact, I prefer to implement this structure array in the stack rather than in the heap, but given that the reader might use C + + to implement the world, it can also be saved using vectors.

In addition to the above structure, I have defined functions to create and destroy these entities.

  1. unsigned int createentity (World *world)
  2. {
  3. unsigned int entity;
  4. For (entity = 0; entity < Entity_count; ++entity)
  5. {
  6. if (world->mask[entity] = = Component_none)
  7. {
  8. return (entity);
  9. }
  10. }
  11. printf ("error!  No more Entities left!\n ");
  12. return (Entity_count);
  13. }
  14. void Destroyentity (World *world, unsigned int entity)
  15. {
  16. World->mask[entity] = Component_none;
  17. }
unsigned int createentity (world *world) {unsigned int entity;for (entity = 0; entity < Entity_count; ++entity) {if (world- >mask[entity] = = Component_none) {return (entity);}} printf ("error!  No more Entities left!\n "); return (Entity_count);} void Destroyentity (World *world, unsigned int entity) {world->mask[entity] = Component_none;}

Instead of creating an entity, the Create method returns the first empty entity subscript in world. The second method simply sets the component representation of the entity to Component_none. Setting an entity to an empty component is a straightforward representation because it is empty, which means that no system will operate on that entity.

I've also written some code to create a complete entity, such as the following code will create a tree that has only displacement and appearance.

  1. unsigned int createtree (World *world, float x, float y)
  2. {
  3. unsigned int entity = createentity (world);
  4. World->mask[entity] = Component_displacement | Component_appearance;
  5. world->displacement[entity].x = x;
  6. World->displacement[entity].y = y;
  7. World->appearance[entity].name = "Tree";
  8. return (entity);
  9. }
unsigned int createtree (world *world, float x, float y) {unsigned int entity = createentity (world); world->mask[entity] = component_displacement | component_appearance;world->displacement[entity].x = X;WORLD->DISPLACEMENT[ENTITY].Y = y;world-> Appearance[entity].name = "Tree"; return (entity);}

In a real game engine, your entity may need additional data to create, but this is no longer in my scope. Still, readers can see how much flexibility such a system would be.

System

In this implementation, the system is the most complex part. Each system is a function method that operates on a component. This is the second time that a component identifier is used, and by component identifiers, we define what components the system will operate on.

  1. #define MOVEMENT_MASK (Component_displacement | component_velocity)
  2. void Movementfunction (World *world)
  3. {
  4. unsigned int entity;
  5. Displacement *d;
  6. Velocity *v;
  7. For (entity = 0; entity < Entity_count; ++entity)
  8. {
  9. if ((World->mask[entity] & movement_mask) = = Movement_mask)
  10. {
  11. D = & (World->displacement[entity]);
  12. v = & (World->velocity[entity]);
  13. V->y-= 0.98f;
  14. D->x + = v->x;
  15. D->y + = v->y;
  16. }
  17. }
  18. }
#define MOVEMENT_MASK (Component_displacement | component_velocity) void Movementfunction (World *world) {unsigned int entity;displacement *d; Velocity *v;for (entity = 0; entity < Entity_count; ++entity) {if (World->mask[entity] & movement_mask) = = Moveme Nt_mask) {d = & (World->displacement[entity]), V = & (World->velocity[entity]); v->y-= 0.98f;d->x + = V->x;d->y + = V->y;}}}

This shows the power of the component designator. With component identifiers, we are able to determine in a function whether this entity has such properties and is fast. If you define each entity as a struct, it can be very time-consuming to determine whether it has these components.

This system automatically adds gravity and then operates on displacement and velocity. If all entities are initialized correctly, then each entity that does so will have an effective displacement and velocity component.

One drawback to this component identifier is that such a combination is limited. In our implementation, it can be at most 32 bits, meaning that it can only have 32 component types at most. C + + provides a class called std::bitset<n>, which can have n-bit types, and I'm sure that if you're using a different programming language, that type is available. In C, you can use an array to extend it, like this:

    1. (Entitymask[0] & systemmask[0]) = = Systemmask[0] && (entitymask[1] & systemmask[1]) = = Systemmask[1]
(Entitymask[0] & systemmask[0]) = = Systemmask[0] && (entitymask[1] & systemmask[1]) = = Systemmask[1]//&A mp;& ...

Such a system can work well in some of my programs, and such a system can be easily extended. It also makes it easy to work in a main loop and can read files from outside to create entity objects as long as you add a small amount of code.

This section will describe some of the issues that may arise in the game mechanics and also describe some of the advanced features of the system.

Upgrade and collision filtering

This question is in the last article, Netizen Krohm put forward to come. He wanted to know how to achieve the special behavior of the game in such a system. He suggested how to avoid collisions with certain types of objects when upgrading.

To solve this problem, we use something called a dynamic component. Let's create a component called Ghostbehavior, which has a list of qualifiers that we can use to determine which entities are able to move objects through the past. For example, a list of component identifiers, or a list of material subscripts. Any component can be removed at any time, anywhere. When the player picks up an upgrade package, the Ghostbehavior component will be added to the list of player entities. We can also create a built-in timer for this component and automatically remove itself from the list once the time is up.

In order not to make some collisions, we can use a classic collision response from the physics engine. In most physics engines, the first step is to detect the collision first, then create contact, and then add a contact force to an object. We assume that these jobs are implemented in a single system, but there is one component that can track the collision contact of each entity, called collidable.

We create a new system while processing Ghostbehavior and collidable. Between the two steps described above, we remove the contact between the entities so that they do not generate force, and there is no collision, so that the object passes through. Such an effect would result in an invalid collision. The same system can also be used to remove Ghostbehavior.

The same strategy can be used to deal with situations where we want to do something specific when a collision occurs. For each specific behavior, we can create a system, or the same system can handle multiple specific actions at the same time. In any case, the system must first determine whether two objects have collided before they can perform specific actions.

Destroy All Monsters.

Another problem is how to kill all the monsters in seconds with a single command.

The key to solving this problem is to implement a system that will take place outside the main loop. Any entity, if it is a monster, then it should have an identical component identifier. For example, an entity that has both AI and blood volume is a monster, which can be judged simply by using component identifiers.

Remember what we said above, each system is actually a function that operates on a component identifier. We define the second kill skill as a system. This system will be implemented with a function. In this function, the core operation is to call the Destroymonster function, but it may also create a particle effect, or play a piece of music. The component identifier for this system may be such a component_health component_ai.

In the previous article, I told you that each entity can have one or more input components, which will include a Boolean value, or a real value, to represent different inputs. We create a magicinputcomponet component that has only one bool value, and once the component is added to the entity, each entity will process the component to eliminate all monsters.

Every second kill skill has a unique ID that will be used to find the lookup table. Once the ID is found in the lookup table, call this ID corresponding function, let this function, to run this system to destroy all monsters.

Remember, the implementation here is just a very simple method. It only works for the test program we have here, and for a complete game, it doesn't have the ability to drive it. Then, I hope that, through this example, you have understood the main principles of designing an ECS system, and can use your own proficient language to implement it independently.

ECS Game Architecture Implementation

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.