mynamekse/godot-gecs

gecs-relationships

Use when connecting entities or adding link-attached data in GECS — Entity-Entity pairs, Entity-Component data links, Entity-Archetype relationships, wildcard queries, stacked removals (buffs/debuffs), and the Rels factory pattern

View source

How to use

  1. Copy the installation command and review the source package before installing it in your own agent environment.
  2. Check the source document for required tools, credentials and supported hosts.
  3. Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/mynamekse/godot-gecs --skill gecs-relationships

Before you start

  • Installation does not include a model subscription, API allowance or permission to access your files.
  • Repository stars and installation counts describe activity, not an independent quality or security test.
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

GECS Relationships in Godot 4.3+

Express links between entities as first-class pairs in GECS v9. A Relationship connects a Source Entity to a Target through a Relation Component. Relationships support arbitrary metadata, stacked instances (e.g. poison stacks), wildcard queries, and graduated cleansing.

Addon: GECS · version v9 · Godot 4.3+ · MIT Related skills: gecs-core for primitives, gecs-queries for relationship queries, gecs-observers for .on_relationship_added().

1. Anatomy of a Relationship

A relationship consists of 3 elements:

  1. Source: The entity that owns the relationship.
  2. Relation: A Component instance defining the relationship type (e.g., R_ChildOf, R_Attacking).
  3. Target: One of three target types:
  • Entity: Linking two entities (e.g., attacker -> target).
  • Component Instance: Storing link-attached data directly on the relationship (e.g., unit -> C_Burning.new(10.0)).
  • Archetype / Class Type: Categorical tagging (e.g., unit -> Food).
mermaid
flowchart LR
    E1["Source Entity (e.g., Player)"] -- "Relationship (R_Attacks)" --> E2["Target Entity (e.g., Boss)"]
    E1 -- "Relationship (R_StatusEffect)" --> C["Target Component (C_Poison.new(5s))"]
    E1 -- "Relationship (R_Likes)" --> A["Target Archetype (Food)"]

2. The Three Relationship Types

1. Entity → Entity Link

gdscript
# e_alice is attacking e_bob
e_alice.add_relationship(Relationship.new(R_Attacks.new(), e_bob))

2. Entity → Component Instance (Link Data)

Attach custom data directly to the connection without modifying the entity's direct archetype:

gdscript
# Apply 50 fire damage over 5 seconds directly to target
entity.add_relationship(Relationship.new(R_Damaged.new(), C_FireDamage.new(50.0, 5.0)))

3. Entity → Archetype

Tag relationships with high-level classifications:

gdscript
e_unit.add_relationship(Relationship.new(R_Likes.new(), Food))

3. Querying Relationships & Wildcards

Exact Match Queries

gdscript
# Match entities attacking e_boss
var attackers = ECS.world.query \
	.with_relationship([Relationship.new(R_Attacks.new(), e_boss)]) \
	.execute()

Wildcard Queries (ECS.wildcard)

gdscript
# 1. Any entity attacking ANY target (wildcard target)
var in_combat = ECS.world.query \
	.with_relationship([Relationship.new(R_Attacks.new(), ECS.wildcard)]) \
	.execute()

# 2. Any relationship targeting e_boss (wildcard relation)
var targeted = ECS.world.query \
	.with_relationship([Relationship.new(ECS.wildcard, e_boss)]) \
	.execute()

# 3. Omitting target defaults to wildcard:
var has_buff = ECS.world.query \
	.with_relationship([Relationship.new(R_Buff.new())]) \
	.execute()
Performance Rule: Wildcard queries scan multiple archetypes (O(N)). Always combine wildcards with a component filter like .with_all([C_Alive]) in performance-critical code!

4. Limited-Count Removal (Buffs, Debuffs & Stacks)

The remove_relationship() method accepts an optional limit argument:

  • limit = -1 (Default): Remove all matching relationships.
  • limit = N (N > 0): Remove up to N matching instances.
  • limit = 0: Removes none (used for validation).

Example: Poison Stacks & Antidote

gdscript
# Apply 4 poison stacks
for i in 4:
	entity.add_relationship(Relationship.new(R_Poison.new(), null))

# Minor antidote cleanses 2 stacks
entity.remove_relationship(Relationship.new(R_Poison.new(), null), 2)

# Verify remaining stacks: entity still has 2 poison stacks

Batch Cleansing

gdscript
# Remove up to 2 stacks across multiple negative effects
entity.remove_relationships([
	Relationship.new(R_Poison.new(), null),
	Relationship.new(R_Bleed.new(), null)
], 2)

5. The Rels Factory Pattern (Recommended)

To avoid allocating new relation objects repeatedly, create a static Rels helper class. See `examples/rels.gd`:

gdscript
class_name Rels

static var attacks: Relationship = Relationship.new(R_Attacks.new(), null)
static var child_of: Relationship = Relationship.new(R_ChildOf.new(), null)

static func attacks_target(target: Entity) -> Relationship:
	return Relationship.new(R_Attacks.new(), target)

static func child_of_entity(parent: Entity) -> Relationship:
	return Relationship.new(R_ChildOf.new(), parent)

Usage in gameplay code:

gdscript
if entity.has_relationship(Rels.attacks):
	pass

entity.remove_relationship(Rels.attacks, 1)

6. Naming Conventions

TypeClass NameFile Name
Relation ComponentR_ChildOf, R_Attacksr_child_of.gd, r_attacks.gd
Static RelationshipsRels.attacks_target(e)rels.gd
from this repository

More skills

All skills