Godot pathfinding / 2026-07-12 / 12 min read
Verified as of Godot 4.7 stable; standalone source, 11-check verifier, extracted-package rerun, and published-file hash verified on 2026-07-12
How to Move Multi-Tile Units on a Grid in Godot
Move 2x2 and larger units on a Godot grid: anchor conventions, the map_to_local half-cell trap, touched-cell checks, rotation, and ownership.
The direct answer
To move a 2x2 or larger unit on a Godot grid, store one authoritative grid position: its anchor cell. Derive the occupied footprint and visual position from that anchor. Before changing it, check three separate things: the destination footprint fits, the transition doesn't touch blocked cells, and the cells required by your turn model aren't owned by another unit. Then commit the new anchor and animate toward the derived visual center.
The animation is never the source of truth.
This article uses the top-left occupied cell as the anchor, rectangular footprints measured with Vector2i, an orthogonal TileMapLayer, and one-cell steps. That matches the convention in the multi-size agent clearance guide and keeps every check in one coordinate system.
First choose the movement contract
“The unit is moving” means different things in different games. Pick the row that describes yours before deciding which cells must remain reserved.
| Your game | Movement contract |
|---|---|
| Sequential turn-based | Commit the destination anchor atomically. The animation is cosmetic, and no other move resolves until it finishes. |
| Queued or simultaneous turns | Resolve conflicts in a stable batch and reserve cells or edges by time step. A static destination claim isn't enough. |
| Real-time grid movement | Keep the grid state for planning, but validate continuous motion with ShapeCast2D or PhysicsDirectSpaceState2D.cast_motion(). |
Fix the map_to_local() half-cell offset
TileMapLayer.map_to_local() returns the centered local position of one cell. That's exactly right for a 1x1 unit and wrong for the visual center of a 2x2 unit.
If the sprite is placed at map_to_local(anchor), a 2x2 body appears half a cell up and left of the four cells it occupies. For a 1x1 footprint the offset below is zero. For 2x2 it is half a cell in both directions; for 3x2 it is one full cell horizontally and half a cell vertically.
The returned position is local to the TileMapLayer. Use to_global() only if the moving node lives in global space. local_to_map() returns the cell containing a local position, so don't treat it as an exact floating-point inverse at cell boundaries.
The Godot docs also note that map_to_local() ignores TileData.texture_origin. Keep logical grid placement independent from tile art; apply a separate explicit visual_offset when the unit artwork needs one. For isometric or half-offset grids, define a layout-specific mapping instead of reusing this orthogonal formula unchanged. The broader coordinate setup lives in the TileMapLayer-to-navigation-grid guide.
Check destination fit, transition geometry, and ownership separately
A legal endpoint doesn't automatically make a legal move. Before committing one step, run three distinct checks:
Fit is a property of a footprint at an anchor, not of an individual cell — transition cells get bounds and traversability checks instead.
- Destination fit: can the complete footprint occupy the new anchor? Use a square clearance check or validate every destination cell. For W x H rectangles or circular bodies, use the shape-aware clearance predicate. If several square sizes share one search grid, the Annotated A* implementation shows where that predicate enters the frontier.
- Transition geometry: are all conservatively touched cells inside the board and traversable?
- Ownership: are the destination or time-indexed cells available under the movement contract chosen above?
Cardinal steps touch the endpoint union
For a one-cell cardinal move, an axis-aligned rectangular body touches the union of its old and new footprints. A 2x2 unit stepping right touches a 3x2 block: six cells.
The delta guard matters. Without it, a helper named “swept cells” can quietly accept jumps or diagonals while returning only endpoint cells.
The downloadable footprint_movement.gd implements these same guarded rules in one helper and is exercised by the headless verifier.
Diagonal steps need a conservative touched-cell set
Consider a 2x2 unit moving from anchor (0, 0) to (1, 1). The endpoint footprints don't contain (2, 0) or (0, 2), but the sliding body intersects both cells during the move.
At grid-cell resolution, a conservative touched set for this one-cell diagonal translation is the bounding rectangle of the two footprints: a 3x3 block with nine cells. This is intentionally conservative; the exact continuous swept polygon isn't itself a rectangle.
If partially touched cells count as blocked, the 3x3 set is a simple deterministic contract. If the body moves continuously and should pass according to collision geometry rather than cell rules, use a Godot physics shape cast. Cardinal-only movement is also valid when it's a deliberate design constraint rather than a hidden workaround.

