Wednesday, July 18, 2012

How to integrate Unity and Twine.

EDIT, 16 May 2013: Unity has changed their Browser to Web Player communications, but just a little. Basically, you don't use "getObjectById()" anymore, you just use "getUnity()" to get a reference to the web player -- more details here.

*****

Okay, I'm one of those people who thinks the problem with interactive fiction is that it's not sexy enough. However, I think IF, as a mode of interaction, is extremely powerful and is quite possibly light years ahead of whatever we're doing with narrative / meaning in the latest 3D whiz-bang video games.

Then one day I realized -- I could combine the CYOA tool Twine with the web player export of Unity, and the two could possibly hook into each other through Javascript. Turns out, they can.

Unfortunately, the project I used it for -- well, it didn't really work out -- but maybe someone else can use it?

The Unity Web Player has a useful method Application.ExternalCall() that can call Javascript methods on the web page. Similarly, you can call SendMessage() from a page script to call a method on a specific GameObject and even pass strings into Unity. That's the gist. If you need more help / my code snippets, a more detailed guide is here:

A lot of the work is about editing the Twine template (header.html) to add your custom Twine macros / hooks into Unity. You want to edit the template (in Twine\targets\) otherwise everytime you build your story in Twine you'll have to hand-edit the HTML again. So, I edited the Sugarcane template.

First, paste something like this right after the <script> tag so the Javascript can grab the web player object...

function GetUnity() {
  if (typeof unityObject != "undefined") {
    return unityObject.getUnity();
    }
  return null;
  }
  if (typeof unityObject != "undefined") {
    var params = {
      disableContextMenu: true // disables right-click
    };
    unityObject.embedUnity("unityPlayer", "WebPlayer.unity3d", 900, 352, params);
}

Next, add your own macros. I have my macros pasted after the macros.print = {}; section. It'll probably depend on the needs of your project, here's a general purpose one though:

macros.unity={
    handler: function(place, macroName, params) {
      GetUnity().SendMessage(params[0], params[1], params[2]); // SendMessage(gameObject:string, function:string, parameter:string);
    }
};

So now, one of your Twine passages might look like this:

THE DUNGEON
You enter the sex dungeon. It smells strongly of bleach. You hear 4 moans from the dark corner.
* [[Turn on flashlight.|flashlight1]]
* [[Turn on fleshlight.|fleshlight1]]
<<unity SexSlave Moan 4>>

However, make sure Unity is converting the string into whatever type you need. The "4" will get passed into Unity as a string; you'll probably want to convert the "4" into an integer to do stuff with it, like this:

// Unity3D, C#
public void Moan (string moanQuantity) {
   int moans = int.Parse(moanQuantity);
   SoundManager.PlayMoans(moans);
}

Now, say you want it the other way around, you want Unity to be able to control Twine. We can do that too. A Unity script might have a function Twine()...

// Unity3D, C#
void Twine (string passage) {
  Application.ExternalCall("TwineDisplay", passage);
}

void KickPlayerOutOfSexDungeon() {
   Twine("TruckStop");
}

Then, you'd paste a TwineDisplay() right before the </script> tag on the page template...

// hook from Unity...
function TwineDisplay(a) {
   Interface.displayPassage(a);
}

... And that's pretty much it. I think stuff like this is one of the strengths of using browser technologies for games, to be able to "break out of the frame" as Darius puts it, and do cool / unexpected things with it. Note that this isn't a super-robust implementation. Twine has its own logic system / variables, and I never bothered to expose that to Unity (nor did I want to spend time figuring out how to hack it to do that.)