Files
godot-shader-experiments/addons/gecs/docs/RELATIONSHIPS.md
2026-01-15 15:27:48 +01:00

30 KiB

Relationships in GECS

Link entities together for complex game interactions

Relationships allow you to connect entities in meaningful ways, creating dynamic associations that go beyond simple component data. This guide shows you how to use GECS's relationship system to build complex game mechanics.

📋 Prerequisites

🔗 What are Relationships?

Think of components as the data that makes up an entity's state, and relationships as the links that connect entities to other entities, components, or types. Relationships can be simple links or carry data about the connection itself.

In GECS, relationships consist of three parts:

  • Source - Entity that has the relationship (e.g., Bob)
  • Relation - Component defining the relationship type (e.g., "Likes", "Damaged")
  • Target - What is being related to: Entity, Component instance, or archetype (e.g., Alice, FireDamage component, Enemy class)

🎯 Relationship Types

GECS supports three powerful relationship patterns:

1. Entity Relationships

Link entities to other entities:

# Bob likes Alice (entity to entity)
e_bob.add_relationship(Relationship.new(C_Likes.new(), e_alice))

2. Component Relationships

Link entities to component instances for type hierarchies:

# Entity has fire damage (entity to component)
entity.add_relationship(Relationship.new(C_Damaged.new(), C_FireDamage.new(50)))

3. Archetype Relationships

Link entities to classes/types:

# Heather likes all food (entity to type)
e_heather.add_relationship(Relationship.new(C_Likes.new(), Food))

This creates powerful queries like "find all entities that like Alice", "find all entities with fire damage", or "find all entities damaged by anything".

🎯 Core Relationship Concepts

Relationship Components

Relationships use components to define their type and can carry data:

# c_likes.gd - Simple relationship
class_name C_Likes
extends Component

# c_loves.gd - Another simple relationship
class_name C_Loves
extends Component

# c_eats.gd - Relationship with data
class_name C_Eats
extends Component

@export var quantity: int = 1

func _init(qty: int = 1):
    quantity = qty

Creating Relationships

# Create entities
var e_bob = Entity.new()
var e_alice = Entity.new()
var e_heather = Entity.new()
var e_apple = Food.new()

# Add to world
ECS.world.add_entity(e_bob)
ECS.world.add_entity(e_alice)
ECS.world.add_entity(e_heather)
ECS.world.add_entity(e_apple)

# Create 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

# Remove relationships
e_alice.remove_relationship(Relationship.new(C_Loves.new(), e_heather)) # alice no longer loves heather

# Remove with limits (NEW)
e_player.remove_relationship(Relationship.new(C_Poison.new(), null), 1)  # Remove only 1 poison stack
e_enemy.remove_relationship(Relationship.new(C_Buff.new(), null), 3)     # Remove up to 3 buffs
e_hero.remove_relationship(Relationship.new(C_Damage.new(), null), -1)   # Remove all damage (default)

🔍 Relationship Queries

Basic Relationship Queries

Query for Specific Relationships:

# Any entity that likes alice (type matching)
ECS.world.query.with_relationship([Relationship.new(C_Likes.new(), e_alice)])

# Any entity that eats apples (type matching)
ECS.world.query.with_relationship([Relationship.new(C_Eats.new(), e_apple)])

# Any entity that eats 5 or more apples (component query)
ECS.world.query.with_relationship([
    Relationship.new({C_Eats: {'quantity': {"_gte": 5}}}, e_apple)
])

# Any entity that likes the Food entity type
ECS.world.query.with_relationship([Relationship.new(C_Likes.new(), Food)])

Exclude Relationships:

# Entities with any relation toward heather that don't like bob
ECS.world.query
    .with_relationship([Relationship.new(ECS.wildcard, e_heather)])
    .without_relationship([Relationship.new(C_Likes.new(), e_bob)])

Wildcard Relationships

Use ECS.wildcard (or null) to query for any relation or target:

# 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)])
ECS.world.query.with_relationship([Relationship.new(C_Likes.new())])  # Omitting target = wildcard

# Any entity with any relation to Enemy entity type
ECS.world.query.with_relationship([Relationship.new(ECS.wildcard, Enemy)])

Component-Based Relationships

