Twine for Beginners: Timers and Live Text

If you want to make your Twine games more interesting, there are few easier ways to do that than the (live:) macro. This thing can do as little as shuffle your random text from time to time, or as much as introduce completely new mechanics into your game. This tutorial will borrow a few ideas from others in the series, but honestly – if all you want to do is make your games a little more dynamic – it shouldn’t be too hard to follow on its own. Here are a few different methods of using (live:) to do interesting things:

Method Zero: What (live:) Actually Does

This macro behaves a little differently to (if:), (else:), (either:), etc. so I think it’s worth taking a moment just to introduce it. If you open up Twine 2 and type in (live:)[Here’s some text I want to appear live.], this is what you’ll see when you run the game:

At a glance, it’ll appear that nothing’s going on. However, what’s actually happening is that the (live:) macro is constantly refreshing that text. You just can’t tell because refreshing the text doesn’t actually do anything. It looks the same every time it shows up, so it doesn’t really matter whether it’s being re-displayed a thousand times a second or it’s displayed once and just stays there. However, the fact that this doesn’t draw attention to itself can actually be pretty useful, as you’ll see in the next step.

Method One: Using (live:) as a Timer

The simplest way to use (live:) is as a timer. Although by default (live:) refreshes its contents at an absurdly fast rate, you can specify the delay. That means you can do things like this:

When the player starts the game, they’ll initially see this:

And then ten seconds later (provided they haven’t clicked one of the options by then), they’ll see this:

The (live:) macro won’t display its contents until the specified time has elapsed (and will then continue refreshing it at the same regular interval), so this is a very simple way of holding back some of your text until a suitable amount of time has passed.

(live: 1s) will refresh once per second, while (live: 10s) will refresh once every ten seconds. Bear in mind, however, that the default unit is milliseconds, meaning that (live: 10) will refresh 100 times per second. If you ever find that Twine seems to ignore your (live:) macros, check that you haven’t forgotten to stick that “s” in there: it may just be running them way, way faster than you intended!

Method 2: Using (live:) to Shuffle Text

If you’ve read my tutorial on random text in Twine, you’ll have a whole lot of tricks up your sleeve for making things read differently each time the player visits a passage. But why wait for them to visit? With (live:), you can shake things up before their very eyes!

This will add a timer just like before (though you might notice that I’ve changed the delay from ten seconds to five), then display this to the reader:

Anything inside (live: 5s)[]‘s square brackets will be re-run every five seconds, which means the (either:) macro will display something fresh every time. Note that the gif above isn’t entirely faithful to how that text will appear in the game. The gif loops, meaning that it cycles through each of the four possible options in turn. In the game, the choice will be entirely random: it’s even possible the player will see the same text turn up twice in a row (in which case it’ll appear to stick around for ten seconds rather than five).

Method Three: Using (live:) as a Timer Without Shuffling Text

Okay, I’ll level with you: (live:) can be kind of buggy. The more you try and do with it, and the faster you try and do it, the more things will go wrong. Sticking a complicated series of (if:) and (else:) macros inside a (live:) macro can sometimes result in it displaying one thing, then abruptly switching to another as the computer finishes working out what it was actually supposed to be doing. This is particularly noticeable if you’re using (live:) to change the style or colour of text, because the default style frequently appears before your custom one has a chance to take effect.

Basically, you don’t want to (live:) running unless you need (live:) running. The solution? Ask it to (stop:).

When (stop:) appears inside a (live:) macro, it stops it from refreshing. This means that the example above will display nothing for five seconds, then either “JUST PICK ONE ALREADY!!!”, “HURRY UP!!!”, “TICK-TOCK!!!”, or “WE’RE WAITING!!!”, but won’t keep refreshing its choice every five seconds after that. Whatever was initially displayed will just stick around.

In Method One, the best practice would actually be this:

(live: 10s)[(stop:)JUST PICK ONE ALREADY!!!].

We didn’t need “JUST PICK ONE ALREADY!!!” to reappear every ten seconds in an identical form – we only wanted it to wait ten seconds before showing up initially – so putting (stop:) somewhere inside the square brackets would have made it remain static on screen instead of merely appearing to remain static on screen.

Most uses for (live:) involve leaving it running – you can’t add a (stop:) if you want that text to keep shuffling – but if that’s not essential (and particularly if you run into problems) shutting it down when you can may save you a headache later on.

Method Four: More Complex Timers

The timer in Method One essentially counts up. (live:) counts to ten, then displays a thing. The player doesn’t even know it’s happening, which is pretty much the point for that specific example. But what if you want to do more? What if you want to count down?

