β›½ Ignitor Docs ← getignitor.com
πŸ‡ΊπŸ‡Έ EN πŸ‡ͺπŸ‡Έ ES

Timers & watchers#

Some of the best moments in an adventure game happen on a delay: the player rings a doorbell, wanders off, and ten seconds later someone upstairs mutters "the doorbell!" and comes down to answer, wherever the player happens to be by then. Ignitor builds this out of two small pieces, timers and watchers, and this guide shows you how to wire them into a loop that works every time, not just the first time.

The three pieces, in plain words#

  • A timer is a kitchen timer. You wind it up with a name and a number of seconds (STARTTIMER:doorbell_timer|10), walk away, and it goes off on its own. Nothing happens when it goes off: it just sits there, rung, until something checks on it.
  • A watcher is a tripwire. It watches a condition every frame ("has doorbell_timer gone off?") and the moment the condition is true it fires its effects, once. After firing, it's disarmed: it will never fire again until something re-arms it (or until its condition goes false, if you set it to fire every time).
  • A behaviour is a routine a character runs on their own (walk here, open that, wander this hallway), no player input involved.

Chain the three and you get the doorbell scene: the click starts the timer, the timer trips the watcher, the watcher launches the reaction.

The loop, drawn out#

This is the actual setup from our Maniac Mansion tribute demo: Weird Ed, upstairs in his room, reacting to the front-door bell:

The doorbell loop: timer, watcher, cutscene, re-arm Flow diagram of a repeating cycle. The player rings the doorbell, which starts a ten second timer. When the timer expires a watcher fires once and goes disarmed. Its effects clear the timer and run a cutscene in which Weird Ed heads for the door. The last cutscene step re-arms the watcher with CLEARWATCHER, closing the loop for the next ring. The player rings the doorbell a hotspot "use" reaction STARTTIMER:doorbell_timer|10 The timer counts down the game keeps playing meanwhile doorbell_timer Β· 10 s expires after 10 seconds The watcher fires, once and goes disarmed when: timerExpired Β· doorbell_timer its effects run Cutscene: Ed heads for the door clears the timer, runs his routine CLEARTIMER + CUTSCENE + BEHAVIOUR the cutscene's last step Re-arm the watcher closes the loop CLEARWATCHER:weird_ed_reacts_to_doorbell re-armed, ready for the next ring
One full lap of the doorbell loop. The purple return arrow is what makes it repeatable.

The example, piece by piece#

Three editors, one piece each:

1. The doorbell hotspot (Room Editor): the use reaction plays a chime and winds up the timer. That's all the hotspot knows; it has no idea Ed exists.

SOUND:assets/audio/sfx/doorbell_chime.ogg
STARTTIMER:doorbell_timer|10

2. The watcher (Puzzle Editor, 🌐 Global Watchers tab): global, because the player has probably wandered into another room by the time the timer goes off, and a global watcher listens everywhere.

id:   weird_ed_reacts_to_doorbell
when: timerExpired Β· doorbell_timer
do:   CLEARTIMER:doorbell_timer
      CUTSCENE:weird_ed_hears_doorbell

3. The cutscene (Cutscene Editor, 🌐 Global bucket): Ed reacts, walks to his door, and hands control back. Then his behaviour takes over: a routine that marches him downstairs, room by room, to answer the front door. The final step is the one that makes the whole thing repeatable:

SWITCHCHAR:weird_ed
SAY:weird_ed|"Ah! The doorbell!"
WAIT:1
SAY:weird_ed|"Maybe that's my package!"
WALKNPCTO:weird_ed|1348|358
SWITCHCHAR:{_prevChar}
BEHAVIOUR:weird_ed|weird_ed_notices_doorbell
CLEARWATCHER:weird_ed_reacts_to_doorbell

Ring the bell again after Ed settles back in, and the same ten-second fuse burns down to the same reaction. Every lap of the loop resets itself.

The two golden rules#

Both of these bit us while building the demo, so they're earned wisdom:

An expired timer stays "expired" until you clear it. When it goes off, the timer doesn't vanish: it sits in its rung state, and a timerExpired condition keeps answering yes forever. Make CLEARTIMER the first effect of whatever watcher listens for it, or that watcher's condition is permanently true.

A watcher can't re-arm itself from its own effects. The engine marks a watcher as fired after running its effect list, so a CLEARWATCHER targeting itself inside its own do un-marks something that isn't marked yet: a no-op, and the watcher stays dead. Put the CLEARWATCHER at the end of the cutscene (or effect chain) the watcher launched instead. Nice side effect: while the reaction is still playing, the watcher stays disarmed, so mashing the doorbell can't stack five Eds.

A watcher that fires every time#

Firing once is the default, and for a story beat it's what you want. When a watcher should react every time its condition comes true (a bar that shows whenever the cursor reaches the edge, a lamp that switches on whenever the player steps into a zone), set Fires to Every time it comes true on its card in the Puzzle Editor.