Link entities to component instances for powerful type hierarchies and data systems:

# Damage system using component targets
class_name C_Damaged extends Component
class_name C_FireDamage extends Component
    @export var amount: int = 0
    func _init(dmg: int = 0): amount = dmg

class_name C_PoisonDamage extends Component
    @export var amount: int = 0
    func _init(dmg: int = 0): amount = dmg

# Entity has multiple damage types
entity.add_relationship(Relationship.new(C_Damaged.new(), C_FireDamage.new(50)))
entity.add_relationship(Relationship.new(C_Damaged.new(), C_PoisonDamage.new(25)))

# Query for entities with any damage type (wildcard)
var damaged_entities = ECS.world.query.with_relationship([
    Relationship.new(C_Damaged.new(), null)
]).execute()

# Query for entities with fire damage >= 50 using component query
var high_fire_damaged = ECS.world.query.with_relationship([
    Relationship.new(C_Damaged.new(), {C_FireDamage: {"amount": {"_gte": 50}}})
]).execute()

# Query for entities with any fire damage (type matching)
var any_fire_damaged = ECS.world.query.with_relationship([
    Relationship.new(C_Damaged.new(), C_FireDamage)
]).execute()

Matching Modes

GECS relationships support two matching modes:

Type Matching (Default)

Matches relationships by component type, ignoring property values:

# Matches any C_Damaged relationship regardless of amount
entity.has_relationship(Relationship.new(C_Damaged.new(), target))

# Matches any fire damage effect by type
entity.has_relationship(Relationship.new(C_Damaged.new(), C_FireDamage.new()))

# Query for any entities with fire damage (type matching)
var any_fire_damaged = ECS.world.query.with_relationship([
    Relationship.new(C_Damaged.new(), C_FireDamage)
]).execute()

Component Query Matching

Match relationships by specific property criteria using dictionaries:

# Match C_Damaged relationships where amount >= 50
var high_damage = ECS.world.query.with_relationship([
    Relationship.new({C_Damaged: {'amount': {"_gte": 50}}}, target)
]).execute()

# Match fire damage with specific duration
var lasting_fire = ECS.world.query.with_relationship([
    Relationship.new(
        C_Damaged.new(),
        {C_FireDamage: {'duration': {"_gt": 5.0}}}
    )
]).execute()

# Match both relation AND target with queries
var strong_buffs = ECS.world.query.with_relationship([
    Relationship.new(
        {C_Buff: {'duration': {"_gt": 10}}},
        {C_Player: {'level': {"_gte": 5}}}
    )
]).execute()

When to Use Each:

  • Type Matching: Find entities with "any fire damage", "any buff of this type"
  • Component Queries: Find entities with exact damage amounts, specific buff durations, or property criteria

Component Queries in Relationships

Query relationships by specific property values using dictionaries:

# Query by relation property
var heavy_eaters = ECS.world.query.with_relationship([
    Relationship.new({C_Eats: {'amount': {"_gte": 5}}}, e_apple)
]).execute()

# Query by target component property
var high_hp_targets = ECS.world.query.with_relationship([
    Relationship.new(C_Targeting.new(), {C_Health: {'hp': {"_gte": 100}}})
]).execute()

# Query operators: _eq, _ne, _gt, _lt, _gte, _lte, _in, _nin, func
var special_damage = ECS.world.query.with_relationship([
    Relationship.new(
        {C_Damage: {'type': {"_in": ["fire", "ice"]}}},
        null
    )
]).execute()

# Complex multi-property queries
var critical_effects = ECS.world.query.with_relationship([
    Relationship.new(
        {C_Effect: {
            'damage': {"_gt": 20},
            'duration': {"_gte": 10.0},
            'type': {"_eq": "critical"}
        }},
        null
    )
]).execute()

Reverse Relationships

Find entities that are the target of relationships:

# Find entities that are being liked by someone
ECS.world.query.with_reverse_relationship([Relationship.new(C_Likes.new(), ECS.wildcard)])

# Find entities being attacked
ECS.world.query.with_reverse_relationship([Relationship.new(C_IsAttacking.new())])

# Find food being eaten
ECS.world.query.with_reverse_relationship([Relationship.new(C_Eats.new(), ECS.wildcard)])

🎛️ Limited Relationship Removal

Control exactly how many relationships to remove for fine-grained management

The remove_relationship() method now supports a limit parameter that allows you to control exactly how many matching relationships to remove. This is essential for stack-based systems, partial healing, inventory management, and fine-grained effect control.

Basic Syntax

entity.remove_relationship(relationship, limit)

Limit Values:

  • limit = -1 (default): Remove all matching relationships
  • limit = 0: Remove no relationships (useful for testing/validation)
  • limit = 1: Remove one matching relationship
  • limit > 1: Remove up to that many matching relationships

Core Use Cases

1. Stack-Based Systems

Perfect for buff/debuff stacks, damage over time effects, or any system where effects can stack:

# Poison stack system
class_name C_PoisonStack extends Component
@export var damage_per_tick: float = 5.0

# Apply poison stacks
entity.add_relationship(Relationship.new(C_PoisonStack.new(3.0), null))
entity.add_relationship(Relationship.new(C_PoisonStack.new(3.0), null))
entity.add_relationship(Relationship.new(C_PoisonStack.new(3.0), null))
entity.add_relationship(Relationship.new(C_PoisonStack.new(3.0), null))  # 4 poison stacks

# Antidote removes 2 poison stacks
entity.remove_relationship(Relationship.new(C_PoisonStack.new(), null), 2)
# Entity now has 2 poison stacks remaining

# Strong antidote removes all poison
entity.remove_relationship(Relationship.new(C_PoisonStack.new(), null))  # Default: remove all

2. Partial Healing Systems

Control damage removal for gradual healing or partial repair:

# Multiple damage sources on entity
entity.add_relationship(Relationship.new(C_Damage.new(), C_FireDamage.new(25)))
entity.add_relationship(Relationship.new(C_Damage.new(), C_FireDamage.new(15)))
entity.add_relationship(Relationship.new(C_Damage.new(), C_SlashDamage.new(30)))
entity.add_relationship(Relationship.new(C_Damage.new(), C_PoisonDamage.new(10)))

# Healing potion removes one damage source
entity.remove_relationship(Relationship.new(C_Damage.new(), null), 1)

# Fire resistance removes only fire damage (up to 2 sources)
entity.remove_relationship(Relationship.new(C_Damage.new(), C_FireDamage), 2)

# Full heal removes all damage
entity.remove_relationship(Relationship.new(C_Damage.new(), null))  # All damage gone

3. Inventory and Resource Management

Handle item stacks, resource consumption, and crafting materials:

# Item stack system
class_name C_HasItem extends Component
class_name C_HealthPotion extends Component
@export var healing_amount: int = 50

# Player has multiple health potions
entity.add_relationship(Relationship.new(C_HasItem.new(), C_HealthPotion.new(50)))
entity.add_relationship(Relationship.new(C_HasItem.new(), C_HealthPotion.new(50)))
entity.add_relationship(Relationship.new(C_HasItem.new(), C_HealthPotion.new(50)))
entity.add_relationship(Relationship.new(C_HasItem.new(), C_HealthPotion.new(50)))

# Use one health potion
entity.remove_relationship(Relationship.new(C_HasItem.new(), C_HealthPotion), 1)

# Vendor buys 2 health potions
entity.remove_relationship(Relationship.new(C_HasItem.new(), C_HealthPotion), 2)

# Drop all potions
entity.remove_relationship(Relationship.new(C_HasItem.new(), C_HealthPotion))

4. Buff/Debuff Management

Fine-grained control over temporary effects:

# Multiple speed buffs from different sources
entity.add_relationship(Relationship.new(C_Buff.new(), C_SpeedBuff.new(1.2, 10.0)))  # Boots
entity.add_relationship(Relationship.new(C_Buff.new(), C_SpeedBuff.new(1.5, 5.0)))   # Spell
entity.add_relationship(Relationship.new(C_Buff.new(), C_SpeedBuff.new(1.1, 30.0)))  # Passive

# Dispel magic removes one buff
entity.remove_relationship(Relationship.new(C_Buff.new(), null), 1)

# Mass dispel removes up to 3 buffs
entity.remove_relationship(Relationship.new(C_Buff.new(), null), 3)

# Purge removes all buffs
entity.remove_relationship(Relationship.new(C_Buff.new(), null))

