Skip to content

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.ts
  • src/level.json

Steps ​

  1. Create src/level.json. Level class Pickup is exported as PickupActor in TypeScript; Pickup itself 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
      }
    }
  ]
}
  1. 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],
});
  1. For healing use give: { "heal": 25 }; full-health players leave it available. For items use give: { "item": "key", "count": 1 } and configure shared inventory as in the door recipe. Zero respawnSeconds means collected forever in this run. Use a derived class's onPickedUp callback 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.

See also ​