A lightweight yet powerful Entity Component System (ECS) framework designed specifically for Godot 4.
Written purely in GDScript, this framework aims to solve the coupling problems arising from increased logic complexity in Godot projects. It provides a smooth transition path from "Godot-like" simple systems to "fully automated scheduled" high-performance parallel systems.
- Pure GDScript Implementation: No GDExtension compilation required, plug-and-play, easy debugging.
- Dual Mode Design:
- Direct Mode (
ECSSystem): Single-threaded, stateful, suitable for UI and game flow control, aligning with Godot intuition. - Scheduled Mode (
ECSParallel): Multi-threaded, automatic dependency analysis, suitable for high-performance computing and massive entity simulation.
- Direct Mode (
- High-Performance Scheduler: Built-in dependency graph (DAG) based on topological sorting, automatically handling system execution order and resource read/write conflicts.
- Automatic Multi-threading: Utilizes Godot 4's
WorkerThreadPoolfor automatic task distribution. - Powerful Query System: Supports
With/Without/AnyOfcomplex queries, with O(1) cached query performance (QueryCache). - Complete Ecosystem: Built-in Serialization/Save System (supports version migration) and Event System.
- Download this repository.
- Copy the
GodotECSandGodotUtilsfolders to your Godot project'sres://root directory. - Done! No plugin configuration needed, reference directly in code.
In your main scene script:
extends Node
var _world: ECSWorld
var _runner: ECSRunner
func _ready() -> void:
# Create world
_world = ECSWorld.new("MyGameWorld")
# Create a runner for single-threaded systems (recommended approach)
_runner = _world.create_runner("GameLogic")
# Add systems to the runner
_runner.add_system("MoveSystem", SysMovement.new())
# Create an entity
var entity = _world.create_entity()
entity.add_component("Position", CompPos.new(0, 0))
entity.add_component("Velocity", CompVel.new(10, 0))
# OLD WAY (deprecated, for reference):
# _world.add_system("MoveSystem", SysMovement.new())
# _world.update(delta)
func _process(delta: float) -> void:
# Drive runner updates (recommended way)
_runner.run(delta)
func _exit_tree() -> void:
_world.clear()Components are simply data containers.
class CompPos extends ECSComponent:
var x: float = 0
var y: float = 0
func _init(px=0, py=0): x=px; y=py
class CompVel extends ECSDataComponent:
# ECSDataComponent comes with a 'data' property
pass ECSRunner is the recommended way to manage single-threaded systems. It provides system grouping, better organization, and a consistent API style with ECSScheduler.
Note: The direct
world.add_system()andworld.update()methods are deprecated but still supported for backward compatibility.
extends Node
var _world: ECSWorld
var _runner: ECSRunner
func _ready() -> void:
# Create world
_world = ECSWorld.new("MyGameWorld")
# Create a named runner for single-threaded systems
_runner = _world.create_runner("GameLogic")
# Add systems to the runner (supports method chaining)
_runner.add_system("MoveSystem", SysMovement.new())
.add_system("RenderSystem", SysRender.new())
# Create an entity
var entity = _world.create_entity()
entity.add_component("Position", CompPos.new(0, 0))
entity.add_component("Velocity", CompVel.new(10, 0))
func _process(delta: float) -> void:
# Drive runner updates (instead of world.update())
_runner.run(delta)
func _exit_tree() -> void:
# Clean up
_world.clear()Benefits of ECSRunner:
- โ Clear system grouping and organization
- โ Multiple runners for different system categories
- โ Consistent API with ECSScheduler
- โ Better scalability and maintainability
- โ Individual system update control
Suitable for game logic, input, and UI.
class SysMovement extends ECSSystem:
func _on_update(delta: float) -> void:
# Get all entities with Position and Velocity
var list = world().multi_view(["Position", "Velocity"])
for item in list:
var pos = item["Position"]
var vel = item["Velocity"]
pos.x += vel.data * deltaSuitable for physics simulation and AI clusters. Supports dependency sorting and automatic parallelism.
class SysPhysics extends ECSParallel:
func _init(): super._init("Physics")
# 1. Declare read/write permissions for scheduler analysis
func _list_components() -> Dictionary:
return {
"Position": ECSParallel.READ_WRITE,
"Velocity": ECSParallel.READ_ONLY
}
# 2. Enable multi-threaded parallel processing
func _parallel() -> bool: return true
# 3. Business logic (Pass in thread-safe CommandBuffer)
func _view_components(view: Dictionary, cmds: ECSParallel.Commands) -> void:
view["Position"].x += view["Velocity"].data * delta| Feature | ECSSystem (Direct) | ECSParallel (Scheduled) |
|---|---|---|
| Primary Use | Game logic, UI, Input, Flow control | Physics, AI, Massive data operations |
| Threading Model | Single-threaded (Main Thread) | Multi-threaded (WorkerThreadPool) |
| State Management | Stateful allowed | Stateless, Pure Logic |
| Execution Order | Manually added order | Automatic dependency sorting (.after/.before) |
| Data Modification | Direct API calls | Deferred modification using CommandBuffer |
GodotECS/: Core framework code (World, Entity, System, Scheduler).GodotUtils/: Utility libraries (EventCenter, Serialization, Factory).
The framework has built-in powerful save support, even supporting data structure upgrades:
# Save
var packer = ECSWorldPacker.new(_world).with_factory(factory)
var data = packer.pack() # Get serializable DataPack
# Load
packer.unpack(data) # Automatically restore world stateIssues and PRs are welcome! If you find a bug or have suggestions for performance optimization, please let us know.
This project is licensed under the MIT License.
