GECS: Godot Entity Component System - Entities and Components
Entities and Components in GECS
Welcome to our second post in the GECS series! This time, we’ll focus on two ECS essentials:
- Entities – The glue code/containers that hold data.
- Components – Pure data objects that live on entities and define the 'thing'.
In GECS (Godot Entity Component System), each entity is a regular Godot node that extends Entity.gd
, and each component is a Resource
extending Component.gd
.
Leaning into the way Godot does things and wrapping the ECS around this is one of the strengths of GECS. You get the pros of Godot and the data organization and execution patterns of ECS.
Entities
Entity Basics
Entities are the core data type you work with in GECS, you'll always be operating on Entities and their Component and Relationships. There are a few ways to create entities.
You can define it in code:
var e_my_entity = Entity.new() # look at me I'm an entity!
e_my_entity.add_components([C_Transform.new(), C_Velocity.new(Vector3.UP)]) # I have the data to move now!
ECS.world.add_entity(e_my_entity) # I can move now! Systems will pick me up.
Since we're in Godot a common thing to do is to create a scene with a root node of type Entity
and save it as e_my_entity.tscn
. We call these types of Entities, Prefabs because they have a bunch of prefabricated nodes ready to go.
With prefabs, we can use any of the Godot Nodes as children of the entity and take advantage of the utility they offer in a compositional way. We can use all the nodes we want and just have a component that references the node be stored on the entity.
Any node extending Entity
can contain components. For example, you might have a player entity with health, position, and an inventory component. You can only have one instance of a component on an entity, but you can have multiple entities with the same component.
In Godot, from within the editor, there is an @export variable named component_resources. This allows you to define components that will be added to the entity whenever it is spawned. It also allows you to run tools from within components as Entity has the @tool annotation. You can easily see and modify the data from within Godot using the standard Godot resource editing tools. Especially with the introduction of the new tool button in 4.4. It unlocks a lot of power for tools on resources to get/set the data they need from the scene or to the scene.
Entities can also serve as glue code to connect different nodes in your scene to components or more commonly, help with initialization.
A common technique is in the on_ready()
lifecycle function, sync the transform of the C_Transform component with the global_transform of the Node3D. This is useful when you want to move the entity around in the editor, and sync that position when the entity is ready.
# filepath: e_player.gd
class_name Player
extends Entity
func on_ready():
Utils.sync_transform(self)
- Attach Components – Add component resources to an entity’s
component_resources
array, or useadd_component(...)
in code. - Enable / Disable – Entities can be enabled or disabled, determining whether systems will process them.
Naming Entities
Entity class names should be in ClassCase, they should be named after the thing they represent. The file should be saved like e_entity_name.gd
.
Examples:
Player
(player character) - Saved ase_player.gd
Enemy
(enemy character) - Saved ase_enemy.gd
Projectile
(bullet or missile) - Saved ase_projectile.gd
Entity Lifecycle
Entities have a lifecycle managed by the World
node.
- When you add an entity to the world, it initializes and adds all the component_resources as components with the data defined in the editor. It then calls define_components() and adds those components to the entity. Allowing you to define components in two places code/editor.
- on_ready() – Called when the entity is ready. You can use this to set up initial states or sync transforms.
- on_update(delta) – Called every frame in systems.
- on_destroy() – Cleanup before removal.
- on_disable() – When the entity is disabled.
- on_enable() – When the entity is enabled.
Relationships vs Components
Components store data for a single entity, while relationships link entities together for cross-referencing. Use them to group or define bonds across entities without bloating component data.
Data-Driven Architecture
I encourage small, single-purpose components. Leverage queries to decouple logic from data. This ensures modularity and clarity, easing maintenance and scaling.
Components
Component Basics
Components only store data, they should never contain direct game logic. Although it is ok if you're providing functions that fetch or set properties, they should never contain logic that modifies the game state, only transforming its own data.
To create a new component, extend Component
:
# filepath: c_health.gd
## Health Component.
## Represents the health of an entity.
## Stores both the total and current health values.
## Used by systems to determine if an entity should be destroyed when health reaches zero.
## Affected by the [Damage] Component.
class_name C_Health
extends Component
## How much total health this has
@export var total := 1
## The current health
@export var current := 1
Use @export
to make properties visible in the Godot inspector, so you can set them in the editor. It also determines what should be serialized when importing/exporting ECS data.
Naming Components
Component class names should start with C_ and be in ClassCase. The file should be saved like c_component_name.gd
.
It helps to group components by their purpose in folders. Each component should reflect the data it carries. Examples:
C_Transform
(holds position, rotation, scale) - Saved asc_transform.gd
C_Velocity
(speed vectors) - Saved asc_velocity.gd
C_Health
(current health, max health, etc.) - Saved asc_health.gd
Adding Components to an Entity
You can add components in the editor by adding them in the entity’s Inspector under component_resources
, these will be added to the entity when the entity is added to the world. You can also add them dynamically in code:
var c_health := C_Health.new()
c_health.max_health = 10
entity.add_component(health_component)
This approach keeps data modular, since your systems identify entities by the presence (or absence) of particular components.
Summary
- Entity = Node with data containers (components).
- Component = Data resource with no logic.
- Naming – Use meaningful component names for clear code.
- Adding – Attach components via the inspector or
add_component(...)
methods.