Chris' Tutorials
Docs/Grid Placement

Grid Placement v6.0

Capturing Tile Positions

Capture and store TileMapLayer grid coordinates when placing objects.

StatusDraft
Versionv6.0
UpdatedDevelopment docs generated from GDScript source

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

Draft — Unreleased:This page is in active development. APIs, class names, and behavior may change before the release is finalized. Use it as a preview of what's coming — not as a stable integration target.

This guide explains how to capture tile coordinates such as Vector2i(5, 3) when an object is placed with Grid Placement.

This is useful for:

  • isometric pathfinding and movement
  • occupancy maps
  • save/load of grid-aligned gameplay state
  • associating placed objects with one or more covered cells

Version note: This guide is for Grid Placement 6.0.0.


Accessing the Current Tile at Runtime

To get the tile coordinate currently hovered by the targeting system, read the active targeting state and convert the GridPositioner2D world position into a map cell.

# Inside a node that has been injected with the container.
func _process(_delta: float) -> void:
    var targeting_state: GridTargetingState = container.get_states().targeting
    var target_map: TileMapLayer = targeting_state.target_map
    var positioner: Node2D = targeting_state.positioner

    if positioner != null and target_map != null:
        var current_tile: Vector2i = PlacementPositioning2DUtils.get_tile_from_global_position(
            positioner.global_position,
            target_map
        )
        print("Hovering over tile: ", current_tile)

Capturing Positions from Placement Results

The most common time to capture a tile position is after an object is successfully placed.

For new code, listen to PlacementState.action_performed. The payload is PlacementActionData, and success/failure lives on data.report.

func _ready() -> void:
    var placement_state: PlacementState = container.get_building_state()
    placement_state.action_performed.connect(_on_action_performed)

func _on_action_performed(data: PlacementActionData) -> void:
    if data == null or data.report == null:
        return

    if not data.report.is_successful():
        return

    var placed: Node = data.report.placed
    var map: TileMapLayer = container.get_states().targeting.target_map
    if placed == null or map == null:
        return

    var tile: Vector2i = PlacementPositioning2DUtils.get_tile_from_global_position(
        placed.global_position,
        map
    )
    print("Object placed at: ", tile)

Do not read data.action, data.status, or data.placed; those are not the 6.0 PlacementActionData API. Read the PlacementReport stored at data.report.


Capturing Terrain Paint Cells

Terrain paint signals carry the target cell directly.

func _ready() -> void:
    var placement_state: PlacementState = container.get_building_state()
    placement_state.post_terrain_paint.connect(_on_post_terrain_paint)

func _on_post_terrain_paint(data: TerrainPaintData) -> void:
    print("Terrain %s painted at cell %s" % [data.terrain_name, data.target_cell])

For multi-cell brushes, use the terrain paint/removal data exposed by the terrain workflow you are consuming. Do not infer a whole brush result from the cursor cell alone.


Best Object Hook: pre_instance_added

PlacementState.pre_instance_added is emitted after the scene is instantiated but before it is added to the tree. This is the best place to stamp tile data onto the placed instance.

func _ready() -> void:
    var placement_state: PlacementState = container.get_building_state()
    placement_state.pre_instance_added.connect(_on_pre_instance_added)

func _on_pre_instance_added(node: Node2D) -> void:
    var map: TileMapLayer = container.get_states().targeting.target_map
    if map == null:
        return

    var tile: Vector2i = PlacementPositioning2DUtils.get_tile_from_global_position(
        node.global_position,
        map
    )
    node.set_meta("anchor_tile", tile)

This works well when your save system needs a stable anchor cell for each placed object.


Multi-Tile Footprint Pattern

If the placed object covers multiple cells, store an array of Vector2i values.

Option A: capture rule/indicator tiles

If your placement rules already generate the exact footprint you care about, use those tile positions.

func get_rule_tiles(rule: TileCheckRule) -> Array[Vector2i]:
    return rule.get_tile_positions()

Option B: derive from a rectangular footprint

func get_rect_tiles(map: TileMapLayer, center_world: Vector2, size_world: Vector2) -> Array[Vector2i]:
    return CollisionUtilities.get_rect_tile_positions(map, center_world, size_world)

Use collision/rule-derived footprints for production buildings when possible; a visual rectangle can include transparent pixels or decorative overhangs.


Save/Load Recommendation

PlaceableInstance.save() stores the instance name, transform, and placeable reference. It does not automatically store tile positions. If tile coordinates matter to your game state, merge them into your own save payload.

func save_with_tiles(instance: PlaceableInstance) -> Dictionary:
    var data := instance.save(true)
    var parent := instance.get_parent()

    if parent != null and parent.has_meta("anchor_tile"):
        data["anchor_tile"] = var_to_str(parent.get_meta("anchor_tile"))

    return data

On load, restore your custom anchor_tile after PlaceableInstance.instance_from_save() creates the object.


Related Guides

Source

docs/v6-0/guides/capturing-tile-positions.md

Plugin docs root:gdscript/plugins/grid_placement_dev/docs