This is the source of truth for authoring a 2D ScenePlacementEntry's packed scene. Use Getting Started for runtime wiring and Troubleshooting for symptom-driven diagnosis.
Scene root is the placement origin
Grid Placement seats the packed scene's root on the targeted cell. Placement replaces the root's saved position, so moving the root in the .tscn is not a reliable placement offset.
Author deliberate visual or collision offsets on child nodes instead:
- keep the root at the scene origin;
- position/offset
Sprite2Dchildren around the intended placement origin; - position collision children to match the gameplay footprint;
- keep sprite and collision transforms consistent unless the mismatch is intentional.
Child transforms are preserved through preview and commit.
Cell anchor modes
ScenePlacementEntry.cell_anchor_mode controls where the root sits inside the targeted 2D cell. Entries default to INHERIT, which follows PlacementSettings.default_cell_anchor_mode (historically CELL_CENTER). An explicit entry mode overrides that setting.
For centered, unscaled artwork whose dimensions are whole grid cells, choose the seat independently on each axis:
- odd number of cells on an axis → seat that axis at the cell center;
- even number of cells on an axis → seat that axis at the minimum cell edge.
On a 32×32 grid:
| Artwork size | Cell span | cell_anchor_mode |
|---|---|---|
| 32×32 | 1×1 | CELL_CENTER |
| 96×96 | 3×3 | CELL_CENTER |
| 96×32 | 3×1 | CELL_CENTER |
| 64×64 | 2×2 | CELL_TOP_LEFT |
| 256×96 | 8×3 | CELL_CENTER_LEFT |
| 96×256 | 3×8 | CELL_TOP_CENTER |
CELL_CENTER_LEFT shifts only X by half a cell. CELL_TOP_CENTER shifts only Y. CELL_TOP_LEFT shifts both axes.
That is why a centered 256×96 sprite on a 32px grid cannot be fixed by CELL_TOP_LEFT: it corrects the even X span but introduces a half-cell error on the odd Y span. This is an even/odd-per-axis rule, not a square-versus-rectangle rule.
After changing cell_anchor_mode, re-select the entry so the preview is rebuilt.
What anchor modes do not infer
Anchor modes do not inspect a texture and do not guess a collision footprint. The table above applies when the visible bounds are centered, unscaled, and span exact whole cells.
For transparent padding, asymmetric art, overhangs, non-centered sprites, scaling, or a gameplay footprint that differs from the texture bounds, author the child transforms around the intended placement origin instead of trying to encode the difference in the root position.
Placing one object on top of another
2D seats one object per cell by default. cell_anchor_mode chooses where that object's root sits inside its cell; it is a seat offset, not a stacking height, so it is not the way to place a second object on the same cell. Surface stacking is a separate opt-in catalog contract — surface_size_2d_cells on the host and stacks_on_surface on the item — and no shipped 2D catalog entry declares either half. The entries must declare both halves; the 2D preview/commit path then resolves sockets automatically. See Placement Rules.
The 3D equivalent is a TOP mount — a roof or an upper wall on a host wall's top edge, selected through PlacementSnapProfile3D.SnapMode rather than through an anchor mode. See 3D Object Placement for that model and Placement Rules for the 2D/3D comparison. TOP seats on a wall edge, not on the top face of an arbitrary prop.
Physics support or surface mount: which one?
Two different systems can put an object "on top of" something in 2D, and they answer different questions. SURFACE stacking does not automatically replace a physics support rule. The platformer box still uses its collision rule, and no shipped 2D catalog entry declares either half of the surface contract, so out of the box every 2D placement is collision-validated, never surface-mounted.
| Question | Physics support check | Surface mount |
|---|---|---|
| What it validates | That something solid is under (or touching) the placed object, measured with physics shapes at commit time | That a named host offers a named free slot, measured against the placement ledger |
| What it records | Nothing about the supporter — the placement is an ordinary CELL placement | The (host_cell, host_slot, slot_index) socket on the world record |
| What moves it | The physics engine after commit (a RigidBody2D still falls, slides, sleeps) |
Nothing — the seat is a placement-time position plus a ledger entry |
| When to use it | The object should rest on terrain, floors, or other solid bodies the way the physics world sees them | A small item belongs to a specific host surface (a book on a table) and must refuse when that surface is full |
Example 1: the physics-supported platformer box
The box entry attaches one entry rule, must_be_on_buildable.tres.
That rule is a CollisionsCheckRule with pass_on_collision = true: it passes
only when its check shapes overlap the Buildable layer (physics layer 11,
mask 1024), and it builds those check shapes from the TileCheckOnly layer
(layer 15, mask 16384).
The box scene provides both halves the rule needs: a BuildableCheck Area2D on
layer 16384 whose 16×16 shape hangs one box-height below the box at
position = (0, 16), probing the supporting surface rather than the box volume.
Support can come from terrain (the tileset's physics layer collides on 1024)
or from another box (the box's own RigidBody2D sits on layers 1+10+11+12,
which include Buildable).
With pass_on_collision = true, each indicator fails when it finds no real
collision, so a box placed over empty air is rejected.
The entry keeps ignore_base_rules = false, so shared and profile rules still
apply on top of the support check.
Note what the rule does NOT do: it never names the supporter and never attaches the box to anything. After commit the box is an ordinary simulated body — if the terrain under it disappears, gravity decides what happens next, not the placement system.
Example 2: a surface-mounted item on a table (the SURFACE contract)
The host half is surface_size_2d_cells on the table entry: a size in CELLS,
not pixels, so one catalog works at any tile size. A 2×3 table declares
Vector2(2, 3) and offers 6 slots.
Slot count is the floored surface area; anything under 1×1 offers zero slots.
surface_height_2d adds extra stacking bands above the top surface (0 = flat,
band 0 only).
The item half is stacks_on_surface = true on the small item (the book). The
flow then seats the book on the table's top surface instead of giving it the
floor cell.
The host keeps its CELL occupancy; the book lives in the separate slot table,
so the two coexist on one cell instead of evicting each other.
The host is resolved from the committed world, and stacked records never answer
as hosts, so a stack cannot grow on itself.
An anchor offset is not a surface mount
cell_anchor_mode moves one object's root INSIDE its own cell (the per-axis
even/odd seat from the table above). A surface mount seats a SECOND object on a
socket the host owns. Shifting an anchor never creates a slot, never checks
occupancy, and never refuses when a surface is full — if two objects share a
cell, that is the surface contract, not an anchor mode.
Can one entry use both checks?
Yes. Surface resolution runs first to decide where the ghost goes, and the entry's collision rules still run afterwards, so a stackable can ALSO require physics support (for example, a potted plant that must sit on a table slot AND overlap soil). Use both when the fiction needs both: the socket for "which surface spot", the collision rule for "what physically holds it up."
Lifecycle boundaries (what SURFACE does and does not do)
Preview and commit agree. The ghost re-resolves its socket every physics
frame in GRID mode, and the commit re-resolves from the live world rather than
trusting the ghost.
A socket taken between the preview tick and the confirm cannot be double-booked.
A stackable over a full surface is refused with the cell named — it never
silently drops to the floor.
With NO host under the cursor, the same stackable falls back to an ordinary
CELL seat, so it stays placeable on bare ground.
Slots are freed when their holder is removed. Unregistering a node and
tree-exit share one removal transaction that releases exactly that node's
socket, and a rolled-back removal puts the socket back.
Mounts survive save/load as the record's "mount" dict, and restore
rebuilds the socket ledger after the world records land (a double-claimed
socket refuses the load rather than evicting silently).
That restore path is test-pinned but no shipped 2D demo wires it yet — expect
your game's save/load wiring to inject the shared socket ledger the way the
tests do.
Explicitly NOT supported — do not rely on these:
No automatic parenting. Committed instances are parented to the world's
placed-parent with the ghost's transform copied; a book is never reparented
under its table.
No carry. Moving or rotating the host does not move stacked items; the move
path updates only the moved node's own rect and occupancy.
No cascading removal. Removing a host does not remove or relocate what was
stacked on it; further placements onto the gone host fail closed
(HOST_UNAVAILABLE).
No physical simulation. Seating is a placement-time position write plus a
ledger entry. If the fiction needs the book to fall when the table is removed,
that is gameplay code you write — the mount alone will not do it.
See Placement Rules for the 2D/3D mounting comparison.
Collision and visual alignment
Treat visual bounds and collision bounds as separate inputs that should normally describe the same placement footprint.
- A sprite can overhang its gameplay footprint; keep the collision shape on the cells that should actually block placement.
- Collision layers/masks determine targeting and validation, not the anchor mode.
- If the indicator is grid-aligned but only the object art is offset, inspect scene authoring and
cell_anchor_modebefore changing grid coordinates.
Directional N/E/S/W artwork
For authored cardinal sprites, logical placement rotation can change while the bitmap stays visually upright and swaps frames.
ManipulatableSettings.disable_rotation_transform supports this presentation pattern. It suppresses the normal visual rotation transform; it does not disable logical/cardinal rotation. The footprint and placement orientation must still rotate correctly, especially for non-square objects.
Per-cell rule indicators are footprint markers, not transformed art: they mark which cells the object claims and stay axis-aligned (no rotation, mirror, or skew inherited from the preview). With transform rotation suppressed, the plugin derives the claimed cells from the logical rotation, so a non-square footprint still turns while the markers stay upright. Your game draws the facing — either with a rotated node or by swapping authored frames.
The top-down demo's Directional Wizard is the reference pattern:
demos/top_down/objects/directional_sprite_rotation_demo.gddemos/top_down/objects/directional_wizard.tscndemos/top_down/placement/placeables/placeable_directional_wizard.tres
Use the supported rotation/cardinal callback to swap presentation frames. Do not manually rewrite placement occupancy or footprint state from the art callback.
Quick diagnosis
| Symptom | First check |
|---|---|
| Preview is half a cell off while indicator is correct | cell_anchor_mode versus odd/even cell span. |
| Moving the scene root changes editor layout but runtime still snaps wrong | Root position is replaced by placement; move/offset children instead. |
| Sprite aligns but collision blocks neighboring cells | Collision child transform/shape does not match the intended footprint. |
| Directional art looks right but occupancy is wrong after rotate | Presentation changed without preserving logical footprint/orientation. |
| Indicator tiles skew, rotate, or mirror with the object | Overlay art must stay axis-aligned; keep the per-cell marker art on the indicator's child nodes. |