Advanced Examples

Component Query + Limit Combination

Combine component queries with limits for precise control:

# Remove only high-damage effects (damage > 20), up to 2 of them
entity.remove_relationship(
    Relationship.new({C_Damage: {"amount": {"_gt": 20}}}, null),
    2
)

# Remove poison effects with duration < 5 seconds, limit to 1
entity.remove_relationship(
    Relationship.new({C_PoisonEffect: {"duration": {"_lt": 5.0}}}, null),
    1
)

# Remove fire damage with specific amount range, up to 3 instances
entity.remove_relationship(
    Relationship.new(
        C_Damage.new(),
        {C_FireDamage: {"amount": {"_gte": 10, "_lte": 50}}}
    ),
    3
)

# Remove all fire damage regardless of amount (no limit, type matching)
entity.remove_relationship(
    Relationship.new(C_Damage.new(), C_FireDamage),
    -1
)

# Remove buffs with specific multiplier, limit to 2
entity.remove_relationship(
    Relationship.new({C_Buff: {"multiplier": {"_gte": 1.5}}}, null),
    2
)

System Integration

Integrate limited removal into your game systems:

class_name HealingSystem extends System

func heal_entity(entity: Entity, healing_power: int):
    """Remove damage based on healing power"""
    if healing_power <= 0:
        return
    
    # Partial healing - remove damage effects based on healing power
    var damage_to_remove = min(healing_power, get_damage_count(entity))
    entity.remove_relationship(Relationship.new(C_Damage.new(), null), damage_to_remove)
    
    print("Healed ", damage_to_remove, " damage effects")

func get_damage_count(entity: Entity) -> int:
    return entity.get_relationships(Relationship.new(C_Damage.new(), null)).size()

class_name CleanseSystem extends System

func cleanse_entity(entity: Entity, cleanse_strength: int):
    """Remove debuffs based on cleanse strength"""
    match cleanse_strength:
        1:  # Weak cleanse
            entity.remove_relationship(Relationship.new(C_Debuff.new(), null), 1)
        2:  # Medium cleanse  
            entity.remove_relationship(Relationship.new(C_Debuff.new(), null), 3)
        3:  # Strong cleanse
            entity.remove_relationship(Relationship.new(C_Debuff.new(), null))  # All debuffs

class_name CraftingSystem extends System

func consume_materials(entity: Entity, recipe: Dictionary):
    """Consume specific amounts of crafting materials"""
    for material_type in recipe:
        var amount_needed = recipe[material_type]
        entity.remove_relationship(
            Relationship.new(C_HasMaterial.new(), material_type), 
            amount_needed
        )

Error Handling and Validation

The limit parameter provides built-in safeguards:

# Safe operations - won't crash if fewer relationships exist than requested
entity.remove_relationship(Relationship.new(C_Buff.new(), null), 100)  # Removes all available, won't error

# Validation operations
entity.remove_relationship(Relationship.new(C_Damage.new(), null), 0)  # Removes nothing - useful for testing

# Check before removal
var damage_count = entity.get_relationships(Relationship.new(C_Damage.new(), null)).size()
if damage_count > 0:
    entity.remove_relationship(Relationship.new(C_Damage.new(), null), min(3, damage_count))

Performance Considerations

Limited removal is optimized for efficiency:

# ✅ Efficient - stops searching after finding enough matches
entity.remove_relationship(Relationship.new(C_Effect.new(), null), 5)

# ✅ Still efficient - reuses the same removal logic
entity.remove_relationship(Relationship.new(C_Effect.new(), null), -1)  # Remove all

# ✅ Most efficient for single removals
entity.remove_relationship(Relationship.new(C_SpecificEffect.new(exact_data), target), 1)

Integration with Multiple Relationships

Works seamlessly with remove_relationships() for batch operations:

# Apply limit to multiple relationship types
var relationships_to_remove = [
    Relationship.new(C_Buff.new(), null),
    Relationship.new(C_Debuff.new(), null),
    Relationship.new(C_TemporaryEffect.new(), null)
]

# Remove up to 2 of each type
entity.remove_relationships(relationships_to_remove, 2)

🎮 Game Examples

Status Effect System with Component Relationships

