Grid Placement supports 3D object placement on GridMap in two coordinate modes:
- GRID — cell- and mount-aligned placement for structures and other grid-aligned objects.
- SMOOTH — free world-space placement with optional world-space sockets.
3D terrain painting is a separate workflow; see 3D Terrain Painting for its tools and limits.
Choose the placement mode first
Use GRID for floors, walls, fences, windows, modular structures, or anything else that must align to cells or structural mounts.
Use SMOOTH for clutter, organic props, furniture, or object-to-object joins that should not be tied to GridMap cells.
See Grid vs Smooth Placement for the detailed tradeoff.
GRID placement lifecycle
A normal GRID placement follows this path:
- Select a
ScenePlacementEntry. - Target a
GridMapcell or mount. - Show the placement preview and validation feedback.
- Rotate yaw when the entry allows it.
- Confirm placement.
- Record occupancy, mount data, transform, and stable placement identity.
- Move, remove, save, or restore the object through the same placement world.
Preview and commit use the same validation rules. Commit checks the current world again, so a change before confirmation can still reject the placement.
CELL, EDGE, FACE, CORNER, and TOP
PlacementSnapProfile3D.SnapMode controls where a GRID entry attaches.
| Mode | Meaning | Typical use |
|---|---|---|
CELL |
Occupies a GridMap cell or footprint |
floor, foundation, crate |
EDGE |
Occupies a canonical edge between neighboring cells | wall, fence panel, door frame |
FACE |
Mounts to a supported provider face | window, shutter, wall decoration |
CORNER |
Occupies a canonical XZ grid intersection | structural corner post |
TOP |
Occupies the top of a host EDGE, or the cell-centered ridge | roof plane, gable, ridge cap |
EDGE/FACE occupancy is canonical from either neighboring cell, so the same physical edge cannot be double-booked by addressing it from the other side. TOP wall-tops use that same canonical EDGE key in a separate occupancy table, so a wall and its roof coexist. Ridge uses edge (0, 0, 0) and requires opposite roof tops on the cell.
CORNER uses the shared grid intersection. A CORNER entry needs two perpendicular hosts at that intersection. Their saved yaw directions must differ by an odd number of quarter turns, so one host runs along X and the other along Z. One host or two parallel hosts is invalid.
TOP is keyed to a host wall edge or the cell-centered ridge. The required wall edge must already have a host, so TOP cannot place a small item on an arbitrary prop's top face.
Cursor picking for EDGE, FACE, and TOP
PlacementInputHandler3D turns a cursor over a cell into the mount the ghost should show. It keeps the picked wall line steady against cursor tremor, mounts an aimed wall face on its own slot, hosts a room's boundary wall on the floored side, continues an existing same-family run, and resolves an occupied slot to the piece already standing there so replacements preview in place. TOP picks hold the chosen roof slope or ridge the same way.
Configure it once with the GridMap, the ObjectPlacementService3D, and optionally the GridPositioner3D whose physics hit it prefers. Each frame, pass the cell and the cursor's point on the cell's surface plane (Vector3.INF when there is no usable ray):
var handler:= PlacementInputHandler3D.new()
handler.configure(grid_map, object_service, positioner)
var mount:= handler.resolve_edge_mount(cell, plane_hit)# [mount_cell, edge]
object_service.update_preview_edge(mount[0], mount[1])Input events, rotate keys, and the commit stay in your game code. Set wall_axis_lock (or call toggle_wall_axis) from your rotate keys, and call reset() when a gesture is cancelled or the cursor leaves the grid. The 3D demo uses it this way.
FACE providers
A provider must explicitly expose mountable faces. The consumer's snap profile must accept the provider family. FACE save data preserves the provider, mount identity, and orientation so restore can rebuild the same relationship.
Preview object vs mount indicator
The placement preview (sometimes called the ghost) shows where the object will be placed.
The indicator shows what is being validated:
- CELL — the evaluated cell or footprint support;
- EDGE — the claimed edge at its structural height;
- FACE — the provider face;
- CORNER — the claimed grid intersection;
- TOP — the host EDGE or cell-centered ridge.
Both come from the same placement result. An elevated mount should therefore be highlighted at its actual height instead of pointing at unrelated ground below it.
Use the shipped 3D demo as the reference for floor → CELL wall → FACE-mounted piece placement and for House Corner placement at wall intersections.
Optional blocked patterns
Color-only previews remain the default. In the 3D demo, enable Blocked preview stripes to add diagonal bands to the invalid-color tint. Valid previews keep their normal tint. Turn it off to return to flat colors.
For your own integration, set ObjectPlacementSettings3D.blocked_feedback to BlockedFeedback.STRIPES (or FLAT_COLOR). The same settings resource controls GRID ghosts, mount/footprint indicators, and host-owned SMOOTH ghosts. You can assign blocked_pattern_texture to replace the built-in grayscale pattern; the invalid color still tints it. Standalone SMOOTH services use attachment_settings for this resource.
Patterns follow the existing validity result, including when valid and invalid colors are identical. They do not change placement rules. Keep text feedback for failure reasons and check contrast against your own scenes.
Snap families
PlacementSnapProfile3D lets entries describe what they provide and what they can attach to.
| Field | Purpose |
|---|---|
family |
Category published by the placed object. |
snaps_with |
Provider families this entry may attach to. |
attachment_requirement |
INHERIT (default, uses the settings default), OPTIONAL (free placement), or REQUIRED (must satisfy the mount's normal compatible-attachment rule). |
corner_host_topology |
CORNER-only: INHERIT (default), ANY_ONE (one compatible host), or TWO_ORTHOGONAL (strict two-host contract). |
edge_dressing |
EDGE-only: DEFAULT keeps along-edge yaw for symmetric pieces; OUTWARD and INWARD orient asymmetric pieces' dressed (+Z) face away from or toward the host side. |
edge_outward_facing |
Legacy compatibility alias. When edge_dressing remains DEFAULT, true resolves to OUTWARD. |
require_edge_span_floor_support |
EDGE-only, default false. When enabled, every host-side tile touched by the authored EDGE span must publish the floor family. |
The consumer decides what it accepts. You can keep names broad (wall, floor) or use project-specific families without changing plugin code.
Attachment strictness is one shared policy across CELL/EDGE/FACE/CORNER/TOP and SMOOTH socket consumption: mount identity (where the piece sits) never changes with the policy — only whether a compatible provider must be present. The legacy require_adjacent_snap boolean is deprecated but still resolves identically (false → OPTIONAL, true → REQUIRED); set attachment_requirement explicitly on new profiles.
Connect the level
Author one PlacementLevelContext in the level and point it at the 3D surface:
| Field | Set to |
|---|---|
target_map_3d |
Your GridMap. This is the surface the plugin snaps to. |
objects_parent |
A Node3D that receives placed scenes — never the GridMap itself. |
Assign exactly one surface: target_map for 2D TileMapLayer levels, target_map_3d for 3D GridMap levels. GridPlacementSettings.dimensions must match (DIMENSIONS_3D); a mismatch is reported as a warning.
Then add the 3D runtime pieces and connect them in the Inspector:
| Node | Set | To |
|---|---|---|
GridPlacementHost |
grid_placement_bundle |
The 3D bundle (settings, templates, actions). Use the same bundle the session carries. |
GridPlacementHost |
session |
Your PlacementSession. This is the session the host starts from. |
GridPlacementHost |
auto_configure |
true — the host configures the 3D services, registers the session, and binds consumers at _ready. |
GridPositioner3D |
session |
The same PlacementSession. |
GridPositioner3D |
grid |
Your GridMap. |
GridPositioner3D |
camera |
The camera the pointer ray-casts through. |
The positioner self-registers its session with the nearest GridPlacementHost at runtime. With auto_configure enabled the host does the rest at _ready — including building the authoritative GRID object service itself from the level context — so a 3D level needs no script at all. Scenes that bind their own service keep working: a bound service always wins. Leave auto_configure off when a controller owns session lifetime — split-screen, or sessions created at runtime or restored by save/load.
Session, catalog, and selection UI
The session is one authored resource with four slots: grid_placement_bundle (take the templates 3D bundle templates/grid_placement_templates/resources/3d/3d_placement_bundle.tres, which carries settings, templates, and actions inline), catalog (your entries, below), and empty settings/actions to use the bundle's. The catalog's scene_entries holds the entries players can pick; leave tile_entries empty unless the level paints terrain entries too.
Instance the shipped UI template (templates/grid_placement_templates/ui/placement_panel/placement_panel.tscn, node PlacementPanel) under a CanvasLayer. It carries the object catalog and the terrain palette behind the Objects/Terrain route tabs. It binds its own host and session at runtime and writes the player's pick back to session.selected_entry, which is what the host dispatches clicks against — no wiring code.
Setting up a entry 3D object
A basic GRID entry needs three pieces of setup:
- Packed scene — the object scene used when placement commits. Use the shipped
demos/3d/freelook/objects/scenes as component-layout references. ScenePlacementEntry— points to the packed scene and defines its placement data.- Catalog registration — add the entry to the
PlacementCatalogused by the session so it can be selected.
For the ScenePlacementEntry, configure the fields that apply to the object:
footprint_3d— occupied cells for each yaw step;snap_profile_3d—family,snaps_with,attachment_requirement, andsnap_mode;seam_bounds_3d— only when the snap profile enablesseam_close_to_partnerand the asset needs mesh-aware seam closure.
Choose the mount mode from the object's actual placement behavior: CELL for cell/footprint pieces, EDGE for edge panels, FACE for provider-mounted pieces, CORNER for intersection posts, and TOP for roof planes, gables, or ridge caps.
The shipped demo provides copyable references in demos/3d/freelook/placement/scene_entries/*.tres, demos/3d/freelook/config/3d_catalog.tres, and demos/3d/freelook/demo_3d.tscn. When adapting one, replace the scene and update its footprint, snap profile, and seam bounds for your own asset instead of only swapping the mesh.
In the demo, selecting an entry shows the preview and mount-aware indicator. You can then rotate, place, move, demolish, save, and restore the committed object through the normal placement workflow.
Footprints and rotation
ScenePlacementEntry.footprint_3d defines the CELL GRID footprint. CELL occupancy and support validation apply to the complete rotated footprint. EDGE pieces instead use footprint_3d_world for their span; set require_edge_span_floor_support only on structural entries that must have floor under every host-side tile they cover.
GRID structures use discrete yaw rotation. Structural pieces remain world-up by default; terrain slope does not automatically tilt walls or foundations.
Ground, slope, and partial support
GRID placement can validate the support below a footprint. Different entries can use different support rules.
Typical controls include:
- maximum allowed ground slope;
- support, height, and planarity tolerance;
- minimum support coverage;
- optional center or edge support requirements;
- stricter physics-required support when the project needs it.
For example, a small prop can allow some overhang while a building foundation requires stronger coverage. Configure that policy on the relevant entry instead of weakening support rules globally.
See 3D Surface & Slope Support.
Foundations, and placing rooms on top of them
Surface support answers "is there ground here". A different question is "is there a placed piece underneath me" — the foundation-under-room rule. Turn it on with require_column_support; snaps_with then names which providers count as being underneath.
Why attachment alone is not enough
snaps_with under CELL attachment checks the four cardinal neighbours in the same layer. A foundation one cell to the north satisfies it. So a room configured only with snaps_with will happily attach to a floor beside it and stand on bare ground:
.foundation .floor .room <- the room is attached, but nothing is under itrequire_column_support checks the same column instead. Every footprint cell needs a COLUMN provider directly underneath it — that column's own occupant, or any overlay stacked on it — publishing a family in snaps_with. A neighbouring foundation does not count.
Setting it up
Provider (the foundation):
| Field | Value |
|---|---|
family |
foundation |
snaps_with |
empty |
occupy_columns |
true |
Consumer (the room):
| Field | Value |
|---|---|
family |
room |
snaps_with |
["foundation", "floor"] |
attachment_requirement |
REQUIRED |
require_column_support |
true |
occupy_columns |
false |
occupy_columns = false on the room matters. A foundation that occupies its columns already owns them, so a room with occupy_columns = true is refused CELL_OCCUPIED before support is ever asked. The room becomes an overlay stacked on the foundation's column — which is how the shipped floors and roofs already work. Either channel counts as the provider, so an occupying foundation and a non-occupying floor deck are interchangeable.
A 2x2 foundation carries all four of its cells, because each cell of the provider's own footprint counts.
Standing on the ground as well
One family name is reserved: PlacementSnapProfile3D.GROUND_FAMILY ("ground"). Listing it in snaps_with makes the level's own ground a provider too, so a single entry can stand on the terrain or on a placed piece — a crate you may drop on the ground and also stack on another crate:
| Field | Value |
|---|---|
family |
crate |
snaps_with |
["crate", "ground"] |
require_column_support |
true |
require_ground_cell |
false |
occupy_columns |
false |
The ground is read from the GridMap tiles a column rests on — the same read require_ground_cell uses — so a level whose support comes from physics alone has no ground to offer. The name is reserved for this vertical channel only: it satisfies "something underneath me" and never counts as a cardinal neighbour, so it cannot host an attachment.
. . . . . . . .
crate crate <- ground below is enough
crate <- the crate below is the providerWhat the player sees
| Situation | Preview | Commit |
|---|---|---|
| No foundation under the cell | invalid | refused |
| Foundation under the cell | valid | accepted |
| Foundation in the next cell only | invalid | refused |
| Supported cell already holding a room | invalid | refused |
A profile that also lists "ground" accepts the same cell when the ground is what is underneath it, and only refuses where there is neither ground nor a placed piece.
The refusal names the rule, and the reason bit is PlacementValidation3D.Reason.NO_COLUMN_SUPPORT:
'Room' (family 'room') needs a foundation underneath every covered tile: the column below each cell must hold ["foundation", "floor"].
A profile that lists "ground" gets the ground named as the other option, with the reserved name kept out of the quoted family list:
'Crate' (family 'crate') needs the ground or a piece underneath every covered tile: the column below each cell must hold ["crate"], or the level's ground.
Preview and commit use the same validator. Commit checks the current world again, so a world change can still make a previously valid preview fail.
Removing or moving a foundation a room still needs is refused rather than leaving the room stranded. Clear or move the room first.
The 3D demo ships this as a worked example: the Room entry (demos/3d/freelook/placement/scene_entries/entry_room.tres) drops onto the Wagon foundation.
Stacked floors
Upper floors use a different mount. A TOP piece needs a host published by an already-committed piece, and there is no anchor-only TOP socket — so an upper floor over open ground is refused with TOP_REQUIRES_EDGE. A cell-centered floor deck needs an opposite EDGE wall pair (east+west, or north+south), and the walls themselves mount on a cell publishing floor. Once the deck is committed the slot is exclusive.
Stacking crates on crates (vertical CELL stacking)
require_column_support also lifts the grounding rule for CELL pieces. Normally a CELL anchor above the GridMap terrain top row is rejected as FLOATING — CELL placement rests on the ground. A piece whose profile sets require_column_support = true is the exception: it may anchor above the terrain when the cell directly below holds a placed piece publishing a family in snaps_with. That is the crate-on-crate rule.
Setup (stackable crate on crates):
| Field | Value |
|---|---|
snap_mode |
CELL |
family |
crate |
snaps_with |
["crate"] |
require_column_support |
true |
occupy_columns |
false |
occupy_columns = false is required: the base crate already owns the column, so the stacked piece rides the same column as an overlay — the same mechanism as a room on a foundation. The rules, all enforced by preview and commit alike:
- Each level rests on the level below. A level whose supporting cell is empty (a gap in the stack, or bare terrain with nothing placed) is refused with
FLOATING. - The support chain is checked directly below only. A two-high stack does not satisfy a crate anchored three cells up; every level in between must exist.
- A stacked piece seats on its support, not on the terrain. Its preview and commit height is the terrain surface under the column plus one cell per row the anchor sits above the terrain top row, so a crate on a crate lands exactly one cell above it.
- Support removal is refused while something stands on it. Demolishing or moving the base crate under a live stack is refused rather than stranding the pieces above; remove the top of the stack first. The refusal comes from the same counterfactual the placement check runs, so "entry there" and "removable from there" can never disagree.
- Save/load round-trips the whole stack — each level is a normal CELL placement record with its anchor cell, so the restore coordinator rebuilds the column as-is.
This is deliberately not the TOP mount. TOP seats a roof or upper wall on a published host socket (a wall top or cell-centered ridge) and carries orientation and stacking-order semantics for kits; column stacking is the simpler gravity rule for props with no sockets — crates, boxes, furniture piles. Use TOP for construction pieces, require_column_support for stackable props. The 2.5D PoC (godot/demos/3d/iso/) is the worked example: build_end_state() stacks crates two and three high and seats crates on raised terrain tiers.
require_column_support applies to CELL pieces; TOP sockets resolve their hosts through the TOP rules above.
When not to use it: leave it off for a piece that must stay on the terrain (a
tree, a terrain-hugging prop, anything whose height is decoration rather than
structure). The flag is what lifts the grounding rule, so an entry that carries
it can anchor above empty ground and be refused only when the column underneath
holds nothing — correct for a stackable crate, wrong for a crate that is scenery.
It also only makes a piece stackable if snaps_with names a family that will be
under it: ["crate"] for crates, ["ground", "room"] for rooms. occupy_columns
stays on for both — turning it off is for overlays (floors, roofs) that
deliberately share a cell with the piece below.
Fences and modular structures
GRID CELL/EDGE/FACE/CORNER/TOP is the supported path for cell-aligned modular structures.
A structure can combine:
- CELL floor/foundation pieces;
- EDGE walls/fences/door pieces;
- walls meeting at 90-degree EDGE corners;
- FACE-mounted pieces such as windows/shutters;
- CORNER-mounted structural posts where the kit uses them;
- TOP-mounted roof planes, gables, and a cell-centered ridge;
- removal and re-placement;
- save → clear → restore → continue.
TOP occupancy is keyed by the host EDGE (separate table), so a wall and its roof can coexist. Ridge uses edge (0,0,0) and requires opposite roof tops. Snap-family names still do not create a new mount type.
The shipped 3D demo's modular house kit uses these placement paths for its floor deck, walls, door, shuttered windows, and corner posts.
Sized pieces and wall runs
Piece sizes are authored, not resized at runtime: there are no scale handles. To offer a longer wall, ship a bigger catalog variant (the demo kit has 1 m, 2x1, and 2x2 wall entries) and let players chain runs with the EDGE line drag, which mounts one panel per authored span width along the dragged run.
EDGE mounting also validates spans: collinear penetration and true pass-through crossings are rejected, while end-to-end joins, 90-degree corners, and T-junctions remain legal. Same-anchor replacement is unaffected. Span size comes from the entry's footprint_3d_world (x = span along the edge, z = thickness across it), positioned at the measured mesh. Structural entries can set require_edge_span_floor_support to require floor under every host-side tile touched by that span; empty-yard fences and wall chaining remain anchor-only unless they opt in.
Line and rectangle fills
ObjectFillSpan3D turns one drag between two grid cells into the pieces a LINE, RECTANGLE_FILL, or RECTANGLE_OUTLINE brush places, and commits them through ObjectPlacementService3D in order. CELL entries place one piece per span cell; EDGE entries mount one piece per authored span width and keep the pressed wall's rotation along the run. Your game supplies the cursor: set the drag corners, the pressed EDGE mount if any, and optional cursor_edge / cursor_mount callables for drags with no direction.
var fill:= ObjectFillSpan3D.new(service)
fill.brush_shape= PlacementEnums.ObjectTool.RECTANGLE_FILL
fill.start_cell= press_cell
fill.end_cell= hover_cell
fill.max_cells= 64
var cells:= fill.cells()# empty when the drag exceeds max_cells
var verdicts:= fill.cell_verdicts(fill.cells_3d(cells))# preview, in commit order
var result:= fill.commit(cells)# on release
print("placed%d of%d" % [result.committed, result.attempted])cell_verdicts judges each anchor the way the commit will, so a 2x2 piece dragged in a line previews the anchors its own footprint covers as blocked. For EDGE entries, preview_edges(cells) tints the service's span ghosts. The 3D demo drives its fill drags through this class.
Roofs and gables
TOP is the supported roof and gable mount in the 6.2.0 contract. Roof and gable entries attach to a host EDGE, while the ridge is cell-centered and requires the opposite roof tops described above.
A gable that should face away from the roof beside it is a game rule, not a plugin mode. The shipped demo flips between two authored variants by the roof's side of the seat (demos/3d/freelook/placement/roof_side_variant_pair.gd). Copy that pattern for your own pairs, and keep the side rule in your game code.
Do not use an arbitrary snap-family name to imply a new mount type.
SMOOTH world-space sockets
SMOOTH can optionally publish and consume world-space sockets for joins that should not use GRID CELL/EDGE/FACE/CORNER/TOP keys.
Common socket roles include ends, perimeter points, faces, and upward points. A consumer still uses family and snaps_with to choose compatible providers.
The default SMOOTH behavior remains free placement. Socket snapping is opt-in through the entry/profile configuration.
GRID mounts and SMOOTH sockets use different occupancy identities:
- GRID mounts use canonical cell, edge, face, corner, or TOP keys.
- SMOOTH sockets use provider identity plus world-space socket identity.
See Smooth Placement.
Validation failures
A 3D placement can reject for reasons such as:
- missing or invalid active entry;
- occupied footprint or mount;
- unhosted or occupied corner intersection;
- incompatible snap family or provider;
- missing required ground or support;
- slope/support policy failure;
- collision or overlap conflict;
- missing physics evidence when a physics-required policy is selected.
Use the report-producing placement APIs when your UI or game needs typed failure information. Keep surface/support failures distinct from occupancy failures so the player receives the correct feedback.
Only the support and SMOOTH-overlap stages look at physics. GRID occupancy is
bookkeeping, not a collision query: a cell is taken because a placed piece's
record claims it, so the answer is the same before the click and after a reload,
and the refusal can name the cells. Give your placed scenes StaticBody3D
children for anything else physics has to do — characters standing on floors,
walls stopping them, the cursor ray targeting a wall — and expect none of it to
change what the plugin allows.
When a placement is rejected as "no ground", run the evidence-side checklist
before touching policy: confirm the GridMap scan range covers the terrain's Y
layers, that the footprint columns actually hold support cells, and that the
support mode matches your world's evidence (GridMap-authored worlds usually
want a GridMap-capable mode, not PHYSICS_REQUIRED). The
Core validation by workflow table names the policy side of the same check.
Action feedback and diagnostics
An occupied cell or an overlap is a normal gameplay refusal. The 3D report APIs
return its reasons in PlacementReport; they do not emit Godot warnings. The
host publishes one building.action_performed event for each 3D object report,
whether or not an ActionLog UI exists. Direct service callers own their feedback.
The optional PlacementActionLog displays these events. Its ActionLogSettings
control the display: show_build_failed hides failures, print_failed_reasons
hides their details, and print_on_drag_build controls drag/area item messages.
These UI filters do not change the report or the placement result. Configuration
and internal failures still use PlacementLogger warnings or errors.
Configure diagnostics in the session's Settings → Debug resource. Set
Level to VERBOSE or TRACE, then choose a Shared - Grid Positioner
Logging mode: MOUSE_INPUT for 3D clicks and host dispatch, VISIBILITY for
indicator state, POSITIONING for position refresh, or STATE_FLOW for
positioner setup. NONE disables these diagnostics. Messages go through
PlacementLogger; there is no separate 3D checkbox. Remove the obsolete
debug_pick_evidence property from authored scenes when migrating.
The logger has one plugin-wide production root. The first initialize() call
sets its debug settings; later sessions reuse that logger while it remains
alive. Share the same debug resource when sessions should use one logging
policy.
Persistence
Supported 3D placements keep stable placement identity plus the transform, occupancy, and mount data needed to restore the same world state. GRID, SMOOTH, CELL/EDGE/FACE/CORNER/TOP mounts, sockets, and shared occupancy are restored together.
See Save and Load.
Supported boundaries
- 3D object placement: supported.
- GRID + SMOOTH: supported.
- Generic GRID roof/top mount: supported as SnapMode.TOP (wall-top and ridge). SMOOTH
UPsockets remain the free-placement path. - CELL / EDGE / FACE / CORNER / TOP structure workflows: supported.
- Configurable GRID slope/support validation: supported.
- Automatic structural GRID pitch/roll to follow terrain: not supported by default.
- 3D terrain editing/painting: supported (paint/erase, LINE/rectangle brushes, game-authored rule evaluation;
FLOOD_FILLis not supported in 3D).
Related guides
- Grid vs Smooth Placement
- 3D Surface & Slope Support — includes the foundation-under-room workflow
- Smooth Placement
- Moving, Rotating, and Scaling Maps
- Save and Load
- Troubleshooting
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.