Skip to content

Grid Placement v5.0.12

Grid-Aware Rotation

Implement grid-aware object rotation for isometric and cardinal-direction games.

Status
Legacy
Version
v5.0.12
Source updated
2026-09-07
Generated on
2026-10-30

6.3. These pages document the 6.3 release, where placeable is now entry (what changed) and PlacementOwner is deprecated (migration). Still on 6.2? Your version's guides ship inside the plugin itself, under addons/grid_placement/guides/; the 6.2 URLs on this site now redirect to this series.

Historical 5.x guide. This page describes a legacy API and is not current integration guidance. For new projects, start with Getting Started.

This guide explains how to implement and use grid-aware object rotation with GridPositioner2D, particularly for isometric games requiring 90-degree cardinal direction rotations.

Overview

The Grid Building plugin now includes comprehensive grid-aware rotation functionality through two main components:

  1. GBGridRotationUtils - Static utility class for grid-aligned rotation operations
  2. GridPositioner2D rotation integration - Keyboard input handling for rotating objects

Quick Start

1. Enable Rotation in Settings

# Configure your GridTargetingSettings
var targeting_settings:= GridTargetingSettings.new()
targeting_settings.enable_keyboard_input= true
targeting_settings.enable_rotation_input= true  # New setting for rotation

2. Configure Input Actions

Ensure your GBActions has rotation mappings defined:

var actions:= GBActions.new()
# Existing movement actions
actions.positioner_up= "ui_up"
actions.positioner_down= "ui_down"
actions.positioner_left= "ui_left"
actions.positioner_right= "ui_right"

# New rotation actions
actions.rotate_left= "rotate_left"     # Q key or custom action
actions.rotate_right= "rotate_right"   # E key or custom action

3. Set Rotation Target

The positioner will automatically determine what to rotate:

# Rotate a specific object
targeting_state.target= my_game_object

# Or rotate the positioner itself (default fallback)
targeting_state.target= null  # Will use positioner as rotation target

GBGridRotationUtils API Reference

Cardinal Direction Enum

enum CardinalDirection {
    NORTH = 0,# 0 degrees / up
    EAST = 1,# 90 degrees / right
    SOUTH = 2,# 180 degrees / down
    WEST = 3    # 270 degrees / left
}

Core Rotation Functions

Basic Direction Operations

# Convert between degrees and cardinal directions
var direction= GBGridRotationUtils.degrees_to_cardinal(90.0)# Returns EAST
var degrees= GBGridRotationUtils.cardinal_to_degrees(GBGridRotationUtils.CardinalDirection.SOUTH)# Returns 180.0

Node Rotation with Grid Alignment

# Rotate a Node2D while maintaining grid alignment
var new_direction= GBGridRotationUtils.rotate_node_clockwise(my_object, tile_map)
var new_direction= GBGridRotationUtils.rotate_node_counter_clockwise(my_object, tile_map)

# Set specific direction
GBGridRotationUtils.set_node_direction(my_object,GBGridRotationUtils.CardinalDirection.EAST, tile_map)

Movement and Utility Functions

# Human-readable strings
var name= GBGridRotationUtils.direction_to_string(GBGridRotationUtils.CardinalDirection.EAST)# "East"

Integration Examples

Example 1: Simple Isometric Building Rotation

extends Node2D

var building_object:Node2D
var tile_map:TileMapLayer

func _ready()-> void:
    setup_building_with_rotation()

func setup_building_with_rotation()-> void:
    # Create your building object
    building_object= create_isometric_building()
    add_child(building_object)

    # Set initial position and rotation
    building_object.global_position= Vector2(200,200)
    GBGridRotationUtils.set_node_direction(building_object,GBGridRotationUtils.CardinalDirection.NORTH, tile_map)

func _input(event:InputEvent)-> void:
    if eventis InputEventKey and event.pressed:
        match event.keycode:
            KEY_Q:# Rotate counter-clockwise
                var new_direction= GBGridRotationUtils.rotate_node_counter_clockwise(building_object, tile_map)
                print("Building rotated to: ",GBGridRotationUtils.direction_to_string(new_direction))

            KEY_E:# Rotate clockwise
                var new_direction= GBGridRotationUtils.rotate_node_clockwise(building_object, tile_map)
                print("Building rotated to: ",GBGridRotationUtils.direction_to_string(new_direction))

Example 2: GridPositioner2D with Custom Rotation Target

extends Node2D

var positioner:GridPositioner2D
var preview_object:Node2D