This example shows how to build a flexible status effect system using component-based relationships:

# Status effect marker
class_name C_HasEffect extends Component

# Damage type components
class_name C_FireDamage extends Component
    @export var damage_per_second: float = 10.0
    @export var duration: float = 5.0
    func _init(dps: float = 10.0, dur: float = 5.0):
        damage_per_second = dps
        duration = dur

class_name C_PoisonDamage extends Component
    @export var damage_per_tick: float = 5.0
    @export var ticks_remaining: int = 10
    func _init(dpt: float = 5.0, ticks: int = 10):
        damage_per_tick = dpt
        ticks_remaining = ticks

# Buff type components  
class_name C_SpeedBuff extends Component
    @export var multiplier: float = 1.5
    @export var duration: float = 10.0
    func _init(mult: float = 1.5, dur: float = 10.0):
        multiplier = mult
        duration = dur

class_name C_StrengthBuff extends Component
    @export var bonus_damage: float = 25.0
    @export var duration: float = 8.0
    func _init(bonus: float = 25.0, dur: float = 8.0):
        bonus_damage = bonus
        duration = dur

# Apply various effects to entities
func apply_status_effects():
    # Player gets fire damage and speed buff
    player.add_relationship(Relationship.new(C_HasEffect.new(), C_FireDamage.new(15.0, 8.0)))
    player.add_relationship(Relationship.new(C_HasEffect.new(), C_SpeedBuff.new(2.0, 12.0)))
    
    # Enemy gets poison and strength buff
    enemy.add_relationship(Relationship.new(C_HasEffect.new(), C_PoisonDamage.new(8.0, 15)))
    enemy.add_relationship(Relationship.new(C_HasEffect.new(), C_StrengthBuff.new(30.0, 10.0)))

# Status effect processing system
class_name StatusEffectSystem extends System

func query():
    # Get all entities with any status effects
    return ECS.world.query.with_relationship([Relationship.new(C_HasEffect.new(), null)])

func process_fire_damage():
    # Find entities with any fire damage effect (type matching)
    var fire_damaged = ECS.world.query.with_relationship([
        Relationship.new(C_HasEffect.new(), C_FireDamage)
    ]).execute()

    for entity in fire_damaged:
        # Get the actual fire damage data using type matching
        var fire_rel = entity.get_relationship(
            Relationship.new(C_HasEffect.new(), C_FireDamage.new())
        )
        var fire_damage = fire_rel.target as C_FireDamage

        # Apply damage
        apply_damage(entity, fire_damage.damage_per_second * delta)

        # Reduce duration
        fire_damage.duration -= delta
        if fire_damage.duration <= 0:
            entity.remove_relationship(fire_rel)

func process_speed_buffs():
    # Find entities with speed buffs using type matching
    var speed_buffed = ECS.world.query.with_relationship([
        Relationship.new(C_HasEffect.new(), C_SpeedBuff)
    ]).execute()

    for entity in speed_buffed:
        # Get actual speed buff data using type matching
        var speed_rel = entity.get_relationship(
            Relationship.new(C_HasEffect.new(), C_SpeedBuff.new())
        )
        var speed_buff = speed_rel.target as C_SpeedBuff

        # Apply speed modification
        apply_speed_modifier(entity, speed_buff.multiplier)

        # Handle duration
        speed_buff.duration -= delta
        if speed_buff.duration <= 0:
            entity.remove_relationship(speed_rel)

func remove_all_effects_from_entity(entity: Entity):
    # Remove all status effects using wildcard
    entity.remove_relationship(Relationship.new(C_HasEffect.new(), null))

func remove_some_effects_from_entity(entity: Entity, count: int):
    # Remove a specific number of status effects using limit parameter
    entity.remove_relationship(Relationship.new(C_HasEffect.new(), null), count)

func cleanse_one_debuff(entity: Entity):
    # Remove just one debuff (useful for cleanse spells)
    entity.remove_relationship(Relationship.new(C_Debuff.new(), null), 1)

func dispel_magic(entity: Entity, power: int):
    # Dispel magic spell removes buffs based on power level
    match power:
        1: entity.remove_relationship(Relationship.new(C_HasEffect.new(), C_SpeedBuff), 1)    # Weak dispel - 1 speed buff
        2: entity.remove_relationship(Relationship.new(C_HasEffect.new(), null), 2)          # Medium dispel - 2 any effects  
        3: entity.remove_relationship(Relationship.new(C_HasEffect.new(), null))             # Strong dispel - all effects

