Development Diaries

Twine: improved YouTube background audio macros

This code is largely based on some original macros posted to the Google Group by, er, somebody. These take a YouTube video ID and plays the audio of that video invisibly in the background of your Twine game. The player will require Flash installed, of course.

http://www.glorioustrainwrecks.com/files/TwineMacros-YouTubeAudio-2.0.0.txt

What I've done is upgrade it in the following ways:
* The volume slider is removed, on the basis that most authors will probably not have much call for it. Also, it required jQuery and jQuery UI, which not only inflated the footprint of the script, but may have interfered with other jQuery-using Twine scripts.
* It supports multiple sound tracks per story, and lets you play them simultaneous with each other if you so wish.
* The script will attempt to preload all of the videos, ensuring that they play as soon as their macros are called. (Note: currently it only preloads the tracks from the start, so this performance gain may not be seen when starting playback from the middle of the track.)
* A few more macros are provided, roughly in line with my HTML5 audio macros: <<unloopbgm>> and <<pausebgm>>.
* The code has less implicit globals. (This is a meaningless distinction.)
* There should be more informative error messages for specific error codes.

The macro names themselves are similar, but I made the following changes: <<playbgm>> now plays the audio once, and <<loopbgm>> causes it to constantly loop. Otherwise, they function identically.

Note: For browser security reasons, local HTML files are prohibited from accessing network files through a Flash embed. So, you have to test these macros by running the game from a web server.

Usage examples
*<<playbgm iHBWZn9re0U 32>> plays the YouTube video starting from 32 seconds in.
*<<playbgm iHBWZn9re0U>> plays the YouTube video from the beginning, or resumes it if it was paused.
*<<stopbgm iHBWZn9re0U>> stops the sound track.
*<<loopbgm iHBWZn9re0U>> makes the sound track constantly loop (although it may not quite be seamless).
*<<unloopbgm iHBWZn9re0U>> stops it from looping.
*<<pausebgm iHBWZn9re0U>> pauses the sound track.
*<<stopbgm iHBWZn9re0U>> stops playback and resets its position to the start.

Feel free to report any bugs to @webbedspace.

Twine: Combined <<Replace>> Macro Set

I've worked hard to combine my <<replace>>, <<timedreplace>>, <<revision>>, <<hoverreplace>> and <<once>> macros, and all their variations, into a single codebase, and included some new variations as well. Here's my combined "Replace Macro Set":

http://l.j-factor.com/twine/macros/ReplaceMacros.min.js

CSS
The first good news is that you only need this much default CSS for all these macros:

.revision-span-in {
	opacity: 0;
}
.revision-span:not(.revision-span-out) {
	transition: 1s; -webkit-transition: 1s;
}
.revision-span-out {
	position:absolute;
	opacity: 0;
}

List of macros
This set includes the following "revision macros":
* <<insert>>, <<replace>>, <<continue>> — triggered by clicking their contents.
* <<timedreplace>>, <<timedinsert>>, <<timedremove>>, <<timedcontinue>>, <<timedcycle>> — triggered by time.
* <<hoverreplace>>, <<hoverremove>> — triggered by the mouse touching their contents, only temporarily.
* <<mousereplace>>, <<mouseremove>>, <<mousecontinue>>, <<mousecycle>> — triggered by the mouse touching their contents, permanently.
* <<once>>, <<later>> — triggered by visiting the passage a certain number of times in the game session.
* <<insertion>>, <<revision>>, <<removal>>, <<cycle>> — triggered by a separate "triggering macro".

It also includes these "triggering macros":
* <<revise>>, <<revert>>, <<randomise>> — hyperlinks.
* <<mouserevise>>, <<hoverrevise>> — sections that the mouse can touch.

Changed syntax
* <<replacewith>>, used to separate sections in <<timedreplace>>, has been replaced with <<becomes>>.
* You can now have multiple sections in most of the revision macros, by separating them with <<becomes>> or <<gains>> macros:

These function just like they do within <<revision>> macros - <<becomes>> replaces the previous section with the next section, and <<gains>> merely appends the next section.
* To reconcile differences between <<replace>> and <<timedreplace>>'s syntax, some macros (currently just the <<replace>>, <<mousereplace>> and <<hoverreplace>> varieties, as well as <<mousecontinue>>) have multiple syntax for specifying sections - the "normal" syntax, where each section is separated by <<becomes>>/<<gains>>, and a "shorthand" syntax, where the first several sections are strings within the opening tag:

The final section must be between the opening tag and the <<endreplace>> macro tag, though.
For most of these, the shorthand strings behave as if the <<becomes>> macro separates them. The exception to this is the "insert" named macros, wherein the strings will behave as if the <<gains>> macro separates them.

New macros
* <<later>> is the antonym of <<once>> - it displays its contained text on the second and subsequent visits.
* <<mousecycle>> creates a section of text that cycles between different versions whenever the mouse touches it. It's similar to <<cyclinglink>> in some respects, though you can't yet easily bind a variable to change when it changes.
* <<timedcycle>> constantly rotates through its sections without stopping. It is similar to <<timedloop>> and may well replace it in future.

Examples

In any of these examples, <<becomes>> can be substituted with <<gains>>.

Version history
* 15-6-13: Initial script.

Feel free to report any bugs to @webbedspace.

Super-Dot's picture

GLORIOUS TRAINWRECKS CREATES JOBS

A little over three years ago, the administration of the Game Dev Club at SJSU wanted to host a game jam, but was worried that nobody would show up. mkapolk suggested that the Club organize a shindig coinciding with THE 371-IN-1 KLIK & PLAY PIRATE KART II: KLIK HARDER, so that if nobody showed up to the physical thing, at least we could hang out with y'all on the Internet.

It was a success, our club contributed 35 games to the Kart!

So we decided to do it again on Klik of the Month. On a whim, to encourage people to come, I threw in that we would teach people how to make games if they didn't already know. I didn't think anyone would take us up on that.

FIVE PEOPLE took us up on that.

Since I was the one who offered it to begin with, I ended up giving them a basic rundown of Game Maker, and all five of them made their first game in two hours. (Actually I think we were there for four hours.)

Since then, I've given a few more tutorials at a few more events, and one of them got video-recorded.

Fast-forward to May 2013, when a woman from a Bay Area summer camp contacted the Club to see if anyone was interested in teaching 5th-8th graders how to make videogames.

I showed her the video and got an interview; I showed her eighteen games I made, eight of them trainwrecks, and got the job. (Also I said some stuff about education and math and computer science.)

Last week, according to the curriculum, I was supposed to lead eight kids (paired into four teams) to make four games.

Last week, I led eight kids (paired into four teams) to make eleven games.

This summer is gonna be a good summer.

td;dr: Thanks in part to Klik of the Month, I have a summer job teaching 5th-8th graders how to make videogames! The results so far are beautiful.

PS: We're using Scratch and Alice, which were good decisions on the part of the curriculum designers.

