Grid Positioner Setup (v5.0)

The GridPositioner is the core component that handles grid positioning, targeting, and coordinate system management for v5.0.0.

For a complete setup overview, see Getting Started Setup.

Overview

The GridPositioner manages the spatial relationship between world coordinates and grid coordinates, providing the foundation for all building and targeting operations.

Template Types

Standard Grid Positioner

  • File: grid_positioner_stack.tscn
  • Use Case: Top-down games, standard rectangular grids
  • Coordinate System: 2D Cartesian coordinates

Isometric Grid Positioner

  • File: grid_positioner_stack_isometric.tscn
  • Use Case: Isometric games, diamond-shaped grids
  • Coordinate System: Transformed isometric coordinates

Node Hierarchy

GridPositioner2D (Node2D) - Main positioning controller
├── TargetingShapeCast2D (ShapeCast2D)
│   ├── Shape: RectangleShape2D or custom indicator shape
│   ├── Script: targeting_shape_cast_2d.gd
│   └── Collision mask: 2048 (for grid detection)
└── ManipulationParent (Node2D)
    ├── Script: manipulation_parent.gd
    └── IndicatorManager (Node2D)
        └── Script: indicator_manager.gd

Note: GridTargetingSystem is a separate system node that lives in the Systems hierarchy, not as part of the GridPositioner stack.

Component Breakdown

GridPositioner2D

The main node that manages grid positioning and sits at the top of the positioning stack hierarchy:

Script: grid_positioner_2d.gd

Hierarchy Position:

GridPositioner2D (Top of positioning stack)
├── TargetingShapeCast2D (Child - handles collision detection)
└── ManipulationParent (Child - manages visual feedback)
    └── IndicatorManager (Grandchild - renders indicators)

Key Properties:

  • z_index: Rendering layer (typically 1000 for grid overlay)
  • Grid coordinate transformation logic
  • Integration with targeting and manipulation systems
  • Input processing toggle controls

Input Toggling: The GridPositioner2D controls whether mouse and keyboard input is processed for grid operations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Enable/disable mouse input for grid targeting
func set_mouse_input_enabled(enabled: bool):
    targeting_shape_cast_2d.enabled = enabled
    # Mouse input will be ignored when disabled

# Enable/disable keyboard input for grid operations  
func set_keyboard_input_enabled(enabled: bool):
    keyboard_input_enabled = enabled
    # Keyboard shortcuts will be ignored when disabled

# Toggle all grid input
func set_grid_input_enabled(enabled: bool):
    set_mouse_input_enabled(enabled)
    set_keyboard_input_enabled(enabled)

# Check current input state
func is_mouse_input_enabled() -> bool
func is_keyboard_input_enabled() -> bool

Core Functions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Convert world coordinates to grid coordinates
func world_to_grid(world_pos: Vector2) -> Vector2i

# Convert grid coordinates to world coordinates  
func grid_to_world(grid_pos: Vector2i) -> Vector2

# Check if a grid position is valid
func is_valid_grid_position(grid_pos: Vector2i) -> bool

# Input processing (only when enabled)
func _input(event: InputEvent):
    if not is_input_enabled():
        return
    # Process keyboard shortcuts and mouse events

Objects Under GridPositioner2D:

  • TargetingShapeCast2D: Direct child, handles mouse collision detection
  • ManipulationParent: Direct child, manages all visual feedback
  • IndicatorManager: Child of ManipulationParent, renders placement previews and validation feedback

TargetingShapeCast2D

Handles grid targeting and collision detection:

Script: targeting_shape_cast_2d.gd

Configuration:

  • Shape: Defines the targeting area
    • Standard: RectangleShape2D (16x16 pixels)
    • Isometric: Custom diamond shape
  • Collision Mask: 2048 (grid detection layer)
  • Target Position: Vector2(0, 0) for local targeting

Key Properties:

1
2
3
@export var shape: Shape2D
@export var collision_mask: int = 2048
@export var target_position: Vector2 = Vector2.ZERO

Functionality:

  • Detects grid cells under cursor
  • Provides targeting feedback
  • Integrates with selection systems

Visual Feedback

Visual grid feedback is handled by the IndicatorManager within ManipulationParent:

Script: indicator_manager.gd

Features:

  • Placement preview indicators
  • Target highlighting
  • Validation feedback (valid/invalid placement)
  • Cell boundary rendering

