Check out our latest project ✨ OpenChapter.io: free ebooks the way its meant to be 📖

TRUCO

An asset by Nitsuboy
The page banner background of a mountain and forest
TRUCO hero image

Quick Information

0 ratings
TRUCO icon image
Nitsuboy
TRUCO

TRUCO is a Godot 4 addon that provides a reusable ECS multiplayer framework purpose-built for card games. It ships as a drop-in plugin — enable it in Project Settings and you instantly get an ECS core (World, Entities, Components, Queries, Systems), a networked replicator for state synchronization, a turn engine, input handling (drag/drop/hover/zoom), zone management, player registry, and a rule validation system — all with zero game-specific coupling.The architecture is hybrid ECS + OOP: pure ECS for data-oriented gameplay logic (cards, zones, turns), and classic OOP for UI, scene tree management, and editor tooling. This avoids the ceremony of pure ECS while keeping its strengths — decoupled systems, data-driven entities, and straightforward networking via component serialization.

Supported Engine Version
4.5
Version String
1.0.0
License Version
MIT
Support Level
community
Modified Date
4 hours ago
Git URL
Issue URL

TRUCO: ECS Multiplayer Framework for Card Games

README Godot README Language README Status README Version README Architecture README Tests

TRUCO: Toolkit para Reutilização e Unificação de Componentes de Objetos para Jogos de Cartas

A Godot 4 addon providing an ECS multiplayer framework for card games with hybrid ECS + OOP architecture and clear core/game separation.


Installation

  1. Copy addons/truco/ into your project's addons/ directory
  2. Enable TRUCO in Project Settings → Plugins
  3. The plugin automatically registers these autoloads:
    • Conn — WebSocket connection management (ecs_connection.gd)
    • Players — Player registry (player_registry.gd)
    • Sync — Sync barrier (sync_barrier.gd)
    • Remote — Remote action RPC (remote_action.gd)
    • Zones — Zone registry (zone_registry.gd)

Architecture

Folder Structure

addons/truco/           # Addon — framework code only
├── core/               # Reusable framework for any card game
│   ├── ecs/            # ECS (Entity-Component-System)
│   ├── network/        # Multiplayer sync (Replicator)
│   ├── turn/           # Turn engine
│   ├── choice/         # Player choice system
│   ├── input/          # Input (drag, drop, hover, zoom)
│   ├── zone/           # Drop zones
│   ├── rule/           # Rule system (Rule, RulePack)
│   ├── card/           # Base card components
│   └── player/         # Player components
├── icons/              # Editor icons for custom types
├── truco.gd            # EditorPlugin
└── plugin.cfg          # Plugin config
scripts/game/           # Game-specific implementation
├── components/         # Game components
├── systems/            # Game systems
├── rules/              # Validation rules
├── card/               # Card visuals
└── ui/                 # Game UI

Core vs Game Separation

Directory Purpose Cannot depend on
addons/truco/core/ Reusable framework for any card game scripts/game/
scripts/game/ Game-specific implementation

Scene Overview

game.tscn
├── Game (game.gd) — initialization, game UI
├── WorldRunner (world_runner.gd)
│   ├── World (world.gd) — ECS core
│   ├── Replicator (replicator.gd) — multiplayer sync
│   ├── PlayerChoiceSystem — player choices
│   ├── InteractionSystem — input (drag, zoom)
│   ├── HoverSystem — card hover
│   ├── TurnMachine — turn engine
│   ├── ValidationSystem — play validation
│   ├── CardSpawnerSystem — card spawn
│   ├── DropSystem — drop detection
│   └── ... (game-specific systems)
└── front (CanvasLayer)
    └── ChoiceUI (dynamically instantiated)

ECS — Entity Component System

Overview

┌──────────────────────────────────────────────────────┐
│                    WorldRunner                        │
│  (Node — _ready injects world/replicator into children)│
│                                                       │
│  ├── World         ─── data (entities, components)    │
│  ├── SystemNode    ─── logic (child systems)          │
│  └── Replicator    ─── remote sync                    │
└──────────────────────────────────────────────────────┘

Layers

Layer Class File Purpose
Storage SparseSet ecs/storage/sparse_set.gd Dense array-of-structs per component type
Identity EntityManager ecs/storage/entity_manager.gd IDs with generational index
Orchestration World ecs/world.gd Facade: create/delete entities, add/remove/get components, queries
Query Query ecs/query.gd Iterate entities with component set
Component Component ecs/component.gd Resource with to_dict/from_dict serialization
System SystemNode ecs/system_node.gd Node with init_system, update, cleanup hooks

World

# Create entity
var e = world.create_entity()

# Components
world.add_component(e, MyComponent.new())
world.has_component(e, MyComponent)  # always check before get_component!
var c = world.get_component(e, MyComponent)
world.remove_component(e, MyComponent)

# Query
world.query([ComponentA, ComponentB]).for_each(func(e, comps):
    var a: ComponentA = comps[0]
    var b: ComponentB = comps[1]
)

Component Inheritance does NOT work

Storage is keyed by exact Script (_storages[component.get_script()]):

class Advanced extends BaseComponent
world.has_component(e, BaseComponent)  # false!
world.query([BaseComponent])           # does not find Advanced

Use composition — multiple components on the same entity.

Core Components

Component Purpose
CardComponent zone_id, face_up, play_order — universal card identifier
NodeRef Reference to visual Node in the scene tree
DragState Marks entity as being dragged
DraggableComponent Card can be dragged
ZoomableComponent Card can be zoomed

CardData

class CardData extends Resource:
    var components: Array[Component] = []

Serializable Resource. When creating a card, the spawner iterates card_data.components and adds each to the entity.

Lifecycle

