So How Do You Hack TextureWriter?
In September, I got a random ping from the Work With Indies discord server. It was a writing workshop for juniors, creating a game for IFComp using the TextureWriter engine, promising all the wonderful goodies you'd expect from a tight-knit group all working to one prompt.
Hell yeah, I said, not taking a second to consider what four classes running from 1am to 2:30am my time when I had work the next morning would do to me.
I was approaching this off the back of my experience with making little HTML games, so once I realised that TextureWriter exports files as HTML, my mind instantly went to how I could mess around with the limits of it. I'd come from the likes of bitsy which, as much as I love bitsy, was always a bit tricky to wrangle when you wanted to do something just slightly out of engine.
Much to my surprise, TextureWriter was delightfully easy to hack. And, theoretically, limitless with what you could make it do.
And here's how you do it.
Going into this, I'm assuming you have a basic handle on HTML, CSS, and a little bit of JavaScript. JS won't be required until you get into complex hacks, so if you're just here to change some fonts and know how CSS classes work, you should be fine.
I'm also assuming you've at least poked around with TextureWriter, even if you haven't made a full game. If you've never touched it and want to try, I recommend the tutorial video or jumping in and seeing how it works for yourself. It's pretty intuitive and easy to get a handle on, and shouldn't take you more than 10 minutes to learn.
How a TextureWriter File is Structured
Let's make a sample story and see what we can do with it. Blue Peter style, here's one I made earlier:
Once you've got your story together and are ready to hack it, you're going to want to download your story. This will download your full TextureWriter game as a single HTML file, and it's wonderfully small to boot.
Now we've got the file, let's pick it apart.
Opening this in your editor of choice will present you with two walls of code. Daunting! But deceptive.
oh god oh man
The first block in the <style> tag is all the CSS that TextureWriter comes with. If you want to go about styling your story, you can edit it however you wish. Or, if you really want, you can nuke the whole thing and style it from the ground up. Scorched earth approach, but a viable one.
The second in the <script> tag is the entire TextureWriter engine. If you want to pick apart how this thing runs, that's where you'd start looking. For this tutorial, we're not going to touch it, so feel free to minimise it and never think about it again.
Scrolling down a bit further, you're going to see another wall. This one is the most important, as it's the story structure that the engine is going to read and execute.
Let's unminify this, and see what's going on here.
press x to json
If this looks unfamiliar to you, this is JSON. In basic terms, it's a form of data storage that JS plays nice with. Here we can see one TextureWriter page stored as a block of JSON.
I don't know the full intricacies of how TextureWriter saves stories, but you really don't need to know them to mess around, so here's a brief walkthrough of what I could figure out. If a value is omitted, it's something I don't know the function of, and for our purposes can be safely ignored.
Top level
i left a ctrl+f on and couldn't be arsed taking another screencap
name: The title of the story that appears on the cover page.
author/genres: Values for stories hosted on the TextureWriter website. Does nothing in HTML.
pages: An array holding all the pages you can visit in your story. This is where the bulk of hacks will be going, so we'll be returning to this in more detail momentarily.
startpage: The first page you visit after the cover page, stored as the id.
cover: What shows up in the subtitle on the cover page. In subtitle whatever string of characters you place in square brackets is the interaction word, and verb is what word you'll be dragging onto it to start the game. If you don't want a cover page at all, and just want to begin on the start page, set enabled to false.
theme: Not, as you might think, values you can set to any font and colour to change the theme. We'll look into how we can do that later.
library/public: Values for stories hosted on the TextureWriter website. Does nothing in HTML.
Now let's get into the meat of this thing. Let's return to the pages array, and find out what each value on a page is.
hey it's this one again
id: A string used to identify a page.
name: String used to display a page in the TextureWriter editor. Irrelevant in HTML.
text: An array that holds a collection of html elements that appear when you reach this page, and where all your lovely writing is stored. elem is any html element, and text is the equivalent of innerHTML. Do you notice the one span element with an id value? Keep it in mind; we'll be coming back to it.
verbs: All your interaction words. We'll return to id later, and name is the word visible on the draggable interaction.
events: This stores what happens when you enter this page and when you exit, notably flags set or unset, if any. I recommend leaving this section alone and handling it in the editor.
The final stretch: actions
I've been deliberately vague on id up to this point, because I wanted to describe how they interact with each other in one place. That place is in actions.
Here's what TextureWriter does for each action block:
You can make edits to your code by hand knowing all this, which I did when working on my own stuff, but if you're not confident with editing code then I highly recommend doing everything you can in the TextureWriter editor before starting to make edits in HTML.
The final part, behaviors, is what happens when an interaction word is dropped. Much like events from the previous section, I recommend leaving behaviors alone and working it in the editor.
TextureWriter in Practice
Alright, now we've got a basic handle on how this is put together, how does all that JSON work at runtime?
After the cover page, TextureWriter finds #story and clears it out. It then finds what page id to start on, grabs all the elements in text, and populates that div with them.
the title page is its own div that gets hidden
After being told what page to go to next based on what interaction words are used, it clears out #story, and repeats this process again.
TextureWriter will also populate #action-container with interaction words, and may require other divs found in the vanilla downloaded HTML to run properly. This I haven't fully tested, so experiment as you wish.
Outside of that? TextureWriter does nothing. You can put literally anything else in this file and the engine will ignore it entirely.
Exhale. Knowing all this, now we can start hacking.
Changing Global Font and Text Colour
Remember when we looked at theme in the JSON? TextureWriter comes with a fixed set of fonts and colours you can pick from, and the only ones that will be parsed are ones that TextureWriter lets you use in the editor. It then applies hardcoded classes based on what options you pick. This will overwrite any attempts to change colours in body if you don't want to get messy with !important.
If you want custom fonts and colours, leave the theme as is when editing in TextureWriter, and look for the following CSS classes:
Names should be self-explanatory.
If you change your page theme in TextureWriter's settings, you'll need to check what values are being read in theme and find which classes to edit. Or, replace them with the values above.
More About Page Elements
Let's return to pages, and then to text.
There's two ways to put a new element with innerHTML in here, either by having an elem first in curly brackets, followed by text in another; or, you can have elem followed by text in curly brackets together.
I don't fully understand why TextureWriter does it this way, and why there's two different ways to do it, but it's pretty easy to get a handle on. Use what TextureWriter gives you as a guide, and make sure there's a comma after each set of brackets.
An important thing to note about id. It turns out, it applies an actual HTML id to an element, which is how TextureWriter finds it in the DOM.
TextureWriter always puts a p element first no matter what you do, so it's best to have your first value be text, especially if your paragraphs are indented.
Interacting with Multiple Words
There might be a way to do this in-editor, but I couldn't find one myself. The way to do this in HTML is very easy, anyway. Simply take whatever words you want to hover over, and place them in the appropriate id.
{ text: "So if we want to hover over " },
{ elem: "span", id: "n-example", text: "this set of words" },
{ text: ", that's very simple." ...
Done and done.
Styling Page Elements with Custom IDs and Classes
Let's take another look at text. Knowing that id translates to HTML ids, your first thought might be that id is arbitrary, and you can use a custom HTML id to style with css.
And, you're absolutely correct! Feel free to use this to style as you wish.
{ elem: "p", id: "id-to-style" ...
But, that's not best practice if you want something reusable. It also means you can't add reusable styling to action words as it can only assign one id. And, well, you really shouldn't be adding more than one id to an element. You're going to want to use classes.
You might try using class instead of id as a value, which is a good line of thinking, but TextureWriter doesn't parse it to HTML classes. What can we do as a workaround?
It turns out, we can put a class inside elem, and TextureWriter will parse it just fine.
{ elem: "p class='class-to-style'" },
{ text: ...
When doing this, make sure that your class is wrapped in single quotes so JSON doesn't start yelling at you.
If you so wish, you can also apply HTML ids in a similar way, or use the id value as shown above. Either way will give you the same result.
Executing Arbitrary Scripts When Reaching a Page
You may be thinking: "If elem takes HTML elements, could I use any element at all?"
The answer is: yes! And that means we can use scripts.
If you want to execute any JS when you reach a specific page, that's as simple as giving elem the value script, and placing whatever code you want to run in text.
Here's our old friend. When we reach this page, we're going to send a message to the console.
{ elem: "script", text: "console.log('Hello world')" }
And in action, it works as such:
imagine if the first message sent by a computer was "fuck you" or something
After some working, however, I found this to get a bit messy. Instead I opted to do this:
{ elem: "script", text: "scriptToRun();" }
texture.start(book, 0, true);

function scriptToRun() {
There's several reasons for this. When you're doing more complex scripts, you still need to crush it all down to one line for TextureWriter to parse it correctly. Having a separate function like this futureproofs you for editing later.
Also, if you move off a page, all the scripts you placed on that page will be removed. If you want something to be triggered once you reach a page and continue to run after you leave, you have to place the script in the HTML file itself and call it.
Equally, if you try to return to a page and expect a script to pick up where you left it, it will instead run again as if it's a brand new script. It will also run no matter what everytime you reach said page. That should be fine if you go to a page and, say, the background is always supposed to turn red there, but if you want something to just be a one off you'll need to do some workarounds with variables.
In short: if you want something persistent, you have to make sure it's not on a page, and in the actual HTML.
It's really that simple. With this one trick, you can pretty much do whatever you want.
Per-page CSS Styling
More an extension to the previous hack, you add some arbitrary CSS in text that gets applied when you reach a specific page. I didn't use this all that much, but it's an available option.
{ elem: "style", text: "body{background-color:red !important}" }
As before, once you move off the page, the style block will be deleted with it. If you want to style something permanently, you're going to have to use a script instead.
Putting it All Together: Nose Bleed
And there we have it. A collection of hacks you can apply to TextureWriter.
If you want to see these hacks in action, and give yourself an idea what you can do with them, give my game Nose Bleed a runthrough. See if you can spot which hacks are used when, and find out if you're correct by downloading it yourself.
So Where Do You Go From Here?
Anywhere, really. TextureWriter's a fun tool. It's extremely easy to play around with and I feel I've only done the bare minimum of exciting stuff you could do here.
And keep in mind, these hacks are working within engine restrictions, and taking advantage of the fact it's web-based. I haven't even touched the code itself. I can't definitely say since I haven't had a look, but hacking that is probably where we're going to get the likes of custom behaviours when selecting a word, running arbitrary scripts at any point, and possibly extending TextureWriter for global variables and conditionals.
I've got other projects in the pipeline, and want to focus more on line-level conditional stuff rather than block-level, so I likely won't be returning to TextureWriter in the near future. If any of this inspires you to add some fun bells and whistles to your stories, or gives you a want to poke around in TextureWriter's guts, I'd love to hear about it. I think we could do some interesting stuff with it.
TextureWriter is designed by Jim Munroe and coded by Juhana Leinonen.
If you want, you can play the hacked game featured in this tutorial, and download it to see how it works.
Give Nose Bleed another run while you're here, have a poke around the file, and a shameless plug for everything else I've made.