Last active
September 10, 2025 15:51
-
-
Save corporatepiyush/a6cd96c659f655b7c5ce1b68d31aadb1 to your computer and use it in GitHub Desktop.
Entity Component System
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import "fmt" | |
| // --- CORE TYPES --- | |
| // Entity is a unique identifier. | |
| type Entity uint32 | |
| // ComponentType is the identifier for a component type, used for bitmasking. | |
| type ComponentType uint64 | |
| // Using iota for compile-time generation of unique component IDs. | |
| // This is the key to avoiding reflection. | |
| const ( | |
| PositionComponentType ComponentType = 1 << iota | |
| VelocityComponentType | |
| RenderableComponentType | |
| HealthComponentType | |
| ) | |
| // --- COMPONENTS --- | |
| // Components remain pure data structs. | |
| type PositionComponent struct { | |
| X, Y float64 | |
| } | |
| type VelocityComponent struct { | |
| VX, VY float64 | |
| } | |
| type RenderableComponent struct { | |
| Sprite rune | |
| } | |
| type HealthComponent struct { | |
| Current, Max int | |
| } | |
| // --- WORLD --- | |
| // The World manages all data, organized for cache-friendly access. | |
| type World struct { | |
| nextEntityID Entity | |
| // Stores the component signature for each entity. Using a slice for dense entity IDs. | |
| signatures map[Entity]ComponentType | |
| systems []System | |
| // --- Component Storage (Struct of Arrays) --- | |
| // Each component type has its own slice. This is the core of the cache-friendly design. | |
| positions []PositionComponent | |
| velocities []VelocityComponent | |
| renderables []RenderableComponent | |
| healths []HealthComponent | |
| // Maps to find the index of an entity's component in the slices above. | |
| entityToPositionIndex map[Entity]int | |
| entityToVelocityIndex map[Entity]int | |
| entityToRenderableIndex map[Entity]int | |
| entityToHealthIndex map[Entity]int | |
| } | |
| // NewWorld creates an initialized World. | |
| func NewWorld() *World { | |
| return &World{ | |
| signatures: make(map[Entity]ComponentType), | |
| // Initialize component slices and index maps | |
| positions: make([]PositionComponent, 0, 128), | |
| velocities: make([]VelocityComponent, 0, 128), | |
| renderables: make([]RenderableComponent, 0, 128), | |
| healths: make([]HealthComponent, 0, 128), | |
| entityToPositionIndex: make(map[Entity]int), | |
| entityToVelocityIndex: make(map[Entity]int), | |
| entityToRenderableIndex: make(map[Entity]int), | |
| entityToHealthIndex: make(map[Entity]int), | |
| } | |
| } | |
| // NewEntity creates a new entity with a unique ID. | |
| func (w *World) NewEntity() Entity { | |
| w.nextEntityID++ | |
| return w.nextEntityID | |
| } | |
| // --- Add Component Methods --- | |
| // We now have specific, type-safe methods for adding each component. | |
| func (w *World) AddPosition(e Entity, comp PositionComponent) { | |
| w.positions = append(w.positions, comp) | |
| w.entityToPositionIndex[e] = len(w.positions) - 1 | |
| w.signatures[e] |= PositionComponentType | |
| } | |
| func (w *World) AddVelocity(e Entity, comp VelocityComponent) { | |
| w.velocities = append(w.velocities, comp) | |
| w.entityToVelocityIndex[e] = len(w.velocities) - 1 | |
| w.signatures[e] |= VelocityComponentType | |
| } | |
| func (w *World) AddRenderable(e Entity, comp RenderableComponent) { | |
| w.renderables = append(w.renderables, comp) | |
| w.entityToRenderableIndex[e] = len(w.renderables) - 1 | |
| w.signatures[e] |= RenderableComponentType | |
| } | |
| func (w *World) AddHealth(e Entity, comp HealthComponent) { | |
| w.healths = append(w.healths, comp) | |
| w.entityToHealthIndex[e] = len(w.healths) - 1 | |
| w.signatures[e] |= HealthComponentType | |
| } | |
| func (w *World) AddSystem(system System) { | |
| w.systems = append(w.systems, system) | |
| } | |
| func (w *World) Update() { | |
| for _, sys := range w.systems { | |
| sys.Update(w) | |
| } | |
| } | |
| // --- SYSTEMS --- | |
| type System interface { | |
| Update(world *World) | |
| } | |
| // MovementSystem updates entity positions based on their velocity. | |
| type MovementSystem struct { | |
| signature ComponentType | |
| } | |
| func NewMovementSystem() *MovementSystem { | |
| return &MovementSystem{ | |
| signature: PositionComponentType | VelocityComponentType, | |
| } | |
| } | |
| func (s *MovementSystem) Update(world *World) { | |
| fmt.Println("-> Running Movement System...") | |
| // We iterate over entities that have a velocity, as that's a smaller set | |
| // than entities that have a position. | |
| for entity, signature := range world.signatures { | |
| if (signature & s.signature) == s.signature { | |
| // Get components by direct index lookup. This is very fast. | |
| pos := &world.positions[world.entityToPositionIndex[entity]] | |
| vel := world.velocities[world.entityToVelocityIndex[entity]] | |
| pos.X += vel.VX | |
| pos.Y += vel.VY | |
| fmt.Printf(" - Moving Entity %d to (%.1f, %.1f)\n", entity, pos.X, pos.Y) | |
| } | |
| } | |
| } | |
| // RenderSystem "draws" entities to the console. | |
| type RenderSystem struct { | |
| signature ComponentType | |
| } | |
| func NewRenderSystem() *RenderSystem { | |
| return &RenderSystem{ | |
| signature: PositionComponentType | RenderableComponentType, | |
| } | |
| } | |
| func (s *RenderSystem) Update(world *World) { | |
| fmt.Println("-> Running Render System...") | |
| for entity, signature := range world.signatures { | |
| if (signature & s.signature) == s.signature { | |
| pos := world.positions[world.entityToPositionIndex[entity]] | |
| render := world.renderables[world.entityToRenderableIndex[entity]] | |
| // Check for health to display it | |
| var healthInfo string | |
| if (signature & HealthComponentType) == HealthComponentType { | |
| health := world.healths[world.entityToHealthIndex[entity]] | |
| healthInfo = fmt.Sprintf(" [HP:%d/%d]", health.Current, health.Max) | |
| } | |
| fmt.Printf(" - Drawing Entity %d ('%c') at (%.1f, %.1f)%s\n", entity, render.Sprite, pos.X, pos.Y, healthInfo) | |
| } | |
| } | |
| } | |
| // DamageSystem reduces health of entities every frame. | |
| type DamageSystem struct { | |
| signature ComponentType | |
| } | |
| func NewDamageSystem() *DamageSystem { | |
| return &DamageSystem{ | |
| signature: HealthComponentType, | |
| } | |
| } | |
| func (s *DamageSystem) Update(world *World) { | |
| fmt.Println("-> Running Damage System...") | |
| for entity, signature := range world.signatures { | |
| if (signature & s.signature) == s.signature { | |
| health := &world.healths[world.entityToHealthIndex[entity]] | |
| health.Current -= 10 | |
| fmt.Printf(" - Damaging Entity %d, new HP: %d\n", entity, health.Current) | |
| if health.Current < 0 { | |
| health.Current = 0 | |
| } | |
| } | |
| } | |
| } | |
| // --- MAIN --- | |
| func main() { | |
| world := NewWorld() | |
| // Create a Player with Position, Velocity, Renderable, and Health. | |
| player := world.NewEntity() | |
| world.AddPosition(player, PositionComponent{X: 10, Y: 10}) | |
| world.AddVelocity(player, VelocityComponent{VX: 1, VY: 0.5}) | |
| world.AddRenderable(player, RenderableComponent{Sprite: 'P'}) | |
| world.AddHealth(player, HealthComponent{Current: 100, Max: 100}) | |
| // Create an Enemy that also has all components. | |
| enemy := world.NewEntity() | |
| world.AddPosition(enemy, PositionComponent{X: 50, Y: 20}) | |
| world.AddVelocity(enemy, VelocityComponent{VX: -0.5, VY: -0.2}) | |
| world.AddRenderable(enemy, RenderableComponent{Sprite: 'E'}) | |
| world.AddHealth(enemy, HealthComponent{Current: 50, Max: 50}) | |
| // Create a Tree that is static and cannot be damaged. | |
| tree := world.NewEntity() | |
| world.AddPosition(tree, PositionComponent{X: 80, Y: 15}) | |
| world.AddRenderable(tree, RenderableComponent{Sprite: 'T'}) | |
| // Add systems in the desired order of execution. | |
| world.AddSystem(NewDamageSystem()) | |
| world.AddSystem(NewMovementSystem()) | |
| world.AddSystem(NewRenderSystem()) // Render last to show final state | |
| // Run the game loop. | |
| for i := 0; i < 3; i++ { | |
| fmt.Printf("\n--- Frame %d ---\n", i+1) | |
| world.Update() | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment