Howdy. The name's Kit. I've been trying to work out how much you can make Twine do that isn't just display the characters you typed in different ways. I'm working on a battle simulator, and I realised that to make it look computer-gamey, it would need health bars. How does one make a bar chart in Twine, anyhow? Well, after a few hours of poking, I can suggest that this is one way - well, three ways. It's best to read them in order, but it's ok, they get simpler. They are all built around the idea of recurring a passage using an if condition nested inside itself, as suggested here.
---
30 OOOOOO
Each bar has three variables - health, which is what the bar is intended to measure, increment, which is the number represented by each unit of the bar, and symbol, which is what each unit of the bar is displayed as. Outer 1 prints the value of health for each bar, divides health by increment, and saves this value as block. It uses a ceiling function (Math.ceil, everyone loves a little javascript) so that if block is not an integer, it is always rounded up. If block is greater than 0, Inner 1 is fired off, which prints the symbol, subtracts 1 from block, and fires itself off if block is still greater than 0. By the time this stops calling itself, it has printed the symbol block times.
Include where you want bar (set the values for health, increment and symbol that you wish - symbol must be in ""s)
<<set $health = 30>><<set $increment = 5>><<set $symbol = "O">><<display 'Outer 1'>>
Outer 1:
<<print $health>> <<set $blocks = Math.ceil($health / $increment)>><<if $blocks gt 0>><<display 'Inner 1'>><<endif>>
Inner 1:
<<print $symbol>><<set $blocks = $blocks - 1>><<if $blocks gt 0>><<display 'Inner 1'>><<endif>>
---
30 ||||||
These bars run using the passages Outer 2 and Inner 2. The bars work exactly the same as in Method 1, except the symbol is not specified. It is "|", hard-coded into Inner 2. If you always intend to use the same symbol, the code should run slightly faster if you use this method rather than that shown in Method 1.
Include where you want bar (set the values for health and increment)
<<set $health = 30>><<set $increment = 5>><<display 'Outer 2'>>
Outer 2:
<<print $health>> <<set $blocks = Math.ceil($health / $increment)>><<if $blocks gt 0>><<display 'Inner 2'>><<endif>>
Inner 2:
|<<set $blocks = $blocks - 1>><<if $blocks gt 0>><<display 'Inner 2'>><<endif>>
---
10 ||||||||||
This is the simplest version of my bars to use. The only variable that has to be specified is health, which will be the length of the bar. Outer 3 has been simplified because of this.
Include where you want bar, specifying health:
<<set $health = 10>><<display 'Outer 3'>>
Outer 3:
<<print $health>> <<if $health gt 0>><<display 'Inner 3'>><<endif>>
Inner 3:
|<<set $health = $health - 1>><<if $health gt 0>><<display 'Inner 3'>><<endif>>
---
There is one bug I have found - if I try to display a bar of length zero, not only will it not print a bar (which is what I'd like), it also won't print the number 0. I have no idea why this is, but if anyone does, any help is appreciated! If you want to see these running, go to the barchartdemo listed below.
An array is essentially an ordered list of data values, such as strings or number expressions. You can declare a Twine variable to be an array using just the set macro.
*To create an empty array called $inv: <<set $inv = [] >>
*To create an array called $inv containing the string "Dagger": <<set $inv = ["Dagger"] >>
*To create an array called $inv containing "Dagger", "Shield" and "Potion" in that order: <<set $inv = ["Dagger", "Shield", "Potion"] >>
To examine or change the values inside an array, a number of methods are available that can be used in the set, if and print macros. Here are some examples.
Examining arrays and their contents
*To print the entire contents of an array in order (usually for debug purposes): <<print $inv >>
*To see if the value "Magic Knife" is inside an array: <<if $inv.indexOf("Magic Knife") gte 0>>
*To get the number of values inside an array: <<set $size = $inv.length >>
*To get the first value inside an array: <<set $first = $inv[0] >>
*To get the last value inside an array: <<set $first = $inv[$inv.length - 1] >>
Changing values in arrays
*To add the value "Magic Knife" to the end of an array: <<set $inv.push("Magic Knife")>>
*To add the values "Blunt Axe", "Key" and $item to the end of an array: <<set $inv.push("Blunt Axe", "Key", $item)>>
(push() can insert many values at once.)
*To add the value "Weird Pear" to the start of an array: <<set $inv.unshift("Weird Pear")>>
*To add the values "Blunt Axe", $item, 2, and $weapon to the start of an array: <<set $inv.unshift("Blunt Axe", $item, 2, $weapon)>>
(unshift() can also insert many values at once.)
*To remove the value "Weird Pear" from an array: <<set $inv.splice($inv.indexOf("Weird Pear"),1)>>
*To sort an array alphabetically: <<set $inv.sort()>>
*To reverse an array: <<set $inv.reverse()>>
*To remove the first value from an array: <<set $inv.shift()>>
*To remove the last value from an array: <<set $inv.pop()>>
*To remove the first value from an array and set $item to it: <<set $item = $inv.shift()>>
*To remove the last value from an array and set $item to it: <<set $item = $inv.pop()>>
Similar to my <<replace>> macro, this code causes the passage text in between the <<timedreplace>>
and <<becomes>>
tags to be replaced with what is between those and the <<endtimedreplace>>
tag, after a certain amount of time has elapsed.
Variants
* <<timedinsert>>
causes its text to be inserted into the page. It doesn't need a <<becomes>>
tag to function.
* <<timedremove>>
works in a matching fashion, removing its contained text after the time elapses.
* <<timedcontinue>>
is similar to <<timedinsert>>
but does not require a final <<endtimedcontinue>>
- instead, it causes all subsequent text in the passage to appear.
Gains
You can also substitute the <<becomes>>
tag with <<gains>>
to cause the next run of text to appear at the end of the previous run, without replacing it.
Chains
If you put multiple <<becomes>>
or <<gains>>
tags in, the macro will make them appear as well after the same amount of time passes.
Install my <<Replace>> Macro Set to use these macros.
This takes CSS time values, which are decimal numbers ending in "s" (for seconds) or "ms" (for milliseconds).
Usage examples:
* <<timedreplace 2s >>You see nothing. <<becomes>>After 2 seconds, you still see nothing.<<endtimedreplace>>
* <<timedreplace 2s >>You see <<gains>>a dog, <<gains>>a walrus, <<gains>>and a civet.<<endtimedreplace>>
* You search. <<timedinsert 1s>>You find nothing.<<endtimedinsert>>
.
* <<timedremove 2.5s >>Something is disappearing... <<endtimedremove>>
* Look <<timedcontinue 3s>>A bird just few by...
Implementation details:
* If inserted text appears and descends below the bottom of the screen, the page should automatically scroll down to make it visible.
* Note: due to the way the browser and Twine interact, any changes made by code inside a <<timedreplace>> tag will be forgotten if you use the Back or Forward browser buttons, unless you use this script.
Version history:
* 16/06/2013 - Updated regarding Combined Replace Macro Set.
* 26/04/2013 - Added timedcontinue.
* 05/04/2013 - Added timedinsert and timedremove, as well as downward scrolling.
* 07/03/2013 - Made the transition CSS-based.
* 01/02/2013 - Initial.
Feel free to report any bugs to @webbedspace.
I figured folks here might appreciate the game I worked on for Global Game Jam this year, seeing as it's a microphone-powered rap battle simulator set in NegaTokyo in the year 20FF. It's pretty short. Check it out, and let me know what you think!
This simple macro produces a usable internal link whose text changes whenever you mouseover it.
version.extensions.hoverlinkMacro={major:1,minor:1,revision:0};macros.hoverlink={handler:function(a,b,c){var d,l=Wikifier.createInternalLink(a,c[0]);
l.className+=" hoverLink";insertElement(l,"span",null,null,c[1]||c[0]);if(c[2]){d=insertElement(l,"span",null,null,c[2]);
d.style.display="none";}l.onmouseover=function(){if(this.childNodes.length>1){this.childNodes[0].style.display="none";
this.childNodes[1].style.display="inline";}};l.onmouseout=function(){if(this.childNodes.length>1){this.childNodes[1].style.display="none";
this.childNodes[0].style.display="inline";}};}};
This uses the << choice >> macro's parameter order - the destination passage name comes first. Then comes the visible label, then the hidden label.
Usage example: <<hoverlink "CryPassage" "Stand and fight, guns blazing" "Sit down and have a cry">>
For obvious reasons, this won't work on iPads!
Implementation details:
* The link has a class of "hoverLink" in addition to the standard internalLink/brokenLink classes.
* Each text label is a <span> inside the <a>, and the mouseover and mouseout events make one or the other visible.
Version history:
This extends my code for applying Twine tags to passage divs, and extends on it as a mechanism for CSS selection. What these macros do, in order, is add a tag to the current passage's tag attribute, remove a tag, and toggle its presence (remove it if it's there, add it if it isn't). This means that conditional application of CSS, controlled by << if >> statements, is possible.
Usage examples: <<addtag "dream">>
<<toggletag "lightworld">> <<removetag "darkworld">>
These are guaranteed to work both inside passages, inside << replace >> macros, and, tantalisingly enough, inside << display >> macros. Interesting, very interesting.
If you're using the latest Twine 1.3.6 Alpha (28-1-13) , use this code:
version.extensions["toggletagMacros"]={major:1,minor:1,revision:0};macros["toggletag"]={handler:function(a,b,c){var p=e(a);var d=document.body;if(p){var t=p.getAttribute("data-tags");var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){var r=t.replace(c[0],"");p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}else{if(b!="removetag"&&i<0){var r=t+" "+c[0];p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}}}function e(f){while(f.parentNode&&!f.classList.contains("passage")){f=f.parentNode;
}if(f!=document&&f.getAttribute("data-tags")){return f;}return null;}}};macros["addtag"]=macros["toggletag"];macros["removetag"]=macros["toggletag"];
If you're using a standard version of Twine, use this code instead:
version.extensions["toggletagMacros"]={major:1,minor:1,revision:0};macros["toggletag"]={handler:function(a,b,c){var p=e(a);var d=document.body;if(p){var t=p.getAttribute("data-tags");var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){var r=t.replace(c[0],"");p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}else{if(b!="removetag"&&i<0){var r=t+" "+c[0];p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}}}else{var t=state.history[0].passage.tags;var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){t.splice(i,1);
}else{if(b!="removetag"&&i<0){t.push(c[0]);}}d.setAttribute("data-tags",t.join(" "));}function e(f){while(f.parentNode&&!f.classList.contains("passage")){f=f.parentNode;}if(f!=document&&f.getAttribute("data-tags")){return f;}return null;}}};macros["addtag"]=macros["toggletag"];macros["removetag"]=macros["toggletag"];
Version history:
This simply produces a link whose text cycles between a number of values whenever you click on it. It otherwise leads nowhere. You can use it as a silly clicky trinket, a cheap alternative to the <<replace>> macro, or (as detailed below) as an input interface element.
Script code
version.extensions.cyclinglinkMacro={major:3,minor:3,revision:0}; macros.cyclinglink={handler:function(a,b,c){var rl="cyclingLink"; function toggleText(w){w.classList.remove("cyclingLinkInit"); w.classList.toggle(rl+"Enabled");w.classList.toggle(rl+"Disabled"); w.style.display=((w.style.display=="none")?"inline":"none")}switch(c[c.length-1]){case"end":var end=true; c.pop();break;case"out":var out=true;c.pop();break}var v="";if(c.length&&c[0][0]=="$"){v=c[0].slice(1); c.shift()}var h=state.history[0].variables;if(out&&h[v]===""){return }var l=Wikifier.createInternalLink(a,null);l.className="internalLink cyclingLink"; l.setAttribute("data-cycle",0);for(var i=0;i<c.length;i++){var on=(i==Math.max(c.indexOf(h[v]),0)); var d=insertElement(null,"span",null,"cyclingLinkInit cyclingLink"+((on)?"En":"Dis")+"abled"); if(on){h[v]=c[i];l.setAttribute("data-cycle",i)}else{d.style.display="none" }insertText(d,c[i]);if(on&&end&&i==c.length-1){l.parentNode.replaceChild(d,l) }else{l.appendChild(d)}}l.onclick=function(){var t=this.childNodes; var u=this.getAttribute("data-cycle")-0;var m=t.length;toggleText(t[u]); u=(u+1);if(!(out&&u==m)){u%=m;if(v){h[v]=c[u]}}else{h[v]=""}if((end||out)&&u==m-(end?1:0)){if(end){var n=this.removeChild(t[u]); n.className=rl+"End";n.style.display="inline";this.parentNode.replaceChild(n,this) }else{this.parentNode.removeChild(this);return}return}toggleText(t[u]); this.setAttribute("data-cycle",u)}}};
Features
If the first parameter begins with the "$" sigil, then it will interpret it as a variable to be altered by clicking the link. When the player visits the page or clicks the link, the variable will be changed to match the text of the link. If the variable already has a string value when you load the passage, then the link text that matches the variable will be selected, instead of the first one.
If the last parameter is the word "end", then it will terminate the cycling link at the parameter before last. The containing that text will replace the link element altogether. Similarly, if the last parameter is the word "out", then it will disappear altogether after the final link text is clicked.
Usage examples:
* You look around, but only see <<cyclinglink "grass" "a flower" "a cloud" "the road">>.
- This is a purely cosmetic, endlessly cycling link.
* You see a bowl containing <<cyclinglink "3 cookies" "2 cookies" "1 cookie" "a few crumbs" end>>.
- This link changes to the words "a few crumbs" when you get to the end.
* You see a dial: <<cyclinglink $heat "off" "low" "high" "fearsome">>.
- The $heat variable will be changed to "off" when the page loads (unless it was already set to "high" or "fearsome"), and then sets it to "low", "high" and "fearsome" as the player clicks the link.
* You see a fuel gauge: <<cyclinglink $fuel "100%" "50%" "10%" "0%" end>>.
* You see an air meter: <<cyclinglink $air "********" "******" "****" "**" out>>.
Example program 1 (cosmetic).
Example program 2 (mechanical).
Example program 3 (mechanical with CSS)..
New: Advanced CSS effects
I've gone to the trouble of writing a number of advanced CSS effects for use with this macro. You can see them all and obtain the CSS code for them by clicking here. These use some very recent CSS animation features in unusual combinations, but they've been tested in both Chrome and Firefox.
Implementation details:
* For CSS users: the <a> tag has the class names "cyclingLink" and "internalLink".
* Each option is a <span> inside the <a> tag. Clicking it causes the next one down to have the class "cyclingLinkEnabled", and the others to have the class "cyclingLinkDisabled". New: on passage load, each option also has the class "cyclingLinkInit", which is removed as each is clicked.
* When you click, the "data-cycle" attribute of the <a> tag increases by 1 (wrapping around to 0 at the end). You could select this with CSS, I guess, using [data-cycle=1]
or somesuch.
Version history:
Similar to my <<timedreplace>> macro, this macro creates an internal link that, when clicked, vanishes and is replaced with whatever is between the << replace >> and << endreplace >> tags. This could be useful if (just for starters) you want to have a passage that can be modified by clicking specific details inside it.
Variations:
* You can also use <<insert>>, which is identical but does not remove the link text, instead merely changing it to a bare <span> (as with Jonah's <<choice>> macro.)
* You can use <<continue>> to make all subsequent text appear when it's clicked. It does not need an <<endcontinue>> at the end! Quite useful when you just need the reader to click to continue the passage.
* You can insert <<becomes>> or <<gains>> tags inside the contained text to create multiple "versions" of the replacement text. Clicking the link will display each successive version, stopping only when the last version is reached.
* If you omit the text in the starting tag, then the first "version" will be used as the link instead. This lets you, for instance, make an image into a replaced link. Of course, this requires that multiple <<becomes>> or <<gains>> tags are within the text.
Install my <<Replace>> Macro Set to use this macro.
Usage examples: You see <<replace "a half-eaten cake">>a plate of crumbs<<endreplace>>
You see <<replace "ten dollars">>five dollars<<becomes>>two dollars<<becomes>>fifty cents<<endreplace>>
You see <<replace>>a //big// boulder!<<becomes>>some pebbles.<<endreplace>>
I find it <<insert "repugnant">>that he's made such comments<<endinsert>>.
You go outside. <<continue "Next.">>The sun blinds you momentarily.<<continue "Next.">>Has it been so long?
A short example program.
Hot hint: if you've got a lot of text, it's good to just put a single << display >> inside a << replace >>, and put the text in a separate passage.
Some notes:
* Code inside << replace >> tags is only executed when you click the link. You may notice that the "mailbox" link in the example program produces different text if you click the "front door" link first.
* If inserted text appears and descends below the bottom of the screen, the page should automatically scroll down to make it visible.
* Note: due to the way the browser and Twine interact, any changes made by code inside a <<timedreplace>> tag will be forgotten if you use the Back or Forward browser buttons, unless you use this script.
For a more powerful version of this macro, see <<revision>>
.
Version history:
* 16-6-2013: Updated regarding Combined Replace Macro Set.
* 26-4-2013: Added << continue >> variant.
* 5-4-2013: Added << insert >> variant, scrolling.
* 7-3-2013: Added CSS hooks.
* 28-1-2013: Initial.
Feel free to report any bugs to @webbedspace.
Suppose you want your Twine game to have that classic feel to it. You want pages to snap into place instantly, without even a moment of fade-in. All business. 90s web style. So sudden that it's like the game can read your thoughts.
Well, here's some CSS code that suppresses the default fade-in transition between passages.
.transition-out { display:none; }
All this code does is remove functionality, which is why it's pretty simple. It will also work with Jonah as well as Sugarcane.
If you want more types of transitions, also see these example files.
Feel free to report any bugs to @webbedspace.
Update: this bug is fixed in Twine 1.4, so this script code is no longer necessary.
I've been a bit annoyed at how the order of startup events in the current versions of the standard Twine formats (which is to say, Sugarcane 1.2.1 and Jonah 2.1) is 1) render the Start passage, 2) load the story's custom JavaScript. This ordering means that various custom things like macros can't be used in the Start passage, because they're loaded afterward.
If you're using Twine 1.3.6 Alpha (28-1-13) then you don't have this problem. If you're using a stable version prior to that, read on.
What I'd much rather do is have the game automatically advance past the Start passage into the next passage, once all of the scripts are loaded. So, here's something to do that. Put this in a "script" passage:
setTimeout(function(){ var next = "Start2";
var h = state.history; if (h[0].passage.title=="Start") { var n = insertElement(null, 'div');n.style.display="none";n.appendChild($("passages").removeChild($("passageStart")));state.display(next,null);$("passages").appendChild(n);}},1);
What this does is conceal the Start passage's div inside a hidden div*, then goes to the page named "Start2". This happens instantaneously, and is imperceptible to the player.
You can change the passage name that the code uses by altering the name inside the quote marks in the first line of the script snippet.
*(Note that I can't just destroy the Start passage div outright, because the passage's fade() interval is still running, and cannot be halted without causing browser console errors (which are technically invisible to the player but are nonetheless untidy coding).)
Feel free to report any bugs to @webbedspace.