Skip to content
ct

Grid Placement v6.0

Placement World Facts Provider

Connect game-owned occupancy, physics, or custom world facts to Grid Placement validation.

Status
Draft
Version
v6.0
Updated
Development docs generated from GDScript source

This is unreleased documentation in active development. APIs, class names, and behavior may change before the final release.

Use this guide when Grid Placement works, but your game needs extra placement truth from the world: occupied cells, reserved build zones, terrain restrictions, custom collision data, or facts from another system.

Version note: This guide is for Grid Placement 6.0.0 on the 2D TileMapLayer path. The provider described here is intentionally source-agnostic. It can be fed by Godot Physics, a project-owned occupancy grid, Avian 2D integration code, hand-authored data, or a test stub.


Beginner Mental Model

Grid Placement should answer:

What is the player trying to place?
Where is the target cell?
Which placement rules should run?
Should the preview/commit pass or fail?

Your game should answer:

What does my world know about this cell?
Is this cell blocked by occupancy, terrain, reserved zones, or game rules?
What short reason should be shown when it is blocked?

The bridge between those two questions is PlacementWorldFactsProvider2D.

Think of it as a small adapter object:

your game world / project data
→ PlacementWorldFactsProvider2D
→ ProviderCellBlockRule2D
→ Grid Placement preview and validation

This keeps Grid Placement reusable. The plugin does not need to know whether your collision truth comes from Godot Physics, Avian 2D, a project-owned occupancy grid, a save file, or a dictionary in a test.


When You Need This

You probably need a world facts provider when:

  • Existing collision rules are not enough for your game.
  • Your real occupancy data lives outside Godot collision objects.
  • Another system in your project decides whether a cell is blocked.
  • You want the same truth source for preview, validation, and placement reports.
  • You are integrating a game-specific data source without making the plugin depend on that source.

You probably do not need this for your first object placement. Start with Getting Started, make a simple object place successfully, then add a provider only when you have game-specific world facts to expose.


The Two Pieces

Piece Lives in Job
PlacementWorldFactsProvider2D Your project or plugin setup Reports whether a cell is blocked and why.
ProviderCellBlockRule2D Placement rules pipeline Reads the provider and turns blocked cells into validation issues.

PlacementWorldFactsProvider2D is permissive by default. If you do nothing, it blocks nothing. That keeps existing projects working.


Minimal Provider Example

Create a script in your game, for example res://placement/my_world_facts_provider.gd:

class_name MyWorldFactsProvider
extends PlacementWorldFactsProvider2D

var blocked_cells:Dictionary = {}

func block_cell(cell:Vector2i, reason:String)-> void:
    blocked_cells[cell]= reason

func is_cell_blocked(cell:Vector2i)-> bool:
    return blocked_cells.has(cell)

func get_block_reason(cell:Vector2i)-> String:
    return String(blocked_cells.get(cell,""))

This example uses a dictionary so the idea is easy to see. A real game might query a physics server, an occupancy grid, a building registry, or a simulation bridge instead.


Connecting the Provider to a Session

The provider is stored on GridTargetingState:

var provider:= MyWorldFactsProvider.new()
provider.block_cell(Vector2i(10,4),"occupied by town hall")

session.get_states().targeting.world_facts_provider_2d= provider

In an editor-composed scene, the exact place you assign this depends on how your scene is wired. The important part is that the active PlacementSession uses the GridTargetingState that has your provider assigned.

Beginner check:

print(session.get_states().targeting.world_facts_provider_2d)

If this prints <null>, the rule has no provider to ask and will not block anything.


Adding the Rule

Add ProviderCellBlockRule2D to the rule list that should use these facts.

Common places:

Location Use when
PlacementSettings.placement_rules Every placement should respect the provider.
PlacementProfile.placement_rules Only a category, such as buildings, should use it.
ScenePlacementEntry.placement_rules Only one specific placeable should use it.

For beginners, start on one ScenePlacementEntry. After you confirm it works, move it to a profile or global settings if it should apply broadly.


What the Player Sees

When the provider blocks a cell, ProviderCellBlockRule2D returns an issue like:

Placement blocked at cell (10, 4): occupied by town hall

Your UI should display placement issues from the placement report/action data. Do not duplicate the same provider check in UI code. The rule result is the source of truth for the preview and the final commit.


Example: Querying a Game Occupancy Registry

This pattern is useful when your game already tracks placed buildings by cell:

class_name BuildingRegistryFactsProvider
extends PlacementWorldFactsProvider2D

var building_registry:Node

func is_cell_blocked(cell:Vector2i)-> bool:
    if building_registry== null:
        return false
    return building_registry.has_building_at(cell)

func get_block_reason(cell:Vector2i)-> String:
    if is_cell_blocked(cell):
        return "occupied by another building"
    return ""

This avoids making Grid Placement depend on your BuildingRegistry class. The dependency stays in your game-owned adapter.


Example: Reading Cell Facts from Another System

If another part of your project owns collision truth, keep that source hidden behind the provider:

class_name ProjectWorldFactsProvider
extends PlacementWorldFactsProvider2D

var world_state:Node

func is_cell_blocked(cell:Vector2i)-> bool:
    if world_state== null:
        return false
    if world_state.has_method("is_cell_blocked"):
        return bool(world_state.call("is_cell_blocked", cell.x, cell.y))
    return false

func get_block_reason(cell:Vector2i)-> String:
    if is_cell_blocked(cell):
        return "blocked by world state"
    return ""

The important design rule is: put source-specific calls in your provider, not inside Grid Placement rules or UI scripts.

Possible sources include external physics engines, server-authoritative worlds, deterministic simulations, ECS storage, or test stubs. Use whichever your project already owns.


Beginner Checklist

  • First placement works without a custom provider.
  • Your provider returns true from is_cell_blocked(cell) for a known blocked cell.
  • Your provider returns a short, readable get_block_reason(cell) string.
  • The active session's targeting.world_facts_provider_2d points to your provider.
  • ProviderCellBlockRule2D is attached to the relevant settings/profile/entry.
  • Your UI shows PlacementReport issues instead of repeating provider logic.
  • You test both a blocked cell and an allowed cell.

Common Mistakes

Symptom Likely cause
Provider rule never blocks anything world_facts_provider_2d is not assigned on the active session.
Preview is red but commit succeeds UI or preview uses custom logic instead of the real rule path.
Commit fails but message is vague get_block_reason() returns an empty or unclear string.
Only some objects use the provider Rule is attached to one entry/profile, not global settings.
Game-specific classes leak into plugin scripts Backend calls were put in plugin code instead of the provider.

Related Guides