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 entryin 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.