Skip to main content

๐Ÿงฉ Entity Component System Deep Dive

Master the architectural pattern powering the worldโ€™s most performant game engines and blockchain applications

Prerequisites: Basic understanding of programming concepts and object-oriented design

๐ŸŽฏ What is Entity Component System?

Entity Component System (ECS) is a software architectural pattern used extensively in game development and increasingly in blockchain applications. It provides a flexible, performant way to compose complex behaviors from simple, reusable parts.

๐ŸŽฎ Interactive ECS Example

๐ŸŽฏ ENTITIES
Entity #001
Player
Entity #042
Monster
Entity #137
Treasure
๐Ÿงฉ COMPONENTS
HealthComponent
current: 85, max: 100
PositionComponent
x: 150, y: 200
InventoryComponent
items: [sword, potion], capacity: 10
AIComponent
state: โ€œpatrolโ€, target: null
โš™๏ธ SYSTEMS
Combat System
Processes Health + Attack components
Movement System
Updates Position based on Velocity
Inventory System
Manages Inventory + Item components

Try this: In traditional OOP, youโ€™d need separate Player, Monster, and Treasure classes. With ECS, any entity can have any combination of components, processed by relevant systems.

Entities

Unique Identifiers - Think of them as empty containers or labels
// Just a number!
let player_entity: u64 = 42;
let monster_entity: u64 = 1337;

Components

Pure Data - Properties and attributes without behavior
struct HealthComponent has store, drop {
    current: u64,
    maximum: u64,
}

Systems

Pure Logic - Functions that operate on entities with specific components
public entry fun healing_system(world: &mut World) {
    // Process all entities with HealthComponent
}

๐Ÿค” Why ECS Instead of Traditional OOP?

The Problem with Inheritance Hierarchies

// Rigid inheritance hierarchy
class GameObject {
  constructor() { }
  update() { }
  render() { }
}

class Character extends GameObject {
  constructor() {
    super();
    this.health = 100;
    this.inventory = [];
  }
  move() { }
  attack() { }
}

class Player extends Character {
  levelUp() { }
}

class NPC extends Character {
  followPath() { }
}

// What about a flying enemy? Swimming player?
// Inheritance becomes messy and inflexible
class FlyingEnemy extends Character {
  fly() { }
  // But it inherits move() which doesn't make sense for flying
}
Problems:
  • ๐Ÿšซ Rigid inheritance chains
  • ๐Ÿšซ Diamond problem (multiple inheritance)
  • ๐Ÿšซ Hard to add new behaviors
  • ๐Ÿšซ Tight coupling between data and behavior
  • ๐Ÿšซ Difficult to test individual behaviors

๐Ÿ—๏ธ ECS Components in Detail

Component Design Principles

Components should contain only data, no behavior or logic.
// โœ… Good: Pure data component
struct WeaponComponent has store, drop {
    damage: u64,
    durability: u64,
    weapon_type: u8, // 0=sword, 1=bow, 2=staff
}

// โŒ Bad: Component with behavior
struct WeaponComponent has store, drop {
    damage: u64,
    durability: u64,
    weapon_type: u8,
}

// Don't do this in components!
impl WeaponComponent {
    public fun attack(&self, target: &mut Entity) {
        // Logic belongs in systems, not components!
    }
}
Each component should represent a single concept or aspect.
// โœ… Good: Focused components
struct HealthComponent has store, drop {
    current: u64,
    maximum: u64,
}

struct PositionComponent has store, drop {
    x: u64,
    y: u64,
}

struct VelocityComponent has store, drop {
    dx: u64,
    dy: u64,
}

// โŒ Bad: Kitchen sink component
struct PlayerComponent has store, drop {
    health: u64,
    max_health: u64,
    x: u64,
    y: u64,
    dx: u64,
    dy: u64,
    level: u8,
    experience: u64,
    inventory: vector<u64>,
    // Too many different concepts in one component!
}
Design components so they work well together in various combinations.
// These components can be mixed and matched:

// Basic movement
// Entity: Position + Velocity

// Player character  
// Entity: Position + Velocity + Health + Inventory + Experience

// Flying creature
// Entity: Position + Velocity + Health + Flying + AI

// Static item
// Entity: Position + Item + Renderable

// Projectile
// Entity: Position + Velocity + Damage + Lifetime

Common Component Patterns

Components that represent the current state of an entity.
struct HealthComponent has store, drop {
    current: u64,
    maximum: u64,
}

struct ManaComponent has store, drop {
    current: u64,
    maximum: u64,
    regeneration_rate: u64,
}

struct PositionComponent has store, drop {
    x: u64,
    y: u64,
    facing_direction: u8,
}

โš™๏ธ Systems: Where the Logic Lives

System Design Principles

Single Responsibility

Each system should handle one specific aspect of game logic

Component Focused

Systems operate on entities that have specific component combinations

Stateless

Systems shouldnโ€™t maintain internal state between calls

Pure Functions

Same inputs should always produce the same outputs

System Examples

public entry fun movement_system() acquires World {
    // Query all entities with Position and Velocity components
    let entities = world::query_entities_with<PositionComponent, VelocityComponent>();
    
    let i = 0;
    while (i < vector::length(&entities)) {
        let entity = *vector::borrow(&entities, i);
        
        // Get mutable references to components
        let position = world::get_mut_component<PositionComponent>(entity);
        let velocity = world::get_component<VelocityComponent>(entity);
        
        // Apply velocity to position
        position.x = position.x + velocity.dx;
        position.y = position.y + velocity.dy;
        
        i = i + 1;
    };
}

๐ŸŽฎ Real-World ECS Patterns

Query Patterns

// Get all entities with a specific component
let healthy_entities = world::query_entities_with<HealthComponent>();

// Get all entities with multiple components (AND query)
let moveable_entities = world::query_entities_with<PositionComponent, VelocityComponent>();

// Check if specific entity has component
if (world::has_component<PlayerTag>(entity_id)) {
    // Handle player-specific logic
};
// Find all living players
public fun find_living_players(): vector<u64> acquires World {
    let all_players = world::query_entities_with<PlayerTag>();
    let living_players = vector::empty<u64>();
    
    let i = 0;
    while (i < vector::length(&all_players)) {
        let player = *vector::borrow(&all_players, i);
        // Only include if they don't have DeadTag
        if (!world::has_component<DeadTag>(player)) {
            vector::push_back(&mut living_players, player);
        };
        i = i + 1;
    };
    
    living_players
}

// Find entities within range
public fun find_entities_in_range(
    center_pos: &PositionComponent, 
    range: u64
): vector<u64> acquires World {
    let all_positioned = world::query_entities_with<PositionComponent>();
    let in_range = vector::empty<u64>();
    
    let i = 0;
    while (i < vector::length(&all_positioned)) {
        let entity = *vector::borrow(&all_positioned, i);
        let pos = world::get_component<PositionComponent>(entity);
        
        if (distance(center_pos, pos) <= range) {
            vector::push_back(&mut in_range, entity);
        };
        i = i + 1;
    };
    
    in_range
}

Event-Driven Architecture

// Events for component changes
struct ComponentAddedEvent<T: store + drop> has drop {
    entity: u64,
    component: T,
}

struct ComponentRemovedEvent has drop {
    entity: u64,
    component_type: String,
}

struct EntitySpawnedEvent has drop {
    entity: u64,
    components: vector<String>,
}

struct EntityDespawnedEvent has drop {
    entity: u64,
}
// Game-specific events
struct PlayerLevelUpEvent has drop {
    player: u64,
    old_level: u8,
    new_level: u8,
}

struct ItemCraftedEvent has drop {
    crafter: u64,
    item: u64,
    recipe: u64,
}

struct CombatEvent has drop {
    attacker: u64,
    defender: u64,
    damage_dealt: u64,
    weapon_used: u64,
}

struct QuestCompletedEvent has drop {
    player: u64,
    quest_id: u64,
    reward_items: vector<u64>,
    experience_gained: u64,
}

๐Ÿš€ Performance Optimization Techniques

Component Storage Optimization

Small Components

Keep components small to minimize gas costs and improve cache performance

Batch Operations

Process multiple entities in single system calls when possible
// โœ… Good: Small, focused components
struct PositionComponent has store, drop {
    x: u64,  // 8 bytes
    y: u64,  // 8 bytes
}         // Total: 16 bytes

struct VelocityComponent has store, drop {
    dx: u64, // 8 bytes
    dy: u64, // 8 bytes  
}         // Total: 16 bytes

