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
- Download GECS and place it in your project's
addons/folder - Enable the plugin: Go to
Project > Project Settings > Pluginsand enable "GECS" - Verify setup: The ECS singleton should be automatically added to AutoLoad
💡 Quick Check: If you see errors, make sure
ECSappears inProject > 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:
EntityextendsNode(notNode3DorNode2D), so create a scene with the appropriate spatial node type as the root, then attach your entity script to it.
Steps:
-
Create a new scene in Godot:
- Click
Scene > New Sceneor pressCtrl+N - Select "Node3D" as the root node type (for 3D games) or "Node2D" (for 2D games)
- Rename the root node to
Player
- Click
-
Attach the entity script:
- With the root node selected, click the "Attach Script" button (📄+ icon)
- Save as
e_player.gd
-
Save the scene:
- Save as
e_player.tscnin your scenes folder
- Save as
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
_initfunction 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
- Create a new scene with a
Nodeas the root - Add a World node as a child (Add Child Node > search for "World")
- 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.tscnscene instead of callingPlayer.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:
1. Create Entity Prefabs (Recommended)
Instead of creating entities in code, use Godot's scene system:
- Create a new scene with your Entity class as the root node
- Add visual children (MeshInstance3D, Sprite3D, etc.)
- Add components via define_components() or
component_resourcesarray in Inspector - Save as .tscn file (e.g.,
e_player.tscn) - 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
- Component Queries - Filter by component property values
- Relationships - Link entities together for complex interactions
- Observers - Reactive systems that respond to changes
- Performance Optimization - Make your games run fast
❓ 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) orNode2D(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."