Files
godot-shader-experiments/addons/gecs/docs/GETTING_STARTED.md
2026-01-15 15:27:48 +01:00

11 KiB

Getting Started with GECS

Build your first ECS project in 5 minutes

This guide will walk you through creating a simple player entity with health and transform components using GECS. By the end, you'll understand the core concepts and have a working example.

📋 Prerequisites

  • Godot 4.x installed
  • Basic GDScript knowledge
  • 5 minutes of your time

Step 1: Setup (1 minute)

Install GECS

  1. Download GECS and place it in your project's addons/ folder
  2. Enable the plugin: Go to Project > Project Settings > Plugins and enable "GECS"
  3. Verify setup: The ECS singleton should be automatically added to AutoLoad

💡 Quick Check: If you see errors, make sure ECS appears in Project > Project Settings > AutoLoad

🎮 Step 2: Your First Entity (2 minutes)

Entities in GECS extend Godot's Node class. You have two options for creating entities:

Option A: Scene-based Entities (For spatial properties)

Use this when you need access to Node3D or Node2D properties like position, rotation, scale, or want to add visual children (sprites, meshes, etc.).

⚠️ Key Point: Entity extends Node (not Node3D or Node2D), so create a scene with the appropriate spatial node type as the root, then attach your entity script to it.

Steps:

  1. Create a new scene in Godot:

    • Click Scene > New Scene or press Ctrl+N
    • Select "Node3D" as the root node type (for 3D games) or "Node2D" (for 2D games)
    • Rename the root node to Player
  2. Attach the entity script:

    • With the root node selected, click the "Attach Script" button (📄+ icon)
    • Save as e_player.gd
  3. Save the scene:

    • Save as e_player.tscn in your scenes folder

File: e_player.gd

# e_player.gd
class_name Player
extends Entity

func on_ready():
    # Sync the entity's scene position to the Transform component
    if has_component(C_Transform):
        var c_trs = get_component(C_Transform) as C_Transform
        c_trs.position = self.global_position

💡 Use case: Players, enemies, projectiles, or anything that needs a position in your game world.

Option B: Code-based Entities (Pure data containers)

Use this when you DON'T need spatial properties and just want a pure data container (e.g., game managers, abstract systems, timers).

# Just extend Entity directly
class_name GameManager
extends Entity

# No scene needed - instantiate with GameManager.new()

💡 Use case: Game state managers, quest trackers, inventory systems, or any non-spatial game logic.


For this tutorial, we'll use Option A (scene-based) since we want our player to move around the screen with a position.

📦 Step 3: Your First Components (1 minute)

Components hold data. Let's create health and transform components:

File: c_health.gd

# c_health.gd
class_name C_Health
extends Component

@export var current: float = 100.0
@export var maximum: float = 100.0

func _init(max_health: float = 100.0):
    maximum = max_health
    current = max_health

File: c_transform.gd

# c_transform.gd
class_name C_Transform
extends Component

@export var position: Vector3 = Vector3.ZERO

func _init(pos: Vector3 = Vector3.ZERO):
    position = pos

File: c_velocity.gd

# c_velocity.gd
class_name C_Velocity
extends Component

@export var velocity: Vector3 = Vector3.ZERO

func _init(vel: Vector3 = Vector3.ZERO):
    velocity = vel

💡 Key Principle: Components only hold data, never logic. Think of them as data containers. ⚠️ Important Note: Components _init function requires that all arguments have a default value or Godot will crash.

⚙️ Step 4: Your First System (1 minute)

Systems contain the logic that operates on entities with specific components. This system moves entities across the screen:

File: s_movement.gd

# s_movement.gd
class_name MovementSystem
extends System

func query():
    # Find all entities that have both transform and velocity
    return q.with_all([C_Transform, C_Velocity])

func process(entities: Array[Entity], components: Array, delta: float):
    # Process each entity in the array
    for entity in entities:
        var c_trs = entity.get_component(C_Transform) as C_Transform
        var c_velocity = entity.get_component(C_Velocity) as C_Velocity

        # Move the entity based on its velocity
        c_trs.position += c_velocity.velocity * delta

        # Update the actual entity position in the scene
        entity.global_position = c_trs.position

        # Bounce off screen edges (simple example)
        if c_trs.position.x > 10 or c_trs.position.x < -10:
            c_velocity.velocity.x *= -1

💡 System Logic: Query finds entities with required components, process() runs the movement logic on each entity every frame.

🎬 Step 5: See It Work (1 minute)

Now let's put it all together in a main scene:

Create Main Scene

  1. Create a new scene with a Node as the root
  2. Add a World node as a child (Add Child Node > search for "World")
  3. Attach this script to the root node:

File: main.gd

# main.gd
extends Node

