basic ECS spawner
This commit is contained in:
724
addons/gecs/docs/BEST_PRACTICES.md
Normal file
724
addons/gecs/docs/BEST_PRACTICES.md
Normal file
@@ -0,0 +1,724 @@
|
||||
# GECS Best Practices Guide
|
||||
|
||||
> **Write maintainable, performant ECS code**
|
||||
|
||||
This guide covers proven patterns and practices for building robust games with GECS. Apply these patterns to keep your code clean, fast, and easy to debug.
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
- Completed [Getting Started Guide](GETTING_STARTED.md)
|
||||
- Understanding of [Core Concepts](CORE_CONCEPTS.md)
|
||||
|
||||
## 🧱 Component Design Patterns
|
||||
|
||||
### Keep Components Pure Data
|
||||
|
||||
Components should only hold data, never logic or behavior.
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Pure data component
|
||||
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
|
||||
|
||||
func _init(max_health: float = 100.0):
|
||||
maximum = max_health
|
||||
current = max_health
|
||||
```
|
||||
|
||||
```gdscript
|
||||
# ❌ Avoid - Logic in components
|
||||
class_name C_Health
|
||||
extends Component
|
||||
|
||||
@export var current: float = 100.0
|
||||
@export var maximum: float = 100.0
|
||||
|
||||
# This belongs in a system, not a component
|
||||
func take_damage(amount: float):
|
||||
current -= amount
|
||||
if current <= 0:
|
||||
print("Entity died!")
|
||||
```
|
||||
|
||||
### Use Composition Over Inheritance
|
||||
|
||||
Build entities by combining simple components rather than complex inheritance hierarchies.
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Composable components via define_components() or scene setup
|
||||
class_name Player
|
||||
extends Entity
|
||||
|
||||
func define_components() -> Array:
|
||||
return [
|
||||
C_Health.new(100),
|
||||
C_Transform.new(),
|
||||
C_Input.new()
|
||||
]
|
||||
|
||||
class_name Enemy
|
||||
extends Entity
|
||||
|
||||
func define_components() -> Array:
|
||||
return [
|
||||
C_Health.new(50),
|
||||
C_Transform.new(),
|
||||
C_AI.new()
|
||||
]
|
||||
```
|
||||
|
||||
### Design for Configuration
|
||||
|
||||
Make components easily configurable through export properties.
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Configurable component
|
||||
class_name C_Movement
|
||||
extends Component
|
||||
|
||||
@export var speed: float = 100.0
|
||||
@export var acceleration: float = 500.0
|
||||
@export var friction: float = 800.0
|
||||
@export var max_speed: float = 300.0
|
||||
@export var can_fly: bool = false
|
||||
|
||||
func _init(spd: float = 100.0, can_fly_: bool = false):
|
||||
speed = spd
|
||||
can_fly = can_fly_
|
||||
```
|
||||
|
||||
## ⚙️ System Design Patterns
|
||||
|
||||
### Single Responsibility Principle
|
||||
|
||||
Each system should handle one specific concern.
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Focused systems
|
||||
class_name MovementSystem extends System
|
||||
func query(): return q.with_all([C_Position, C_Velocity])
|
||||
|
||||
class_name RenderSystem extends System
|
||||
func query(): return q.with_all([C_Position, C_Sprite])
|
||||
|
||||
class_name HealthSystem extends System
|
||||
func query(): return q.with_all([C_Health])
|
||||
```
|
||||
|
||||
### Use System Groups for Processing Order
|
||||
|
||||
Organize systems into logical groups using scene-based organization. Systems are grouped in scene nodes and processed in the correct order.
|
||||
|
||||
```gdscript
|
||||
# main.gd - Process systems in correct order
|
||||
func _process(delta):
|
||||
world.process(delta, "run-first") # Initialization systems
|
||||
world.process(delta, "input") # Input handling
|
||||
world.process(delta, "gameplay") # Game logic
|
||||
world.process(delta, "ui") # UI updates
|
||||
world.process(delta, "run-last") # Cleanup systems
|
||||
|
||||
func _physics_process(delta):
|
||||
world.process(delta, "physics") # Physics systems
|
||||
world.process(delta, "debug") # Debug systems
|
||||
```
|
||||
|
||||
### Early Exit for Performance
|
||||
|
||||
Return early from system processing when no work is needed.
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Early exit patterns
|
||||
class_name HealthRegenerationSystem extends System
|
||||
|
||||
func query():
|
||||
return q.with_all([C_Health]).with_none([C_Dead])
|
||||
|
||||
func process(entities: Array[Entity], components: Array, delta: float):
|
||||
for entity in entities:
|
||||
var health = entity.get_component(C_Health)
|
||||
|
||||
# Early exit if already at max health
|
||||
if health.current >= health.maximum:
|
||||
continue
|
||||
|
||||
# Apply regeneration
|
||||
health.current = min(health.current + health.regeneration_rate * delta, health.maximum)
|
||||
```
|
||||
|
||||
## 🏗️ Code Organization Patterns
|
||||
|
||||
### GECS Naming Conventions
|
||||
|
||||
```gdscript
|
||||
# ✅ GECS Standard naming patterns:
|
||||
|
||||
# Components: C_ComponentName class, c_component_name.gd file
|
||||
class_name C_Health extends Component # c_health.gd
|
||||
class_name C_Position extends Component # c_position.gd
|
||||
|
||||
# Systems: SystemNameSystem class, s_system_name.gd file
|
||||
class_name MovementSystem extends System # s_movement.gd
|
||||
class_name RenderSystem extends System # s_render.gd
|
||||
|
||||
# Entities: EntityName class, e_entity_name.gd file
|
||||
class_name Player extends Entity # e_player.gd
|
||||
class_name Enemy extends Entity # e_enemy.gd
|
||||
|
||||
# Observers: ObserverNameObserver class, o_observer_name.gd file
|
||||
class_name HealthUIObserver extends Observer # o_health_ui.gd
|
||||
```
|
||||
|
||||
### File Organization
|
||||
|
||||
Organize your ECS files by theme for better scalability:
|
||||
|
||||
```
|
||||
project/
|
||||
├── components/
|
||||
│ ├── ai/ # AI-related components
|
||||
│ ├── animation/ # Animation components
|
||||
│ ├── gameplay/ # Core gameplay components
|
||||
│ ├── gear/ # Equipment/gear components
|
||||
│ ├── item/ # Item system components
|
||||
│ ├── multiplayer/ # Multiplayer-specific
|
||||
│ ├── relationships/ # Relationship components
|
||||
│ ├── rendering/ # Visual/rendering
|
||||
│ └── weapon/ # Weapon system
|
||||
├── entities/
|
||||
│ ├── enemies/ # Enemy entities
|
||||
│ ├── gameplay/ # Core entities
|
||||
│ ├── items/ # Item entities
|
||||
│ └── ui/ # UI entities
|
||||
├── systems/
|
||||
│ ├── combat/ # Combat systems
|
||||
│ ├── core/ # Core ECS systems
|
||||
│ ├── gameplay/ # Gameplay systems
|
||||
│ ├── input/ # Input systems
|
||||
│ ├── interaction/ # Interaction systems
|
||||
│ ├── physics/ # Physics systems
|
||||
│ └── ui/ # UI systems
|
||||
└── observers/
|
||||
└── o_transform.gd # Reactive systems
|
||||
```
|
||||
|
||||
## 🎮 Common Game Patterns
|
||||
|
||||
### Player Character Pattern
|
||||
|
||||
```gdscript
|
||||
# e_player.gd
|
||||
class_name Player
|
||||
extends Entity
|
||||
|
||||
func on_ready():
|
||||
# Common pattern: sync scene transform to component
|
||||
if has_component(C_Transform):
|
||||
var transform_comp = get_component(C_Transform)
|
||||
transform_comp.transform = global_transform
|
||||
add_to_group("player")
|
||||
```
|
||||
|
||||
### Enemy Pattern
|
||||
|
||||
```gdscript
|
||||
# e_enemy.gd
|
||||
class_name Enemy
|
||||
extends Entity
|
||||
|
||||
func on_ready():
|
||||
# Sync transform and add to enemy group
|
||||
if has_component(C_Transform):
|
||||
var transform_comp = get_component(C_Transform)
|
||||
transform_comp.transform = global_transform
|
||||
add_to_group("enemies")
|
||||
```
|
||||
|
||||
## 🚀 Performance Best Practices
|
||||
|
||||
### Choose the Right Query Method ⭐ NEW!
|
||||
|
||||
**Query Performance Ranking** (v5.0.0-rc4+):
|
||||
|
||||
```gdscript
|
||||
# 🏆 FASTEST - Enabled/disabled queries (constant time)
|
||||
class_name ActiveEntitiesOnly extends System
|
||||
func query():
|
||||
return q.enabled(true) # ~0.05ms for any number of entities
|
||||
|
||||
# 🥈 EXCELLENT - Component queries (heavily optimized)
|
||||
class_name MovementSystem extends System
|
||||
func query():
|
||||
return q.with_all([C_Position, C_Velocity]) # ~0.6ms for 10K entities
|
||||
|
||||
# 🥉 GOOD - Use with_any strategically
|
||||
class_name DamageableSystem extends System
|
||||
func query():
|
||||
return q.with_any([C_Player, C_Enemy]).with_all([C_Health]) # ~5.6ms for 10K
|
||||
|
||||
# 🐌 AVOID - Group queries are slowest
|
||||
class_name PlayerSystem extends System
|
||||
func query():
|
||||
return q.with_group("player") # ~16ms for 10K entities
|
||||
# Better: q.with_all([C_Player])
|
||||
```
|
||||
|
||||
### Use iterate() for Batch Performance
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Batch processing with iterate()
|
||||
class_name TransformSystem
|
||||
extends System
|
||||
|
||||
func query():
|
||||
# Use iterate() to get component arrays
|
||||
return q.with_all([C_Transform]).iterate([C_Transform])
|
||||
|
||||
func process(entities: Array[Entity], components: Array, delta: float):
|
||||
# Batch access to components for better performance
|
||||
var transforms = components[0] # C_Transform array from iterate()
|
||||
for i in range(entities.size()):
|
||||
entities[i].global_transform = transforms[i].transform
|
||||
```
|
||||
|
||||
### Use Specific Queries
|
||||
|
||||
```gdscript
|
||||
# ✅ BEST - Combine enabled filter with components
|
||||
class_name ActivePlayerInputSystem extends System
|
||||
func query():
|
||||
return q.with_all([C_Input, C_Movement]).enabled(true)
|
||||
# Super fast: enabled filtering + component matching
|
||||
|
||||
# ✅ GOOD - Specific component query
|
||||
class_name ProjectileSystem extends System
|
||||
func query():
|
||||
return q.with_all([C_Projectile, C_Velocity]) # Fast and specific
|
||||
|
||||
# ❌ AVOID - Group-based queries (slow)
|
||||
class_name PlayerSystem extends System
|
||||
func query():
|
||||
return q.with_group("player") # Use q.with_all([C_Player]) instead
|
||||
|
||||
# ❌ AVOID - Overly broad queries
|
||||
class_name UniversalMovementSystem extends System
|
||||
func query():
|
||||
return q.with_all([C_Transform]) # Too broad - matches everything
|
||||
```
|
||||
|
||||
## 🎭 Entity Prefabs (Scene Files)
|
||||
|
||||
### Using Godot Scenes as Entity Prefabs
|
||||
|
||||
The most powerful pattern in GECS is using Godot's scene system (.tscn files) as entity prefabs. This combines ECS data with Godot's visual editor:
|
||||
|
||||
```
|
||||
e_player.tscn Structure:
|
||||
├── Player (Entity node - extends your e_player.gd class)
|
||||
│ ├── MeshInstance3D (visual representation)
|
||||
│ ├── CollisionShape3D (physics collision)
|
||||
│ ├── AudioStreamPlayer3D (sound effects)
|
||||
│ └── SkeletonAttachment3D (for equipment)
|
||||
```
|
||||
|
||||
**Benefits of Scene-based Prefabs:**
|
||||
|
||||
- **Visual Editing**: Design entities in Godot's 3D editor
|
||||
- **Component Assignment**: Set up ECS components in the Inspector
|
||||
- **Godot Integration**: Leverage existing Godot nodes and systems
|
||||
- **Reusability**: Instantiate the same prefab multiple times
|
||||
- **Version Control**: Scene files work well with git
|
||||
|
||||
**Setting up Entity Prefabs:**
|
||||
|
||||
1. **Create scene with Entity as root**: `e_player.tscn` with `Player` entity node.
|
||||
- Another trick here is to add a CharacterBody3d and then extend that CharacterBody3D with the e_player.gd script this way you get Entity class and CharacterBody3D class data
|
||||
2. **Add visual/physics children**: Add MeshInstance3D, CollisionShape3D, etc. as children
|
||||
3. **Configure components in Inspector**: Add components to the `component_resources` array
|
||||
4. **Save as reusable prefab**: Save the .tscn file for instantiation
|
||||
5. **Set up on_ready()**: Handle any initialization logic
|
||||
|
||||
### Component Assignment in Prefabs
|
||||
|
||||
**Method 1: Inspector Assignment (Recommended)**
|
||||
|
||||
Set up components directly in the Godot Inspector:
|
||||
|
||||
```gdscript
|
||||
# In e_player.tscn entity root node Inspector:
|
||||
# Component Resources array:
|
||||
# - [0] C_Health.new() (max: 100, current: 100)
|
||||
# - [1] C_Transform.new() (synced with scene transform)
|
||||
# - [2] C_Input.new() (for player controls)
|
||||
# - [3] C_LocalPlayer.new() (mark as local player)
|
||||
```
|
||||
|
||||
**Method 2: define_components() (Programmatic)**
|
||||
|
||||
```gdscript
|
||||
# e_player.gd attached to Player.tscn root
|
||||
class_name Player
|
||||
extends Entity
|
||||
|
||||
func define_components() -> Array:
|
||||
return [
|
||||
C_Health.new(100),
|
||||
C_Transform.new(),
|
||||
C_Input.new(),
|
||||
C_LocalPlayer.new()
|
||||
]
|
||||
|
||||
func on_ready():
|
||||
# Initialize after components are ready
|
||||
if has_component(C_Transform):
|
||||
var transform_comp = get_component(C_Transform)
|
||||
transform_comp.transform = global_transform
|
||||
add_to_group("player")
|
||||
```
|
||||
|
||||
**Method 3: Hybrid Approach**
|
||||
|
||||
```gdscript
|
||||
# Core components via Inspector, dynamic components via script
|
||||
func on_ready():
|
||||
# Sync scene transform to component
|
||||
if has_component(C_Transform):
|
||||
var transform_comp = get_component(C_Transform)
|
||||
transform_comp.transform = global_transform
|
||||
|
||||
# Add conditional components based on game state
|
||||
if GameState.is_multiplayer:
|
||||
add_component(C_NetworkSync.new())
|
||||
|
||||
if GameState.debug_mode:
|
||||
add_component(C_DebugInfo.new())
|
||||
```
|
||||
|
||||
### Instantiating Entity Prefabs
|
||||
|
||||
**Basic Spawning Pattern:**
|
||||
|
||||
```gdscript
|
||||
# Spawn system or main scene
|
||||
@export var player_prefab: PackedScene
|
||||
@export var enemy_prefab: PackedScene
|
||||
|
||||
func spawn_player(position: Vector3) -> Entity:
|
||||
var player = player_prefab.instantiate() as Entity
|
||||
player.global_position = position
|
||||
get_tree().current_scene.add_child(player) # Add to scene
|
||||
ECS.world.add_entity(player) # Register with ECS
|
||||
return player
|
||||
|
||||
func spawn_enemy(position: Vector3) -> Entity:
|
||||
var enemy = enemy_prefab.instantiate() as Entity
|
||||
enemy.global_position = position
|
||||
get_tree().current_scene.add_child(enemy)
|
||||
ECS.world.add_entity(enemy)
|
||||
return enemy
|
||||
```
|
||||
|
||||
**Advanced Spawning with SpawnSystem:**
|
||||
|
||||
```gdscript
|
||||
# s_spawner.gd
|
||||
class_name SpawnerSystem
|
||||
extends System
|
||||
|
||||
func query():
|
||||
return q.with_all([C_SpawnPoint])
|
||||
|
||||
func process(entities: Array[Entity], components: Array, delta: float):
|
||||
for entity in entities:
|
||||
var spawn_point = entity.get_component(C_SpawnPoint)
|
||||
|
||||
if spawn_point.should_spawn():
|
||||
var spawned = spawn_point.prefab.instantiate() as Entity
|
||||
spawned.global_position = entity.global_position
|
||||
get_tree().current_scene.add_child(spawned)
|
||||
ECS.world.add_entity(spawned)
|
||||
|
||||
spawn_point.mark_spawned()
|
||||
```
|
||||
|
||||
**Prefab Management Best Practices:**
|
||||
|
||||
```gdscript
|
||||
# Organize prefabs in preload statements
|
||||
const PLAYER_PREFAB = preload("res://entities/gameplay/e_player.tscn")
|
||||
const ENEMY_PREFAB = preload("res://entities/enemies/e_enemy.tscn")
|
||||
const WEAPON_PREFAB = preload("res://entities/items/e_weapon.tscn")
|
||||
|
||||
# Or use a prefab registry
|
||||
class_name PrefabRegistry
|
||||
|
||||
static var prefabs = {
|
||||
"player": preload("res://entities/gameplay/e_player.tscn"),
|
||||
"enemy": preload("res://entities/enemies/e_enemy.tscn"),
|
||||
"weapon": preload("res://entities/items/e_weapon.tscn")
|
||||
}
|
||||
|
||||
static func spawn(prefab_name: String, position: Vector3) -> Entity:
|
||||
var prefab = prefabs[prefab_name]
|
||||
var entity = prefab.instantiate() as Entity
|
||||
entity.global_position = position
|
||||
get_tree().current_scene.add_child(entity)
|
||||
ECS.world.add_entity(entity)
|
||||
return entity
|
||||
```
|
||||
|
||||
## 🏗️ Main Scene Architecture
|
||||
|
||||
### Scene Structure Pattern
|
||||
|
||||
Organize your main scene using the proven structure pattern:
|
||||
|
||||
```
|
||||
Main.tscn
|
||||
├── World (World node)
|
||||
├── DefaultSystems (Node - instantiated from default_systems.tscn)
|
||||
│ ├── run-first (Node - SystemGroup)
|
||||
│ │ ├── VictimInitSystem
|
||||
│ │ └── EcsStorageLoad
|
||||
│ ├── input (Node - SystemGroup)
|
||||
│ │ ├── ItemSystem
|
||||
│ │ ├── WeaponsSystem
|
||||
│ │ └── PlayerControlsSystem
|
||||
│ ├── gameplay (Node - SystemGroup)
|
||||
│ │ ├── GearSystem
|
||||
│ │ ├── DeathSystem
|
||||
│ │ └── EventSystem
|
||||
│ ├── physics (Node - SystemGroup)
|
||||
│ │ ├── FrictionSystem
|
||||
│ │ ├── CharacterBody3DSystem
|
||||
│ │ └── TransformSystem
|
||||
│ ├── ui (Node - SystemGroup)
|
||||
│ │ └── UiVisibilitySystem
|
||||
│ ├── debug (Node - SystemGroup)
|
||||
│ │ └── DebugLabel3DSystem
|
||||
│ └── run-last (Node - SystemGroup)
|
||||
│ ├── ActionsSystem
|
||||
│ └── PendingDeleteSystem
|
||||
├── Level (Node3D - for level geometry)
|
||||
└── Entities (Node3D - spawned entities go here)
|
||||
```
|
||||
|
||||
### Systems Setup in Main Scene
|
||||
|
||||
**Scene-based Systems Setup (Recommended)**
|
||||
|
||||
Use scene composition to organize systems. The default_systems.tscn contains all systems organized by execution groups:
|
||||
|
||||
```gdscript
|
||||
# main.gd - Simple main scene setup
|
||||
extends Node
|
||||
|
||||
@onready var world: World = $World
|
||||
|
||||
func _ready():
|
||||
Bootstrap.bootstrap() # Initialize any game-specific setup
|
||||
ECS.world = world
|
||||
# Systems are automatically registered via scene composition
|
||||
```
|
||||
|
||||
**Creating a Default Systems Scene:**
|
||||
|
||||
1. Create `default_systems.tscn` with system groups as Node children
|
||||
2. Add individual system scripts as children of each group
|
||||
3. Instantiate this scene in your main scene
|
||||
4. Systems are automatically discovered and registered by the World
|
||||
|
||||
### Processing Systems by Group
|
||||
|
||||
```gdscript
|
||||
# main.gd - Process systems in correct order
|
||||
extends Node3D
|
||||
|
||||
func _process(delta):
|
||||
if ECS.world:
|
||||
ECS.process(delta, "input") # Handle input first
|
||||
ECS.process(delta, "core") # Core logic
|
||||
ECS.process(delta, "gameplay") # Game mechanics
|
||||
ECS.process(delta, "render") # UI/visual updates last
|
||||
|
||||
func _physics_process(delta):
|
||||
if ECS.world:
|
||||
ECS.process(delta, "physics") # Physics systems
|
||||
```
|
||||
|
||||
## 🛠️ Common Utility Patterns
|
||||
|
||||
### Transform Synchronization
|
||||
|
||||
Common transform synchronization patterns:
|
||||
|
||||
```gdscript
|
||||
# Sync entity transform TO component (scene → component)
|
||||
static func sync_transform_to_component(entity: Entity):
|
||||
if entity.has_component(C_Transform):
|
||||
var transform_comp = entity.get_component(C_Transform)
|
||||
transform_comp.transform = entity.global_transform
|
||||
|
||||
# Sync component transform TO entity (component → scene)
|
||||
static func sync_component_to_transform(entity: Entity):
|
||||
if entity.has_component(C_Transform):
|
||||
var transform_comp = entity.get_component(C_Transform)
|
||||
entity.global_transform = transform_comp.transform
|
||||
|
||||
# Common usage in entity on_ready()
|
||||
func on_ready():
|
||||
sync_transform_to_component(self) # Sync scene position to C_Transform
|
||||
```
|
||||
|
||||
### Component Helpers
|
||||
|
||||
Build helpers for common component operations:
|
||||
|
||||
```gdscript
|
||||
# Helper functions you can add to your project
|
||||
static func add_health_to_entity(entity: Entity, max_health: float):
|
||||
var health = C_Health.new(max_health)
|
||||
entity.add_component(health)
|
||||
return health
|
||||
|
||||
static func damage_entity(entity: Entity, amount: float):
|
||||
if entity.has_component(C_Health):
|
||||
var health = entity.get_component(C_Health)
|
||||
health.current = max(0, health.current - amount)
|
||||
return health.current <= 0 # Return true if entity died
|
||||
return false
|
||||
```
|
||||
|
||||
## 🎛️ Relationship Management Best Practices
|
||||
|
||||
### Limited Removal Patterns
|
||||
|
||||
**Use Descriptive Constants:**
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Clear intent with constants
|
||||
const WEAK_CLEANSE = 1
|
||||
const MEDIUM_CLEANSE = 3
|
||||
const STRONG_CLEANSE = -1 # All
|
||||
|
||||
# ✅ Good - Stack-based constants
|
||||
const SINGLE_STACK = 1
|
||||
const PARTIAL_STACKS = 3
|
||||
const ALL_STACKS = -1
|
||||
|
||||
func cleanse_debuffs(entity: Entity, power: int):
|
||||
match power:
|
||||
1: entity.remove_relationship(Relations.any_debuff(), WEAK_CLEANSE)
|
||||
2: entity.remove_relationship(Relations.any_debuff(), MEDIUM_CLEANSE)
|
||||
3: entity.remove_relationship(Relations.any_debuff(), STRONG_CLEANSE)
|
||||
```
|
||||
|
||||
**Validate Before Removal:**
|
||||
|
||||
```gdscript
|
||||
# ✅ Excellent - Safe removal with validation
|
||||
func safe_partial_heal(entity: Entity, heal_amount: int):
|
||||
var damage_rels = entity.get_relationships(Relations.any_damage())
|
||||
if damage_rels.is_empty():
|
||||
print("Entity has no damage to heal")
|
||||
return
|
||||
|
||||
var to_heal = min(heal_amount, damage_rels.size())
|
||||
entity.remove_relationship(Relations.any_damage(), to_heal)
|
||||
print("Healed ", to_heal, " damage effects")
|
||||
|
||||
# ✅ Good - Helper function with built-in safety
|
||||
func remove_poison_stacks(entity: Entity, stacks_to_remove: int):
|
||||
if stacks_to_remove <= 0:
|
||||
return
|
||||
entity.remove_relationship(Relations.poison_effect(), stacks_to_remove)
|
||||
```
|
||||
|
||||
**System Integration Patterns:**
|
||||
|
||||
```gdscript
|
||||
# ✅ Excellent - Integration with game systems
|
||||
class_name StatusEffectSystem extends System
|
||||
|
||||
func process(entities: Array[Entity], components: Array, delta: float):
|
||||
# Example: process spell casting entities
|
||||
for entity in entities:
|
||||
var spell = entity.get_component(C_SpellCaster)
|
||||
if spell.is_casting_cleanse():
|
||||
process_cleanse_spell(entity, spell.target, spell.power)
|
||||
|
||||
func process_cleanse_spell(caster: Entity, target: Entity, spell_power: int):
|
||||
# Calculate cleanse strength based on spell power and caster stats
|
||||
var cleanse_strength = calculate_cleanse_strength(caster, spell_power)
|
||||
|
||||
# Apply graduated cleansing based on strength
|
||||
match cleanse_strength:
|
||||
1..3: target.remove_relationship(Relations.any_debuff(), 1)
|
||||
4..6: target.remove_relationship(Relations.any_debuff(), 2)
|
||||
7..9: target.remove_relationship(Relations.any_debuff(), 3)
|
||||
_: target.remove_relationship(Relations.any_debuff()) # Remove all
|
||||
|
||||
func process_antidote_item(user: Entity, antidote_strength: int):
|
||||
# Remove poison based on antidote quality
|
||||
user.remove_relationship(Relations.poison_effect(), antidote_strength)
|
||||
|
||||
# Remove poison resistance temporarily to prevent immediate repoison
|
||||
user.add_relationship(Relations.poison_immunity(), 5.0) # 5 second immunity
|
||||
|
||||
class_name InventorySystem extends System
|
||||
|
||||
func consume_item_stack(entity: Entity, item_type: Script, count: int):
|
||||
# Consume specific number of items from inventory
|
||||
entity.remove_relationship(
|
||||
Relationship.new(C_HasItem.new(), item_type),
|
||||
count
|
||||
)
|
||||
|
||||
func use_consumable(entity: Entity, item: Component, quantity: int = 1):
|
||||
# Use consumable items with quantity
|
||||
entity.remove_relationship(
|
||||
Relationship.new(C_HasItem.new(), item),
|
||||
quantity
|
||||
)
|
||||
```
|
||||
|
||||
**Performance Optimization:**
|
||||
|
||||
```gdscript
|
||||
# ✅ Good - Cache relationships for multiple operations
|
||||
func optimize_bulk_removal(entity: Entity):
|
||||
# Cache the relationship for reuse
|
||||
var poison_rel = Relations.poison_effect()
|
||||
var damage_rel = Relations.any_damage()
|
||||
|
||||
# Multiple targeted removals
|
||||
entity.remove_relationship(poison_rel, 2) # Remove 2 poison
|
||||
entity.remove_relationship(damage_rel, 1) # Remove 1 damage
|
||||
entity.remove_relationship(poison_rel, 1) # Remove 1 more poison
|
||||
|
||||
# ✅ Excellent - Batch removal patterns
|
||||
func batch_cleanup(entities: Array[Entity]):
|
||||
var cleanup_rel = Relations.temporary_effect()
|
||||
|
||||
for entity in entities:
|
||||
# Remove up to 3 temporary effects from each entity
|
||||
entity.remove_relationship(cleanup_rel, 3)
|
||||
```
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
Now that you understand best practices:
|
||||
|
||||
1. **Apply these patterns** in your projects
|
||||
2. **Learn advanced topics** in [Core Concepts](CORE_CONCEPTS.md)
|
||||
3. **Optimize performance** with [Performance Guide](PERFORMANCE_OPTIMIZATION.md)
|
||||
|
||||
**Need help?** [Join our Discord](https://discord.gg/eB43XU2tmn) for community discussions and support.
|
||||
|
||||
---
|
||||
|
||||
_"Good ECS code is like a well-organized toolbox - every component has its place, every system has its purpose, and everything works together smoothly."_
|
||||
Reference in New Issue
Block a user