Repository navigation
EN Create Moving Characters
Français · Type: how-to · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.3.0
Make a character walk on its own: a guard who patrols a room, a nurse who goes from room to room and catches the hero,
Grandpa who goes home once the key is found. Everything here is the DSL as it is: a script, walk, moveActor,
an event, and conditions.
A character that moves between two spots of a room, or between rooms, on a loop or when something happens; rules that know where it is; and a game the solver still proves, because the patrol is part of the state it explores.
- A created project (
game/) or the engine repository (games/<id>/); the same files and commands, see the layout page. - From 4.3.0 for this page. Scripts, events and
moveActorexist since 1.3 ("World"); schema 3 (stable ids,stepIds) since 3.0.
- The character declared in
game.tswith awalkpose in itssprites(without one, it slides in its current pose) (characters). - The rooms placed in the editor: the character's actor placed in each room it can be in, with a walk area (geometry).
- Scripts and events, for the fields used below.
Write the content in the game's files (rooms/<room>.ts, game.ts, cast.ts). Check from the root of the project:
npm run validate and npm run solve in the engine repository, npx quest-n-click validate and
npx quest-n-click solve in a created project.
-
A looping script in the room. The guard walks to the vault, waits, walks to the window, waits, forever:
{ walk: [x, y], who: 'guard' }and{ wait }in a script withloop: true. A loop must contain await,waitUntilorwaitEvent.
-
Say where he is with a flag. Conditions cannot read a character's position inside a room; let the script set
a flag at each stop (
{ set: ['guard_at', 'vault'] }), and let the rules test it ({ flag: 'guard_at', eq: 'vault' }).
-
Stop the patrol when it no longer matters:
while: '!vault_open'rewinds the script and stops it once the vault is open (or{ stopScript: 'guard_patrol' }from a rule).
- The rules. The special case first: opening the vault while he is there gets caught; otherwise it works.
{
"room": {
"id": "vault_hall",
"name": "The vault hall",
"decor": "decor/vault_hall",
"actors": { "guard": { "char": "guard" } },
"hotspots": { "vault": { "name": "vault door" } },
"look": { "guard": "A guard. He walks. He turns. He walks.", "vault": "The vault door." },
"on": [
{ "id": "hall.vault-watched", "verb": "open", "a": "vault", "if": { "flag": "guard_at", "eq": "vault" },
"do": [{ "say": ["guard", "Hey! Step away from that door."] }] },
{ "id": "hall.vault-open", "verb": "open", "a": "vault", "if": "!vault_open",
"do": ["Quick, while he is at the window.", { "set": "vault_open" }] }
],
"scripts": [
{ "id": "guard_patrol", "loop": true, "while": "!vault_open",
"stepIds": ["to-vault", "at-vault", "wait-vault", "to-window", "at-window", "wait-window"],
"do": [
{ "walk": [520, 340], "who": "guard" },
{ "set": ["guard_at", "vault"] },
{ "wait": 4000 },
{ "walk": [140, 340], "who": "guard" },
{ "set": ["guard_at", "window"] },
{ "wait": 4000 }
] }
],
"hints": [{ "id": "hall.hint", "until": "vault_open", "lines": ["Wait until the guard is at the window."] }]
},
"layout": {
"walk": { "area": [[20, 300], [620, 300], [630, 395], [10, 395]] },
"entries": { "default": [320, 360] },
"hotspots": { "vault": { "rect": [480, 140, 90, 170], "approach": [470, 350] } },
"actors": { "guard": { "x": 140, "y": 340 } }
},
"characters": {
"guard": { "name": "Guard", "color": "#8fd3ff", "kind": ["person"], "sprites": { "idle": ["guard/idle"], "walk": ["guard/walk1", "guard/walk2"] } }
}
}
The walk … who coordinates are the one place where a position is typed: a scripted move. Where the guard stops is
kept in the save.
-
Give the character a starting room. A character declared as an actor in several rooms normally shows in all of them (Grandma in the kitchen and the garden). Give it a
room, and only its actor in that room shows;moveActorsends it elsewhere:// cast.ts edna: { name: 'Edna', color: '#f0f', room: 'kitchen', sprites: { idle: ['edna/idle'], walk: […] } }, // hall.ts and kitchen.ts both declare actors: { edna: { char: 'edna' } } and place her in their layout. // anywhere: { moveActor: ['edna', 'hall'] } at her layout position in the hall // { moveActor: ['edna', 'hall'], at: 'door' } at that entry point (or a point) of the hall // conditions: { actorIn: ['edna', 'hall'] }
The character is removed from the room it leaves and appears in the one it reaches, on screen if the player is there. The validator refuses
moveActortoward a room where the character is not an actor, or for a character with noroom.
-
A game script moves her and says so. After each move it emits an event; a game listener catches the hero when they meet, and each room's
onEntercatches him when he walks in on her:// game.ts scripts: [{ id: 'edna_patrols', loop: true, stepIds: ['w1', 'to-hall', 'moved-1', 'w2', 'to-kitchen', 'moved-2'], do: [ { wait: 6000 }, { moveActor: ['edna', 'hall'] }, { emit: 'edna_moved' }, { wait: 6000 }, { moveActor: ['edna', 'kitchen'] }, { emit: 'edna_moved' }, ] }], events: [{ id: 'edna.catches', on: 'edna_moved', if: { any: [{ all: [{ room: 'hall' }, { actorIn: ['edna', 'hall'] }] }, { all: [{ room: 'kitchen' }, { actorIn: ['edna', 'kitchen'] }] }] }, do: [{ say: ['edna', 'Got you!'] }, { goto: 'dungeon' }] }], // hall.ts (and the same in kitchen.ts): onEnter: [{ if: { actorIn: ['edna', 'hall'] }, then: [{ say: ['edna', 'Got you!'] }, { goto: 'dungeon' }] }],
The dungeon has a loose brick back to the hall. The whole game is a test fixture,
tests/fixtures/classics.ts(mansion), and the mechanic is described on classic mechanics.
-
Checkpoints. A checkpoint starts with every moving character in its starting room;
where: { edna: 'hall' }puts one elsewhere. A checkpoint withgoalsmust be a state the game can really reach when those goals hold:npm run solve -- --prove --chaptersproves each chapter from every reachable boundary state and reports a checkpoint that matches none, with the dimensions that differ (room: house vs market).used,seenandplayers[].usedlet a checkpoint name what a real playthrough leaves behind (an item already used, a once-listener already fired) (chapters).
Limit ·
turn-by-turnA patrol is turn by turn, not real time. Scripts run between the player's actions, never during one: the nurse cannot walk in while the hero is mid-sentence or mid-walk. That is what lets the solver prove the puzzle (it waits for her to leave, then crosses) and what a save restores exactly. A sequence that must not be interrupted goes in acutscene.
Limit ·
no-position-conditionNo condition reads where a character stands inside a room, nor its line of sight. Model "the guard is at the door" with a flag the script sets, and "the guard is in this room" with{ actorIn }.
Guarantee ·
patrol-turn-by-turn· referenced · scope self-checked by the author (AI), not reviewed A patrolling character moved by a script catches the hero turn by turn: when the hero enters her room, or when she enters his.
- On the dev server, the guard walks back and forth while you play; opening the vault while he stands by it gets the warning, at the window it opens.
-
npm run validate(ornpx quest-n-click validate) prints no error. -
npm run solvefinishes, and its path shows the script's turns as steps (Script edna_patrols): the solver waits for the patrol to move, then acts.
Guarantee ·
patrol-solved· referenced · scope self-checked by the author (AI), not reviewed The solver proves the patrolling nurse's game: it lets her script run and waits for her to leave.
| Symptom | Cause | Fix |
|---|---|---|
loop script "…" never waits (add a wait, waitUntil or waitEvent) |
A loop without a pause | Add a { wait }
|
schema v3 script needs one stable step id per command (n expected) |
stepIds missing or the wrong length |
One id per top-level command |
character "…" has no starting room ("room" in its definition), needed to move it between rooms |
moveActor on a character without room
|
Add room in its definition |
"…" is not an actor in <room>: declare it there (actors) and place it in the layout |
moveActor toward a room that does not declare it |
Declare the actor there and place it |
| The character shows in every room at once | No room on the character |
Give it a starting room |
| The character slides instead of walking | No walk pose |
Add walk to its sprites
|
| The patrol stops for good after an item changes hands | A while checked before every command |
Gain the new item before losing the old one |
rooms/<room>.ts (the room's script, rules, onEnter), game.ts (game scripts, listeners, checkpoints), cast.ts
(the starting room), layout/<room>.json (the actor placed in each room). In a save: the script's position, where each
moving character is (where), each actor's position and the flags the script sets.
Removing a script or renaming its steps changes what an old save holds: add a migration step (renameScript,
renameScriptStep, dropScript) when players have saves (state and saves).
- Scripts and events, flags, variables and events, conditions, classic mechanics.
-
src/engine/core/movement.ts,src/engine/core/script-runtime.ts,tests/classics.test.ts.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits