# 3. Lock-and-Key Data Part of [[Hazards, Traps and Key-Door Puzzles]]. Prev: [[2. Timing Traps and Rhythm]]. Next: [[4. Checkpoints and Respawn]]. Level 12 in [[Level Up! (Review)]]: **"Show 'em the door and then send 'em after the key"** and **"No cat mustaches"** (no illogical solutions; both are 1st-edition headings cited in the review). The first is a layout rule. The second is a logic rule. [[Inventory Systems]] already flagged `questItem` so a key cannot be dropped, sold, or lost. This part is the door that *reads* that inventory, and the space that makes the door visible before the key hunt starts. ## 3.1 Layout first 1. The player **sees the locked door** (cyan in the gray-box language, a unique silhouette, a short line of UI or a prompt). 2. The path to the key is **foreshadowed** (a side door, a landmark, a quest line - [[Quest Systems]] can hook a Reach objective on the door and a Collect on the key). 3. The key is a **real object** in the world (a pickup), not a flag flipped in an invisible trigger unless the fiction is "you already have it." 4. The solution is **causal**: the brass key opens the brass door. A banana should not open the clock tower ("no cat mustaches"). 5. If the bag can be full, a key pickup must **not silently vanish**. `Inventory.Add` returns a leftover; the world pickup stays ([[Inventory Systems]] "Love Thy Player"). Do not hide the only key behind a lethal one-shot trap with no checkpoint. That is a soft-lock with extra steps. ## 3.2 Key items Create an `ItemDefinition` (the class from the inventory note): - `id` stable, for example `brass-key` (this is what a save stores, never a Unity object reference). - `questItem = true` so `Drop` refuses it. - `maxStack = 1` unless you have a reason for key rings. - Icon that reads at 48 px (Level 8 / the inventory note). The door holds a **reference to that asset**, not a string you type twice, unless you are resolving through `ItemDatabase` on purpose (multi-scene). Either is fine; do not mix both in one door. ## 3.3 The door Interact uses the Input System. The prompt itself can stay crude (a world-space label) until the onboarding tutorial in a later batch; the *logic* is the door. ```csharp using System; using UnityEngine; using UnityEngine.InputSystem; /// <summary> /// A lock that reads Inventory.Has and optionally consumes the key. /// NOT compiled in Unity. Requires InventoryHolder / ItemDefinition from Inventory Systems. /// </summary> public sealed class KeyDoor : MonoBehaviour { [SerializeField] ItemDefinition key; [SerializeField] InventoryHolder fallbackHolder; // used if the interactor has no holder [SerializeField] bool consumeKey; [SerializeField] bool startLocked = true; [SerializeField] Collider blockingCollider; [SerializeField] Animator animator; [SerializeField] string lockedPrompt = "Locked. Need the brass key."; [SerializeField] string openPrompt = "Open"; [SerializeField] string needKeyPrompt = "It's locked."; [SerializeField] InputActionReference interactAction; static readonly int OpenHash = Animator.StringToHash("Open"); public bool IsLocked { get; private set; } public event Action<KeyDoor> Unlocked; public event Action<KeyDoor> Opened; InventoryHolder _nearby; void Awake() { IsLocked = startLocked; SetBlocking(IsLocked); } void OnEnable() { if (interactAction == null || interactAction.action == null) return; interactAction.action.performed += OnInteract; interactAction.action.Enable(); } void OnDisable() { if (interactAction == null || interactAction.action == null) return; interactAction.action.performed -= OnInteract; } void OnTriggerEnter(Collider other) { var holder = other.GetComponentInParent<InventoryHolder>(); if (holder != null) _nearby = holder; } void OnTriggerExit(Collider other) { var holder = other.GetComponentInParent<InventoryHolder>(); if (holder != null && holder == _nearby) _nearby = null; } void OnInteract(InputAction.CallbackContext _) { if (_nearby == null) return; TryUse(_nearby); } public bool TryUse(InventoryHolder holder) { if (holder == null || holder.Inventory == null) return false; if (!IsLocked) { Open(); return true; } if (key == null) { Debug.LogWarning(quot;{name}: KeyDoor has no key assigned.", this); return false; } if (!holder.Inventory.Has(key)) { Debug.Log(needKeyPrompt); // replace with UI / dialogue when you have it return false; } if (consumeKey) { int removed = holder.Inventory.Remove(key, 1); if (removed < 1) return false; } IsLocked = false; Unlocked?.Invoke(this); Open(); return true; } public void Open() { SetBlocking(false); if (animator != null) animator.SetTrigger(OpenHash); Opened?.Invoke(this); } public void Lock() { IsLocked = true; SetBlocking(true); } public string PromptFor(InventoryHolder holder) { if (!IsLocked) return openPrompt; if (holder != null && holder.Inventory != null && key != null && holder.Inventory.Has(key)) return openPrompt; return lockedPrompt; } void SetBlocking(bool blocked) { if (blockingCollider != null) blockingCollider.enabled = blocked; } } ``` Design choice: **consumeKey**. A story key that opens one ceremonial door can stay in the inventory as a souvenir (Level 13). A "key card" that should not open every later door of the same type should be consumed, or you use a different `ItemDefinition` per door. Reusing one `brass-key` for six doors is how you skip half a level. The door's trigger is a *use volume* (player nearby), not the blocking collider. Keep them as two colliders so disabling the blocker does not also kill Interact. ## 3.4 "Already saw the door" If you want the key pickup to *remind* the player of the door, raise a small event when the door is first seen (a trigger in front of it) and when the key is collected. [[Quest Systems]] already models this as Reach then Collect. You do not need a second quest log; hook the existing one, or a one-line toast. ## 3.5 Exercise Place the door in the first room of a two-room box, the key in the second room behind a *visible* window so the player sees the key *after* the door. That is "show the door, then the key" even in a space you can cross in ten seconds. Then try `questItem` off and drop the key in a pit with no retrieve - that is the soft-lock you are forbidding. ## Sources - [[Inventory Systems]] - `Has`, `Remove`, `questItem`, leftover on Add - [[Quest Systems]] - [[Level Up! (Review)]] Level 12 - [[Unity 2D Point-and-Click System]] See also (added 2026-11-10): [[Context-Sensitive Prompts and Onboarding]] / [[5. Worked Example Door Prompt]] - `KeyDoor.PromptFor` plus `Inventory.Changed` drive the volume verb.