Skip to content

TargetInformer UI Component

Status: Stable - Refactored in v5.1.0 to use view-only architecture

The TargetInformer component displays information about the current game object being targeted, manipulated, or built. It automatically shows the highest-priority object based on the game state with no need for manual state management.

TargetInformer reads from three authoritative state sources and displays information about whichever has the highest priority:

  1. Manipulation State (Highest Priority) - Active manipulated objects
  2. Building State (Medium Priority) - Building preview objects
  3. Targeting State (Lowest Priority) - Hovered/targeted objects

This design eliminates state synchronization issues by reading directly from the source rather than maintaining a separate copy.

TargetInformer is a pure view component that:

  • Reads from authoritative state sources
  • Never maintains independent state
  • Automatically updates when any state source changes
  • Computes display target on-demand via the get_display_target() method
## Returns the current display target based on priority:
## 1. Manipulation (highest) → 2. Building → 3. Targeting (lowest)
func get_display_target() -> Node:
# Priority 1: Manipulation
if _manipulation_state and _manipulation_state.get_active_root() != null:
return _manipulation_state.get_active_root()
# Priority 2: Building preview
if _building_state and _building_state.preview != null:
return _building_state.preview
# Priority 3: Targeting (lowest)
if _targeting_state:
return _targeting_state.get_target()
return null

All state changes trigger a unified _on_state_changed() handler:

func resolve_gb_dependencies(p_container: GBCompositionContainer) -> void:
# ... resolve state references ...
# Connect to state changes
if _manipulation_state:
_manipulation_state.active_manipulatable_changed.connect(_on_state_changed)
_manipulation_state.started.connect(_on_state_changed)
_manipulation_state.canceled.connect(_on_state_changed)
_manipulation_state.finished.connect(_on_state_changed)
if _building_state:
_building_state.preview_changed.connect(_on_state_changed)
if _targeting_state:
_targeting_state.target_changed.connect(_on_state_changed)

When any state changes, _on_state_changed() is called, which triggers refresh():

func _on_state_changed(_unused1 = null, _unused2 = null):
refresh()
func refresh():
var current = display_target
# If target changed, rebuild UI
if current != _last_display_target:
_last_display_target = current
setup_for_target(current)
return
# Update position every frame (for moving targets)
if _position_label and current is Node2D:
_position_label.text = _format_position(current)
# Create and add to scene
var informer = TargetInformer.new()
add_child(informer)
# Inject dependencies
var container = GBCompositionContainer.new()
informer.resolve_gb_dependencies(container)
# Now automatically displays highest-priority target
# No further manual updates needed
# Get the current display target
var target = informer.get_display_target()
# Target is automatically determined by priority:
# - Active manipulation has highest priority
# - Building preview is second priority
# - Targeted object is lowest priority
if target != null:
print("Displaying: %s" % target.name)

The TargetInfoSettings resource controls display formatting:

# Position display format
settings.position_format = "Position: (%s, %s)"
settings.position_decimals = 2 # Round to 2 decimal places

TargetInformer respects ModeState for visibility control:

# Automatically hides when mode is OFF
# Automatically shows when mode is ON
# (handled internally by _on_mode_changed handler)

The priority system determines what gets displayed:

Scenario: User is targeting object A, but picks up object B to manipulate
display_target returns: B (manipulation has priority over targeting)
After manipulation ends:
display_target returns: A (targeting becomes active again)

To change the priority order, modify the display_target property getter:

# Example: If you want Building to have highest priority
var display_target: Node:
get():
# Priority 1: Building preview (new highest)
if _building_state and _building_state.preview != null:
return _building_state.preview
# Priority 2: Manipulation
if _manipulation_state and _manipulation_state.get_active_root() != null:
return _manipulation_state.get_active_root()
# Priority 3: Targeting (lowest)
if _targeting_state:
return _targeting_state.get_target()
return null

TargetInformer displays the object name using GBObjectUtils.get_display_name():

# Override _to_string() on your objects for custom names:
class_name MyObject extends Node
func _to_string() -> String:
return "Custom Object Name"

For Node2D and Node3D objects, position is displayed:

# Uses settings.position_format and settings.position_decimals
# Format: "Position: (X, Y)"
# Example: "Position: (10.50, 20.75)"

TargetInformer creates labels in the info_parent container:

@export var info_parent: Control
# Automatically populates with:
# - _name_label: displays object name
# - _position_label: displays position (if applicable)

Problem: TargetInformer shows stale information

Solution: Ensure all state sources are properly injected. In runtime builds prefer non-failing checks and helpful logging:

# Verify states are assigned (runtime-safe checks)
if _manipulation_state == null or _building_state == null or _targeting_state == null:
push_warning("One or more Grid Building states not injected; ensure GBCompositionContainer is provided")

Problem: Display_target returns unexpected object

Solution: Check priority order. Highest-priority state source will always be shown:

# Debug: Check which state has priority
if _manipulation_state and _manipulation_state.get_active_root():
print("Manipulation active - will be displayed")
elif _building_state and _building_state.preview:
print("Building preview active - will be displayed")
elif _targeting_state and _targeting_state.get_target():
print("Targeting active - will be displayed")

Problem: Position label doesn’t appear

Checks:

  • Ensure target is Node2D or Node3D (not Node)
  • Verify TargetInfoSettings has valid position_format
  • Check that info_parent is properly configured
  • Description: Returns the highest-priority display target
  • Returns: The target to display, or null if no state source has a target
  • Type: Public
  • Priority: Manipulation > Building > Targeting

resolve_gb_dependencies(p_container: GBCompositionContainer)

Section titled “resolve_gb_dependencies(p_container: GBCompositionContainer)”
  • Description: Inject dependencies and connect to state signals
  • Parameters: Composition container with states and settings
  • Signal Connections: Connects all state source signals to unified handler
  • Description: Update display based on current state
  • Timing: Called every frame and on state changes
  • Behavior: Detects target changes and rebuilds UI if needed
  • Description: Build UI labels for the given target
  • Parameters: Target node to display (can be null)
  • Clears: All existing labels before creating new ones

_format_position(p_target: Node) -> String

Section titled “_format_position(p_target: Node) -> String”
  • Description: Format position based on settings
  • Parameters: Node2D or Node3D object
  • Returns: Formatted position string

Last Updated: October 2025 | Version: 5.1.0+