Search code examples
jsongoserializationmarshalling

What Is The Best Way to Implement Entity-Component System


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?


Solution

  • Gor one thing, you can just use functions instead of reflection:

    type componentMaker func() Component  // Define generator signature
    
    var componentRegistry = make(map[string]componentMaker) // Update map type
    
    componentRegistry["ChiComponent"] = func() Component { return new(ChiComponent) } // Define generators
    
    entity.Components[v] = componentRegistry[v]() // Call generator
    

    And so on. No reflection needed.