func setup_custom_rotation_target()-> void:
    # Create positioner with rotation enabled
    positioner= GridPositioner2D.new()
    add_child(positioner)

    # Configure for rotation
    var targeting_settings:= GridTargetingSettings.new()
    targeting_settings.enable_keyboard_input= true
    targeting_settings.enable_rotation_input= true

    # Create preview object that will rotate
    preview_object= create_preview_object()
    add_child(preview_object)

    # Set preview as rotation target
    var targeting_state:= GridTargetingState.new(GBOwnerContext.new())
    targeting_state.target= preview_object

    # Inject dependencies
    positioner._targeting_state= targeting_state
    positioner._targeting_settings= targeting_settings
    # ... other dependencies

# Override rotation target selection
func custom_get_rotation_target()-> Node2D:
    if Input.is_action_pressed("select_building_mode"):
        return preview_object# Rotate preview in building mode
    else:
        return positioner# Rotate positioner in other modes

Example 3: Programmatic Rotation with Animation

extends Node2D

func animate_rotation_to_direction(node:Node2D, target_direction:GBGridRotationUtils.CardinalDirection)-> void:
    var current_rotation= rad_to_deg(node.rotation)
    var target_rotation= GBGridRotationUtils.cardinal_to_degrees(target_direction)

    # Ensure shortest rotation path
    var rotation_diff= target_rotation- current_rotation
    if rotation_diff> 180:
        rotation_diff-= 360
    elif rotation_diff< -180:
        rotation_diff+= 360

    # Animate to target rotation
    var tween= create_tween()
    tween.tween_property(node,"rotation",deg_to_rad(current_rotation+ rotation_diff),0.3)
    await tween.finished

    # Snap to grid after animation
    if tile_map:
        GBGridRotationUtils._snap_node_to_grid(node, tile_map)

Isometric-Specific Features

The rotation system is designed with isometric games in mind:

  1. 90-degree increments - Perfect for isometric tile alignment
  2. Grid snapping - Maintains proper tile alignment after rotation
  3. Cardinal directions - North/East/South/West mapping works naturally with isometric layouts
  4. Direction deltas - Tile movement vectors that work with isometric coordinate systems

Isometric Direction Mapping

In isometric games, the cardinal directions typically map as follows:

  • North: Up-left diagonal (towards top of diamond)
  • East: Up-right diagonal (towards right point of diamond)
  • South: Down-right diagonal (towards bottom of diamond)
  • West: Down-left diagonal (towards left point of diamond)

The rotation utilities handle this naturally through the tile delta system and existing GBPositioning2DUtils integration.

Testing

The rotation system includes comprehensive unit tests:

# Run rotation utility tests
./run_tests_simple.sh godot/test/grid_placement_test/unit/gb_grid_rotation_utils_test.gd

Test coverage includes:

  • Cardinal direction conversion (degrees ↔ enum)
  • Node rotation with grid alignment

Best Practices

  1. Always use grid alignment - Call rotation functions with a valid TileMapLayer for proper snapping
  2. Test rotation targets - Verify the target object exists before rotating
  3. Provide user feedback - Use direction_to_string() for user-friendly rotation state display
  4. Handle edge cases - Check for null objects and valid tile maps in your rotation logic
  5. Combine with existing systems - Leverage existing GBPositioning2DUtils for movement after rotation

Migration from Manual Rotation

If you have existing manual rotation code, you can easily migrate to the grid-aware system:

# Old manual rotation
object.rotation+= PI/2  # 90 degrees

# New grid-aware rotation
var new_direction= GBGridRotationUtils.rotate_node_clockwise(object, tile_map)

The new system provides automatic grid alignment and consistent cardinal direction handling.

Troubleshooting

Common Issues

  1. Object not rotating: Check that enable_rotation_input is true in GridTargetingSettings
  2. Rotation off-grid: Ensure you're passing a valid TileMapLayer to rotation functions
  3. Wrong rotation target: Verify targeting_state.target points to the desired object
  4. Input not detected: Confirm rotate_left and rotate_right actions are properly mapped

Debug Output

Enable debug logging to see rotation events:

# GridPositioner2D will log rotation events when trace logging is enabled
var debug_settings:= GBDebugSettings.new()
debug_settings.enable_trace_logging= true

This will show rotation input detection and target selection in the console.

Using this in a game?

Grid Placement v6.3.0 — full source, playable demos, commercial license

The guide above is the same setup the shipped demos use. Try the build in your browser before you buy it.