Scratch is incredibly intuitive, and it supports some surprising things, like Cloud Variables (which are stored on MIT's servers), webcam/microphone asset creation, and an entirely web-based interface for making and playing games. I think it'd be great for prototyping and trainwrecks, and I encourage KnP refugees to check it out!

Alice is a little complicated at first, but it's probably a better basic introduction to 3D gamemaking than Unity, especially for non-coders. If you found Unity too hard, check it out too!

FlaviusMaximus's picture

My soundcloud

i just opened a soundcloud, and i'm posting my stuff there, usually using mmx soundfonts. don't expect to like anything there.

https://soundcloud.com/quartzflask

Twine: mix HTML tags and Twine markup syntax

Just now I was thinking to myself, "Why is it that inline HTML must be stuck between the <html> tags in Twine, anyway? Would it be possible to make HTML usable intersperced with Twine syntax?" So I decided to give it a try. The resulting Javascript code is as follows:

(function () {
	var bs = String.fromCharCode(92);
	Wikifier.formatters.unshift({
		name: "htmltag",
		match: "<"+bs+"w+(?:(?:"+bs+"s+"+bs+"w+(?:"+bs+"s*="+bs+"s*(?:"+'"'+".*?"+'"'+"|'.*?'|[^'"+'"'+">"+bs+"s]+))?)+"+bs+"s*|"+bs+"s*)"+bs+"/?>",
		tagname: "<("+bs+"w+)",
		void: ["br", "hr", "area", "img", "input", "embed", "param", "source", "track"],
		handler: function (a) {
			var re, tn, e;
			re = new RegExp(this.tagname).exec(a.matchText);
			tn = re && re[1];
			if(tn) {
				e = document.createElement(a.output.tagName);
				e.innerHTML = a.matchText;
				e = e.firstChild;
				if(this.void.indexOf(tn.toLowerCase()) == -1) {
					a.subWikify(e, "<" + bs + "/" + bs + "s*" + tn + bs + "s*>");
				}
				a.output.appendChild(e);
			}
		}
	});
}());

This code will allow you to put HTML tags in Twine passage text, and have them function in the compiled game. Note: it requires all HTML tags to be properly closed, with the exception of the so-called "void tags" that never have a closing pair (br, hr, area, img, input, embed, param, source, track). Unmatched end tags will be ignored!

Feel free to report any bugs to @webbedspace.

Smedis2's picture

argh

so yeah i took down ANOTHER game and not because of offensive stuff
tpgb12 didn't want everyone to see what he looks like

Smedis2's picture

The Game Which We Need Forget

howtoindie.PNG

Uploaded this for Snapman.

Taken down from the games section because I didn't want to get anyone mad.

Bitches don't know bout my vorticons

bitches_dont_know_bout_my_vorticons.jpg

So i couldn't think up a game for this one
Kind of like a "where are they now"
And it was kind of obvious to me that if Keen has a yorp
then McMire should own a garg but then i felt maybe
he should own a shockshund as it's more doglike
so he owns both because whatever

Twine: apply CSS to individual characters

This script causes every single character in passages to be wrapped in a <span class="char"> element. This enables a number of mildly interesting CSS effects.

Note: in some versions of Twine, this won't take effect in the Start passage!

Javascript code is as follows:

Wikifier.formatters.push({name:"char",match:".",handler:function(a){insertElement(a.output,"span",null,"char",a.matchText);
}});Wikifier.formatters.forEach(function(e){if(e.name=="emdash"){e.handler=function(a){var b=insertElement(a.output,"span",null,"char",String.fromCharCode(8212));
};}else{if(e.name=="prettyLink"){e.handler=function(a){var b=new RegExp(e.lookahead,"mg");
b.lastIndex=a.matchStart;var c=b.exec(a.source);if(c&&c.index==a.matchStart&&c[2]){var d=Wikifier.createInternalLink(a.output,c[1]);
setPageElement(d,null,c[1]);a.nextMatch+=c[1].length+2;}else{if(c&&c.index==a.matchStart&&c[3]){var f;
if(tale.has(c[4])){f=Wikifier.createInternalLink(a.output,c[4]);}else{f=Wikifier.createExternalLink(a.output,c[4]);
}setPageElement(f,null,c[1]);a.nextMatch=c.index+c[0].length;}}};}}});

These characters have the class of "char", and also a class equal to themselves ("a" for the letter "a", "2" for "2", etc.) It's recommended that you use the :nth-child pseudo-class to select them. Some potential CSS effects that can be performed include the following (examples only):

Wavy text:

 .char{ position:relative; }
.char:nth-child(8n) { top:0px; }
.char:nth-child(8n+1) { top:-1px; }
.char:nth-child(8n+2) { top:-1.5px; }
.char:nth-child(8n+3) { top:-1px; }
.char:nth-child(8n+4) { top:-0px; }
.char:nth-child(8n+5) { top: 1px; }
.char:nth-child(8n+6) { top: 1.5px; }
.char:nth-child(8n+7) { top: 1px; }

Animated wavy text:

.passage {
  font-size: 3em;
}
.char { 
  position:relative;
}
.char:nth-child(8n) { 
  animation: wavetext 4s 0s infinite;
  -webkit-animation: wavetext 4s 0s infinite;
}
.char:nth-child(8n+1) { 
  animation: wavetext 4s -0.5s infinite;
  -webkit-animation: wavetext 4s -0.5s infinite;
}
.char:nth-child(8n+2) { 
  animation: wavetext 4s -1s infinite;
  -webkit-animation: wavetext 4s -1s infinite;
}
.char:nth-child(8n+3) { 
  animation: wavetext 4s -1.5s infinite;
  -webkit-animation: wavetext 4s -1.5s infinite;
}
.char:nth-child(8n+4) { 
  animation: wavetext 4s -2s infinite;
  -webkit-animation: wavetext 4s -2s infinite;
}
.char:nth-child(8n+5) { 
  animation: wavetext 4s -2.5s infinite;
  -webkit-animation: wavetext 4s -2.5s infinite;
}
.char:nth-child(8n+6) { 
  animation: wavetext 4s -3s infinite;
  -webkit-animation: wavetext 4s -3s infinite;
}
.char:nth-child(8n+7) { 
  animation: wavetext 4s -3.5s infinite;
  -webkit-animation: wavetext 4s -3.5s infinite;
}
@keyframes wavetext {
  0%, 100% { top: 0em; } 50% { top: 0.5em; }
}
@-webkit-keyframes wavetext {
  0%, 100% { top: 0em; } 50% { top: 0.5em; }
}

Rapid rainbow text:

.char:nth-child(8n) { color:hsl(45,100%,75%); }
.char:nth-child(8n+1) {color:hsl(90,100%,75%); }
.char:nth-child(8n+2) {color:hsl(135,100%,75%); }
.char:nth-child(8n+3) {color:hsl(180,100%,75%); }
.char:nth-child(8n+4) {color:hsl(225,100%,75%); }
.char:nth-child(8n+5) {color:hsl(270,100%,75%); }
.char:nth-child(8n+6) {color:hsl(315,100%,75%); }
.char:nth-child(8n+7) {color:hsl(0,100%,75%); }

Illuminate letters on mouseover

.char { 
  transition: all 5s; -webkit-transition: all 5s;
  opacity: 0.4;
}
.char:hover {
  transition: all 0.1s; -webkit-transition: all 0.1s;
  opacity:1;
  text-shadow: 0 0 1em white;
}

Erase text on mouseover

.char { 
  transition: opacity 999s step-end; -webkit-transition: opacity 999s step-end;
}
.char:hover {
  opacity:0;
  transition: opacity 1ms; -webkit-transition: opacity 1ms;
}

Remove all the T's in the passage text:
.char.t {
  display:none;
}

Change "u" to "U":

.char.u {
  visibility:hidden;
}
.char.u::before {
  content: "U";
  position:absolute;
  visibility:visible;
}

These are to be considered basic examples - prompts for more practical uses.

This code also enables some particularly interesting Javascript visual effects to be performed, which I shall explore in a future blog post.

Feel free to report any bugs to @webbedspace.

Twine: Improved back and forward buttons in Sugarcane

This script allows Sugarcane to use HTML5 history management instead of URL hash strings to alter the browser history. This means that various non-deterministic game state changes (random numbers, player data input, state changes inside <<replace>> macros, etc.) will be properly remembered when you use the browser's Back button. This code also updates <<back>> and <<return>> and the Rewind menu.

Note: due to a conflict in the Twine engine, I've had to include Transition CSS with this script. So, you must also include one of the CSS transitions on that page. The default transition CSS code is as follows:

.transition-in{opacity:0;position:absolute}.passage:not(.transition-out){transition:1s;-webkit-transition:1s}
.transition-out{opacity:0;position:absolute}

Note 2: IE9 users will be given the old hashchange functionality anyway.

Javascript code follows:

(function(){var hasPushState=(typeof window.history.pushState=="function");
History.prototype.display=function(d,b,a){var c=tale.get(d);if(a!="back"){this.history.unshift({passage:c,variables:clone(this.history[0].variables)});
this.history[0].hash=this.save();if(hasPushState&&this.history&&this.history.length>2){window.history.pushState(this.history,document.title);
}}this.history[0].hash=this.save();var e=c.render();e.style.visibility="visible";
if(a!="offscreen"){var p=$("passages");for(var j=0;j<p.childNodes.length;
j+=1){var q=p.childNodes[j];q.classList.add("transition-out");
setTimeout(function(){if(q.parentNode){q.parentNode.removeChild(q);
}},1000);}e.classList.add("transition-in");setTimeout(function(){e.classList.remove("transition-in");
},1);p.appendChild(e);}if((a=="quietly")||(a=="offscreen")){e.style.visibility="visible";
}if(a!="offscreen"){document.title=tale.title+": "+c.title;this.hash=this.save();
if(!hasPushState){this.hash=this.save();window.location.hash=this.hash;
}window.scroll(0,0);}return e;};History.prototype.restart=function(){window.location.reload();
};macros["return"]=macros.back={handler:function(a,b,e){var el,d="";
var steps=1;if(e[0]){if(e[1]=="steps"){if(isNaN(e[0])){throwError(a,"parameter before 'steps' must be a number.");
return;}else{if(e[0]<state.history.length){d=state.history[e[0]].passage.title;
steps=e[0];}}}else{if(tale.get(e[0]).id==undefined){throwError(a,"The "+e[0]+" passage does not exist");
return;}for(var c=0;c<state.history.length;c++){if(state.history[c].passage.title==e[0]){d=e[0];
steps=c;break;}}}}else{d=state.history[1].passage.title;}if(!d){return;
}else{el=document.createElement("a");el.className="return";el.onclick=function(){if(b=="back"){if(hasPushState){window.history.back();
return;}while(steps>=0){if(state.history.length>1){state.history.shift();
}steps--;}}state.display(d);};el.href="javascript:void(0)";el.innerHTML="<b>«</b> "+b[0].toUpperCase()+b.slice(1);
a.appendChild(el);}}};Interface.buildSnapback=function(){var c=false;
removeChildren(document.getElementById("snapbackMenu"));for(var a=state.history.length-1;
a>=0;a--){if(state.history[a].passage&&state.history[a].passage.tags.indexOf("bookmark")!=-1){var b=document.createElement("div");
b.pos=a;b.onclick=function(){var p=this.pos;var n=state.history[p].passage.title;
window.history.go(-(p+1));while(p>=0){if(state.history.length>1){state.history.shift();
}p--;}state.display(n);};b.innerHTML=Passage.prototype.excerpt.call(state.history[a].passage);
document.getElementById("snapbackMenu").appendChild(b);c=true;
}}if(!c){var b=document.createElement("div");b.innerHTML="<i>No passages available</i>";
document.getElementById("snapbackMenu").appendChild(b);}};History.prototype.init=function(){if(!this.restore()){this.display("Start",null);
}if(!hasPushState){this.hash=window.location.hash;this.interval=window.setInterval(function(){a.watchHash.apply(a);
},250);}};window.onpopstate=function(e){console.log("   Popped");
console.log(e.state);if(e.state===null){return;}if(e.state&&e.state.length>0){state.history=e.state;
}else{state=new History();state.init();}state.display(state.history[0].passage.title,null,"back");
};if(hasPushState){clearInterval(state.interval);}}());

Feel free to report any bugs to @webbedspace.

Syndicate content
pensive-mosquitoes