1. game.tscn is instantiated
2. WorldRunner._ready()
   ├── Creates World
   ├── Injects world/replicator into child SystemNodes
   ├── Registers systems (world.register_system)
   └── Calls init_system() on each system

3. Server:
   ├── Creates entities, adds components
   ├── push_state() via Replicator → batch sent via RPC
   └── Client applies batch → emits batch_applied

4. Every frame:
   └── WorldRunner._process(delta) → sys.update(delta) for each system

5. Input:
   ├── Visual Node → emits world.events.on_card_input
   └── InteractionSystem receives → processes drag/zoom

Multiplayer / Sync

Replicator (core/network/replicator.gd)

Syncs ECS state via RPCs:

  1. Server modifies components locally
  2. Replicator.push_state() → creates a batch with the changes
  3. Batch sent via RPC to all clients
  4. Client receives and applies via _apply_batch()
  5. After applying, emits replicator.batch_applied

Systems MUST use replicator.batch_applied instead of local server events to ensure server and client process the same changes.

# Server: after modifying components
Replicator.push_state()

# Client: process changes
replicator.batch_applied.connect(_on_batch_applied)

Rule: use replicator.batch_applied, not local events

# ✅ Correct
func init_system() -> void:
    replicator.batch_applied.connect(_on_batch_applied)

# ❌ Incorrect — runs only on server, client sees nothing
func update(_delta: float) -> void:
    if not multiplayer.is_server():
        return

UDP Discovery (core/network/udp_discovery.gd)

LAN server discovery.


Turns

TurnMachine (core/turn/turn_machine.gd)

Generic turn engine:

  • Card locks — prevent interaction outside active turn
  • Hand visibility — show/hide active player's hand
  • Turn phases — managed by state

Game-specific systems extend the machine with custom sequences.


Validation

Rule System

Validation rules (not inline in systems):

class_name BaseRule extends RefCounted

func get_id() -> String:
    return "rule_id"

func validate(entity_id: int, target_zone: DropZone, context: Dictionary) -> bool:
    return true

RulePack

var rule_pack = RulePack.new()
rule_pack.rules = [
    preload("res://scripts/game/rules/my_rule.gd").new(),
]

Input

InteractionSystem (core/input/interaction_system.gd)

Input flow:

  1. card.gd detects mouse events → world.events.on_card_input(entity_id, event)
  2. InteractionSystem._on_card_input() decides if drag or zoom
  3. During drag, uses duck-typing: parent.move_card(ref.node)

The core cannot know about game classes (PlayerHand, Card). Uses duck-typing to call game methods:

var parent = ref.node.get_parent()
if parent and parent.has_method("move_card"):
    parent.move_card(ref.node)

Choices

PlayerChoiceSystem (core/choice/player_choice_system.gd)

Generic system for requesting choices from players:

  • request_choice(player_id, type, data) — server requests a choice
  • choice_received — emitted when player responds (or timeout)
  • choice_ui_requested — emitted for the game to open the appropriate UI
choice_sys.choice_ui_requested.connect(
    func(request_id: String, type: String, data: Dictionary):
        ChoiceUI.open(request_id, type, data, $front)
)

System Events

Event Emitter Purpose
on_card_input(entity_id, event) Card visual → world.events Card input
on_card_dropped(entity_id, dropzone) InteractionSystem → world.events Card dropped on zone
on_game_entity_ready(entity_id) GameSetupSystem → world.events Game entity created
choice_ui_requested(request_id, type, data) PlayerChoiceSystem Open choice UI
batch_applied(entries) Replicator Replication batch applied

Hotspots

File Function Modify To
core/ecs/world.gd ECS World Storage, queries, systems
core/ecs/storage/sparse_set.gd SparseSet Component storage
core/ecs/storage/entity_manager.gd EntityManager Entity IDs
core/ecs/component.gd Component New components
core/ecs/system_node.gd SystemNode System base
core/network/replicator.gd Replicator Multiplayer sync
core/turn/turn_machine.gd TurnMachine Turn engine
core/rule/rule.gd Rule Validation rules
core/choice/player_choice_system.gd PlayerChoiceSystem Player choices
core/input/interaction_system.gd InteractionSystem Card input
game/systems/ Game systems Game-specific logic

Requirements

  • Godot 4.x
  • GDScript

TRUCO is a Godot 4 addon that provides a reusable ECS multiplayer framework purpose-built for card games. It ships as a drop-in plugin — enable it in Project Settings and you instantly get an ECS core (World, Entities, Components, Queries, Systems), a networked replicator for state synchronization, a turn engine, input handling (drag/drop/hover/zoom), zone management, player registry, and a rule validation system — all with zero game-specific coupling.

The architecture is hybrid ECS + OOP: pure ECS for data-oriented gameplay logic (cards, zones, turns), and classic OOP for UI, scene tree management, and editor tooling. This avoids the ceremony of pure ECS while keeping its strengths — decoupled systems, data-driven entities, and straightforward networking via component serialization.

Reviews

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
TRUCO icon image
Nitsuboy
TRUCO

TRUCO is a Godot 4 addon that provides a reusable ECS multiplayer framework purpose-built for card games. It ships as a drop-in plugin — enable it in Project Settings and you instantly get an ECS core (World, Entities, Components, Queries, Systems), a networked replicator for state synchronization, a turn engine, input handling (drag/drop/hover/zoom), zone management, player registry, and a rule validation system — all with zero game-specific coupling.The architecture is hybrid ECS + OOP: pure ECS for data-oriented gameplay logic (cards, zones, turns), and classic OOP for UI, scene tree management, and editor tooling. This avoids the ceremony of pure ECS while keeping its strengths — decoupled systems, data-driven entities, and straightforward networking via component serialization.

Supported Engine Version
4.5
Version String
1.0.0
License Version
MIT
Support Level
community
Modified Date
4 hours ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers