# 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]]