Add a door that needs a key
For a kit door in one level entry, use HingedDoor with lockedBy: 'key': pick the right door.
Goal
Copy templates/blank. Place a key pickup and a locked sliding door. Both actors use one inventory, indexed by player seat; solo uses seat zero.
Files you will edit
src/game.tssrc/level.json
Steps
- Create
src/level.json. The item idkeyis inventory data; the mesh idsphere-mintcomes from the template's asset manifest.
json
{
"format": 1,
"actors": [
{
"class": "PlayerStart",
"position": [0, 1.2, 0]
},
{
"class": "MeshActor",
"position": [0, -0.5, 0],
"props": {
"asset": "cube-muted",
"scale": [20, 1, 20],
"collision": "block",
"halfExtents": [10, 0.5, 10]
}
},
{
"class": "Pickup",
"position": [2, 0.9, -2],
"props": {
"asset": "sphere-mint",
"give": {
"item": "key",
"count": 1
},
"bob": 0
}
},
{
"class": "Door",
"name": "gate",
"position": [0, 1, -4],
"props": {
"asset": "cube-mint",
"lockedBy": "key",
"consumeKey": true,
"openOffset": [0, 3, 0],
"seconds": 1,
"autoCloseSeconds": 3,
"facingDegrees": 180
}
}
]
}- Replace
src/game.ts. Keep configuration ininit, before the first actor step. Loading the level happens beforeinit, but collection starts later. Preserve the interaction component's HUD writer by avoiding another HUD model in this example.
ts
import {
defineGame,
prefab,
PlayerStart,
MeshActor,
validateLevel,
PickupActor,
Door,
configureGameplayActors,
createInventory,
type GameContext,
} from 'gameable';
import settings from '../settings.json';
import data from './level.json';
// Register the level's classes in the guest bundle.
void PlayerStart;
void MeshActor;
void PickupActor;
void Door;
const Hero = prefab({
asset: 'cube-mint',
scale: [0.6, 1.8, 0.6],
body: { shape: 'capsule', dims: [0.4, 0.5], kind: 'character' },
});
function walk(ctx: GameContext): void {
const move = ctx.input.axis2('KeyA', 'KeyD', 'KeyS', 'KeyW');
ctx.physics.moveCharacter(ctx.player, move.x * 4, 0, -move.y * 4);
}
const bag = createInventory({ owners: 1, slots: 4, items: [{ id: 'key' }] });
export default defineGame({
features: settings.features,
world: settings.world,
assets: ['cube-mint', 'cube-muted', 'sphere-mint'],
level: validateLevel(data),
player: { prefab: Hero, spawn: 'playerStart', camera: 'thirdPerson' },
init(ctx) {
configureGameplayActors(ctx, { inventory: bag });
},
systems: [walk],
});lockedByclears after a successful unlock; consuming a key does not relock the door after it closes.openYawselects rotation instead ofopenOffsettranslation when nonzero. The stock door collider is fixed at [0.5,1,0.15] half extents; its optional mesh uses its own asset dimensions. For rooms, provision inventory owners for all seat indices.
Verify
From the copied game root run npx tsc --noEmit -p tsconfig.json, then npm run build (in this checkout use npm run build -w templates/blank). Blank is a bundle template, with no dev-server script: open the resulting bundle in the common Gameable player used by your app (examples/play/README.md describes local hosting).
Check it: approach within two metres before collecting the key: the prompt is Locked and E does nothing. Collect the sphere, return, and press E: the door slides up over one second. It closes three seconds after opening fully, and E can reopen it without another key. bag.count(0, 'key') is now zero.