func antidote_healing(entity: Entity, antidote_strength: int):
    # Antidote removes poison effects based on strength
    entity.remove_relationship(Relationship.new(C_HasEffect.new(), C_PoisonDamage), antidote_strength)

func partial_fire_immunity(entity: Entity):
    # Fire immunity spell removes up to 3 fire damage effects
    entity.remove_relationship(Relationship.new(C_HasEffect.new(), C_FireDamage), 3)

func get_entities_with_damage_effects():
    # Get entities with any damage type effect (fire or poison)
    var fire_damaged = ECS.world.query.with_relationship([
        Relationship.new(C_HasEffect.new(), C_FireDamage)
    ]).execute()
    
    var poison_damaged = ECS.world.query.with_relationship([
        Relationship.new(C_HasEffect.new(), C_PoisonDamage)
    ]).execute()
    
    # Combine results
    var all_damaged = {}
    for entity in fire_damaged:
        all_damaged[entity] = true
    for entity in poison_damaged:
        all_damaged[entity] = true
        
    return all_damaged.keys()

Combat System with Relationships

# Combat relationship components
class_name C_IsAttacking extends Component
@export var damage: float = 10.0

class_name C_IsTargeting extends Component
class_name C_IsAlliedWith extends Component

# Create combat entities
var player = Player.new()
var enemy1 = Enemy.new()
var enemy2 = Enemy.new()
var ally = Ally.new()

# Setup relationships
enemy1.add_relationship(Relationship.new(C_IsAttacking.new(25.0), player))
enemy2.add_relationship(Relationship.new(C_IsTargeting.new(), player))
player.add_relationship(Relationship.new(C_IsAlliedWith.new(), ally))

# Combat system queries
class_name CombatSystem extends System

func get_entities_attacking_player():
    var player = get_player_entity()
    return ECS.world.query.with_relationship([
        Relationship.new(C_IsAttacking.new(), player)
    ]).execute()

func get_high_damage_attackers():
    var player = get_player_entity()
    # Find entities attacking player with damage >= 20
    return ECS.world.query.with_relationship([
        Relationship.new({C_IsAttacking: {'damage': {"_gte": 20.0}}}, player)
    ]).execute()

func get_player_allies():
    var player = get_player_entity()
    return ECS.world.query.with_reverse_relationship([
        Relationship.new(C_IsAlliedWith.new(), player)
    ]).execute()

Hierarchical Entity System

# Hierarchy relationship components
class_name C_ParentOf extends Component
class_name C_ChildOf extends Component
class_name C_OwnerOf extends Component

# Create hierarchy
var parent = Entity.new()
var child1 = Entity.new()
var child2 = Entity.new()
var weapon = Weapon.new()

# Setup parent-child relationships
parent.add_relationship(Relationship.new(C_ParentOf.new(), child1))
parent.add_relationship(Relationship.new(C_ParentOf.new(), child2))
child1.add_relationship(Relationship.new(C_ChildOf.new(), parent))
child2.add_relationship(Relationship.new(C_ChildOf.new(), parent))

# Setup ownership
child1.add_relationship(Relationship.new(C_OwnerOf.new(), weapon))

# Hierarchy system queries
class_name HierarchySystem extends System

func get_children_of_entity(entity: Entity):
    return ECS.world.query.with_relationship([
        Relationship.new(C_ParentOf.new(), entity)
    ]).execute()

func get_parent_of_entity(entity: Entity):
    return ECS.world.query.with_reverse_relationship([
        Relationship.new(C_ParentOf.new(), entity)
    ]).execute()

🏗️ Relationship Best Practices

Performance Optimization

Reuse Relationship Objects:

# ✅ Good - Reuse for performance
var r_likes_apples = Relationship.new(C_Likes.new(), e_apple)
var r_attacking_players = Relationship.new(C_IsAttacking.new(), Player)

# Use the same relationship object multiple times
entity1.add_relationship(r_attacking_players)
entity2.add_relationship(r_attacking_players)

