# GECS Core Concepts Guide > **Deep understanding of Entity Component System architecture** This guide explains the fundamental concepts that make GECS powerful. After reading this, you'll understand how to architect games using ECS principles and leverage GECS's unique features. ## 📋 Prerequisites - Completed [Getting Started Guide](GETTING_STARTED.md) - Basic GDScript knowledge - Understanding of Godot's node system ## 🎯 Why ECS? ### The Problem with Traditional OOP Traditional object-oriented approaches often bundle data and behavior together. Over time, this can become unwieldy and force complicated inheritance structures: ```gdscript # ❌ Traditional OOP problems class BaseCharacter: # Lots of shared code class Player extends BaseCharacter: # Player-specific code mixed with shared code class Enemy extends BaseCharacter: # Enemy-specific code, some overlap with Player class Boss extends Enemy: # Even more inheritance complexity ``` ### The ECS Solution ECS keeps data (components) separate from logic (systems), providing clear organization around three core concepts: 1. **Entities** – IDs or "slots" for your game objects 2. **Components** – Pure data objects that define state (e.g., velocity, health) 3. **Systems** – Logic that processes entities with specific components This pattern simplifies organization, collaboration, and refactoring. Systems only act upon relevant components. Entities can freely change their makeup without breaking the overall design. ## 🏗️ GECS Architecture GECS extends standard ECS with Godot-specific features: - **Integration with Godot nodes** - Entities can be scenes, Components are resources - **World management** - Central coordination of entities and systems - **ECS singleton** - Global access point for queries and processing - **Advanced queries** - Property-based filtering and relationship support - **Relationship system** - Define complex associations between entities ## 🎭 Entities ### Entity Fundamentals Entities are the core data containers you work with in GECS. They're Godot nodes extending `Entity.gd` that hold components and relationships. **Creating Entities in Code:** ```gdscript # Create entity class with components class_name MyEntity extends Entity func define_components() -> Array: return [C_Transform.new(), C_Velocity.new(Vector3.UP)] # Use the entity var e_my_entity = MyEntity.new() ECS.world.add_entity(e_my_entity) ``` **Entity Prefabs (Recommended):** Since GECS integrates with Godot, create scenes with Entity root nodes and save as `.tscn` files. These "prefabs" can include child nodes for visualization while maintaining ECS data organization. ```gdscript # e_player.gd - Entity prefab class_name Player extends Entity func on_ready(): # Sync transform from scene to component var c_trs = get_component(C_Transform) as C_Transform if not c_trs: return transform_comp.transform = self.global_transform # This works because the TSCN base type is Node3D and we extend Node3D with Entity (Which itself extends from Node) ``` ### Entity Lifecycle Entities have a managed lifecycle: 1. **Initialization** - Entity added to world, components loaded from `component_resources` 2. **define_components()** - Called to add components via code 3. **on_ready()** - Setup initial states, sync transforms 4. **on_destroy()** - Cleanup before removal 5. **on_disable()/on_enable()** - Handle enable/disable states > **Note:** In GECS v5.0+, entity logic should be handled by Systems, not in entity methods. Entities are pure data containers. ### Entity Naming Conventions **GECS follows consistent naming patterns throughout the framework:** - **Class names**: `ClassCase` representing the thing they are - **File names**: `e_entity_name.gd` using snake_case **Examples:** ```gdscript # e_player.gd class_name Player extends Entity # e_enemy.gd class_name Enemy extends Entity # e_projectile.gd class_name Projectile extends Entity # e_pickup_item.gd class_name PickupItem extends Entity ``` ### Entity as Glue Code Entities can serve as initialization and connection points: ```gdscript class_name Player extends Entity @onready var mesh_instance = $MeshInstance3D @onready var collision_shape = $CollisionShape3D func on_ready(): # Connect scene nodes to components var c_sprite = get_component(C_Sprite) if c_sprite: sprite_comp.mesh_instance = mesh_instance # Sync editor-placed transform to component var c_trs = get_component(C_Transform) if c_trs: transform_comp.transform = self.global_transform ``` ## 📦 Components ### Component Fundamentals Components are pure data containers - they store state but contain no game logic. They can emit signals for reactive systems. ```gdscript # c_health.gd - Example component class_name C_Health extends Component signal health_changed ## How much total health this entity has @export var maximum := 100.0 ## The current health value @export var current := 100.0 func _init(max_health: float = 100.0): maximum = max_health current = max_health ``` ### Component Design Principles **Data Only:** ```gdscript # ✅ Good - Pure data class_name C_Health extends Component @export var current: float = 100.0 @export var maximum: float = 100.0 @export var regeneration_rate: float = 1.0 ``` **No Game Logic:** ```gdscript # ❌ Avoid - Logic in components class_name C_Health extends Component @export var current: float = 100.0 func take_damage(amount: float): # This belongs in a system! current -= amount if current <= 0: print("Entity died!") ``` ### Component Naming Conventions **GECS uses a consistent C\_ prefix system:** - **Class names**: `C_ComponentName` in ClassCase - **File names**: `c_component_name.gd` in snake_case - **Organization**: Group by purpose in folders **Examples:** ```gdscript # c_health.gd class_name C_Health extends Component # c_transform.gd class_name C_Transform extends Component # c_velocity.gd class_name C_Velocity extends Component # c_user_input.gd class_name C_UserInput extends Component # c_sprite_renderer.gd class_name C_SpriteRenderer extends Component ``` **File Organization:** ``` components/ ├── gameplay/ │ ├── c_health.gd │ ├── c_damage.gd │ └── c_inventory.gd ├── physics/ │ ├── c_transform.gd │ ├── c_velocity.gd │ └── c_collision.gd └── rendering/ ├── c_sprite.gd └── c_mesh.gd ``` ### Adding Components **Via Editor (Recommended):** Add to entity's `component_resources` array in Inspector - these auto-load when entity is added to world. **Via define_components():** ```gdscript # e_player.gd - Define components programmatically class_name Player extends Entity func define_components() -> Array: return [ C_Health.new(100), C_Transform.new(), C_Input.new() ] # Via Inspector: Add to component_resources array # Components automatically loaded when entity added to world # Dynamic addition (less common): var entity = Player.new() entity.add_component(C_StatusEffect.new("poison")) ECS.world.add_entity(entity) ``` ## ⚙️ Systems ### System Fundamentals Systems contain game logic and process entities based on component queries. They should be small, atomic, and focused on one responsibility. Systems have two main parts: - **Query** - Defines which entities to process based on components/relationships - **Process** - The function that runs on entities ### System Types **Entity Processing:** ```gdscript class_name LifetimeSystem extends System func query() -> QueryBuilder: return q.with_all([C_Lifetime]) func process(entities: Array[Entity], components: Array, delta: float): # Process each entity - all systems use the same signature for entity in entities: var c_lifetime = entity.get_component(C_Lifetime) as C_Lifetime c_lifetime.lifetime -= delta if c_lifetime.lifetime <= 0: ECS.world.remove_entity(entity) ``` **Optimized Batch Processing with iterate():** ```gdscript class_name VelocitySystem extends System func query() -> QueryBuilder: # Use iterate() to get component arrays for faster access return q.with_all([C_Velocity]).iterate([C_Velocity]) func process(entities: Array[Entity], components: Array, delta: float): # components[0] contains all C_Velocity components var velocities = components[0] for i in entities.size(): # Direct array access is faster than get_component() var position: Vector3 = entities[i].transform.origin position += velocities[i].velocity * delta entities[i].transform.origin = position ``` ### Sub-Systems Group related logic into one system file - all subsystems use the unified signature: ```gdscript class_name DamageSystem extends System func sub_systems(): return [ # [query, callable] - all use same unified process signature [ q .with_all([C_Health, C_Damage]), damage_entities ], [ q .with_all([C_Health]) .with_none([C_Dead]) .iterate([C_Health]), regenerate_health ] ] func damage_entities(entities: Array[Entity], components: Array, delta: float): # Process entities with damage for entity in entities: var c_health = entity.get_component(C_Health) var c_damage = entity.get_component(C_Damage) c_health.current -= c_damage.amount entity.remove_component(c_damage) if c_health.current <= 0: entity.add_component(C_Dead.new()) func regenerate_health(entities: Array[Entity], components: Array, delta: float): # Batch process using component arrays from iterate() var healths = components[0] for i in entities.size(): healths[i].current = min(healths[i].current + 1 * delta, healths[i].maximum) ``` ### System Dependencies Control system execution order with dependencies: ```gdscript class_name RenderSystem extends System func deps() -> Dictionary[int, Array]: return { Runs.After: [MovementSystem, TransformSystem], # Run after these Runs.Before: [UISystem] # Run before this } # Special case: run after ALL other systems class_name TransformSystem extends System func deps() -> Dictionary[int, Array]: return { Runs.After: [ECS.wildcard] # Runs after everything else } ``` ### System Naming Conventions - **Class names**: `SystemNameSystem` in ClassCase (TransformSystem, PhysicsSystem) - **File names**: `s_system_name.gd` (s_transform.gd, s_physics.gd) ### System Lifecycle Systems follow Godot node lifecycle: - `setup()` - Initial setup after system is added to world - `process(entities, components, delta)` - Unified method called each frame for matching entities - System groups for organized processing order ## 🔍 Query System ### Query Builder GECS uses a fluent API for building entity queries: ```gdscript ECS.world.query .with_all([C_Health, C_Position]) # Must have all these components .with_any([C_Player, C_Enemy]) # Must have at least one of these .with_none([C_Dead, C_Disabled]) # Must not have any of these .with_relationship([r_attacking_player]) # Must have these relationships .without_relationship([r_fleeing]) # Must not have these relationships .with_reverse_relationship([r_parent_of]) # Must be target of these relationships .iterate([C_Health]) # Fetch these components and add to components array for quick iteration ``` ### Query Methods **Basic Query Operations:** ```gdscript var entities = query.execute() # Get matching entities var filtered = query.matches(entity_list) # Filter existing list var combined = query.combine(another_query) # Combine queries ``` ### Query Types Explained **with_all** - Entities must have ALL specified components: ```gdscript # Find entities that can move and be damaged q.with_all([C_Position, C_Velocity, C_Health]) ``` **with_any** - Entities must have AT LEAST ONE of the components: ```gdscript # Find players or enemies (anything controllable) q.with_any([C_Player, C_Enemy]) ``` **with_none** - Entities must NOT have any of these components: ```gdscript # Find living entities (exclude dead/disabled) q.with_all([C_Health]).with_none([C_Dead, C_Disabled]) ``` ### Component Property Queries Query based on component data values: ```gdscript # Find entities with low health q.with_all([{C_Health: {"current": {"_lt": 20}}}]) # Find fast-moving entities q.with_all([{C_Velocity: {"speed": {"_gt": 100}}}]) # Find entities with specific states q.with_all([{C_State: {"current_state": {"_eq": "attacking"}}}]) ``` **Supported Operators:** - `_eq` - Equal to - `_ne` - Not equal to - `_gt` - Greater than - `_lt` - Less than - `_gte` - Greater than or equal - `_lte` - Less than or equal - `_in` - Value in list - `_nin` - Value not in list ## 🔗 Relationships ### Relationship Fundamentals Relationships link entities together for complex associations. They consist of: - **Source** - Entity that has the relationship - **Relation** - Component defining the relationship type - **Target** - Entity or type being related to ```gdscript # Create relationship components class_name C_Likes extends Component class_name C_Loves extends Component class_name C_Eats extends Component @export var quantity: int = 1 # Create entities var e_bob = Entity.new() var e_alice = Entity.new() var e_heather = Entity.new() var e_apple = Food.new() # Add relationships e_bob.add_relationship(Relationship.new(C_Likes.new(), e_alice)) # bob likes alice e_alice.add_relationship(Relationship.new(C_Loves.new(), e_heather)) # alice loves heather e_heather.add_relationship(Relationship.new(C_Likes.new(), Food)) # heather likes food (type) e_heather.add_relationship(Relationship.new(C_Eats.new(5), e_apple)) # heather eats 5 apples ``` ### Relationship Queries **Specific Relationships:** ```gdscript # Any entity that likes alice ECS.world.query.with_relationship([Relationship.new(C_Likes.new(), e_alice)]) # Any entity that eats 5 apples ECS.world.query.with_relationship([Relationship.new(C_Eats.new(5), e_apple)]) # Any entity that likes the Food type ECS.world.query.with_relationship([Relationship.new(C_Likes.new(), Food)]) ``` **Wildcard Relationships:** ```gdscript # Any entity with any relation toward heather ECS.world.query.with_relationship([Relationship.new(ECS.wildcard, e_heather)]) # Any entity that likes anything ECS.world.query.with_relationship([Relationship.new(C_Likes.new(), ECS.wildcard)]) # Any entity with any relation to Enemy type ECS.world.query.with_relationship([Relationship.new(ECS.wildcard, Enemy)]) ``` **Reverse Relationships:** ```gdscript # Find entities that are being liked by someone ECS.world.query.with_reverse_relationship([Relationship.new(C_Likes.new(), ECS.wildcard)]) ``` ### Relationship Best Practices **Reuse Relationship Objects:** ```gdscript # Reuse for performance var r_likes_apples = Relationship.new(C_Likes.new(), e_apple) var r_attacking_players = Relationship.new(C_IsAttacking.new(), Player) # Consider a static relationships class class_name Relationships static func attacking_players(): return Relationship.new(C_IsAttacking.new(), Player) static func chasing_anything(): return Relationship.new(C_IsChasing.new(), ECS.wildcard) ``` ## 🌍 World Management ### World Lifecycle The World is the central manager for all entities and systems: ```gdscript # main.gd - Simple scene-based setup extends Node @onready var world: World = $World func _ready(): Bootstrap.bootstrap() # Initialize game-specific setup ECS.world = world # Systems are automatically registered via scene composition # Process systems by groups in order func _process(delta): world.process(delta, "run-first") # Initialization world.process(delta, "input") # Input handling world.process(delta, "gameplay") # Game logic world.process(delta, "ui") # UI updates world.process(delta, "run-last") # Cleanup func _physics_process(delta): world.process(delta, "physics") # Physics systems world.process(delta, "debug") # Debug systems ``` ### System Groups and Processing Order Organize systems using scene-based composition with execution groups: ``` default_systems.tscn Structure: ├── run-first (SystemGroup) │ ├── VictimInitSystem │ └── EcsStorageLoad ├── input (SystemGroup) │ ├── ItemSystem │ ├── WeaponsSystem │ └── PlayerControlsSystem ├── gameplay (SystemGroup) │ ├── GearSystem │ ├── DeathSystem │ └── EventSystem ├── physics (SystemGroup) │ ├── FrictionSystem │ ├── CharacterBody3DSystem │ └── TransformSystem ├── ui (SystemGroup) │ └── UiVisibilitySystem ├── debug (SystemGroup) │ └── DebugLabel3DSystem └── run-last (SystemGroup) ├── ActionsSystem └── PendingDeleteSystem ``` **Scene Setup Benefits:** - **Visual Organization**: See system hierarchy in Godot editor - **Easy Reordering**: Drag systems between groups - **Inspector Configuration**: Set system properties in editor - **Reusable Scenes**: Share system configurations between projects ## 🔄 Data-Driven Architecture ### Composition Over Inheritance Build entities by combining simple components rather than complex inheritance: ```gdscript # ✅ Composition approach in entity definition class_name Player extends Entity func define_components() -> Array: return [ C_Health.new(100), C_Movement.new(200.0), C_Input.new(), C_Inventory.new() ] # Same components reused for different entity types enemy.add_component(C_Health.new(50)) enemy.add_component(C_Movement.new(100.0)) enemy.add_component(C_AI.new()) enemy.add_component(C_Sprite.new("enemy.png")) ``` ### Modular System Design Keep systems small and focused: ```gdscript # ✅ Focused systems class_name MovementSystem extends System # Only handles position updates class_name CollisionSystem extends System # Only handles collision detection class_name HealthSystem extends System # Only handles health changes ``` This ensures: - **Easier debugging** - Clear separation of concerns - **Better reusability** - Systems work across different entity types - **Simplified testing** - Each system can be tested independently - **Performance optimization** - Systems can be profiled and optimized individually ## 🎯 Next Steps Now that you understand GECS's core concepts: 1. **Apply these patterns** in your own projects 2. **Experiment with relationships** for complex entity interactions 3. **Design component hierarchies** that support your game's needs 4. **Learn optimization techniques** in [Performance Guide](PERFORMANCE_OPTIMIZATION.md) 5. **Master common patterns** in [Best Practices Guide](BEST_PRACTICES.md) ## 📚 Related Documentation - **[Getting Started](GETTING_STARTED.md)** - Build your first ECS project - **[Best Practices](BEST_PRACTICES.md)** - Write maintainable ECS code - **[Performance Optimization](PERFORMANCE_OPTIMIZATION.md)** - Make your games run fast - **[Troubleshooting](TROUBLESHOOTING.md)** - Solve common issues --- _"Understanding ECS is about shifting from 'what things are' to 'what things have' and 'what operates on them.' This separation of data and logic is the key to scalable game architecture."_