A simple ECS (Entity Component System) pattern implemented in Go and using Ebiten as the renderer.
The code sample was inspired by ecsy's documentation example.
The idea is to implement ECS as a pattern rather than a set of tools or a library/package.
Components are implemented as structs.
package main
type velocity struct {
VX, VY float32
}
type position struct {
X, Y float32
}
type shape struct {
Primitive primitive
}
type renderable struct {
Flag bool
}
It is important that these structs are composed of value types (no reference or pointer types) to preserve data locality as much as possible (no pointer chasing).
Entities are implemented as structs that embed component structs. These essentially act as archetypes (a definition of an entity's shape).
type gameObject struct {
velocity
shape
position
renderable
}
Archetypes are created at design-time. As a limitation, we cannot create or manipulate archetypes at runtime. This is not an issue for small to medium sized projects, but for anything larger or with complex data interactions, you might have to resort to other ECS implementations or libraries/packages.
If name clashing is a concern, then just name the component fields instead of embedding them.
In order to keep track of the instances, we use slices.
gameObjects []gameObject
Since entities and components are purely value types, there is no need to maintain an object pool.
Systems are implemented as functions.
func movableSystem(delta float32) {
count := len(world.gameObjects)
for i := 0; i < count; i++ {
e := &world.gameObjects[i]
vel := e.velocity
pos := e.position
pos.X += vel.VX * delta
pos.Y += vel.VY * delta
if pos.X > canvasWidth+shapeHalfSize {
pos.X = -shapeHalfSize
}
if pos.X < -shapeHalfSize {
pos.X = canvasWidth + shapeHalfSize
}
if pos.Y > canvasHeight+shapeHalfSize {
pos.Y = -shapeHalfSize
}
if pos.Y < -shapeHalfSize {
pos.Y = canvasHeight + shapeHalfSize
}
e.velocity = vel
e.position = pos
}
}
You can be as simple or as fancy as you want to build up your systems, but they are just essentially functions that will be called within the game loop (or even outside the loop if you only need them to run once).