/ebiten-ecs

⚙ A simple ECS (Entity Component System) pattern implemented in Go and using Ebiten as the renderer.

Primary LanguageGoMIT LicenseMIT

ebiten-ecs

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.

screenshot-ecs

Implementation

The idea is to implement ECS as a pattern rather than a set of tools or a library/package.

Components

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

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

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).