What Is The Best Way to Implement Entity-Component System

Hi Guys,

Recently I am planing to implement a Entity-Component-System like Overwatch.

The major challenge(and difficulty) in my project is that, my engine allows user defined customized map, in which user was allowed to define customized unit. In another word, user can select which component they need for an entity type

For example

type Component interface {
	ComponentName() string
}

type HealthComponent struct {
	HP uint
}

type ManaComponent struct {
	MP uint
}

type ChiComponent struct{
        Chi uint
}
// assuming each component has implemented Component interface

The corresponding Entity definition is:

type Entity struct {
	ID         EID
	EntityName string
	Components map[string]Component
}

A user will have some entity definition in JSON format:

{
 "EntityName": "FootMan",
 "Components": {
  "HealthComponent": {
   "HP": 500
  }
 }
}
---------------------------------------------
{
 "EntityName": "Warlock",
 "Components": {
  "HealthComponent": {
   "HP": 250
  },
  "ManaComponent": {
   "MP": 100
  }
 }
}
---------------------------------------------
{
 "EntityName": "Monk",
 "Components": {
  "HealthComponent": {
   "HP": 250
  },
  "ChiComponent": {
   "Chi": 100
  }
 }
}

Please notice that ID is not included in JSON because we need to initialize it at run-time

So here comes the problem:
What is the most efficient way to build such an entity with a given JSON definition?
Currently my solution is using a registry to maintain a map between struct type and component name

var componentRegistry = make(map[string]reflect.Type)
func init() {

	componentRegistry["ChiComponent"] = reflect.TypeOf(ChiComponent{})
	componentRegistry["HealthComponent"] = reflect.TypeOf(HealthComponent{})
	componentRegistry["ManaComponent"] = reflect.TypeOf(ManaComponent{})

}

The builder code is

func ComponentBuilder(name string) Component {

	v := reflect.New(componentRegistry[name])
	fmt.Println(v)
	return v.Interface().(Component)
}

func EntityBuilder(EntityName string, RequireComponent []string) *Entity {

	var entity = new(Entity)
	entity.ID = getNextAvailableID()
	entity.EntityName = EntityName
	entity.Components = make(map[string]Component)
	for _, v := range RequireComponent {
		entity.Components[v] = ComponentBuilder(v)
	}

	return entity
}

For each system that want to access a component in this entity needs to do following:

    var d = monk_entity.Components["ChiComponent"].(*ChiComponent)
	d.Chi = 13

	var h = monk_entity.Components["HealthComponent"].(*HealthComponent)
	h.HP = 313

It works, but I am using too much reflection in this approach and I am not able to assign initial value to entity, which was store in user defined JSON file. Is there any better way to do so?

A quick attempt to remove reflection from the scenario:

package main

import "fmt"

type Entity struct {
	ID         int
	EntityName string
	Components map[string]Component
}

type Component interface {
	ComponentName() string
}

type HealthComponent struct {
	HP uint
}

func (h *HealthComponent) ComponentName() string {
	return "HealthComponent"
}

type ManaComponent struct {
	MP uint
}

func (m *ManaComponent) ComponentName() string {
	return "ManaComponent"
}

type ChiComponent struct {
	Chi uint
}

func (c *ChiComponent) ComponentName() string {
	return "ChiComponent"
}

var componentRegistry = make(map[string]Component)

func main() {

	componentRegistry["ChiComponent"] = &ChiComponent{}
	componentRegistry["HealthComponent"] = &HealthComponent{}
	componentRegistry["ManaComponent"] = &ManaComponent{}

	fmt.Printf("%v\n", componentRegistry)

	fmt.Println(componentRegistry["ChiComponent"].ComponentName())
}

Playground link

Although if each component consists of a single uint only, there would be room for simplifying the code further, like, for example, using a map[string]int.

And as a side note, names like “ComponentBuilder” or “EntityBuilder” sound very much Java-ish (factories galore!) - in Go, it is more common to call them “NewComponent” or “NewEntity”.

2 Likes

Building further on that and the global component registry, here is one way to handle deserializing JSON into the components.

https://play.golang.org/p/W56oC-yjUB

The main idea is some boilerplate around creating new components from JSON. The registry is then a registry of factories (yeah… :/) that creates Components, used by the Entity in deserialization.

// A ComponentMaker returns a new Component from the given JSON data.
type ComponentMaker interface {
	NewFromJSON([]byte) (Component, error)
}

type HealthComponentMaker struct{}

func (HealthComponentMaker) NewFromJSON(data []byte) (Component, error) {
	var c HealthComponent
	if err := json.Unmarshal(data, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

The entity pulls in the component map from JSON, leaving the component specific data as json.RawMessage. It then grabs the right type from the registry and uses it to initialize the component:

	for key, raw := range proxy.Components {
		// Get a maker from the registry
		mk, ok := componentRegistry[key]
		if !ok {
			return fmt.Errorf("unknown component type %q", key)
		}

		// Use the maker to create the component
		comp, err := mk.NewFromJSON(raw)
		if err != nil {
			return fmt.Errorf("component %q: %v", key, err)
		}

		// Keep it
		e.Components[key] = comp
	}

And use signed int for the values, or you’ll get bitten when (not if) at some point you do something like

creature.HP -= hitpoints
if creature.HP <= 0 {
   // creature is dead
}

and suddenly have undead immortal creatures forever roaming your world.

3 Likes

Thank you so much for your suggestion and the side note is very helpful. I will try that later

That was a great point! I just checked json.RawMessage, I believe this should work!