Chris' Tutorials
Docs/Grid Placement

Grid Placement v6.0

Multiplayer and Split-Screen

Set up multiple controllers with independent placement sessions for split-screen or multiplayer.

StatusDraft
Versionv6.0
UpdatedDevelopment docs generated from GDScript source

This is unreleased documentation in active development. APIs, class names, and behavior may change before the final release.

Draft — Unreleased:This page is in active development. APIs, class names, and behavior may change before the release is finalized. Use it as a preview of what's coming — not as a stable integration target.

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:

  1. Each controller has its own PlacementSession.
  2. Each session has its own PlacementStates.
  3. Selection such as selected_terrain_name lives on the session.
  4. The host resolves the session per input device.
  5. 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, and GridTargetingSettings are 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