Customization:

  • Indicator colors for different states
  • Preview transparency
  • Highlight effects

ManipulationParent

Container for manipulation operations:

Script: manipulation_parent.gd

Purpose:

  • Manages object manipulation state
  • Coordinates with IndicatorManager
  • Handles drag and drop operations

Key Functions:

1
2
3
4
5
6
7
8
# Start manipulation at position
func start_manipulation(world_pos: Vector2, object: Node2D)

# Update manipulation during drag
func update_manipulation(world_pos: Vector2)

# Complete manipulation operation
func complete_manipulation()

IndicatorManager

Manages visual indicators for placement and manipulation:

Script: indicator_manager.gd

Responsibilities:

  • Show placement previews
  • Display validation feedback
  • Handle manipulation indicators
  • Manage indicator lifecycle

Setup Instructions

1. Choose Template Type

Select the appropriate template based on your game type:

1
2
3
4
5
# For top-down games
var grid_positioner = preload("res:///templates/grid_positioner/grid_positioner_stack.tscn").instantiate()

# For isometric games  
var grid_positioner = preload("res:///templates/grid_positioner/grid_positioner_stack_isometric.tscn").instantiate()

2. Add to Scene

  1. Create a GridPositioner node in your world
  2. Instance the chosen template
  3. Position appropriately in your scene hierarchy

3. Configure Targeting

Set up the TargetingShapeCast2D:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Access the targeting component
var targeting = grid_positioner.get_node("TargetingShapeCast2D")

# Configure collision layers
targeting.collision_mask = 2048  # Grid detection layer

# Set targeting shape if needed
var shape = RectangleShape2D.new()
shape.size = Vector2(16, 16)
targeting.shape = shape

4. Connect to Systems

Wire the GridPositioner to other systems:

1
2
3
4
5
6
7
8
# Connect to building system
building_system.grid_positioner = grid_positioner

# Connect to manipulation system
manipulation_system.grid_positioner = grid_positioner

# GridTargetingSystem (separate node in Systems hierarchy) will reference this
# GridPositioner for coordinate conversion and targeting operations

Input Toggling Usage

Common Scenarios

1. UI Menu Open:

1
2
3
4
5
6
7
8
# When opening a menu, disable grid input
func open_build_menu():
    grid_positioner.set_grid_input_enabled(false)
    # Show menu UI

func close_build_menu():
    grid_positioner.set_grid_input_enabled(true)
    # Hide menu UI

2. Cutscene or Dialog:

1
2
3
4
5
6
7
# Disable building during cutscenes
func start_cutscene():
    grid_positioner.set_mouse_input_enabled(false)
    grid_positioner.set_keyboard_input_enabled(false)

func end_cutscene():
    grid_positioner.set_grid_input_enabled(true)

3. Different Game States:

1
2
3
4
5
6
7
8
9
# Enable only keyboard input for keyboard-only mode
func enable_keyboard_only_mode():
    grid_positioner.set_mouse_input_enabled(false)
    grid_positioner.set_keyboard_input_enabled(true)

# Enable only mouse input for mouse-only mode
func enable_mouse_only_mode():
    grid_positioner.set_mouse_input_enabled(true)
    grid_positioner.set_keyboard_input_enabled(false)

Input State Management

1
2
3
4
5
6
7
8
# Check current state before processing
func handle_build_input():
    if not grid_positioner.is_mouse_input_enabled():
        return
    
    var mouse_pos = get_global_mouse_position()
    var grid_pos = grid_positioner.world_to_grid(mouse_pos)
    # Process building logic

Configuration Options

Grid Size

Configure grid cell size in your settings:

1
2
3
4
# In BuildingSettings
@export var grid_size: Vector2i = Vector2i(16, 16)

# This affects coordinate conversion and snapping

Coordinate Systems

Standard (Cartesian)

Grid: (0,0) (1,0) (2,0)
      (0,1) (1,1) (2,1)  
      (0,2) (1,2) (2,2)

World: Standard 2D coordinates

Isometric

Grid: Same coordinate system
World: Transformed for isometric view

Collision Layers

Set up proper collision layers:

1
2
3
4
5
6
# Layer 10 (bit 9): Grid detection
# Layer 11 (bit 10): Building objects
# Layer 12 (bit 11): Manipulation objects

# TargetingShapeCast2D mask
collision_mask = 1 << 9  # Only detect grid layer

