quest'n'click 4.5.0 · r1

Built for quest'n'click 4.5.0, revision r1. Whether it is published, and which one is current, is recorded at publication.json.

Heroes: several characters and hero choice

Français · Type: tutorial + reference + explanation · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.4.0

A game can have more than one hero in two different ways. Choosing the hero (4.3.0): at New Game the player picks who the hero is (a knight, a witch or a robot; a boy or a girl), and the whole game follows that choice. Several playable characters (Day of the Tentacle style): the player switches between characters who each stand in their own room. This page walks through the first with the example game, then gives the reference of both and says when to use which.

What you will build

A game whose New Game screen asks "Who am I this morning?" and shows one portrait card per hero. Once a hero is chosen, the character on screen has that hero's look and name, the other characters' lines use that name ({hero}) and the words that fit that hero ({form?…}), in every language, with voices recorded per hero. A save keeps the hero.

The finished example is in the engine repository: games/heroes, "Who Am I Today?" (4.4.0). One garden, one puzzle (the gate key under the watering can), three heroes on portrait cards (Pixel, Biscuit, Gaby the gull), {hero} and switches in Grandma's lines, a French table that adds its own agreements (Prêt{form?gull=e|*=}), Grandma's lines voiced per hero in both languages, one rule only the gull gets, and a save that keeps the hero.

What you need

Steps

  1. Play the example. In the engine repository, on your machine:

    # in the engine repository
    GAME=heroes npm run dev
    

    Open the URL it prints, press New Game: three cards, "Pixel", "Biscuit" and "Gaby". Pick one; Grandma greets that hero by name.

  1. One character, one variant per hero. Pick a flag name for the hero (form here; any name works). Write one character whose written look is one of the heroes, and a variant per other hero, each read from the flag and each with its own name, sprites and portrait (the first block of the reference). The game's hero is that character.
  1. The intro sets the flag. In start.intro, a choice with one option per hero, each with img (its portrait) and { set: ['form', '<value>'] }. The intro runs before the first room's arrival, so the hero is chosen before anyone talks. Reload: the New Game screen shows the cards.
  1. The others' words follow the hero. In the lines, {hero} is the hero's name, and {form?knight=him|witch=her|*=it} picks the words for the flag's value (the second block of the reference). Talk to the character: the line reads as that hero.
  1. Check every hero. npm run solve stops at the first winning path, which one hero may be enough for. Prove them all:

    # in the engine repository
    GAME=heroes npm run solve -- --prove
    
    # in a created project
    npx quest-n-click solve --prove
    

    The proof checks every reachable state, so every hero, and reports a hero who can no longer finish as a softlock.

  1. Translate and voice it. A translation writes its own words inside the braces, with the same flag and values; voices are recorded once per value. See Translations and Voices below.

Limit · heroes-tutorial-scope This walk-through does not cover the art of the heroes (their sheets and portraits): see Images. The example's voice clips are stand-ins (a babble per hero, declared in games/heroes/provenance.json).

Reference: letting the player choose the hero

feature since 4.3.0

The player picks the hero at New Game: a knight, a witch or a robot, a boy or a girl, as many heroes as the game declares. The hero keeps the same puzzles and rooms; what changes is the look, the name, and the words the others use for the hero. It takes one flag (form here, any name works), one character with a variant per hero, an intro choice that sets the flag, and switches in the texts that depend on it.

Where the commands run: this page writes them for the engine repository (npm run validate, npm run solve, with GAME=<id> in front for another game than the default). In a created project, the same tools are npx quest-n-click validate, npx quest-n-click solve (--prove, --chapters), npx quest-n-click lint, npx quest-n-click i18n status and npx quest-n-click voices, and the sources are in game/ instead of games/<id>/.

