Godot Entity Component System
A lightweight, performant ECS framework for Godot 4.x that integrates seamlessly with Godot's node system.
- 🎯 Full integration with Godot's node system
- 🚀 Query-based entity filtering with optimized component indexing
- 🔧 System groups for organized processing
- 📦 Component resources that work in the editor
- 🎮 Easy setup with automatic node management
- 🕹️ Full Example game Zombies Ate My Neighbors and a 2D Breakout Clone being made with system
- Introduction
- Installation
- Getting Started
- Creating Components
- Creating Entities
- Creating Systems
- The World and ECS Singleton
- Example Project
- Advanced Usage
- Conclusion
GECS is an Entity Component System (ECS) framework designed for the Godot Engine. It provides a simple yet powerful way to organize your game logic using the ECS pattern, allowing for better code organization, reusability, and scalability.
This documentation will guide you through the setup and usage of the GECS addon, providing examples from a sample game project to illustrate key concepts.
-
Download the Addon: Clone or download the GECS addon and place it in your project's
addons
folder. -
Enable the Addon: In the Godot editor, go to
Project > Project Settings > Plugins
, and enable theGECS
plugin. -
Autoload ECS: The addon requires the
ECS
singleton to be autoloaded. This should be handled automatically when you enable the plugin. If not, go toProject > Project Settings > Autoload
, and addECS
pointing tores://addons/gecs/ecs.gd
.
Each class has a full set of in-editor Godot documentation. Check there as well!
Before diving into the usage of the GECS addon, it's important to understand the basic concepts of an Entity Component System (ECS):
-
Entity: A container or placeholder that represents an object in your game. Entities can have multiple components added to them to define their behavior and properties.
-
Component: A data container that holds specific attributes or properties. Components do not contain game logic.
-
Archetypes: A specific named Entity that has a specific set of components (at ready) and usually is using script inheritance to take advantage of any godot nodes features. OR... just a named Entity. Very handy for relationship queries.
-
System: A system contains two parts. The query which defines which entities to operate on and the logic that operates on each entity with specific components.
-
World: The context in which entities and systems exist and interact.
-
World Query: A way to query for specific entities in the world based on the components they contain or the relationships they have.
-
Component Query In A world query we can define criteria. Allowing us to further refine world queries beyond just presence of components. See more: Component_Queries.md
-
Relationship: A resource that represents a relationship between a target and a source based on a relation. See more: Relations.md
-
ECS Singleton: Provides global access to the current
World
instance and offers utility functions for processing.
Components in GECS are resources that extend the Component
class. They are simple data containers without any logic. They may contain functions but only for getting/setting properties on the component.
Here's how to create a new component:
- Create a New Script: Create a new script extending
Component
.
# c_bounce.gd
class_name CBounce
extends Component
@export var normal := Vector2.ZERO
@export var should_bounce := false
- Define Properties: Add any properties that represent the data for this component. Use the
@export
keyword to make them editable in the inspector. This allows you to use them in the godot editor to design entities with components and modify those properties.
Entities in GECS are nodes that extend the Entity
class. They can have components added to them to define their behavior.
-
Create a New Scene: Create a new scene with a root node extending
Entity
. -
Add Components: Use the
component_resources
exported array to add instances of your components.
# e_ball.gd
class_name Ball
extends Entity
func on_ready() -> void:
Utils.sync_transform(self)
- Initialize Components: In the
_ready()
function, components listed incomponent_resources
are automatically added to the entity but you can also override a method calleddefine_components()
and return an array of components.
Systems in GECS are nodes that extend the System
class. They contain the logic that operates on entities with specific components.
- Create a New Script: Create a new script extending
System
.
# s_bounce.gd
class_name BounceSystem
extends System
func query():
# All entities that all have transform, velocity and bounce components
return q.with_all([CTransform, CVelocity, CBounce])
func process(entity: Entity, delta: float):
var c_bounce: CBounce = entity.get_component(CBounce)
if c_bounce.should_bounce:
var c_velocity: CVelocity = entity.get_component(CVelocity)
c_velocity.direction = c_bounce.normal
c_bounce.should_bounce = false
-
Override the
query()
Function: In thequery()
function, specify the components that entities must have for the system to process them. Use the providedq
(QueryBuilder) to build the query. -
Implement the
process()
Function: Theprocess()
function contains the logic to be applied to each relevant entity. It is called for each entity that matches the query.
The World
class manages all entities and systems in your game. It processes systems and handles entity queries.
-
ECS: A singleton autoloaded to provide access to the current
World
. -
Setting Up the World: In your main scene, add a node extending
World
and add your entities and systems as children.
_ Executing the systems in the world: Make sure to call ECS.process(delta, group) to run your systems. To process all active systems regardless of their group, omit the group
parameter
# main.gd
extends Node
@onready var world: World = $World
func _ready() -> void:
ECS.world = world
func _process(delta):
# Process only systems in the "gameplay" group
ECS.process(delta, "gameplay")
func _physics_process(delta):
# Process only systems in the "physics" group
ECS.process(delta, "physics")
ALSO Check out the example games being made on the branch zombies-ate-my-neighbors and breakout
To illustrate the usage of GECS, let's look at an example project that simulates a simple Breakout game.
- Bounce: Indicates that an entity can bounce off surfaces.
# c_bounce.gd
class_name CBounce
extends Component
@export var normal := Vector2.ZERO
@export var should_bounce := false
- Velocity: Controls the movement speed and direction of an entity.
# c_velocity.gd
class_name CVelocity
extends Component
@export var direction := Vector2.ZERO
@export var speed := 0.0
- Transform: Manages the position, rotation, and scale of an entity.
# c_transform.gd
class_name CTransform
extends Component
@export var transform: Transform2D
- Ball: Represents the ball in the game.
# e_ball.gd
class_name Ball
extends Entity
func on_ready() -> void:
Utils.sync_transform(self)
In the scene, the Ball
entity includes Bounce
, Velocity
, and Transform
components.
- Paddle: Represents the player's paddle.
# e_paddle.gd
class_name Paddle
extends Entity
func on_ready() -> void:
Utils.sync_transform(self)
Includes CPlayerMovement
, CVelocity
, CTransform
, and CFriction
components.
- BounceSystem: Handles the bouncing logic of entities.
# s_bounce.gd
class_name BounceSystem
extends System
func query():
return q.with_all([CTransform, CVelocity, CBounce])
func process(entity: Entity, delta: float):
var c_bounce: CBounce = entity.get_component(CBounce)
if c_bounce.should_bounce:
var c_velocity: CVelocity = entity.get_component(CVelocity)
c_velocity.direction = c_bounce.normal
c_bounce.should_bounce = false
- VelocitySystem: Updates entity positions based on their velocity.
# s_velocity.gd
class_name VelocitySystem
extends System
func query():
return q.with_all([CVelocity, CTransform])
func process(entity: Entity, delta: float):
var c_velocity: CVelocity = entity.get_component(CVelocity)
var c_transform: CTransform = entity.get_component(CTransform)
var velocity_vector: Vector2 = c_velocity.direction.normalized() * c_velocity.speed
c_transform.transform.origin += velocity_vector * delta
- Transform2DSystem: Synchronizes the
Transform
component with the entity's actual transform.
# s_transform_2d.gd
class_name Transform2DSystem
extends System
func query():
return q.with_all([CTransform])
func process(entity: Entity, delta):
Utils.sync_transform(entity)
The QueryBuilder
class provides an advanced query function to retrieve entities based on their components.
In classes extending System it is exposed in the q
variable
q
.with_all([]) # Find entities that have all these components
.with_any([]) # Find entities that have any of these components
.with_none([]) # Exclude entities that have these components
.with_relationship([]) # Must have these relationships
.without_relationship([]) # must not these relationships
.with_reverse_relationship([]) # must have these reverse relationships
- with_all: Entities must have all of these components.
- with_any: Entities must have at least one of these components.
- with_none: Entities must not have any of these components.
- with_relationship: Entities must have these relationships
- without_relationship: Entities must not have these relationships
- with_reverse_relationship: This finds the entities of reverse relationships (aka the target of the relationship, not the source)
Example:
var entities_with_velocity_and_not_captured = q.with_all([CVelocity]).with_none([CCaptured])
Systems have properties that allow for customizing their execution:
- active: Determines whether the system runs during processing. If false, the system will be skipped.
- group: Assigns the system to a specific group. This allows you to control the execution of systems in groups. You can process systems of a specific group by providing the group name to the ECS.process(delta, group) function.
Example:
func _physics_process(delta):
ECS.process(delta, "physics")
func _process(delta):
ECS.process(delta, "gameplay")
This will only process systems that are in the "physics" group in the physics process function and gameplay system in the _process function
To process all active systems regardless of their group, omit the group
parameter:
Systems can be assigned to specific groups, enabling you to control when and how they are processed. This is particularly useful for separating logic that should run at different times or frequencies.
Assigning Systems to Groups:
In your system script, set the group
property to specify which group the system belongs to.
The GECS addon provides a flexible and efficient way to implement the ECS pattern in your Godot projects. By separating data (components) from logic (systems), you can create reusable and maintainable game code.
Feel free to explore and expand upon the example project provided, and refer to this documentation and especially the in editor documentation as you integrate GECS into your own games.
- Build a better GUI for seeing Systems and their groups
- GUI For Seeing all Systems executing
- Gui for seeing all Entities and Components and Values in those components
- GUI TO see all Components by type