Grid Placement supports multiple independent placement interactions through one PlacementSession per player/controller.
The important split is:
Player A session ─┐
├─ GridPlacementHost → shared committed placement world
Player B session ─┘Selections/previews are independent. Committed occupancy/mounts are shared when both players are building in the same world.
Register one session per controller
var session:= PlacementSession.new()
session.grid_placement_bundle= shared_bundle
host.register_session(self, session, player_device)Unregister it when the controller leaves:
host.unregister_session(self)Use a device id when your local input setup needs device-based routing. For keyboard/mouse or custom input routing, wire the project input layer intentionally rather than assuming the plugin can infer player ownership.
What stays independent
Each session can have its own:
- selected placeable/terrain;
- GRID vs SMOOTH mode;
- current preview/target;
- pending manipulation/brush action;
- unlock/catalog availability;
- controller-specific UI/input state.
What is shared
Sessions in the same placement world must agree on committed facts:
- placed object identity;
- GRID/SMOOTH occupancy;
- CELL/EDGE/FACE/CORNER/TOP/socket occupancy;
- save/restore world records.
If Player A occupies an EDGE mount, Player B should see that same edge as occupied even though their previews/selections are independent.
Split-screen presentation
State isolation does not automatically build a split-screen UI for you. Your game owns:
- cameras/viewports;
- player-specific cursor/target adapters;
- per-player HUD/catalog presentation;
- device/action mapping.
Make each HUD read its player's PlacementSession. Avoid global mutable selection state.
Network multiplayer boundary
Grid Placement provides placement mechanics/state, not a complete network-authority model.
Your game still decides:
- client prediction vs server authority;
- RPC/replication format;
- permission/ownership checks;
- rollback/reconciliation;
- persistence ownership.
A networked game should have its authoritative game layer validate/accept placement requests through the supported placement APIs rather than trusting a client-side preview.
2D and 3D
The same session/world split applies to:
- 2D objects and terrain interaction state;
- 3D GRID/SMOOTH object placement;
- CELL/EDGE/FACE/CORNER/TOP/socket structures;
- shared occupancy and restore.
Do not create separate competing placement worlds just because players use different coordinate modes.
3D GRID objects: one service, one slot per session
A 3D scene binds a single ObjectPlacementService3D with
GridPlacementHost.set_object_placement_service_3d(). That service holds the
committed occupancy for everyone, plus one selection/preview slot per session:
the selected entry, the ghost, its yaw, and its mount. When a session gets
input (its controller's events, select_placeable_for_session, or a
PlacementRuntime commit), the host binds that session's slot. The other
players' ghosts stay where they left them.
Drive a specific player's preview through the host, not a cached service reference:
var service:= host.get_object_placement_service_3d_for_session(player_two_session)
service.update_preview_cell(cell)
service.rotate_preview(1)
service.try_place_report(cell)# recorded under player two's session idA parked ghost is not revalidated while its player is idle. If another player commits on that spot, the parked ghost keeps its last tint until its owner's next preview update, and the commit is still refused.
Common mistakes
| Symptom | Cause to check |
|---|---|
| Player A changes Player B's selection | Sessions are shared or UI points at the wrong session. |
| Two players place into the same occupied location | They are not sharing the same authoritative placement world/registry. |
| Wrong player reacts to controller input | Device/action routing or controller→session registration. |
| HUD shows another player's state | UI reads a global/default session instead of its owner session. |
| Network client can bypass rules | Game/server authority is trusting client state instead of validating placement. |