@onready var world: World = $World

func _ready():
    ECS.world = world

    # Load and instantiate the player entity scene
    var player_scene = preload("res://e_player.tscn")  # Adjust path as needed
    var e_player = player_scene.instantiate() as Player

    # Add components to the entity
    e_player.add_components([
        C_Health.new(100),
        C_Transform.new(),
        C_Velocity.new(Vector3(2, 0, 0))  # Move right at 2 units/second
    ])

    add_child(e_player)  # Add to scene tree
    ECS.world.add_entity(e_player)  # Add to ECS world

    # Create the movement system
    var movement_system = MovementSystem.new()
    ECS.world.add_system(movement_system)

func _process(delta):
    # Process all systems
    if ECS.world:
        ECS.process(delta)

Run your project! 🎉 You now have a working ECS setup where the player entity moves across the screen and bounces off the edges! The MovementSystem updates entity positions based on their velocity components.

💡 Scene-based entities: Notice we load and instantiate the e_player.tscn scene instead of calling Player.new(). This is required because we need access to spatial properties (position). For entities that don't need spatial properties, Entity.new() works fine.

🎯 What You Just Built

Congratulations! You've created your first ECS project with:

  • Entity: Player - a container for components
  • Components: C_Health, C_Transform, C_Velocity - pure data containers
  • System: MovementSystem - logic that moves entities based on velocity
  • World: Container that manages entities and systems

📈 Next Steps

Now that you have the basics working, here's how to level up:

Instead of creating entities in code, use Godot's scene system:

  1. Create a new scene with your Entity class as the root node
  2. Add visual children (MeshInstance3D, Sprite3D, etc.)
  3. Add components via define_components() or component_resources array in Inspector
  4. Save as .tscn file (e.g., e_player.tscn)
  5. Load and instantiate in your main scene
# Improved e_player.gd with define_components()
class_name Player
extends Entity

func define_components() -> Array:
    return [
        C_Health.new(100),
        C_Transform.new(),
        C_Velocity.new(Vector3(1, 0, 0))  # Move right slowly
    ]

func on_ready():
    # Sync scene position to component
    if has_component(C_Transform):
        var c_trs = get_component(C_Transform) as C_Transform
        c_trs.position = self.global_position

2. Organize Your Main Scene

Structure your main scene using the proven scene-based pattern:

Main.tscn
├── World (World node)
├── DefaultSystems (instantiated from default_systems.tscn)
│   ├── input (SystemGroup)
│   ├── gameplay (SystemGroup) 
│   ├── physics (SystemGroup)
│   └── ui (SystemGroup)
├── Level (Node3D for static environment)
└── Entities (Node3D for spawned entities)

Benefits:

  • Visual organization in Godot editor
  • Easy system reordering between groups
  • Reusable system configurations

3. Learn More Patterns

🧠 Understand the Concepts

Core Concepts Guide - Deep dive into Entities, Components, Systems, and Relationships

🔧 Add More Features

Try adding these to your moving player:

  • Input system - Add C_Input component and system to control movement with arrow keys
  • Multiple entities - Create more moving objects with different velocities
  • Collision system - Add C_Collision component and detect when entities hit each other
  • Gravity system - Add downward velocity to make entities fall

📚 Learn Best Practices

Best Practices Guide - Write maintainable ECS code

🔧 Explore Advanced Features

Having Issues?

Player not responding?

  • Check that ECS.process(delta) is called in _process()
  • Verify components are added to the entity via define_components() or Inspector
  • Make sure the system is added to the world
  • Ensure transform synchronization is called in entity's on_ready()

Can't access position/rotation properties?

  • ⚠️ Entity extends Node, not Node3D: To access spatial properties, create a scene with Node3D (3D) or Node2D (2D) as the root node type
  • Attach your entity script (that extends Entity) to the Node3D/Node2D root
  • Load and instantiate the scene file (don't use .new() for spatial entities)
  • If you don't need spatial properties: Using Entity.new() is perfectly fine for pure data containers
  • See Step 2 for both entity creation approaches

Errors in console?

  • Check that all classes extend the correct base class
  • Verify file names match class names
  • Ensure GECS plugin is enabled

Still stuck?Troubleshooting Guide

🏆 What's Next?

You're now ready to build amazing games with GECS! The Entity-Component-System pattern will help you:

  • Scale your game - Add features without breaking existing code
  • Reuse code - Components and systems work across different entity types
  • Debug easier - Clear separation between data and logic
  • Optimize performance - GECS handles efficient querying for you

Ready to dive deeper? Start with Core Concepts to really understand what makes ECS powerful.

Need help? Join our Discord community for support and discussions.


"The best way to learn ECS is to build with it. Start simple, then add complexity as you understand the patterns."