hero: 'kid',
characters: {
  kid: {
    name: 'Robo', sprites: cat('robot'), portrait: 'robot/portrait',          // the written look is a hero too
    variants: [
      { if: { flag: 'form', eq: 'knight' }, name: 'Sir Lance', sprites: cat('knight'), portrait: 'knight/portrait' },
      { if: { flag: 'form', eq: 'witch' }, name: 'Morwen', sprites: cat('witch'), portrait: 'witch/portrait' },
    ],
  },
},
start: { room: 'hall', intro: [
  'Who am I this morning?',
  { choice: [
    { text: 'Sir Lance', img: 'knight/portrait', do: [{ set: ['form', 'knight'] }] },
    { text: 'Morwen', img: 'witch/portrait', do: [{ set: ['form', 'witch'] }] },
    { text: 'Robo', img: 'robot/portrait', do: [{ set: ['form', 'robot'] }] },
  ] },
] },
checkpoints: {
  door_witch: { room: 'hall', inventory: ['key'], flags: { form: 'witch', key_found: true } },
},

And in a room:

talk: { grandma: [{ topic: 'Good morning, Grandma!', do: [
  { say: ['grandma', 'Good morning, {hero}! Ready, my {form?knight=brave knight|witch=clever witch|*=shiny robot}?'] },
] }] },
look: { grandma: 'Grandma. Dust makes {form?knight=him|witch=her|robot=it} sneeze.' },

Guarantee · hero-choice-save-keeps-hero · referenced · executed on aff6caab7514 · scope self-checked by the author (AI), not reviewed A save keeps the hero: the flag that chose it is in the saved state, so a game loaded in a new engine shows the same hero, with its name. Limit: the test loads a save of the heroes fixture (tests/fixtures/heroes.ts) in the same engine version; what a save does across an engine upgrade is in State and saves.

Translations

Translations write their own words inside the braces, with the same flag and values (the switch is filled after the translation): room:hall/look.grandma → Grand-mère. La poussière {form?knight=le|witch=la|robot=le} fait éternuer.. A language may add a switch its source does not need, for its grammar: Ready? becomes Prêt{form?witch=e|*=} ?. The variants' names are translated too (char:kid/variants[0].name). npm run i18n -- status lists the translations that drop a choice the source makes (a flag it no longer switches on, a value neither listed nor covered by *=); a switch or a value added for the grammar is fine. More: Translations.

Voices

A line whose words follow a flag ({form?m=him|f=her}) is recorded once per value: put its clips in audio.voices under <id>~<flag>=<value> (grandma_saw~form=f). With several flags in one line, sort them by name and join them with ~ (greet~form=f~mood=calm); values are written as text (true, 2). The engine plays the clip for the flags as they stand and falls back to the plain id when that clip is missing or a flag is not set yet, so a game can ship before every recording exists. voicesByLang uses the same keys. npm run voices (and the Studio's Voices tab) lists one row per value, each with its text, and npm run validate warns when a voiced line lacks a value's clip. The keys are only map keys: the file names stay yours. {hero} alone is not a switch: such a line keeps one clip. More: Voices.

In the Studio

What the tools say

The example's own checks: GAME=heroes npm run solve -- --prove proves it for every hero; its browser test (games/heroes/e2e.mjs) picks each hero in English and in French, saves, reloads, continues and finishes.

Reference: several playable characters

hero: 'bernard',
players: { ids: ['bernard', 'hoagie', 'laverne'], start: { hoagie: { room: 'past_lab' }, laverne: { room: 'future_lab' } },
  give: 'Here, {nom}: the {objet}.' },

Two classic examples written this way: the tree planted in the past and the Chron-O-John.

Which to use, and why

Choosing the hero is one character with variants, not players. Playable characters each have a button in the tools row and stand in their own rooms, so the player could switch heroes mid-game: right for a team (Day of the Tentacle's three kids in three times), wrong for "who am I". The hero choice changes the look, the name and the words, never the rooms or the puzzles; what must differ by hero is written as logic (if on the flag), which the solver and the proof see.

Why a flag and switches, not one copy of each line per hero. A switch keeps one line with one id, so a translation, a voice key and the Studio's matrix stay attached to it; a value covered by *= needs no copy. The cost: a switch is filled only where a text is shown to the player (not in names, verbs or ui), and a flag counted with inc cannot be listed value by value.

Limit · hero-choice-not-mid-game Nothing switches the hero mid-game in the player's interface: the choice is made in the intro. A command can set the flag again later, and the variant then follows, but no screen offers it.

The design record: ADR 0022, decision D49 in docs/dev/DECISIONS.md.

What you learned

Where to go next


Edit the source of this page · Report an error