It still fires once when its condition turns true, and then stays quiet while the condition holds, so it never repeats every frame. What changes is the re-arming: the moment the condition goes false again, the watcher re-arms by itself, and the next time it comes true it fires again. No CLEARWATCHER needed.

Two things to keep in mind:

  • It re-arms by looking at the condition, not at what it launched. If the watcher starts a cutscene and the condition comes true again while it's still playing, it fires again. That's why the doorbell above keeps Once and its CLEARWATCHER at the end of the cutscene: Ed has to finish before the bell counts again.
  • A timerExpired watcher still needs its CLEARTIMER. An expired timer keeps the condition true (rule one), so the watcher would never see it go false and never re-arm.

A timer as a cooldown gate#

A timer isn't only a fuse for a watcher; you can also ask whether it's still running and use that as a gate. Two conditions read the same timer from opposite ends:

  • timerExpired: true only after the timer has rung (and stays true until you CLEARTIMER it, per the first golden rule above).
  • timerRunning: true while the timer is still counting down, and false the instant it runs out, or before it ever starts.

That turns a cooldown into a one-liner. Start a timer as the "cooling" window, then guard the action on timerRunning:

  • While timerRunning β†’ do nothing (or say "not yet"). The window is still open.
  • Otherwise β†’ do the thing, and start the timer again. That re-opens the window and blocks a repeat until it burns down.

No companion flag, no re-arming watcher: the timer itself is the memory of "done recently". Reach for timerExpired when you want a delayed one-shot (the doorbell); reach for timerRunning when you want to suppress something during a window: a button that can't be spammed, an NPC that won't repeat a line for a while.

A watcher that follows the cursor#

Some interface only shows up when the player reaches for it: a verb bar that appears when the cursor touches the bottom of the screen, a panel that drops in from the top, an exit arrow at the side. Two conditions read where the cursor is right now:

  • cursorAtEdge: true while the cursor sits on one edge of the screen (top, bottom, left or right), inside a strip a few pixels thick: 4 unless you give it another thickness. Going past the edge counts too, so the black bars around the picture, when the window doesn't match your game's shape, still count as that edge.
  • cursorInRect: true while the cursor is inside a rectangle: the x and y of its top-left corner, then its width and height. Those are screen pixels, not room pixels, so the camera never moves the rectangle.

The mouse and a controller's stick move the same cursor, so both conditions work with either. A touchscreen has no cursor resting anywhere, so there both stay false: give touch players another way in, like a button.

"Show it on the edge, hide it when the cursor leaves" takes two watchers, both set to fire every time:

id:    bar_show
when:  cursorAtEdge Β· bottom
fires: Every time it comes true
do:    SHOWGUI

id:    bar_hide
when:  NOT cursorAtEdge Β· bottom Β· 60
fires: Every time it comes true
do:    HIDEGUI

Two details make it work:

  • Each watcher re-arms on its own. bar_show re-arms as soon as the cursor leaves the edge, and bar_hide as soon as it's back inside the strip, so the pair keeps working however many times the player comes and goes.
  • The hide strip is thicker than the show strip. bar_hide waits for the cursor to leave the bottom 60 pixels, not just the 4-pixel edge, so the bar stays put while the player moves across it. Set that number to your bar's height.

If you built this recipe before Fires existed, each watcher re-armed the other with a CLEARWATCHER at the end of its effects. That version keeps working as it is.

When the game starts the cursor isn't on the edge yet, so bar_hide fires right away and the bar starts out hidden, which is what you want. SHOWGUI and HIDEGUI show and hide every on-screen GUI at once.

For the main bar itself there's a built-in option that also slides it and keeps it up while an item is in hand: Slide in from the edge in the GUI Editor. Reach for the watcher recipe when it's something else that should appear: a panel, an exit arrow, a hint.

When something doesn't fire#

  • "My watcher only works once." It fired and disarmed itself. That's the default. If it should fire every time its condition comes true, set Fires to Every time it comes true. If it should wait until its reaction is over, keep Once and add a CLEARWATCHER:<id> at the end of the reaction it triggers (see rule two).
  • "My reaction repeats every frame." The watcher is being re-armed while its condition is still true: almost always a timerExpired with no CLEARTIMER. Clear the timer in the watcher's own effects (see rule one).
  • "The second ring does nothing." The timer is starting again (STARTTIMER always rewinds it); it's the watcher that's still disarmed. Check that the CLEARWATCHER step actually runs: it must sit on a path the reaction always reaches, like the last step of the cutscene.
  • "The timer never goes off at all." Check that its STARTTIMER actually carries a duration. Seconds starts blank and nothing forces you to fill it in, so it's easy to wire a timer with a name and no number. A STARTTIMER with no duration is ignored: no timer starts, so timerExpired never becomes true and nothing downstream ever runs.

Where to go next#

  • Puzzle Editor: where watchers and global watchers are authored, with the full condition catalog.
  • Cutscene Editor: authoring the step lists that reactions like this one launch.
  • Effect tokens: the reference table for STARTTIMER, CLEARTIMER, CLEARWATCHER, BEHAVIOUR, and everything else the effects widget offers.