Posted: 12th Aug 2023 9:58
The Entity Component System (ECS)

I set myself the task to develop such a system in AGK-Basic (Tier 1). In an object oriented environment like C# or C++ this is no problem.
The possibility to create classes with inheritance and overrides are very helpful. But AppGameKit does not offer this comfort.

ECS - what is that?
Some of you may have already heard or read something about it. I'll try to define it in my own words.

Entity
An entity, roughly speaking, can be anything in a game environment. Every object that is displayed. But also what is not visible. For example also any triggers. It does not have any functions of its own. It is rather a collection of data (components) that describe a function or behavior. So the entity is a description about the appearance, behavior and function of an object.

Component
The component contains only low-level data. No functionality or logic. Only data. Typical components would be for example position, acceleration and appearance. The position would have x,y and z values. Acceleration would have corresponding acceleration values and Appearance would have, for example, sprite data. The components are then used to describe the entities.

System
The systems execute the logic, behavior and functions of the entities. They are the control centers of all entities. Usually a system is developed for each behavior. For example For motion, the motion system would go through components that have an acceleration and position and calculate and write back the new position data. While the representation system would then go through all the sprite components and represent them. Thus, a system would have to be developed for each behavior.

Here would be a link for a better description.
I think the concept can be implemented in several ways.

As an example of an implementation of ECS, I have developed a Space Invaders clone.
The player, for example, is an entity. This entity has certain properties. It must be represented. It has to move. It is controllable. So we have three systems that have to be implemented.
- Display system
- Motion system
- Control system
In addition, there is an entity system. This system is responsible for the creation and deletion of entire entities. It also generates unique ID's that are used to identify the entities. With it then also the individual components are connected with one another. Thus can be determined which component to which Entity belongs.
This system is not a system in the sense of ECS. A better designation, in order to avoid confusion, would be Entity server. Because it serves us with new entities or deletes them.

So how do all these parts work together?
The entity server creates the entities, these call the system functions to create the components which are important for the respective system. The individual components are stored in arrays. In order for them to be found again, they need the entity name, or number in my case. The system then goes through the components and determines the behavior according to their components.

Here is an example of a component from my project.
+ Code Snippet
// speed component for setting a speed for an entity
type TComponent_Velocity
  entity as integer
  speed as float
endtype


For example, the player entity is created like this.
+ Code Snippet
Function SysEntity_CreatePlayer(posx as float, posy as float)
  typCmp As TComponent_EntityType

  spr_id as integer
  ent_id as integer

  // create entity id and sprite id
  ent_id = SysEntity_GetFreeID()
  spr_id = CloneSprite(SPRID_PLAYER)

  // create entity type
  typCmp.entity = ent_id
  typCmp.typ = SYSENT_TYPE_PLAYER
  gComponents.types.insertsorted(typCmp)

  // create players property
  SysRenderer_AddProperty(ent_id,spr_id,posx,posy,3)
  SysMovement_AddProperty(ent_id,128,0,0)
  SetSpriteVisible(spr_id,0)

  // move left (left arrow key, joystick x-axis)
  SysAction_AddProperty(ent_id,CMP_ACTION_MOVELEFT,37,-1)
  SysAction_AddProperty(ent_id,CMP_ACTION_MOVELEFT,CMP_INPUT_JOYAXISX,0)

  // move right (right arrow key, joystick x-axis)
  SysAction_AddProperty(ent_id,CMP_ACTION_MOVERIGHT,39,1)
  SysAction_AddProperty(ent_id,CMP_ACTION_MOVERIGHT,CMP_INPUT_JOYAXISX,0)

  // shoot (space key, joystick button 1)
  SysAction_AddProperty(ent_id,CMP_ACTION_FIRE,32,1)
  SysAction_AddProperty(ent_id,CMP_ACTION_FIRE,CMP_INPUT_JOYBUTTON1,0)

  // add screen-bound collision
  SySCollision_AddProperty(ent_id,32,0,GetVirtualWidth()-32,GetVirtualHeight())

  // add collision group
  SysCollision_AddPropertyGroupCollision(ent_id,CMP_GROUP_PLAYER,0)

  // add players weapon
  SysWeapon_AddProperty(ent_id, CMP_WEAPONTYPE_PLAYER)

  // add players statistics
  SysHUD_AddPropertyStatistic(ent_id)
EndFunction ent_id

In the last code snippet you can already see which system I use.
A .._AddProperty finally adds all the components of the system.

For the movement, it looks like this.
+ Code Snippet
/*
  All the components necessary for the 'property movement' are created here.

PARAMETER
  ent   - the entity to whom the property should be attributed.
  vel   - sets the speed of the entity
  xdir,
  ydir  - the direction in which the entity is moved. It is a normalised vector.
*/
Function SysMovement_AddProperty(ent as integer, vel as float, xdir as float, ydir as float)
  dirCmp as TComponent_Direction
  velCmp as TComponent_Velocity

  // setup move component
  dirCmp.entity = ent
  dirCmp.dx = xdir
  dirCmp.dy = ydir
  gComponents.direction.insertsorted(dirCmp)

  // setup velocity component
  velCmp.entity = ent
  velCmp.speed = vel
  gComponents.velocity.insertsorted(velCmp)
EndFunction


For the update of this system it goes through the array velocity.
+ Code Snippet
/*
  Calculates the new position of all entities that have a position comoponent,
  velocity component and direction component.

PARAMETER
  ft    - The movement is time-based, so the time between the last frame and
        this frame is passed here. (frame time)
*/
Function SysMovement_Update(ft as float)
  vel as TComponent_Velocity
  dir as TComponent_Direction
  pos as TComponent_Position
  i as integer
  index as integer
  typ As Integer

  For i = 0 To gComponents.velocity.length
    vel = gComponents.velocity[i]

    // if direction component not available, continue loop, otherwise get component
    index = GetComponentDirection(vel.entity)
    If index < 0 then continue else dir = gComponents.direction[index]

    typ = SysEntity_GetEntityType(vel.entity)
    // if current entity a player entity? then ...
    If typ = SYSENT_TYPE_PLAYER
      // ... reset movement, movement must be set for each new frame
      gComponents.direction[index].dx = 0
      gComponents.direction[index].dy = 0
    //ElseIf typ = SYSENT_TYPE_SWARM
      //Inc vel.speed,(55-gComponents.swarm[0].rel_pos.length)*3
    EndIf


    // if direction component not available, continue loop
    // we then need the index of the position component to write back the values.
    index = GetComponentPosition(vel.entity)
    if index < 0 then continue

    // calculate the new position
    Inc gComponents.position[index].px, dir.dx*(vel.speed*ft)
    Inc gComponents.position[index].py, dir.dy*(vel.speed*ft)
  Next
EndFunction


This is of course only a rough outline. A finished project is available for download. First the DOCUMENTED source code without media. The second is the compiled game. Packed with Vishnu Studio. Great Program for media protection .
I have omitted the sound for now, because I wanted to try it out first how everything could work under BASIC.

I really enjoyed this experiment. Because thinking in the ECS sense is different than the typical approach I usually followed in game development. That's how I feel about it.

What about you?
Are you already familiar with ECS?
Have you already used it?
What does your implementation look like?
Posted: 14th Aug 2023 22:51
Nice one, interesting experiment . Does it offer any speed improvements over 'standard' coding, eg, the massive amount of units you can do with ECS in Unity?
Posted: 15th Aug 2023 13:19
I think under AppGameKit you will not have any speed advantage.
Agk has to constantly search the component lists for the right components that belong to an entity.

I think here the advantage is the easy extensibility.
In my SpaceInvaders example I don't have a UFO flying along the top of the screen.
Such an entity should be easy to add with the existing components and systems.
Posted: 16th Aug 2023 1:15
Interesting experiment, and interesting approach ..

I have been using an ECS (Entt) in my raylib projects with such a framework its kind of a mandatory requirement else the code is messy and it made implementing things like a animation manager and box2d fairly simple tasks, I had not ever thought of implementing Entt into AppGameKit as it already has some basic form of entity management out the box (as in the ID system) but still needs classes to manage things a ECS is probably a better solution

this *could* be a good system even in tier1 if used correctly it would not *exactly* speed up the code or give any beneficial speed increase over standard coding styles but could help prevent bottle necks caused by multiple management loops, for example, if all dynamic sprites and objects have a velocity and direction and these components are managed in a single loop ... all movement for the game is handled in that single loop, player, enemy's, platforms, lifts etc etc ... so in theory yes it can help speed up game code but only by virtue of its intended use case and yes it would/does make extending things much easier

its certainly something to think about, especially in tier2

Have you looked at Entt? (its not anything that cant be done with a class wrapping a std::vector it just saves reinventing the wheel and provides the operators and helper functions for managing the ECS)
Posted: 17th Aug 2023 17:19
I did not know this before. I had seen some ECS libs, but this one didn't catch my eye until now.
It seems to be very flexible and potent.
However, I must say that it is more fun to develop something like this myself.