// โŒ Bad: Large, unfocused component
struct MassiveComponent has store, drop {
    position_x: u64,
    position_y: u64,
    velocity_x: u64,
    velocity_y: u64,
    health: u64,
    max_health: u64,
    mana: u64,
    max_mana: u64,
    level: u8,
    experience: u64,
    inventory: vector<u64>, // Variable size!
    // ... many more fields
}                        // Total: 100+ bytes plus vector data

System Execution Optimization

1

System Ordering

// Execute systems in logical order
public entry fun game_tick() {
    // 1. Process input and AI decisions
    input_system();
    ai_system();
    
    // 2. Apply physics and movement
    movement_system();
    collision_system();
    
    // 3. Handle interactions
    combat_system();
    pickup_system();
    
    // 4. Update derived state
    animation_system();
    sound_system();
    
    // 5. Clean up
    death_system();
    cleanup_system();
}
2

Conditional System Execution

// Only run expensive systems when needed
public entry fun conditional_systems() acquires World {
    // Only run AI system if there are AI entities
    if (!vector::is_empty(&world::query_entities_with<AIComponent>())) {
        ai_system();
    };
    
    // Only run combat system if there are attack intents
    if (!vector::is_empty(&world::query_entities_with<AttackIntentComponent>())) {
        combat_system();
    };
    
    // Always run movement (most entities move)
    movement_system();
}

๐Ÿงช Testing ECS Systems

Unit Testing Components

#[test]
public fun test_health_component_creation() {
    let health = HealthComponent {
        current: 100,
        maximum: 100,
    };
    
    assert!(health.current == 100, 0);
    assert!(health.maximum == 100, 0);
}

#[test]
public fun test_position_component_update() {
    let mut pos = PositionComponent {
        x: 10,
        y: 20,
    };
    
    pos.x = pos.x + 5;
    pos.y = pos.y + 10;
    
    assert!(pos.x == 15, 0);
    assert!(pos.y == 30, 0);
}

Integration Testing

// Frontend integration tests
describe('ECS Integration', () => {
  let client: DubheClient;
  
  beforeEach(async () => {
    client = await TestClient.setup();
  });
  
  it('should create entity with components', async () => {
    const entity = await client.createEntity();
    
    await client.setComponent(entity, 'HealthComponent', {
      current: 100n,
      maximum: 100n
    });
    
    await client.setComponent(entity, 'PositionComponent', {
      x: 10n,
      y: 20n
    });
    
    const health = await client.getComponent('HealthComponent', entity);
    const position = await client.getComponent('PositionComponent', entity);
    
    expect(health.current).toBe(100n);
    expect(position.x).toBe(10n);
  });
  
  it('should execute systems correctly', async () => {
    const entity = await client.createEntity();
    
    // Set up initial state
    await client.setComponent(entity, 'PositionComponent', {
      x: 0n,
      y: 0n
    });
    
    await client.setComponent(entity, 'VelocityComponent', {
      dx: 5n,
      dy: 10n
    });
    
    // Execute movement system
    await client.tx.movementSystem.update();
    
    // Check new position
    const position = await client.getComponent('PositionComponent', entity);
    expect(position.x).toBe(5n);
    expect(position.y).toBe(10n);
  });
});

๐ŸŽฏ ECS Best Practices

Doโ€™s โœ…

Keep Components Simple

Components should be pure data with no logic

Make Systems Focused

Each system should handle one specific concern

Use Composition

Build complex behaviors by combining simple components

Emit Events

Use events to communicate between systems

Donโ€™ts โŒ

Don't Put Logic in Components

Components should never contain functions or behavior

Don't Make Giant Components

Avoid components that try to do everything

Don't Couple Systems

Systems should not directly depend on each other

Don't Ignore Performance

Consider gas costs and query efficiency

๐Ÿ“š Advanced Topics

Schema-Driven Development

Learn how Dubhe generates ECS code from schemas

Move Integration

Deep dive into Move-specific ECS patterns

Performance Optimization

Advanced optimization techniques for ECS systems

Testing Guide

Comprehensive testing strategies for ECS applications

๐Ÿš€ Next Steps

1

Practice with Examples

Try building the Monster Hunter tutorial to see ECS in action
2

Learn Schema Design

Master schema-driven development for automatic code generation
3

Build Your Own Game

Apply ECS principles to create your own blockchain game