Home About Games Writing

Music 2021




A retrospective of five games' worth of mashing these two engines togethering, featuring:
  • The Tower
  • The Horizon
  • Superlunary
  • Echoes and Other Poems
  • Under A Caustic Snow I Danced

  • Hacks used:
    RGBA Colours
    bitsy-twine comms,
    save hack

    All games written in Twine 1.4.2.

    Whilst nothing in this gets too heavily technical, it will presume some basic knowledge at using Bitsy and Twine, as well as a basic understanding of html/css.

    UPDATE: A template for doing this is now available online!

    THE TOWER: gotta start somewhere

    Screenshot from The Tower

    This is the first game I made combining Bitsy and Twine. As implementations go, this was pretty much the most minimal method I could have done so.

    Inside the twine passage, it looks like this:
    <div style="align:center">
    <iframe id="bitsyone" src="bitsy/towerone.html" style="border:none" 
    height=512px width=512px></iframe>
    <span id="spaceAct">[[~|A1S4]]</span>

    So what's happening here?

    Firstly, we have the bitsy game contained within an iframe, with a relative source based off where the twine index.html is. "border:none" is used to prevent the default iframe border from appearing, and the height/width are explicitly given.

    Secondly, the "spaceAct" span is part of the macro I use for enabling keyboard control in a twine. Usually, pressing spacebar would trigger the passage link contained within the span. However, that requires keyboard focus. And, to navigate in the bitsy game, the iframe requires keyboard focus.

    The original build did nothing about this. The player was expected to click on the bitsy iframe, use the arrow keys to move, and then either manually click on the ~ to advance the passage, or else click anywhere outside the iframe and then press spacebar. This was an inelegant solution.

    Build #2: Give the iframe focus.

    This is what the same passage looks like in the second build of The Tower:
    <div style="align:center">
    <iframe id="bitsyone" src="bitsy/towerone.html" style="border:none" 
    height=512px width=512px onload=focusFrame()></iframe>
    <span id="spaceAct">[[~|A1S4]]</span>
    function focusFrame() {
    var iframe = document.getElementById("bitsyone");
    setTimeout(function () {
            iframe.onblur = function () {
    }, 800);

    So what's happening now? We've added a little script after the passage contents, and invoked it in the iframe. When first loaded, the iframe will pull keyboard focus. Then, every 800ms, it will attempt to regain focus if it's lost it. This doesn't get around the need for the player to manually click out of the iframe / click the next passage link; but it halves the number of key clicks needed to play this passage, which was at least an improvement.

    What this doesn't allow in this iteration is to give the focus BACK to the twine passage. So what do we do about that?

    THE HORIZON: who uses a keyboard anyway

    Screenshot from The Horizon

    To get around the difficulty I had in giving keyboard focus back to Twine, I simply.... sidestepped the entire problem. In The Horizon, the player is never required to use the keyboard to advance the passage; the whole game is on a timer. Passages with Bitsy games automatically focus the keyboard, and then after a set amount of time the passage automatically changes. Crucially, neither the narrative or mechanics require the player to actually ACHIEVE anything in the bitsy games; they are illustrative. This method would not work if the player HAD to do something in the embedded bitsy, because it would risk them not achieving this in time.

    However, The Horizon does some other interesting things, which I will go through now.

    #1: Persistant Position across passages

    The Horizon displays the same bitsy multiple times in several passages, along with text underneath (the two are contained in a single table, for ease); in the bitsy, the player can move around multiple rooms to explore. With the same style iframe as The Tower, the player's position would be reset to the default Avatar start position in the bitsy each time a new passage loaded. I didn't want this.

    Using the Save hack, I got around this. Every 800ms (I like that number), the bitsy game will save the player's position to Local Storage, and it's set to Load Data whenever the bitsy game is restarted. This means the player remains in the same position with each new passage; some tweaking of the timer number is needed to make sure a fast player isn't sent back too many moves.

    What if the player wants to start the game again, though? Simple. The twine game has a custom 1-line macro:

    macros['clearStorage'] =
    	handler: function()
    Invoking clearStorage at any time will scrub the save data. However, there's a snag.

    Running the twine game OFFLINE (a.k.a from a local file) will cause the save hack to make duplicate saves; as far as I can tell, this is one for the parent .html and then one for the iframe .html. The parent is then only able to clear its own, and not the iframe-level save. The bitsy, though, is perfectly able to read this and will load as normal. This made playtesting a minor nightmare (constantly loading the Inspector and manually clearing the Local Storage before each new test); and also meant that anyone downloading the .html as-is to play offline would run into this problem if they wanted to replay it. A significant number of people DO download my twines to play offline, via the itchio app, so this was... a pain. But how many people would play the game offline MULTIPLE TIMES? So I shelved this bug and lived with the knowledge.

    (chronologically, this comes after Superlunary)

    Fucksake. With an offline showing coming up at Wordplay, I had to fix the persisting-save bug. I turned to... Sean.

    Build #2 of The Horizon now ran in a little node.js server, which did nothing but auto-load the default browser and serve up the .html build of the game. Whilst it was a little galling to up the filesize to a whopping 12mb, needs must. The standalone build worked a treat.

    However, it also presented a new challenge. The Horizon is a TIMED game; it always lasts half an hour. But what if someone walked off after five minutes because they decided it wasn't for them? I needed a way to be able to reset the game at will. But there's no extraenous buttons; The Horizon uses full-width colours for effects, using the entire background and sometimes showing blank black/white screens. I didn't want to add a floating RESET button on top of this.

    It was easy on the non-bitsy passages - using the same keyboard-control macro as The Tower, but bound to R instead, using Twine's inbuilt restart function:
    <span id="rAct" class="hiddenLink">[[Restart|Start][state.restart()]]</span>

    But for the Bitsy? It was stealing keyboard control automatically, so the above wouldn't work. What I ended up doing was.... totally redoing it to drop the iframes and use the Bitsy-Twine Comms hack, for the express purpose of making it send keypresses upwards to the Twine parent, to trigger an invisible reset button.


    On the passage side, this meant each passage now looked like this (compare The Tower, above):

    <table class="table1" width="512" height="562">
      <td><div class="subtitles1"><span id="text1"></span>
    	<<timed 6s>><<replace "#text1">>How long have I been here?<</replace>>\
    	... text goes on ...
    	<<next>><<goto "B1S2">><</timed>>
    <span id="rAct" class="hiddenLink">[[Restart|Start][state.restart()]]</span>
    The "hiddenLink" classes for a and a:hover just differ from the regular link by having their color set to transparent. Otherwise, the bitsy iframe details are contained in the Bitsy-Twine Comms hack, as the <<bitsy>> macro. Take care when you follow the link to the macro; you'll need to also get the twine-side version for your relevant story format (I use Sugarcube v2, personally) and add that in a Script passage in Twine.

    There's two custom bits in the macro; on the Twine side, it listens for a specific message ("Restart") and just does that:
    case 'restart':
    On the Bitsy side, the following is added into the InputManager section:
    function tryRestartGame(e) {
           /* RESTART GAME */
        if ( e.keyCode === key.r ) {
          window.parent.postMessage({ type: 'restart' }, "*");
    And into theBitsy-Twine comms section:
    // hook up dialog commands
    ].forEach .... (default hack continues here)
    This implementation let the bitsy iframe send the Restart command up to the Twine macro, which in turn fired off Twine's built-in restart function.

    SUPERLUNARY: we need the players to do the thing

    Screenshot from Superlunary

    Forget the usage of the Bitsy-Twine comms hack to make the two engines talk to each other. When Superlunary was born, this was in the dark ages before that time. So, back to the same problem as The Tower: how do we ease the need for the player to manually change focus between the iframe and the parent?

    Simple. We don't, and instead we make it a key part of the experience.

    Superlunary is, to some extent, a spaceship UI with a game hidden behind it. The player clicks between various screens - sector map, messages, a Launch Code input. This requirement to use the mouse at all times, as if they are the operator of the ship, is the perfect cover for the clunkiness.

    In this version, the bitsy iframes (done manually, as in The Tower Build #2) still automatically pull focus, via a twine macro instead of the standalone code, and don't give it back. It doesn't matter, though, because if the player clicks away, it's probably to click one of many links elsewhere.

    Another feature of Superlunary is the multiple different Bitsies using multiple different save files. This is simple; same as The Horizon, but just give each bitsy a different key in the Save hack, and tweak the clearStorage macro to target each key:
    function clearStorage()
    The save hack first really comes into its own in (spoilers) the Zeta Station section; the player is able to move in the bitsy, then check what other characters are doing, before returning to the bitsy at the same position as before. In this example the player can read multiple passages of radio chatter, at any point during the game.

    Now, a nice trick you can do is to use the same key for multiple different bitsies. If you don't have any method of sending data between twine and bitsy (e.g. the Comms hack), but want a player to visit a bitsy, visit a twine passage, and then return to the bitsy with, say, a door opened, you can just have TWO bitsies to represent each different game state, and share the same LocalStorage key between them. Superlunary does this in the first Sector:
    <<if visited ("AnStationOutro")>>
    <iframe id="bitsyone" src="bitsies/Spinwards Map 1b.html" 
    style="border:none;" height="512" width="512" onload=focusFrame()></iframe>
    <iframe id="bitsyone" src="bitsies/Spinwards Map 1.html" 
    style="border:none;" height="512" width="512" onload=focusFrame()></iframe>
    So that's gating BITSY content with stuff you do in TWINE. What about gating TWINE content via stuff you do in BITSY?

    Without using the bitsy-twine comms hack to actively share data, I thought about the fact that the player already has to operate the game manually to some degree, and made this a feature. When the player visits certain planets in the System-Map bitsy, they will be given a launch code. The player can then manually type this into a text-input field outside the bitsy, which will unlock a link to change passage. p r e t t y n e a t h u h

    The last thing to mention is prettifying the rest of the passage. The Tower and The Horizon both just have the bitsy kinda floating in the game; Superlunary embeds everything in a nice UI surround. This is just a table; the full twine text of the Zeta Station passage is below, for illustration:
    <table class="background" width="800" height="600" id="screen3">
    <td width="22" height="66"></td>
    <td width="512" height="66"></td>
    <td width="22" height="66"></td>
    <td width="222" height="66"></td>
    <td width="22" height="66"></td>
    <td width="22" height="512"></td>
    <td width="512" height="512" style="line-height:0.5"><iframe id="bitsyone" 
    src="bitsies/Zeta.html" style="border:none;" height="512" width="512" 
    <td width="22" height="512"></td>
    <td width="222" height="512" style="vertical-align: top;">
    <div style="margin-left:5px;margin-top:5px;">
    SHIP STATUS: <span style="color:orange;">LANDED</span>
    <b>*</b>LT. ESSEX: [[OUTSIDE|Zeta]]
    <<if visited ("zetaSuzae")>>PILOT SUZAE: [[COMMS|zetaSuzae2]]<
    <else>>PILOT SUZAE: [[COMMS|zetaSuzae]]<</if>>
    DR. BATHORY: [[IN QUARTERS|zetaBathory]]
    <<set $answer to ''>>\
    <span id='textbox-reply'>COORDINATES:
    - - -</span>
    <span style="width:100"><<textbox '$answer' '' >></span><<button 'CMD INPUT'>>
            <<set $answer to $answer.trim().toUpperCase().replace(/\s\s+/g, ' ')>>
            <<if $answer is 'WTXH'>>
                <<replace '#textbox-reply'>>\
                    COORDINATES: WTXH
    [[LAUNCH|ZetaOutro][$progress = $progress +1]]\
                <<replace '#textbox-reply'>>\
    COORDINATES: $answer              
    - - -\
    <span id='textbox-submit'>\
    <td width="22" height="512"></td>
    <td width="22" height="22"></td>
    <td width="512" height="22" ></td>
    <td width="22" height="22"></td>
    <td width="222" height="22"></td>
    <td width="22" height="22"></td>
    </table><<display framescript>>
    So, you can see that the table has a fixed size, padded out with blank cells; the CSS for the table, below, pulls in the background image itself:
    table.background { 
    background: url("images/map hud.png") no-repeat; 
    And that's that! A lot of the heavy lifting of making bitsy and twine talk to eachother is offloaded onto the player; this is a situational solution, depending on your game design. For Superlunary, it makes sense for the player to be responsible for operating things; for a more seamless experience, I wouldn't use this method.

    ECHOES AND OTHER POEMS: now make it pretty

    Echoes And Other Poems is an interesting one; there's no gameiness. The bitsy games are just illustrations with minimal interaction, and each poem plays automatically after you click to load each one, afterwards showing a clickable Go Back button.

    However, each of the three does interesting things visually. As well as using the WebGL canvas hack and a modified version of Sweetheartsquad's shader from ROUND TABLE, it plays with external features in the twine.

    Screenshot from Echoes

    ECHOES overlays the bitsy game upon a screenshot of a different bitsy game (specifically, Oh To Be(e) In Love). This is achieved by giving the bitsy game a transparent background (see the RGBA hack), and then the table itself a background image. Simple!

    Screenshot from Falling

    FALLING, on first appearance, is just a bitsy game. But I fooled you! It's a bitsy game on top of a background again, but with the appearance of text carefully designed to move the bitsy game downwards on the screen, revealing the separation between the bitsy and background. This was, actually, an accidental realisation that I then redesigned the bitsy around. What happens is, as text is added to the table cell, the iframe containing the bitsy is moved down with the lower bounds of the table, to which it is aligned.

    Screenshot from Function

    FUNCTION doesn't move anything around, but instead plays carefully with varying opacities to let the background image (actually a .gif, including the scribbled heart) and the bitsy tiles combine to make new colours - look at the overlap between the lying-down woman and the face.

    These three tricks don't necessarily require a twine game as the parent; they're replicatable with just an .html page. Wrapping a bitsy game in another .html to allow these effects would be a simpler way of making this game than by including twine. I just like twine and it's what I'm used to.

    UNDER A CAUSTIC SNOW, I DANCED: seamlessness, and faux bitsies

    Screenshot from Under A Caustic Snow, I Danced

    This was the first time I used the Bitsy-Twine comms hack from the ground up; it went pretty smoothly and has helped make a seamless experience throughout. The formatting of the game borrows from The Horizon, but with some edits so that it superficially appears not to be a twine game. Heh heh heh.

    As in Horizon Build #2, there's two parts: the story-format-dependent code you put in Twine, in a script passage; and the code you put in the Bitsy .html, within <script> tags. For this game, they're both as standard in the hack linked above. Personally I use the Sugarcube story format for twine, so paste in this twine-side code.

    Keyboard control is presented to the player to advance the passage when prompted; each of the twine-only passages are formatted to LOOK a bit like a bitsy game. This is done by having a 512px image as the background of a table, and the text appearing over the top.

    To preserve the keyboard control scheme; - Spacebar to advance - the first bitsy game has the control scheme edited to make both spacebar and all cursor keys move the player right. I also changed the dialogue arrow for the fun of it to match the snowflake the twine passages use.

    Two bitsy games follow each other, automatically gaining keyboard focus so the player never needs to use the mouse. The default hack allows bitsy dialogue to trigger Twine passage changes using the following, with "Inn1" as the name of the passage:
    (twinePlay "Inn1")
    Nice and simple! So after talking to the village of people, the player can return to the inn and will be sent to the next passage.

    At the end of the 2nd bitsy, it sends the player to a text-only passage. This needed a bit of tweaking from the existing hack to give the keyboard focus back to the passage itself (re-enabling spacebar-to-continue). This was just a tiny custom twine macro, invoked with <<restoreFocus>>:
    macros['restoreFocus'] =
    	handler: function()
    (I believe Sean is looking into fixing this for the hack).

    In one of the bitsy passages, there's a .gif overlay that adds a snow effect. The passage looks like the following:
    <table class="table1" width="512" height="512">
    <div style="position:absolute; z-index:5;">
    <div style="position:absolute;align-left:0px; align-top:0px; z-index:9;">
    <img src="bitsy/snow.gif">
    Just by tweaking the z-index, we can layer things on top without issue. In theory, if I had a transparent-background bitsy I could do the same in reverse, instead of using the table background-image CSS as in Echoes.

    Otherwise, many of the passages are either screenshots of bitsy games, or else pictures drawn from scratch with the same aesthetic. When doing the latter, make sure to draw at 128x128 resolution and then upscale by 4 to the final size!

    I think this was the smoothest and most stress-free method of combining the two, but that's also having built on the foundation of the previous games' experience.

    Was I to make a new bitsy-twine hybrid from scratch from the first time, I would probably either go with the Tower route - bare minimum, player handles the navigation - or else the Under A Caustic Snow route, using the bitsy-twine hack in its default implementation.


    So, that was five different methods of doing broadly the same thing. Some of them were more convoluted than others; some of that was my own fault.

    Whilst this was predominantly talking about bitsy, there's no reason the same method of combining html-based game engines together shouldn't work; Superlunary does the same thing with Flicksy games, although currently that implementation does not allow for direct cross-talk.

    The compartmentalised effect that lots of the above give is partly down to the way I tend to write the games; using bitsy as just one decorative feature, along with still images/gifs/other games. A more mechanics-based combination is easily possible, though - Superlunary does that without smart hacks, and Under A Caustic Snow, I Danced could easily have had branching handled by the built-in hack functions.

    Really, it's up to you. Go make some bitsy-twine hybrids! Please blow me away with something incredible.

    Peace out,