Entity Component System framework for Ebitengine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
3.0 KiB

package gohan
import (
"fmt"
"log"
"os"
"strings"
"time"
"github.com/hajimehoshi/ebiten/v2"
)
var (
gameComponents = make(map[EntityID]map[ComponentID]interface{})
gameSystems []System
gameSystemEntities [][]EntityID // Slice of entities matching each system.
gameSystemReceivesUpdate []bool
gameSystemReceivesDraw []bool
debug bool
)
func init() {
debugEnv := os.Getenv("GOHAN_DEBUG")
debugEnv = strings.TrimSpace(debugEnv)
debugEnv = strings.ToLower(debugEnv)
debug = debugEnv == "1" || debugEnv == "t" || debugEnv == "y" || debugEnv == "on" || debugEnv == "yes" || debugEnv == "true"
}
// print prints debug information (when enabled).
func print(s string) {
if !debug {
return
}
log.Println(s)
}
func attachEntitiesToSystem(system System) {
// This function is always called on a newly added system.
systemID := len(gameSystemEntities) - 1
for entity := EntityID(0); entity < nextEntityID; entity++ {
if system.Matches(entity) {
gameSystemEntities[systemID] = append(gameSystemEntities[systemID], entity)
print(fmt.Sprintf("Attached entity %d to system %d.", entity, systemID))
}
}
}
// RegisterSystem registers a system to start receiving Update and Draw calls.
func RegisterSystem(system System) {
gameSystems = append(gameSystems, system)
gameSystemReceivesUpdate = append(gameSystemReceivesUpdate, true)
gameSystemReceivesDraw = append(gameSystemReceivesDraw, true)
gameSystemEntities = append(gameSystemEntities, nil)
attachEntitiesToSystem(system)
}
func updateSystem(i int) (int, error) {
updated := 0
for _, entity := range gameSystemEntities[i] {
err := gameSystems[i].Update(entity)
if err != nil {
if err == ErrSystemWithoutUpdate {
// Unregister system from Update events.
gameSystemReceivesUpdate[i] = false
return 0, nil
}
return 0, fmt.Errorf("failed to update system %d for entity %d: %s", i, entity, err)
}
updated++
}
return updated, nil
}
// Update updates the game state.
func Update() error {
var t time.Time
if debug {
t = time.Now()
}
var systems int
for i, registered := range gameSystemReceivesUpdate {
if !registered {
continue
}
updated, err := updateSystem(i)
if err != nil {
return err
}
print(fmt.Sprintf("System %d: updated %d entities.", i, updated))
systems++
}
if debug {
print(fmt.Sprintf("Finished updating %d systems in %.2fms.", systems, float64(time.Since(t).Microseconds())/1000))
}
return nil
}
// Draw draws the game on to the screen.
func Draw(screen *ebiten.Image) error {
DRAWSYSTEMS:
for i, registered := range gameSystemReceivesDraw {
if !registered {
continue
}
for _, entity := range gameSystemEntities[i] {
err := gameSystems[i].Draw(entity, screen)
if err != nil {
if err == ErrSystemWithoutDraw {
// Unregister system from Draw events.
gameSystemReceivesDraw[i] = false
continue DRAWSYSTEMS
}
return fmt.Errorf("failed to draw system %d for entity %d: %s", i, entity, err)
}
}
}
return nil
}