Per-session unlocks let one game catalog serve different players, controllers, game modes, or progression states.
The host's GridPlacementBundle describes what exists in the game. A PlacementUnlocks resource on PlacementSession describes what this session can use right now.
Semantics
Each filter axis supports FilterMode.ALLOWLIST or FilterMode.DENYLIST.
| Mode | Empty list | Non-empty list |
|---|---|---|
ALLOWLIST |
Allow all | Only listed ids |
DENYLIST |
Allow all | Block listed ids |
An optional runtime filter can be set with set_runtime_filter(Callable). Runtime filters are not serialized.
Setup Example
var session := PlacementSession.new()
session.grid_placement_bundle = preload("res://config/grid_placement_bundle.tres")
session.catalog = preload("res://catalog.tres")
var unlocks := PlacementUnlocks.new()
unlocks.unlocked_entry_ids = PackedStringArray(["hut", "grass"])
unlocks.unlocked_terrain_ids = PackedStringArray(["grass"])
session.unlocks = unlocks
host.register_session(self, session)
Use the same pattern for split-screen or controller-specific sessions. Each session can have different unlocks.
Reading Available Entries
# Filtered list for gameplay or palettes:
for entry in session.get_available_entries():
populate_palette(entry)
# Single-entry gate:
if session.is_entry_available(&"sawmill"):
enable_build_button()
UI Behavior
PlaceableSelectionUI and TerrainPaletteUI read the active session from placement dependency resolution.
When a filter is active, locked entries can stay visible but disabled/greyed out, depending on the UI settings:
shows_entry_as_lockedshows_terrain_as_lockedshows_sequence_as_locked
This lets the player understand that an option exists but is not unlocked yet.
Related Guides
Source
docs/v6-0/guides/per-session-unlocks.md
Plugin docs root:gdscript/plugins/grid_placement_dev/docs