Static Relationship Factory (Recommended):

# ✅ Excellent - Organized relationship management
class_name Relationships

static func attacking_players():
    return Relationship.new(C_IsAttacking.new(), Player)

static func attacking_anything():
    return Relationship.new(C_IsAttacking.new(), ECS.wildcard)

static func chasing_players():
    return Relationship.new(C_IsChasing.new(), Player)

static func interacting_with_anything():
    return Relationship.new(C_Interacting.new(), ECS.wildcard)

static func equipped_on_anything():
    return Relationship.new(C_EquippedOn.new(), ECS.wildcard)

static func any_status_effect():
    return Relationship.new(C_HasEffect.new(), null)

static func any_damage_effect():
    return Relationship.new(C_Damage.new(), null)

static func any_buff():
    return Relationship.new(C_Buff.new(), null)

# Usage in systems:
var attackers = ECS.world.query.with_relationship([Relationships.attacking_players()]).execute()
var chasers = ECS.world.query.with_relationship([Relationships.chasing_anything()]).execute()

# Usage with limits:
entity.remove_relationship(Relationships.any_status_effect(), 1)  # Remove one effect
entity.remove_relationship(Relationships.any_damage_effect(), 3)  # Remove up to 3 damage effects
entity.remove_relationship(Relationships.any_buff())              # Remove all buffs

Limited Removal Best Practices:

# ✅ Good - Clear intent with descriptive variables
var WEAK_CLEANSE = 1
var MEDIUM_CLEANSE = 3
var STRONG_CLEANSE = -1  # All

entity.remove_relationship(Relationships.any_debuff(), WEAK_CLEANSE)

# ✅ Good - Helper functions for common operations
func remove_one_poison(entity: Entity):
    entity.remove_relationship(Relationship.new(C_Poison.new(), null), 1)

func remove_all_fire_damage(entity: Entity):
    entity.remove_relationship(Relationship.new(C_Damage.new(), C_FireDamage))

func partial_heal(entity: Entity, healing_power: int):
    entity.remove_relationship(Relationship.new(C_Damage.new(), null), healing_power)

# ✅ Excellent - Validation before removal
func safe_remove_effects(entity: Entity, count: int):
    var current_effects = entity.get_relationships(Relationship.new(C_Effect.new(), null)).size()
    var to_remove = min(count, current_effects)
    if to_remove > 0:
        entity.remove_relationship(Relationship.new(C_Effect.new(), null), to_remove)
        print("Removed ", to_remove, " effects")

Naming Conventions

Relationship Components:

  • Use descriptive names that clearly indicate the relationship
  • Follow the C_VerbNoun pattern when possible
  • Examples: C_Likes, C_IsAttacking, C_OwnerOf, C_MemberOf

Relationship Variables:

  • Use r_ prefix for relationship instances
  • Examples: r_likes_alice, r_attacking_player, r_parent_of_child

🎯 Next Steps

Now that you understand relationships, component queries, and limited removal:

  1. Design relationship schemas for your game's entities
  2. Experiment with wildcard queries for dynamic systems
  3. Use component queries to filter relationships by property criteria
  4. Implement limited removal for stack-based and graduated systems
  5. Combine type matching with component queries for flexible filtering
  6. Optimize with static relationship factories for better performance
  7. Use limit parameters for fine-grained control in healing, crafting, and effect systems
  8. Learn advanced patterns in Best Practices Guide

Quick Start Checklist for Component Queries:

  • Try basic component query: Relationship.new({C_Damage: {'amount': {"_gt": 10}}}, null)
  • Use query operators: _eq, _ne, _gt, _lt, _gte, _lte, _in, _nin
  • Query both relation and target properties
  • Combine queries with wildcards for flexible filtering
  • Use type matching for "any component of this type" queries

Quick Start Checklist for Limited Removal:

  • Try basic limit syntax: entity.remove_relationship(rel, 1)
  • Build a simple stack system (buffs, debuffs, or damage)
  • Create helper functions for common removal patterns
  • Integrate limits into your game systems (healing, cleansing, etc.)
  • Test edge cases (limit > available relationships)
  • Combine component queries with limits for precise control

"Relationships turn a collection of entities into a living, interconnected game world where entities can react to each other in meaningful ways."