Development docs. This page is part of the unreleased 6.3 guide set and is not on the published site yet. It assumes a project that has installed
addons/grid_placementand nothing else — no template scenes, no configured session, no demo copy, and nothing pre-built to open. Every scene and resource below is authored in the step that needs it, so the walkthrough starts from a genuinely empty project.Version this needs: 6.3 or newer — verified, not assumed. 6.3 is the first release that books 3D occupancy per storey. Run the proof below against anything older and it stops at step 7 ("place the same 2x1 room on the storey above"): 6.2.2, 6.2.1, 6.2.0, 6.1.2 and 6.1.1 all key 3D occupancy by column, so the second storey is refused with
Cell (0, 0, 0) is already occupied.andPlacement overlaps an existing object.Your copy is on the usable side of that floor when hovering a placed room puts the ghost one row up; if that preview turns red instead, the stacking steps (§9) do not apply to it yet. What has to exist for the page to hold:footprint_3dandsnap_profile_3donScenePlacementEntry;snap_mode,family,occupy_columns,require_column_support,snaps_withonPlacementSnapProfile3D; the entry scripts underplacement/scene_entry/; and 3D occupancy recorded per storey rather than per column. A release missing any of those is out of scope. To move the floor — up for a new requirement, down if someone proves an older release — re-run the proof against that release; it prints theplugin.cfgversion it actually ran against, so the answer is read, not guessed.Demo content is not evidence of plugin behaviour.
demos/3d/rooms/— the room scenes,room_doors.gd, the storefront bundle — is demo glue that ships alongside the plugin, not part ofaddons/grid_placement. A behaviour that only appears in a demo does not set the version floor for this guide, and nothing below is copied from, or depends on, that demo.
This guide builds rooms the way a base-building game does it: one room is one entry object, and the same object stacks. The player picks a room size, the plugin validates the whole footprint as a unit, one commit produces the slab, the walls and the shell together, and picking that same room again while pointing at a placed room stands it one storey up. There is no "lay the deck, then ring the walls" step, and there is no upstairs catalogue item, because a player of a room-building game has neither.
Storeys are grid rows, not a second system: a storey is one GridMap row, so
the room above the ground floor is that same cell with y one higher. The
plugin books occupancy per storey, which is what lets one column hold a
room on the ground and another directly above it, while a second room on a row
that already holds one is still refused.
Per-storey booking is the column-support rule, not a new global one: a piece
that does not ask to stand on the piece below it (require_column_support off)
still reserves its whole column, so 2D and 2.5D pieces and their neighbours keep
blocking each other exactly as before. Turning require_column_support on is
what buys the room its own storey.
The rooms in this guide are open on the side that faces the camera, so you can see the interior. That is art, not rules: the plugin never cares how many walls a room scene draws.
Open the camera on that side. A room game shows rooms from the front, because a
view turned away from the open wall is three blank walls — so the pose you
author is the pose the view opens at. Turning is worth having after that (a
far side, a roof), but it must be a choice the player makes, not the default.
The rooms demo and the tutorial use one script for it, RoomCamera3D
(demos/3d/rooms/room_camera_3d.gd): pan, lift, zoom, and a right-drag turn
around the point the authored pose already looks at.
Rooms in this guide also carry a StaticBody3D with one shape per wall and one
for the floor. That collision is for characters and the cursor ray, not for
placement: the plugin never asks a body whether a cell is free. Occupancy is a
record of which cells each placed room claims, so the preview can answer before
the click, the same answer survives a reload, and a refusal can say which cells
belong to which piece. A room with no body is still refused where it overlaps —
it just has no floor for a dweller to stand on.
Read Getting Started first if you have not placed a single object yet. The 3D Object Placement guide is the reference for mounts, footprints and snap families; this page only covers what changes when the thing you are placing is a room.
The short version
Setup — once, from a scene that has only a ground GridMap and a camera.
- Install
addons/grid_placement, enable the plugin, run Project → Tools → Grid Placement → Setup Default Input Actions. - Run Project → Tools → Grid Placement → Setup Scene and pick 3D. It
adopts the
GridMapyou already have as the level surface and the camera you already have as the one the positioner aims through; it creates only what is missing — host, level context, owner, positioner — plus a session, bundle, catalog and terrain entry written beside the scene. - Read the report it prints.
createdis the nodes it added — on a bare rig,GridPlacementHost, Objects, PlacementLevelContext, PlacementOwner, GridPositioner3D.preservedis what it adopted or reused instead of duplicating, and it also names the resources it wrote:root as World (3D scene owns its GridMap),TargetMap3D (reused existing GridMap: …),Camera3D (<your camera>),bundle (session),catalog (session),session,terrain palette. The twoneeds configlines are reminders, not outstanding work: it prints the owner-root and camera lines even when it has just wired both. The three links still deserve a look:PlacementLevelContext.target_map_3d→ yourGridMap,PlacementOwner.owner_root→ the node the player steers,GridPositioner3D.camera→ your camera. - Fill the catalog: select
GridPlacementHostand add the rooms under Session → Catalog → Scene Entries, then save the scene. Setup Scene writes that catalog to<scene folder>/grid_placement/catalog_3d.tresand the scene points at that file, so the inspector and the file edit one list. It starts with your terrain entries and an empty room list. One entry per room size —CELLsnap mode, familyroom,footprint_3d1x1/2x1/3x1,occupy_columnson,require_column_supporton,snaps_with["ground", "room"], and the room's own scene. Duplicating one entry and changing the footprint is the whole job. - Press Setup UI so the room panel can select those entries, then play the scene.
Placing — every session.
- Hover shows the whole footprint before you commit; one click is one room.
- Hover a room you already placed with the same entry selected: the cursor resolves the row above it by itself, and the ghost stands one storey up. Click there and that is the second storey; hover it again for the third. Code that drives placement passes that cell.
- Expect refusals for a cell another room already claims on that storey, for a footprint past the plot edge, and for a room one storey up with no room underneath it.
Everything after this list is the reasoning behind those steps. Section 2 lists what Setup Scene created, so you can author the rig by hand instead, and section 1 lists the rest of the Tools submenu.
A room is one entry with a footprint
| Piece | snap_mode |
family |
footprint_3d |
require_column_support |
snaps_with |
|---|---|---|---|---|---|
| Room 1x1 | CELL |
room |
Vector2i(1,1) |
on | ["ground", "room"] |
| Room 2x1 | CELL |
room |
Vector2i(2,1) |
on | ["ground", "room"] |
| Room 3x1 | CELL |
room |
Vector2i(3,1) |
on | ["ground", "room"] |
The size is data, not a different kind of object: three entries, one snap
profile shape, and the plugin's multi-cell footprint machinery does the rest.
One entry per size serves every storey. There is no upstairs entry and no
room_upper family: require_column_support says "rest on what is underneath
me", snaps_with lists both the plot (ground) and a room (room) as things
that can be underneath, and occupy_columns stays on so the room books the
cells it covers on its own row.
That last part is the whole trick, and section 9 takes it apart.
The room's scene is authored centred on its footprint. A 2x1 room's origin
sits on the shared grid line between its two cells, which is why one
try_place_report produces art that spans both of them.
1. Install and run the editor setup actions
- Copy
addons/grid_placementinto your project. - Project Settings → Plugins → Grid Placement → Enable.
- Run Project → Tools → Grid Placement → Setup Default Input Actions.
- Restart the editor, then confirm the placement actions exist in Project Settings → Input Map.
Step 3 is the only input setup this version ships. The rest of the same Tools submenu, for reference:
| Command | What it does |
|---|---|
Grid Placement → Setup Default Input Actions |
Creates the placement actions in the project Input Map. |
Grid Placement → Setup Scene |
Asks for a level type, then adds that type's rig to the open scene. |
Grid Placement → Setup Hierarchy/… |
The same rig for one type — Platformer 2D, Top-down 2D, Isometric 2D, Hex 2D, 3D — without the picker. |
Grid Placement → Setup UI |
Adds the scene-entry selection panel and points it at the session. |
Grid Placement → Validate Current Scene Setup |
Checks the open scene's host/session/level wiring. |
Grid Placement → Open Getting Started |
Opens the getting-started guide. |
6.2.2 ships no migration command: the 5.x converter was removed, not deferred. Setup Scene builds the rig and reports what it created, preserved, and what still needs configuring; section 2 lists the minimum node set and the assignments, so you can let Setup Scene place the nodes and then match this section, or author them yourself. The panel is the separate Setup UI press. Use Validate Current Scene Setup after wiring to catch the usual mistakes.
2. Author the 3D scene
Minimum node set. Names are suggestions; the types and assignments are not.
Main (Node3D)
├─ Systems (Node)
│ ├─ GridPlacementHost (Node + GridPlacementHost script)
│ └─ GridPositioner3D (Node3D + GridPositioner3D script, session = your session)
├─ GridMap3D (GridMap — your floor surface + a MeshLibrary)
├─ PlacedObjects (Node3D)
├─ Level (Node + PlacementLevelContext script)
│ └─ LevelPersistence (Node + persistence script) # only if you save
├─ Camera3D (current = true; RoomCamera3D drives it — no player body needed)
│ └─ PlacementOwner (Node + PlacementOwner script, owner_root = Camera3D)
└─ UI (CanvasLayer)
└─ EntrySelectionUI (instance from templates/, or your own)Then connect them:
| # | On node | Set field | To |
|---|---|---|---|
| 1 | GridPlacementHost |
session |
Your PlacementSession. |
| 2 | GridPlacementHost |
auto_configure |
true for a single-player scene. |
| 3 | GridPlacementHost |
grid_placement_bundle |
Must match the bundle used by the session. |
| 4 | Level (PlacementLevelContext) |
target_map_3d |
GridMap3D. This is the 3D surface selector. |
| 5 | Level (PlacementLevelContext) |
objects_parent |
PlacedObjects. Never place onto the GridMap itself. |
| 6 | PlacementOwner |
owner_root |
Camera3D (or your player body, if the scene has one). |
| 7 | GridPositioner3D |
session |
The same PlacementSession. |
| 8 | GridPositioner3D |
camera |
Camera3D. |
Camera3D is not optional: GRID commits resolve the aim through the current
camera projection and fail closed without one. Author the pose as the front
view you want on open: square-on to the side the rooms open toward, looking
slightly down, with the plot filling the frame. Camera height decides which
storey you see into (section 9) while yaw decides whether you see into the room
at all, which is why that pose is the default and not something the player has
to restore. RoomCamera3D keeps it and works around it.
The room scene you hang off the positioner is authored in section 3.
Assign the 3D placement settings
The host composes the 3D object service itself, from the level context and the
bundle, so an authored scene needs no setup script. It needs one resource from
you: create an ObjectPlacementSettings3D (preview colors, presentation
profile, support policy) and assign it to the bundle's
object_placement_settings_3d slot.
With that assigned and the level context bound, the host builds the service and
owns it — smooth placement, save/load and teardown all go through one authority.
If your game already builds an ObjectPlacementService3D and hands it to the
host, the host keeps yours instead.
The plot is the GridMap. A GridMap with cells holding a ground item is
what the plugin reads as buildable ground; an empty GridMap refuses every room
with a "no ground" style issue. That is the mechanism behind "you can only build
on your base": author the slab you want buildable, and a room whose footprint
runs past it is refused. The demo uses a 24×24 flat plot of a single Ground
MeshLibrary item.
3. Author the room scene
No room ships with this guide, and the repository's
test/fixtures/room_tutorial/ scenes are the automated proof's stage set
(section 10) — test content, not a starting point. A walkthrough that began by
opening them would skip the art it is meant to teach. Author one room here, then
duplicate it per size in section 5.
Cells are 1 m. The root is a plain Node3D named for the size, and its origin
is the centre of the footprint, not a corner: a 2x1 room's origin sits on the
grid line between its two cells, so art authored around it spans both.
- Floor.
MeshInstance3Dwith aBoxMeshofsize = Vector3(2, 0.08, 1)aty = 0.04, so its top face is the grid plane and it covers cells -1 and 0. - Back wall. A box one cell tall on the
-zside, inset a little so neighbours do not z-fight:size = Vector3(1.84, 1, 0.08)atz = -0.46. - End walls, with a doorway. On each
xend, two stubs either side of the opening plus a lintel above it (the rooms demo uses0.3m stubs and a0.4m wide,0.62m tall opening). Leave the+zside fully open — that is the side the fixed camera looks through. - Door panels. One
MeshInstance3Dper shared side, namedDoorWestandDoorEast, filling the opening. They are visible by default, so a lone room reads as a closed room rather than a hole. Section 7 opens the ones that have a neighbour. - Collision. A
StaticBody3Dwith oneCollisionShape3D+BoxShape3Dper floor and wall. It is for characters and the cursor ray, not for placement: the plugin validates against its own cell records and never asks a body. - Materials. A
StandardMaterial3Deach for floor, wall and door, the door a distinct shade so an open doorway reads at a glance. - Save as
res://rooms/room_2x1.tscn. Duplicate for 1x1 and 3x1 and widen the floor, back wall and end-wall offsets; keep the root at the footprint centre. - A
Manipulatablecomponent carrying aManipulatableSettingsresource withdemolishableon. This is what makes the room a piece the player can take back: a demolish click acquires its target by finding that component under the aimed piece, so a room scene without one cannot be acquired at all and the click is refused with "Room 2x1 can't be demolished: it's not marked as demolishable." — a scene authoring gap, never an aiming bug.movabledecides whether Move mode can grab the room; leaverotatableand the flip flags off, because a room's orientation comes from its entry's footprint.
The room on the storey above is this same scene and the same entry: section 9 adds no art.
What makes a cutaway room read
A floor and three walls under a camera that looks down are two flat bands until something tells the eye which band is horizontal. The demo's rooms add four cheap cues, and any of them can be dropped without affecting placement:
- Wall caps. A thin, lighter box on top of every wall, sunk 7 mm into it. The top face is what the camera sees; without it the wall top is the wall colour edge-on and the room has no outline.
- Baseboard and floor lip. A dark strip where the back wall meets the floor and a lip on the open edge: the two edges that otherwise dissolve into the ground behind the room. Sink both into the surface they sit on, or the coplanar faces z-fight into a dotted line.
- Floor seams. The plot's own world-space grid shader on a plane just above the floor, so the floor shows the cells the room covers.
- One interior light per room. A shadowless warm
OmniLight3Dnear the ceiling with its falloff doing the shading: it is what separates floor from wall, and it is cheaper and steadier than a shadow-casting sun at this scale. Anything standing in the room (the demo's crate, the light itself) is what finally tells the eye which band is the floor.
All of it is art and none of it is a plugin rule: a room with none of these cues places exactly the same, and a room whose open side faces another way needs the mirror of it.
4. Create the session and bundle
- Right-click in FileSystem → New Resource… →
GridPlacementBundle. Leave its defaults; it creates its settings/templates/actions subresources. - New Resource… →
PlacementSession. Assign the bundle togrid_placement_bundle. Leavesettingsandactionsempty to use the bundle's; assigncatalogin section 5. - Put the session on
GridPlacementHost.sessionandGridPositioner3D.session.
One session per player. Do not share a session between split-screen players.
5. Create the room entries
For each size you ship, create a ScenePlacementEntry:
packed_scene— the room scene from section 3. Required.display_name— what the player sees (Room 2x1).icon— the catalog thumbnail.profiles— at least onePlacementProfile, or the shipped selection UI shows no category tab and the entry cannot be selected.footprint_3d— the room's size in cells, e.g.Vector2i(2, 1).snap_profile_3d— aPlacementSnapProfile3Dwith:snap_mode = CELL(rests on the plot and claims whole cells),family = "room",occupy_columnson — the room books every cell of its footprint on the row it stands on,require_column_supporton — it needs something accepted underneath every covered cell, on that same column,snaps_with = ["ground", "room"]— the plot counts as underneath, and so does another room, which is what makes the next storey legal,attachment_requirement = Optionalso the first room of a row can be placed with nothing next to it.
Those fields make the entry stackable and grounded by the same rules, so the first room of the scene is placed on the plot and the one above it is placed on the room below, with no second entry and no per-storey variant.
Which flags you need — and which you do not
Three fields decide this, and each one is only needed for a job it names. Setting one "just in case" changes what the piece is allowed to do.
| You are building | require_column_support |
snaps_with |
occupy_columns |
Why |
|---|---|---|---|---|
| Rooms that stack (this guide) | on | ["ground", "room"] |
on | Ground floor rests on the plot, upper floors on the room below, and each row is still claimed. |
| A room on the plot, never upstairs | off | [] |
on | Nothing can float, nothing can stack. The safest default. |
| Free floor slots, no support rule | off | [] |
on | Any row is legal, including one over nothing. Overlap on a row is still refused. |
| A floor, roof or deck sharing a cell with the piece below | off | [] |
off | Two pieces in one column is the point: it is an overlay, not a claim. |
| A prop that stacks on a crate | on | ["crate"] |
on | Same rule as a room; only the accepted family differs. |
The two mistakes worth naming:
require_column_supporton a piece that should never stack. It lifts the grounding rule, so the piece may anchor above the terrain — that is exactly what lets the third storey be refused only when nothing is underneath, instead of always.occupy_columnsoff on a room. The room stops claiming its row, so nothing refuses a second room that overlaps it there. Overlays are the one case that wants that; a room is not.
None of the three is set by the plugin for you, and none is inferred from the scene. They are authored, per entry, in the snap profile.
Add the sizes to the session's catalog in ascending order so the UI reads as 1x1 → 2x1 → 3x1. That is the whole catalogue: three entries, every storey.
6. Build a row
- Select a size. Hover a free cell: the preview must be green before the click, and it must cover the whole footprint, not just the anchor cell.
- Click. One commit produces the room — slab, walls and shell.
- Select the next size and click the cell next to the first room. Rooms butt up on the grid line without any extra rule; the second room's own shell is what you see at the boundary.
That is the whole placement workflow. There is no second step to "finish" a room, and nothing to clean up afterwards. Section 9 adds the upstairs.
7. Shared walls between adjacent rooms
Each room ships its own complete shell, so two adjacent rooms produce two thin walls with a hand's width between them rather than one shared wall. Pick one of these, in order of cost:
- Accept the seam. Two inset walls read as separate pods, which is fine for a bunker and needs no art work.
- Author half-thickness edges. Walls on the shared sides sit just inside the footprint, so neighbours' walls meet with a hairline gap instead of a visible double wall.
- Move walls into a neighbour-aware piece. That is a different model — see the appendix.
There is no automatic "merge these two rooms" step in 6.2, and a tutorial that promised one would be lying.
Optional doorways: the demo's neighbour hook
Not part of placing or stacking, and nothing below is needed to finish this guide. The rooms demo authors a door panel on each side of a room scene, visible by default, and a demo-owned node opens only the doors that have a neighbour on the same storey:
func _on_object_placed(_cell:Vector3i, p_node:Node3D, _entry:ScenePlacementEntry)-> void:
p_node.tree_exiting.connect(refresh,CONNECT_ONE_SHOT)
refresh()refresh() reads each placed room's floor-plate AABB and toggles DoorWest /
DoorEast for the pairs whose plates touch on one storey. The storey test is
not decoration: the room above shares x and z with the room below, so an
x/z-only test would open a doorway through the floor slab. object_placed is
the hook, not a plugin rule — the service still decides only which cells a room
claims.
The implementation is
demos/3d/rooms/room_doors.gd,
its regression suite is
test/demos/integration/3d/room_doors_test.gd,
and Custom Placement Rules covers the hook
itself. Copy it if your rooms need neighbour-aware art; skip it otherwise.
8. What the plugin refuses
| Attempt | Why it is refused |
|---|---|
| Room on cells another room covers on that row | CELL occupancy, booked per storey: every footprint cell of that row is claimed. |
| Room overlapping part of a room on the same row | Same rule — the overlap is per cell, not per anchor. |
| Room whose footprint runs past the GridMap | No ground under one or more footprint cells. |
| Room one storey up with no room underneath it | require_column_support: every covered cell of that column must hold room, or the plot. |
| The same room again at the same cell and rotation | It is already placed there. |
Note the first rule's row: the same cells one storey up are free. That is not an exception to occupancy — it is occupancy, booked per row.
Every one of those is the plugin talking, not the demo. A room demo must surface the refusal rather than draw the room anyway — that is the difference between a tutorial and a mock-up.
Rules the plugin cannot own
A rule only your game knows — rooms on the front row, a piece with one facing, a
reserved band — is a PlacementCellPolicy3D you write and attach to the entry's
cell_policy_3d. That is game code, so it lives in your project, not in the
plugin and not in this tutorial: the rig below attaches none, because everything
it demonstrates is the plugin's own refusal.
The demo project keeps a working one beside its rooms,
demos/3d/rooms/row_band_policy.gd,
as a starting point. It is inert there — no entry in the demo attaches it — so
copy it, change the numbers, attach it to the entry. See
Custom placement rules.
What the status line reads on this rig, copied from the proof run rather than written by hand:
- 1x1 room over cells a room covers on that row —
Cell (0, 0, 0) is already occupied.thenPlacement overlaps an existing object. - 3x1 room running past the plot edge —
No ground under this spot. - Room one storey up over bare plot —
Cell (0, 1, 2) floats above the terrain: CELL placement rests on the ground; mounting above ground needs an edge/face/top/corner socket.then'Room 2x1' (family 'room') needs the ground or a piece underneath every covered tile: the column below each cell must hold ["room"], or the level's ground.
The coordinates follow the proof's own cells; the reasons are the same ones you will read in your project. Both lines of a two-line refusal come from the same report, so a HUD that shows only the first one hides why the placement failed. The second line is what tells a room builder what is missing: the first is the plugin saying the row is unsupported, the second says by what.
9. Storeys: the same room one row up
A storey is the same placement on the row above. There is no upstairs entry
and no storey switch to fit: the room you already picked stands on the room you
already placed, and the cell handed to the service is y + 1.
What makes that legal is authored on the entry, not on a second one:
| Field | Value | What it does here |
|---|---|---|
family |
room |
One name for every storey: it hosts itself, safely. |
require_column_support |
on | The row must rest on something accepted underneath. |
snaps_with |
["ground", "room"] |
The plot holds the ground floor; a room holds the next. |
occupy_columns |
on | The room books its cells on that row. |
footprint_3d |
(2,1) |
The shape must be covered below, cell by cell. |
cell y |
0, 1, 2 … |
One GridMap row per storey. |
Three separate rules fall out of those five fields, and each is a rule the plugin already enforced — none of it is demo code:
- Occupancy is per storey — for pieces that ask to stack. A CELL entry with
require_column_supporton books its cells at(x, y, z), so a room on row 0 and a room on row 1 of the same column are two claims, not one conflict. (An entry with it off keeps counting every row of its column, exactly as before — per-storey booking is what the support rule buys, not a global swap.) That is what makes the stack legal whileoccupy_columnsstays on: the room still claims its own row, so a second room that overlaps it on that row is refused. - Support is per column.
require_column_supportasks the cell directly below to hold a family insnaps_with. Listinggroundlets the first room rest on the plot; listingroomlets every room above rest on the one below. A room one storey up over bare plot has neither, so it is refused twice over: the row floats, and the column holds nothing. - The room below is load-bearing. Removing it while a room stands on it is refused, because the same counterfactual that validates a fresh placement finds nothing to hold the storey.
The cursor does the climbing
Nothing in a game script lifts the piece. With require_column_support on, the
cursor resolves the cell above an accepted provider itself: hover a placed room
and the ghost, the preview report and the click all name y + 1 on that
column. Hover the room you just stacked and it resolves y + 2. A third
storey is the same entry one row higher — no extra resource, no UI.
That is also why this guide has no storey selector. An earlier revision of the
rooms demo shipped one: a HUD panel that offset the hovered cell by a chosen
row, because the pieces then relied on a separate room_upper entry. With
occupancy booked per storey and snaps_with naming the room below, the row is
the cursor's job, and a selector on top of it would offset twice.
A game that wants free floor slots rather than support-chained rooms (build
anywhere, no structural rule) is a different policy: drop
require_column_support and every row is legal. The plugin still refuses
overlap on a row; it just stops asking what is underneath.
Each room carries its own floor plate, and the plate of the room above is the
ceiling of the room below. With the camera fixed to the front, the plate is what you see when
you look at the upstairs, and the room underneath is what you see when you pan
down one storey — which is the whole point of the locked rig. The rooms proof
captures that pair as 04-stacked-rooms and 05-lower-storey, and
04b-three-storeys for the third.
The row is the cursor's job, and so is the column. A piece snapped onto a room
stands on the column you pointed at, not on the anchor of the room underneath:
aim the middle of a 3x1 and the 3x1 above it sits one cell right of the room
below, not pulled back over it. What the piece stands on is the room under the
cursor; where it goes is the cell under the cursor. 04c-aimed-off-the-anchor
captures that offset storey, and the positioner's pick evidence reports both
cells (ground_cell, volume_owner_cell) so a placement log can show the same
thing in numbers.
10. Verify
Run the editor check on the scene you wired:
- Project → Tools → Grid Placement → Validate Current Scene Setup.
- Enter the scene, select a room, hover a cell: the preview must be green and must cover the room's full footprint before you click.
- Place a 2x1 room, then a 3x1 beside it, then a 1x1 beside that.
- With the same 2x1 entry still selected, hover the room you placed: the preview must appear one row up and be green; click it. Hover the room you just stacked: the preview must appear one row above that. Nothing in the scene changes between those clicks.
- Hover bare plot with a room selected: green on the ground row, and red one row up — a room with nothing underneath is refused, not drawn.
- Hover a cell a room already claims on the row you are aiming at: red. The room one storey up over it is still legal, which is the difference between per-storey occupancy and a column rule.
Steps 4–6 are the whole stacking contract, and each one is the plugin talking: the preview, the commit and the refusal all come from the same validation.
The endpoint is proven outside the demos
test/smoke/room_tutorial_endpoint_proof.gd boots
test/fixtures/room_tutorial/room_tutorial.tscn — a scene built only from
addons/grid_placement plus its own primitive boxes, with no res://demos/
dependency — and walks this guide's build steps in the order a player meets
them: scene wiring (§2), session (§4), entries (§5), a row (§6), a storey (§9),
and then the refusals listed in §8. The fixture is the proof's own stage set: it
exists so the steps can be checked unattended, and it is not the project you
build in sections 2–5.
xvfb-run -a -s "-screen 0 1920x1080x24" godot --path godot \
-s res://test/smoke/room_tutorial_endpoint_proof.gd \
-- --out /tmp/room-tutorial-proof --size 1600x900A pass prints RESULT: PASS in order plus the plugin.cfg version it ran
against, and writes start, one-room, row, stacked and end-state screenshots, so
the tutorial's before/after is captured from the same run that checked it.
That is how this page's version floor was set: the same fixture copied next to
6.2.2, 6.2.1, 6.2.0, 6.1.2 and 6.1.1 still fails at step 7 — "place the same
2x1 room on the storey above" is refused with Cell (0, 0, 0) is already occupied. there, because those releases book 3D occupancy per column. Running
it against a release is the only thing that moves the floor — a release that
merely looks newer is not evidence.
If you have the repository checkout, the same workflow is also pinned by the rooms demo's suites, which drive the shipped room entries through the demo scene — one for the service path, one for the real cursor path (hover → preview → click, and the climb onto the storey above):
python3 scripts/testing/run_test_tree.py \
--target test/demos/integration/3d/room_build_e2e_test.gd
python3 scripts/testing/run_test_tree.py \
--target test/demos/integration/3d/room_cursor_e2e_test.gd11. The same room model in 2D
Nothing in sections 5–8 is 3D-specific. A 2D room is one ScenePlacementEntry
with a multi-cell footprint_2d, placed as a single object on a TileMapLayer:
- a 1x1 room keeps the default
CELL_CENTERanchor; - centered art an even number of cells wide uses
CELL_CENTER_LEFT(orCELL_TOP_LEFTfor tall art), so the room covers whole cells instead of straddling half of each neighbour; - the same occupancy rule applies: one commit produces the whole room, its cells are claimed, and a room over those cells is refused.
test/grid_placement/integration/placement/room_2d_feasibility_test.gd pins
that contract on the 2D object service, so the workflow the rooms demo teaches
is verified in both dimensions rather than assumed.
Appendix: building a room out of parts
The 3D demo also ships a modular kit — floors, edge walls, upper decks, story walls — for games where the player designs the shell. That is a different model with different rules (each piece is its own placement and save record), and it is covered by 3D Object Placement, not by this page. Do not mix the two in one room: a room object plus loose wall pieces will double up on the shell.
Related guides
- 3D Object Placement — mounts, footprints, snap families.
- Validation — the failure report and what each issue means.
- Save and Load — persisting built rooms.
- Grid vs Smooth Placement — when a piece should not be grid-aligned.
This page ships with the plugin's 6.3 guide set and moves to the published
site when 6.3 ships. The rooms demo is the worked example; its scenes and
scripts are under
demos/3d/rooms/ in the repository.
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.