Skip to content

Grid Targeting

Overview of the Grid Targeting subsystem and how cursor positions map to grid coordinates.

This page introduces the Grid Targeting subsystem used by the building workflow and explains how mouse/viewport positions become snapped grid coordinates other systems can use.

Grid Targeting is responsible for turning pointer input into a stable, snapped target that the building and validation pipelines can consume.

v5.0.0 Architectural Improvement: Clear separation between positioning and targeting:

Core Responsibilities:

  • Convert viewport/world coordinates to grid-space
  • Apply snapping rules (cell, edge, corner) and epsilon filtering to avoid flicker at boundaries
  • Expose the current target info for other systems (validation, indicators, placement)

See also: Building System Process

  1. Input is sampled from the active viewport (mouse/touch position)
  2. GridTargetingSystem computes a target using GridPositioner2D and settings from GridTargetingSettings
  3. Resulting target info is stored in GridTargetingState
  4. Downstream systems consume it:
    • Indicators highlight the hovered cell(s)
    • Placement validation evaluates rules against the target
    • Building system uses it to place or preview objects
  • World position → grid coords: sampling the world-space cursor and converting to integer cell coordinates based on configured cell size/origin.
  • Snapping modes: center-of-cell, edge-aligned, or corner-snapping depending on your chosen settings.
  • Epsilon filtering: small movement within a cell won’t constantly resignal target changes; this prevents UI flicker.

Most systems don’t compute targeting directly—they read it from state or receive updates.

  • Poll model/state: read the latest target info from GridTargetingState
  • Event-driven: connect to target update signals emitted by your coordination layer
  • Render-time: indicators query the state and render highlights for hovered cells

Typical consumers:

The TargetInformer is a dynamic UI component that displays object information with intelligent priority handling across different game states.

  • Responsive to Targeting: Shows info immediately when hovering over objects via GridTargetingState.target_changed signal
  • Manipulation Priority: Automatically switches to show manipulated objects when manipulation is active
  • Building Preview: Displays info for building preview objects during placement
  • Graceful Fallbacks: Handles missing settings and null targets without errors

The TargetInformer implements a three-tier priority system:

PriorityContextSignal SourceUse Case
1. HighestManipulationManipulationState.active_target_node_changedShows info for objects being moved/demolished
2. MiddleBuildingBuildingState.preview_changedShows info for building preview objects
3. LowestTargetingGridTargetingState.target_changedShows info for hovered objects (passive targeting)

Example Behavior:

# Scenario 1: Just hovering over objects
# Player moves mouse over smithy → TargetInformer shows "Smithy" info
# Scenario 2: Start manipulation
# Player selects smithy for move → TargetInformer switches to show manipulation info
# Scenario 3: Hover another object while manipulating
# Player hovers over house while still moving smithy
# TargetInformer STAYS on smithy (manipulation has priority)
# Scenario 4: End manipulation
# Player completes move → TargetInformer returns to showing hovered object

The TargetInformer connects automatically to all required signals when you call resolve_gb_dependencies():

var informer = TargetInformer.new()
informer.info_parent = Control.new() # Container for info labels
add_child(informer)
informer.add_child(informer.info_parent)
# Connect to composition container
informer.resolve_gb_dependencies(composition_container)
# Now responds to targeting, manipulation, and building state changes

The TargetInformer uses TargetInfoSettings for display formatting:

# These settings are typically configured in your GBSettings resource
target_info.position_format = "Position: (%s, %s)"
target_info.position_decimals = 2
# The TargetInformer automatically uses these settings when available
# Falls back to default formatting if settings are null

The TargetInformer works seamlessly with the Info mode toggle:

# When mode changes to INFO
mode_state.current = GBEnums.Mode.INFO
# TargetInformer becomes visible (if mode_state is connected)
# When mode changes to OFF
mode_state.current = GBEnums.Mode.OFF
# TargetInformer hides automatically

Override _to_string() on your object nodes to customize the display name:

# On your placeable object script
func _to_string() -> String:
return "Smithy (Level 2)" # Shows in TargetInformer

Use GridTargetingSettings to control:

  • Cell size and origin
  • Snapping mode and tolerance (epsilon)
  • Edge/corner behavior for non-centered snapping

These settings are typically provided via your composition container and shared with the GridPositioner2D.

  • Visualize: enable an indicator layer to color the active cell and neighbors.
  • Inspect text: add GridTargetingDebugText to render current cell, world pos, and snap mode.
  • Validate: run your rule set through PlacementValidator using the current target to confirm expected acceptance/rejection.

The Grid Targeting system controls when the positioner is visible based on multiple interaction factors:

  1. Mouse Input Enabled: When enable_mouse_input is true, positioner follows mouse and respects all visibility settings
  2. Mouse Input Disabled: When enable_mouse_input is false, positioner operates without mouse interaction and ignores mouse-specific visibility rules

The hide_on_handled setting provides smart visibility management:

Mouse Inputhide_on_handledUI Element HoveredPositioner Visible
✅ Enabled✅ true❌ No✅ Yes
✅ Enabled✅ true✅ Yes❌ No (hidden)
✅ Enabled❌ false❌ No✅ Yes
✅ Enabled❌ false✅ Yes✅ Yes (visible)
❌ Disabled✅ trueAny✅ Yes (ignores UI)
❌ Disabled❌ falseAny✅ Yes
# Example 1: Mouse-driven with auto-hide
settings.enable_mouse_input = true
settings.hide_on_handled = true
# Result: Positioner follows mouse, hides when hovering UI elements
# Example 2: Keyboard-only mode
settings.enable_mouse_input = false
settings.hide_on_handled = true # This setting is ignored
# Result: Positioner stays visible, controlled by keyboard/code
# Example 3: Always visible mode
settings.enable_mouse_input = true
settings.hide_on_handled = false
# Result: Positioner follows mouse, always visible even over UI