Ownership depends on when other moves resolve
For sequential tactical movement, commit the new anchor and destination footprint atomically, then animate the sprite. No other unit resolves a move during that animation, so the renderer doesn't redefine logical ownership frame by frame.
The reservation-table article ships a separate verified Godot project for that contract: destination claims, conflict ownership, undo, release, and 2x2 footprint reservations.
For queued or simultaneous movement, destination ownership isn't enough. Two units can swap positions, cross the same edge, or occupy the same swept region at different moments. Resolve requests in a deterministic batch and add a time dimension to reservations. Holding the endpoint union until an animation callback fires can be a conservative policy, but it isn't a universal substitute for space-time conflict resolution.
For real-time movement, reservations solve planning conflicts; they don't prove physical clearance along the animated trajectory. Use a shape sweep for that.
Rotation needs a declared pivot
A rectangular footprint changes dimensions when it turns. If the pivot is the top-left anchor, a 1x2 unit becomes 2x1 while keeping the same anchor. Validate the rotated footprint exactly as you would a move: bounds, traversability, clearance, and ownership.
That convention shifts the visual center. On 80x56 cells, the verified 1x2 to 2x1 example moves the center by (40, -28) pixels while the anchor stays fixed. That's the consequence of rotating around the top-left corner.
If the unit must rotate around its visual center, compute a new anchor as part of the rotation command. Some even/odd footprint combinations place that center between cells, so define the pivot and rounding rule before writing the move. Never rotate the sprite first and discover afterward that the new footprint is inside a wall.
What the verified Godot 4.7 proof covers
The source project contains the full helper, a runnable before/after scene, and a headless verifier. Its machine-readable receipt records 11 named checks on Godot 4.7 stable.
The checks cover 1x1, 2x2, 1x2 and 2x1 footprints, non-square tile dimensions, six-cell cardinal coverage, nine-cell conservative diagonal coverage, invalid moves, bounds, the top-left rotation contract, and runnable-scene smoke.
Measurement-gap: this article makes no performance or throughput claim, so it doesn't publish a benchmark or Dataset. Its evidence is the named correctness suite and reproducible source project. The receipt also states what the project doesn't prove: simultaneous reservation policy and exact continuous physics sweeps.
| Artifact | Verified state |
|---|---|
| Source ZIP | 8,272 bytes · SHA-256 recorded in the receipt |
| Headless checks | 11 passed · 0 failed · Godot 4.7 stable |
| Package rerun | Final ZIP extracted; bundled parser and verifier passed |
| Evidence boundary | Discrete correctness only · no performance or continuous-physics claim |
Common mistakes
Centering the sprite on map_to_local(anchor). That's the center of one cell, not an even-sized footprint.
Keeping both a sprite position and an anchor authoritative. They disagree after an interrupted tween, load, or replay. Store the anchor and derive the visual position.
Using size: int before adding rectangular rotation. A square-only model can't represent 1x2 becoming 2x1.
Checking only the destination. A diagonal body can touch cells outside both endpoint footprints.
Calling every touched cell a clearance check. Clearance answers whether a footprint fits at an anchor; transition cells need bounds and traversability checks.
Holding old cells until animation finishes without defining the turn model. That can be unnecessarily restrictive for sequential turns and still insufficient for simultaneous edge or time conflicts.
Rotating without naming the pivot. Keeping the same anchor means top-left pivot, not center pivot.
Applying orthogonal tile-size arithmetic to isometric grids. Define a layout-specific mapping instead.
Frequently asked questions
How do I move a 2x2 unit on a grid in Godot?
Store its top-left anchor as the authoritative grid position. Derive the four occupied cells and the sprite's footprint center from that anchor. Before a one-cell move, validate destination fit, conservative transition cells, and the ownership required by your turn model. Commit the new anchor, then animate the sprite to the derived center.
Why is my 2x2 unit offset by half a cell?
TileMapLayer.map_to_local() returns the center of the anchor cell, not the center of the complete footprint. On an orthogonal grid, add cell_size * Vector2(footprint_size - Vector2i.ONE) * 0.5.
Should a multi-tile unit reserve both its old and new cells while moving?
Only if the movement contract requires it. In a sequential turn system where no other move resolves during animation, atomic destination ownership can be enough. Simultaneous movement needs time-aware conflict resolution. Real-time collision needs a physics shape sweep.
How do I stop a 2x2 unit clipping a corner diagonally?
Validate a conservative touched-cell set, not just the endpoint footprints. For a 2x2 unit moving one cell diagonally, that set is the 3x3 bounding rectangle of the endpoints. For exact continuous collision, use ShapeCast2D or cast_motion().
How do I rotate a 1x2 unit into a 2x1 footprint?
Swap the footprint dimensions and validate the result before committing it. If the anchor stays fixed, the pivot is top-left and the visual center moves. If the center must stay fixed, calculate and validate a new anchor as part of the rotation.
Do I need both clearance and reservations for multi-tile units?
Yes. Clearance answers whether this footprint can fit at an anchor. Reservations answer whether another unit owns the required cells now or at a particular time step. They solve different failures.