Catalog Construction

Overview

GridPlacement’s catalog is the registry of available placeables — buildings, items, terrain props — that players can place, move, and demolish. This guide explains where catalogs come from, how they’re wired to the placement system, and where configuration belongs.

The Catalog Architecture

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
IPlacementProvider (Core interface — engine-agnostic addon contract)
└── GridPlacementProvider : Resource, IPlacementProvider (Godot drop-in)
    ├── IManipulationRuntimeCatalog Catalog → ManipulationRuntimeCatalog (auto-init)
    ├── AddCatalogEntry(ManipulationEntryData) → populates catalog
    ├── SetCatalog(IManipulationRuntimeCatalog) → replace catalog entirely
    └── GridConfiguration, InputBridge, ModeService

UnifiedCatalogLoader (Core loader — loads from Godot resource folders)
└── LoadFromEngineResources(folderPath) → .tres files in folder
└── LoadFromConfigFile(jsonPath) → JSON manifest

PlaceableDefinition (demo factory — creates ManipulationEntryData from code)
└── CreateChest(), CreateFence(), CreateWallSegment() → demo-specific definitions

PlacementSignalBus (Godot signals)
└── Emits: ManipulationEntrySelected, EntityPlaced, PreviewUpdated

Key principle: The view (UI) reads from the catalog. The composition root builds the catalog. The catalog is never built by the view.


Catalog Entry: ManipulationEntryData

Every placeable in the catalog is represented by a ManipulationEntryData:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class ManipulationEntryData
{
    public string Id { get; init; }           // "stone_foundation"
    public int IdHash { get; init; }          // StringHash.GetHash(Id)
    public string Name { get; init; }         // "Stone Foundation"
    public string Category { get; init; }     // "buildings"
    public string FilePath { get; init; }     // "res://placeables/stone_foundation.tres"
    public bool IsLinear { get; init; }       // true for fences/walls/roads
    public int TileSize { get; init; }        // world units
}

Three Ways to Populate the Catalog

Create a composition root in your game that owns the catalog lifecycle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyGameBootstrap.cs — your composition root (Node)
public partial class MyGameBootstrap : Node
{
    [Export] public GridPlacementProvider PlacementProvider;

    public override void _Ready()
    {
        base._Ready();

        // Initialize the provider (creates ECS store + PlacementBootstrap internally)
        PlacementProvider.Initialize();

        // Register placeables programmatically
        PlacementProvider.AddCatalogEntry(
            PlaceableDefinition.CreateChest().ToManipulationEntryData());

        PlacementProvider.AddCatalogEntry(
            PlaceableDefinition.CreateFence().ToManipulationEntryData());

        PlacementProvider.AddCatalogEntry(
            PlaceableDefinition.CreateWallSegment().ToManipulationEntryData());

        // Wire UI signals
        PlacementProvider.PlacementSignals.EntityPlaced += OnEntityPlaced;
    }
}

Where to put this: Your game’s MyGameBootstrap is the composition root. It owns:

  • Provider initialization
  • Catalog population
  • Signal wiring
  • Game-specific post-placement logic (deduct currency, trigger achievements, etc.)

Use UnifiedCatalogLoader to scan Godot .tres files from a folder:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// In your composition root
var loader = new UnifiedCatalogLoader(new CatalogLoadConfig
{
    EnginePlaceablesFolder = "res://placeables/",
    LoadFromEngineResources = true,
    MergeStrategy = ManipulationCatalogMergeStrategy.OverwriteExisting
});

var catalog = loader.Load();  // returns ManipulationRuntimeCatalog
PlacementProvider.SetCatalog(catalog);

The folder res://placeables/ should contain ManipulationEntryData resource files (.tres). This is useful for games with content that can be added/modified without code changes.

1
2
3
4
5
6
7
8
9
var loader = new UnifiedCatalogLoader(new CatalogLoadConfig
{
    ConfigFilePath = "res://config/placeables.json",
    LoadFromConfigFile = true,
    MergeStrategy = ManipulationCatalogMergeStrategy.MergeProperties
});

var catalog = loader.Load();
PlacementProvider.SetCatalog(catalog);

The JSON file references .tres paths or embeds full ManipulationEntryData definitions.


Where Configuration Lives

ConfigurationWhereWhy
Grid size[Export] GridSize on GridPlacementProviderInspector-friendly, per-instance
Catalog entriesComposition root (MyGameBootstrap)Game logic, not UI
Placeables folderCatalogLoadConfig.EnginePlaceablesFolderComposition root passes to UnifiedCatalogLoader
Validation rulesPlacementSettings.AddGameRules()Composition root wires rules
TileMap target[Export] TargetTileMap on PlacementBootstrapScene-specific wiring

GridPlacementProvider vs PlacementBootstrap

GridPlacementProvider is the addon drop-in for external developers. It implements IPlacementProvider and owns an internal PlacementBootstrap.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GridPlacementProvider (for addon consumers)
├── Creates PlacementBootstrap internally on Initialize()
├── Exposes IPlacementProvider surface: Catalog, InputBridge, ModeService, GridConfiguration
├── Auto-initializes ManipulationRuntimeCatalog (empty by default)
└── AddCatalogEntry() / SetCatalog() for population

PlacementBootstrap (internal plugin implementation)
├── Owns ECS systems: PlacementSystem, ManipulationSystem, GridOccupancySystem
├── Owns signal buses: PlacementSignals, ManipulationSignals
├── Used by composition roots that need deeper access
└── GridPlacementProvider wraps this for addon consumers

For most games: Use GridPlacementProvider. Drop it in your scene, configure grid size, and populate the catalog from your composition root.

For complex games needing direct ECS access: Use PlacementBootstrap directly via scene wiring.


Common Pattern: DemoPlacementBootstrap (Composition Root Reference)

The plugin’s demo uses DemoPlacementBootstrap as its composition root:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public partial class DemoPlacementBootstrap : Node
{
    [Export] public GridPlacementProvider Provider;

    private void RegisterPlaceables()
    {
        Provider.AddCatalogEntry(
            PlaceableDefinition.CreateChest().ToManipulationEntryData());
        Provider.AddCatalogEntry(
            PlaceableDefinition.CreateFence().ToManipulationEntryData());
        // ...
    }
}

This is the correct pattern — the composition root owns catalog construction. The UI (PlaceableSelectionUI) only:

  1. Displays items from Provider.Catalog
  2. Emits selection events via PlacementSignalBus.PublishManipulationEntrySelected()

Anti-Patterns to Avoid

❌ Building the catalog from the view

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// WRONG — PlaceableSelectionUI should not populate the catalog
public partial class PlaceableSelectionUI : Control
{
    [Export] public GridPlacementProvider Provider;

    private void LoadItems()
    {
        // This is view code — not its job to populate data
        Provider.AddCatalogEntry(item);
    }
}

The view should read from the catalog, not write to it.

❌ Wiring catalog loading inside a node’s _Ready()

1
2
3
4
5
6
// WRONG — input handling and catalog loading are separate concerns
public override void _Ready()
{
    var loader = new UnifiedCatalogLoader(...);
    Provider.SetCatalog(loader.Load());
}

Catalog loading belongs in your composition root, not in a node’s _Ready().



Last Updated: 2026-05-31