Grid Placement 6.0.0 supports multiple simultaneous builders through per-controller sessions registered with a GridPlacementHost.
Each controller or player owns a PlacementSession with independent state: selected placeable, selected terrain, targeting, mode, and action history. The host routes input to the correct session using Godot's InputEvent.device when a device id is registered.
Version note: This guide is for advanced setups. For a first single-player setup, start with Getting Started.
Core Model
Player A / device 0 Player B / device 1
│ │
├── PlacementSession A ├── PlacementSession B
│ └── states + selected terrain │ └── states + selected terrain
│ │
└── register_session(self, A, 0) └── register_session(self, B, 1)
│ │
└───────────────┬───────────────────────┘
▼
GridPlacementHost
├── session registry
├── device → session map
├── PlacementServiceGroup
└── input dispatch
Player A can select grass while Player B selects snow because the selections live in different sessions. The shared services point at whichever session is active for the input event being handled.
Setup
1. One host
Add a GridPlacementHost to the scene, or let your template/injector setup configure one.
For manual setup:
var host := GridPlacementHost.new()
add_child(host)
host.configure(default_session)
2. Each controller registers its session
From the controller's _ready():
@export var player_device: int = 0
@export var host: GridPlacementHost
var session := PlacementSession.new()
func _ready() -> void:
session.grid_placement_bundle = preload("res://config/grid_placement_bundle.tres")
host.register_session(self, session, player_device)
func _exit_tree() -> void:
if host != null:
host.unregister_session(self)
Use the same bundle/settings for both players when they should share game rules. Use separate catalogs/unlocks when their available entries should differ.
3. Input routing
When a device id is supplied to register_session, the host can resolve the session for events from that device.
For Godot's input system to distinguish devices, each player's input actions must be mapped to the intended device. See Godot's input documentation for project-level device mapping.
Session Registry API
| Method | Purpose |
|---|---|
register_session(controller, session, device) |
Register a controller's session. device is optional. |
unregister_session(controller) |
Remove a controller and clean up its mappings. |
get_session_for_controller(controller) |
Look up a session by controller node. |
get_active_sessions() |
Return all currently registered sessions. |
get_session_registry_count() |
Return the number of live registered sessions. |
State Isolation Guarantee
Player A's selection and mode should not leak into Player B because:
- Each controller has its own
PlacementSession. - Each session has its own
PlacementStates. - Selection such as
selected_terrain_namelives on the session. - The host resolves the session per input device.
- Before dispatch, services swap to the resolved session's state objects.
Conceptual host dispatch shape:
var session := _resolve_session_for_device(event.device)
_activate_session(session)
# dispatch placement intent using that session's states
Single-Player Convenience
For a single-controller game, you usually do not need explicit registration code. PlacementInjectorSystem can auto-register its exported session with the host as a convenience, and GridPlacementHost.configure(session) also provides a default session path.
Use explicit register_session(...) only when you need per-controller ownership, per-device input routing, or multiple active sessions.
Visual Adapters and UI
State isolation does not automatically draw two separate cursors, previews, or HUDs for you.
For split-screen, plan your visual layer intentionally:
- Each player may need its own
GridPositioner2D/ targeting adapter setup. - Each player may need session-scoped UI.
- HUD controls should read from that player's
PlacementSession, not from a global singleton. - Shared settings are fine; shared mutable session state is not.
Limitations and Responsibilities
- Settings are usually shared.
PlacementSettings,ManipulationSettings, andGridTargetingSettingsare game configuration, not per-player state. - Per-player UI is game-owned. The plugin isolates placement state; your game decides how to render per-player HUDs.
- Device mapping is project-owned. The host can route by
InputEvent.device, but your project still needs correct input map/device setup. - Complex multiplayer authority is game-owned. Network replication, server validation, and rollback are outside the plugin's built-in scope.
Related Guides
Source
docs/v6-0/guides/multiplayer-and-split-screen.md
Plugin docs root:gdscript/plugins/grid_placement_dev/docs