Add a pickup
Goal
Copy templates/blank. Award ten points when the player touches a mint sphere, hide it for three seconds, then make it collectible again.
Files you will edit
src/game.tssrc/level.json
Steps
- Create
src/level.json. Level classPickupis exported asPickupActorin TypeScript;Pickupitself is the older ECS marker.
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": [0, 0.9, -3],
"props": {
"asset": "sphere-mint",
"give": {
"score": 10
},
"respawnSeconds": 3,
"bob": 0
}
}
]
}- Replace
src/game.ts. The premade actor handles overlap eligibility, rotation, hiding and cooldown; the system reads its shared seat score.
ts
import {
defineGame,
prefab,
PlayerStart,
MeshActor,
validateLevel,
PickupActor,
score,
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;
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 model = { text: { score: 0 } };
function showScore(ctx: GameContext): void {
model.text.score = score.get(0);
ctx.hud.set(model);
}
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' },
systems: [walk, showScore],
});- For healing use
give: { "heal": 25 }; full-health players leave it available. For items usegive: { "item": "key", "count": 1 }and configure shared inventory as in the door recipe. ZerorespawnSecondsmeans collected forever in this run. Use a derived class'sonPickedUpcallback for custom behaviour; callbacks belong in TypeScript, never JSON.
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: walk forward into the sphere. Score becomes 10 and the sphere hides. After three seconds it returns; if still standing inside, it immediately collects again. Leave its radius to watch it reappear. Reload resets the score.