Integration Examples

Top-Down Game Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Scene setup
World (Node2D)
├── GridPositioner (instance of grid_positioner_stack.tscn)
├── LevelContext (Node)
└── Buildings (Node2D)

# Configuration
var grid_positioner = $GridPositioner
grid_positioner.position = Vector2.ZERO

# Connect to level context
$LevelContext.target_map = $GroundTileMap

Isometric Game Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Scene setup with isometric transform
World (Node2D)
├── GridPositioner (instance of grid_positioner_stack_isometric.tscn)
├── Camera (Camera2D)
└── LevelContext (Node)

# Isometric-specific configuration
var grid_positioner = $GridPositioner
grid_positioner.rotation = PI / 6  # 30 degrees
grid_positioner.scale = Vector2(1.0, 0.577)  # Isometric aspect ratio

Custom Grid Positioners

Creating Custom Templates

  1. Copy an existing template
  2. Modify the hierarchy as needed
  3. Customize scripts for special behavior
  4. Save as new template

Custom Coordinate Systems

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Custom grid positioner script
extends "res:///systems/grid_targeting/grid_positioner/grid_positioner_2d.gd"

func world_to_grid(world_pos: Vector2) -> Vector2i:
    # Custom transformation logic
    var local_pos = to_local(world_pos)
    return Vector2i(
        int(local_pos.x / grid_size.x),
        int(local_pos.y / grid_size.y)
    )

func grid_to_world(grid_pos: Vector2i) -> Vector2:
    # Custom inverse transformation
    return to_global(Vector2(
        grid_pos.x * grid_size.x,
        grid_pos.y * grid_size.y
    ))

Performance Considerations

Optimization Tips

  1. Batch Operations: Process multiple grid operations together
  2. Spatial Partitioning: Use spatial data structures for large grids
  3. Lazy Updates: Only update visible portions of the grid
  4. Caching: Cache coordinate conversions when possible

Memory Management

1
2
3
4
5
# Clean up indicators when not needed
func cleanup_indicators():
    for indicator in active_indicators:
        indicator.queue_free()
    active_indicators.clear()

Troubleshooting

Common Issues

  1. Incorrect Coordinate Conversion

    • Check grid_size settings
    • Verify transform properties
    • Ensure proper parent hierarchy
  2. Targeting Not Working

    • Verify collision mask settings
    • Check layer assignments
    • Ensure TargetingShapeCast2D is enabled
  3. Visual Feedback Missing

    • Check IndicatorManager visibility
    • Verify indicator materials
    • Ensure proper z_index ordering
    • Check ManipulationParent is properly connected

Debug Tools

1
2
3
4
5
6
7
# Enable debug visualization
grid_positioner.debug_mode = true

# Log coordinate conversions
func debug_coordinates(world_pos: Vector2):
    var grid_pos = world_to_grid(world_pos)
    print("World: ", world_pos, " -> Grid: ", grid_pos)

Best Practices

1. Consistent Coordinate Usage

  • Always use grid coordinates for game logic
  • Convert to world coordinates only for rendering
  • Maintain coordinate system documentation

2. Proper Layer Management

  • Use dedicated layers for grid detection
  • Keep building objects on separate layers
  • Document layer assignments

3. Performance Monitoring

  • Monitor grid operation performance
  • Profile coordinate conversion functions
  • Optimize for target frame rate

4. Testing Coverage

  • Test coordinate edge cases
  • Verify targeting accuracy
  • Validate manipulation behavior

Advanced Features

Multi-Grid Support

1
2
3
4
5
6
7
# Support multiple grid types
class MultiGridPositioner extends GridPositioner2D:
    var grid_configs: Dictionary = {}
    
    func set_grid_type(type: String):
        current_config = grid_configs[type]
        update_grid_settings()

Dynamic Grid Resizing

1
2
3
4
5
# Resize grid at runtime
func resize_grid(new_size: Vector2i):
    grid_size = new_size
    update_coordinate_system()
    notify_systems_of_change()

Grid Validation

1
2
3
4
# Custom grid validation rules
func is_valid_placement(grid_pos: Vector2i, object: Resource) -> bool:
    # Check terrain, obstacles, etc.
    return check_terrain(grid_pos) and check_obstacles(grid_pos)

The GridPositioner serves as the foundation for all grid-based operations in v5.0.0, providing robust coordinate management and targeting capabilities for various game types and use cases.