Entities & Components

Entities and components are the primary building blocks of the ECS concept. This chapter explains their representation and manipulation in Arche.

Entities

An Entity ( ecs.Entity ) in Arche is merely an ID and contains no data itself.

The only method of an entity is ecs.Entity.IsZero . The only entity that can be directly created by the user is the zero entity, in two possible ways:

1var zero1 ecs.Entity
2fmt.Println(zero1.IsZero()) // prints true
3
4zero2 := ecs.Entity{}
5fmt.Println(zero2.IsZero()) // prints true

All other entities must be created through the ecs.World (see section Creating entities below)

Components

With each entity, an arbitrary number of Components can be associated. Components are simple, user-defined Go structs (or other go types):

 1// Position component
 2type Position struct {
 3    X float64
 4    Y float64
 5}
 6
 7// Heading component
 8type Heading struct {
 9    Angle float64
10}

Components are stored in the World and accessed through Queries or through the world itself (see World Entity Access).

Component IDs

Each component type has a unique ID, which is used to access it in the ID-based API. Component IDs can be registered as well as obtained through ecs.ComponentID .

1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4headID := ecs.ComponentID[Heading](&world)
5
6_, _ = posID, headID

When a type is used as a component the first time, it is automatically registered. Thus, it is not necessary to register all required components during initialization.

Create entities

The most basic way to create an entity is ecs.World.NewEntity :

1world := ecs.NewWorld()
2
3entity := world.NewEntity()
4_ = entity

Here, we get an entity without any components. However, NewEntity takes an arbitrary number of components IDs for the components that should be associated with the entity:

1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4headID := ecs.ComponentID[Heading](&world)
5
6_ = world.NewEntity(posID)
7_ = world.NewEntity(posID, headID)

We get an entity with Position, and another one with Position and Heading. In this case, the components are initialized with their zero value. Alternatively, entities can be created with initialized components through ecs.World.NewEntityWith :

1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4headID := ecs.ComponentID[Heading](&world)
5
6_ = world.NewEntityWith(
7	ecs.Component{ID: posID, Comp: &Position{X: 1, Y: 2}},
8	ecs.Component{ID: headID, Comp: &Heading{Angle: 180}},
9)

We get an entity with Position and Heading, initialized according to the passed pointers.

Generic API

Creating entities using the generic API requires a generic MapX, like generic.Map2 :

1world := ecs.NewWorld()
2
3builder := generic.NewMap2[Position, Heading](&world)
4
5_ = builder.New()

We get an entity with Position and Heading, initialized to their zero values.

Equivalent to ecs.World.NewEntityWith , generic MapX’s have NewWith :

1world := ecs.NewWorld()
2
3builder := generic.NewMap2[Position, Heading](&world)
4
5_ = builder.NewWith(
6	&Position{X: 1, Y: 2},
7	&Heading{Angle: 180},
8)
Note

The 2 in Map2 stands for the number of components. In the generic API, there are also FilterX and QueryX. All these types are available for X in range 0 (or 1) to 12.

Batch Creation

For faster batch creation of many entities, see chapter Batch Operations.

Add and remove components

Components are added to and removed from entities through the world, with ecs.World.Add and ecs.World.Remove . With generics, use a generic.Map2 again:

1world := ecs.NewWorld()
2
3mapper := generic.NewMap2[Position, Heading](&world)
4
5entity := world.NewEntity()
6
7mapper.Add(entity)
8mapper.Remove(entity)
1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4headID := ecs.ComponentID[Heading](&world)
5
6entity := world.NewEntity()
7
8world.Add(entity, posID, headID)
9world.Remove(entity, posID, headID)

First, we add Position and Heading to the entity, then we remove both.

Important

Note that generic types like MapX should be stored and re-used where possible, particularly over time steps.

It is also possible to assign initialized components with ecs.World.Assign / generic.Map2.Assign , similar to ecs.World.NewWith :

 1world := ecs.NewWorld()
 2
 3mapper := generic.NewMap2[Position, Heading](&world)
 4
 5entity := world.NewEntity()
 6
 7mapper.Assign(
 8	entity,
 9	&Position{X: 1, Y: 2},
10	&Heading{Angle: 180},
11)
 1world := ecs.NewWorld()
 2
 3posID := ecs.ComponentID[Position](&world)
 4headID := ecs.ComponentID[Heading](&world)
 5
 6entity := world.NewEntity()
 7
 8world.Assign(
 9	entity,
10	ecs.Component{ID: posID, Comp: &Position{X: 1, Y: 2}},
11	ecs.Component{ID: headID, Comp: &Heading{Angle: 180}},
12)

Exchange components

Sometimes one or more components should be added to an entity, and others should be removed. This can be bundled into a single exchange operation for efficiency. This is done with ecs.World.Exchange , or using a generic.Exchange :

 1world := ecs.NewWorld()
 2
 3builder := generic.NewMap1[Position](&world)
 4entity := builder.New()
 5
 6exchange := generic.NewExchange(&world).
 7	Adds(generic.T[Heading]()).    // Component(s) to add.
 8	Removes(generic.T[Position]()) // Component(s) to remove.
 9
10exchange.Exchange(entity)
 1world := ecs.NewWorld()
 2
 3posID := ecs.ComponentID[Position](&world)
 4headID := ecs.ComponentID[Heading](&world)
 5
 6entity := world.NewEntity(posID)
 7
 8world.Exchange(entity,
 9	[]ecs.ID{headID}, // Component(s) to add.
10	[]ecs.ID{posID},  // Component(s) to remove.
11)

Remove entities

Entities can be removed from the world with ecs.World.RemoveEntity :

1world := ecs.NewWorld()
2
3entity := world.NewEntity()
4world.RemoveEntity(entity)

After removal, the entity will be recycled. For that sake, each entity has a generation variable which allows to distinguish recycled entities. With ecs.World.Alive , it can be tested whether an entity is still alive:

1world := ecs.NewWorld()
2
3entity := world.NewEntity()
4fmt.Println(world.Alive(entity)) // prints true
5
6world.RemoveEntity(entity)
7fmt.Println(world.Alive(entity)) // prints false