Dasifefe

Programmer in Rust programming language, biologist and illustrator. Interested in operating system development and game development.

Multiple ECS in my personal game project

I was thinking about why I have not being able to use ECS on my game project. In my understanding of ECS, every component of a type will be stored in a single container for that component type, which is good for data contiguity. This means that all `Transform` components will be contained in one array, no matter if some `Transform` components belong to entities of type A and others belong to entities of type B.

My problem is that I like to follow these two rules, which I strongly believe to be good game design rules:

In the usual ECS, the `Transform` components would be all inside a single array `array_transform: [Transform; 1160]`, which makes difficult to follow the rules above. I would have to have extra variables to keep track of the total of `Transform` for each entity type, and I would have to keep track of where to activate each element in the array or sort the elements to keep the priority of update. Awkward solutions.

Solution: multiple ECS

Instead of shoving all components in one array for each type of component, having a contained ECS for each entity type would satisfy my self-imposed rules:

const CHARACTER_TOTAL: usize = 20;

pub struct GroupCharacter {
    array_identifier:   [u64;                CHARACTER_TOTAL],
    array_transform:    [Transform;          CHARACTER_TOTAL],
    array_model:        [Model;              CHARACTER_TOTAL],
    array_status:       [CharacterStatus;    CHARACTER_TOTAL],
}

const OBJECT_TYPE_A_TOTAL: usize = 100;

pub struct GroupObjectA {
    array_identifier:   [u64;                OBJECT_TYPE_A_TOTAL],
    array_transform:    [Transform;          OBJECT_TYPE_A_TOTAL],
    array_model:        [Model;              OBJECT_TYPE_A_TOTAL],
    array_effect:       [GroupObjectAEffect; OBJECT_TYPE_A_TOTAL],
}

The number of components that can be used for each entity type is simply the number of elements in the array, which means that there is less logical steps to keep track.

| First update characters:
|
`-> Update all Characters:transform -. <- SystemUpdateTransform
.------------------------------------´
`-> Update all Characters:model -----. <- SystemUpdateModel
.------------------------------------´
`-> Update all Characters:status ----. <- SystemUpdateCharacterStatus
.------------------------------------´
|
| Now update objects:
|
`-> Update all Objects:transform ----. <- SystemUpdateTransform
.------------------------------------´
`-> Update all Objects:model --------. <- SystemUpdateModel
.------------------------------------´
`-> Update all Objects:effect......... <- SystemUpdateObjectAEffect
fn update_group_character(&mut self) {
    self.update_transform(&mut self,
        /* Pass arrays needed to update character transform. */);
    self.update_model(&mut self,
        /* Pass arrays needed to update character model. */);
    self.update_character_status(&mut self,
        /* Pass arrays needed to update character status. */);
}

fn update_group_object(&mut self) {
    self.update_transform(&mut self,
        /* Pass arrays needed to update object transform. */);
    self.update_model(&mut self,
        /* Pass arrays needed to update object model. */);
    self.update_object_effect(&mut self,
        /* Pass arrays needed to update object effect. */);
}

pub fn update(&mut self) {
    self.update_group_character(&mut self);
    self.update_group_object(&mut self);
}

Systems can be reused, and priority of update is easy to be done running systems separately, first running for characters (transform, model, then status), then running for objects type A (transform, model, then effect).

Note

Nothing here is meant to be teaching material or advice. These are only my thoughts on how I am implementing my own game project.