The example above might look like it involves a lot of steps, but is actually pretty straightforward, and can be adapted to do all sorts of things. If you aren’t already familiar with using variables, you might like to have a look at this other tutorial and then pop back. Here’s a line-by-line explanation of how it all works:

  1. Though the various macros involved are set out on separate lines in the editor, they’re enclosed within {curly brackets}, which means everything will appear immediately after “Time until explosion:” when it’s displayed to the player. (This is covered in more detail in the tutorial linked above.)
  2. (set: $time to 60) creates a variable called $time which has a value of 60. Best practice would actually be to set this in a dedicated “startup” passage (which is covered in my variables tutorial), but for the sake of transparency I’m doing it right before the (live:) macro that controls the timer.
  3. (live: 1s) (with [square brackets] enclosing everything that follows) causes the following to refresh once a second.
  4. (set: $time to it – 1) sets the $time variable (which starts at 60) to one less than its current value. Since (live: 1s) is re-displaying it once per second, this will count down indefinitely at a regular rate. (It would actually count into negative numbers if we let it, but more on that in a moment.)
  5. $time, written on its own like this, displays the value of the $time variable. This is the only portion of this entire mess of text that the player will actually see. Its purpose is to display the countdown in real time: it refreshes once a second immediately after the (set:) macro has run.
  6. (if: $time is <= 0)[(goto: “KABOOM”) checks whether $time has reached (or somehow dipped below) 0. If it has, then it displays a (goto:) macro that immediately takes the player to a separate passage titled “KABOOM.” This macro is fairly handy if you want to redirect the player somewhere else under particular circumstances – you simply place it within an (if:)[] macro specifying those conditions – but it’s spectacularly helpful for (live:)[] situations such as this, where the bomb timer hitting zero should immediately display a “GAME OVER” screen of some kind.

The effect of all this is that the player will see a steady countdown in real-time, and be immediately whisked away to an appropriate “GAME OVER” screen if they allow it to reach zero:

Now we just need something for Lauren to do.

Method Five: Game Mechanics

This one will be complicated, but I promise the end results are worth it. You might like to make use of my tutorial on importing games into Twine so you can fiddle around with this example for yourself. Here’s what we’re aiming to produce:

This is a sort of bomb disposal minigame. The bomb is either ARMED or SAFE, with Variable Vinnie instructing the player (by way of Live Lauren) to cut either the blue or red wire. The end result is very simple, even though it might look like a huge jumble of macros within Twine.

Both the bomb’s status and the wire to cut are set within (live:) macros, so will change unexpectedly. The challenge for the player is to cut the correct wire while the bomb is SAFE. Here’s a guide to how all that actually fits together:

At the very start of the passage:

  1. (set: $firstGuess to true) sets a variable we can check within a later (live:) macro. Because (live:) won’t display anything until the specified time has elapsed, we need to provide Variable Vinnie with a sort of “placeholder” line before he begins saying “red” or “blue” at random.
  2. (set: $wire to (either: “red”, “blue”) gives $wire a value of either red or blue. It’ll be reset over and over again while the player is in this passage, but we need to set it to something up-front so everything else that depends on it will display properly the first time.
  3. (set: $delay to 5000) sets a variable that, again, we’ll need for a later (live:) macro. We’ll be using this to make it refresh every 5000 milliseconds. (It’s generally simplest to use milliseconds when dealing with variables as Twine won’t recognise “5s” as a number.)

Within the description of the bomb:

  1. (text-colour: “yellow”) makes the following text display as yellow rather than the default white. This is purely cosmetic, but does a whole lot to highlight that this particular word is significant and that the player should pay particularly close attention to it.
  2. (live: 2s) makes the following refresh every two seconds. Note that because this is inside (text-colour:)‘s square brackets, it won’t flicker in and out of the default style as described above (and as you’ll see later on).
  3. (set: $bomb to (either: “ARMED”, “ARMED”, “SAFE”)) sets a variable named $bomb to either ARMED or SAFE. This value will be refreshed every two seconds due to the (live:) macro above. However, since ARMED is written twice and SAFE only once, the bomb will remain armed two thirds of the time on average. You can balance the difficulty of the game by changing the ratio of these two words: “ARMED”, “ARMED”, “ARMED”, “ARMED”, “SAFE” would offer the player very few opportunities to defuse the bomb, while “ARMED”, “SAFE”, “SAFE” would render it harmless most of the time.
  4. $bomb displays the value of the $bomb variable – either ARMED or SAFE – so the player knows when they should be trying to cut the wire.

Variable Vinnie’s initial instructions:

  1. (live: 250) makes the following refresh every 250 milliseconds (ie. every quarter of a second). This is quick enough that the player will likely not notice the delay before the text initially appears.
  2. (if: $firstGuess is true) displays the following only if $firstGuess is true, as set above. What we’re doing here is establishing what should happen before a later (live:) macro comes into effect. Remember how we used (live:) to make some text appear after a delay in Method One? In this case we don’t want it to turn up late, so we’re throwing in some equivalent text until it gets there.
  3. Okay, you need to cut the (text-colour: $wire)[$wire] wire. This looks complicated, but is actually dead simple. When displayed, it will read either “…you need to cut the red wire” or “…you need to cut the blue wire,” with red or blue coloured appropriately. Since (text-colour:) will accept red or blue as valid colours, and we’ve set $wire to red or blue anyway, we can avoid a lot of if/else nonsense simply by telling it to write $wire (“red” or “blue”) using the colour $wire (red or blue). However, because this colour is set within a (live:) macro, you’ll notice it flickering as it updates: this is part of the reason why the macro is set to refresh only every 250 milliseconds, and not constantly (as it would by default).
  4. (else:)[(stop:)] has a very important job to do. If the (live:) macro containing all of this refreshes and $firstGuess isn’t true, then this (stop:) will stop it entirely. This allows us to move from Variable Vinnie’s initial instructions into…

Variable Vinnie’s looping instructions:

  1. (live: $delay) makes the following refresh at a regular interval, which is initially the 5000 milliseconds we set at the very start of the passage.
  2. (set: $delay to (random: 1000, 6000)) sets the $delay variable to a random number, meaning that the next time this (live:) macro refreshes could be 6,000 milliseconds away, or it could happen in just a second. This makes Variable Vinnie’s instructions suitably, uh, variable. As with the ARMED/SAFE setup, you could use this to tweak the difficulty of the game. (random: 2000, 6000) would slow things down somewhat, while (random: 1000, 3000) could set a far more panicked pace.
  3. (set: $firstGuess to false) is what tells the initial instructions they’re no longer needed. You may notice a flicker on screen as the initial and looping instructions both briefly coexist, but – because the initial ones refresh every 250 milliseconds – it should be no more than a quarter of a second.
  4. (if: $wire is “blue”)[(set: $wire to “red”)] ensures that Vinnie will suddenly change his mind if he’s just told you to cut the blue wire, and…
  5. (else:)[(set: $wire to “blue”)] ensures that he’ll also change his mind if he just told you to cut the red. This could be done with a simple (set: $wire to (either: “red”, “blue”)), as we did with ARMED/SAFE, but that would allow the possibility that he’d tell you to cut the blue wire instead of the blue wire, which isn’t ideal. The following dialogue makes more sense this way, and the more often he changes the mind the more difficult the game is.
  6. (either: “No, wait!”, “Hang on!”, “Wait! No!”, “Oops! My bad!”) simply gives Vinnie something new to yelp whenever he changes his mind about which wire to cut.
  7. You need to cut the (text-colour: $wire)[$wire] wire! does exactly the same job as in the initial instructions. However, this time it won’t flicker because the colour is set at the same time as the variable itself, and refreshes much less frequently than before.

Finally:

  • We finish up with the exact same timer system as before (minus (set: $time to 60), since we can simply continue the countdown from the previous passage). As before, if it reaches zero then KABOOM.
  • There are options to cut either the red wire or the blue wire (though the player will also have to make sure the bomb isn’t ARMED when they choose either one).

The logic necessary to sort out whether the player has won or lost is extremely simple: we’ve got two passages, almost identical to one another. If the player chose the correct colour while the bomb was SAFE, a winner is them. If either they picked the wrong colour or the bomb was ARMED, they go to the same “KABOOM” passage as if the timer had run down.

That’s it!

If you’ve read through all of this, you’ve now got a decent handle on how to use the (live:) macro in Twine. Remember that you can use my tutorial on importing stories to open up the example used in this tutorial, Live Lauren and the Boolean Bomb. If you’d like any further examples of how the (live:) macro can be used, you might be interested in doing the same with Project Pandora 3 and/or Damon L. Wakes’ Beer-on-the-Wall Simulator, both of which make extensive use of it.

(live:) is an underrated tool, in my opinion, and if you end up doing anything with it then I’d be keen to see: please do share a link in the comments below!

 

2 comments

  1. Pingback: Twine for Beginners: Colouring Text | Damon L. Wakes
  2. Pingback: More places for Twine help :) – Electronic Literature & Digital Writing [2]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.