Twine on Ouya via CocoonJS with Gamepad as Cursor

Over a month ago now, I posted about a proof-of-concept video of Twine running on the Ouya using CocoonJS. At the time, I was cautiously optimistic about getting Twine projects on the Ouya. It seemed as if it was just a matter of figuring out a few things.

Two of those were getting gamepad input and IAP working. Gamepad input, as I’ve come to find out, only works with CocoonJS’ accelerated mode. It is the mode that has a reduced DOM, no CSS model, and is missing XML support. It makes Canvas-based projects run faster as a result, but can often be a stumbling block to common website design practices.

The other real problem was in-app purchases (IAP). Previous to a blog post two days ago announcing that demos, which use IAP, are not longer a requirement on the Ouya, it seemed as if CocoonJS wasn’t going to be a viable platform for anything Ouya related. IAP, you see, depends on a cryptographic exchange. Unlike Google Play and Amazon, which handle authentication on their end, Ouya had developers doing authentication in-memory and then making a request.

(The two dominant Ouya libraries for Unity and AIR have no real trouble with this. They have cryptographic functionality built-in. JavaScript, as found in a browser, usually doesn’t. And since CocoonJS is more or less a browser itself, it doesn’t either. I was already looking into ways around this, but it’s no longer a factor.)

Since posting that video and learning about those issues, I haven’t really worked too hard on getting Twine on the Ouya. I had turned to working several HTML5 libraries and submitting patches for various things. However, after a recent quick conversations with Anna Anthropy on Twitter yesterday, I thought I would look into it again and see if I could come up with a way to load a Twine file and use a gamepad as input.

The following video is what I came up with after a few hours of tinkering with some code.

It uses a technique of loading CocoonJS in its accelerated mode and then creating a WebView. Using JavaScript calls between them, messages can be passed and code run. It is not unlike having a webpage load up an iframe with code and talking between them. (That’s exact what the CocoonJS code does in desktop browsers, in fact.)

My “index.html” file looks like the following. It loads the necessary CocoonJS extensions and my own gamepad code.

<!DOCTYPE html>
<html>
<head>
<title>Twine Testing</title>
<script src ="js/CocoonJSExtensions/CocoonJS.js"></script>
<script src ="js/CocoonJSExtensions/CocoonJS_App.js"></script>
<script src ="js/CocoonJSExtensions/CocoonJS_App_ForCocoonJS.js"></script>
<script src="js/gamepad.js"></script>
<script>
var cursorSpeed = 5;
CocoonJS.App.onLoadInTheWebViewSucceed.addEventListener(function()
{
CocoonJS.App.showTheWebView(0, 0, window.innerWidth, window.innerHeight);
setInterval(pollCode, 1000 / 60);
});
CocoonJS.App.loadInTheWebView("ouya.html");
function pollCode() {
if (Gamepad.moved(0, "LEFT_X") > 0 && Gamepad.moved(0, "LEFT_X") <= 1.2) {
CocoonJS.App.forwardAsync("moveDiv(" + cursorSpeed + "," + 0 + ")");
}
if (Gamepad.moved(0, "LEFT_X") < 0) {
CocoonJS.App.forwardAsync("moveDiv(" + -cursorSpeed + "," + 0 + ")");
}
if (Gamepad.moved(0, "LEFT_Y") < 0) {
CocoonJS.App.forwardAsync("moveDiv(" + 0 + "," + -cursorSpeed + ")");
}
if (Gamepad.moved(0, "LEFT_Y") > 0 && Gamepad.moved(0, "LEFT_Y") <= 1.2) {
CocoonJS.App.forwardAsync("moveDiv(" + 0 + "," + cursorSpeed + ")");
}
}
</script>
</head>
<body>
</body>
</html>

Each time it detects movement, it makes a forwardAsync call (to the WebView with the Twine file) and tells it to move a <div> around on the page. On the WebView side, the Twine file loads a JavaScript file with the global function “moveDiv(x,y)” that makes a getElementById call and sets its style “top” or “left” to the new adjusted value + “px” for the CSS placement.

It’s far from ideal, but I was excited about how quickly I was able to put something together. And with the IAP gone, it looks like I’m going to try to sell some projects on Ouya as soon as that requirement is lifted too.