In the previous blog we went over kick-starting the AirConsole port using Unity3D. In this blog we’ll start looking into creating our own controller. I won’t be able to provide the complete code that we used but I’ll explain the general concepts with explanatory code.

1. Creating custom HTML

Even though the tools provided by AirConsole are very nice to kick-start the project with, it can be hard to customize that to the specific needs of your game. The goal is to create our own custom controller and for that we need to create our own HTML. For the ease of fast testing the below controller is the first version that I made, the inline CSS and % based layout should not be used for production, but it does give us fast results to start working with!

This is a simple recreation of the controller that we made in part 1 of this series:



<html> <head> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/> </head> <body> <div id="joystick_container" style="float:left;width:40%;background-color:white;height:100%"></div> <div id="start_container" style="float:left;width:19%;background-color:red;height:40%"></div> <div style="float:right;width:40%;background-color:black;height:100%"> <div id="grab_container" style="height: 50%"> Grab</div> <div id="jump_container" style="height: 50%; background-color:green"> Jump</div> </div> </body> </html>

2. Creating the Javascript: Sending our first data

The first thing I wanted to achieve at this point was sending my own data from the controller to Unity, creating this in these small steps ensures you always have something ready to test and play. So without saying too much, lets send the input of the jump button to Unity:

<!-- including the AirConsole API ---> <script type="text/javascript" src="https://www.airconsole.com/api/airconsole-1.6.0.js"></script> <script type="text/javascript"> /* starting the AirConsole object with landscape orientation */ var airconsole = new AirConsole({orientation: AirConsole.ORIENTATION_LANDSCAPE}); /* getting the jump element */ var jumpElement = document.getElementById("jump_container"); /* adding an eventListener */ jumpElement.addEventListener("click", jumpHandler, false); /* the jump event callback */ function jumpHandler(event) { /* sending a message to device 0 (the screen) */ airconsole.message(0, {"jump": true}); } </script>

That’s all we need! Although not being a pretty solution yet, we can send data to Unity!

Including this javascript joystick here’s the HTML/Javascript that was used in the first custom controller of Basher Beatdown!

<html> <head> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/> </head> <body> <div id="joystick_container" style="float:left;width:40%;background-color:white;height:100%"></div> <div id="start_container" style="float:left;width:19%;background-color:red;height:40%"></div> <div style="float:right;width:40%;background-color:black;height:100%"> <div id="grab_container" style="height: 50%"> Grab</div> <div id="jump_container" style="height: 50%; background-color:green"> Jump</div> </div> <!-- including the AirConsole API ---> <script type="text/javascript" src="https://www.airconsole.com/api/airconsole-1.3.0.js"></script> <!-- including the joystic ---> <script src="virtualjoystick.js"></script> <script type="text/javascript"> /* starting the AirConsole object with landscape orientation */ var airconsole = new AirConsole({orientation: AirConsole.ORIENTATION_LANDSCAPE}); /* initiating the joystick */ var joystick = new VirtualJoystick({ container : document.getElementById('joystick_container'), mouseSupport : true, limitStickTravel: true, stickRadius : 50 }) /**** JUMP ****/ var jumpElement = document.getElementById("jump_container"); jumpElement.addEventListener("click", jumpHandler, false); var jump = false; function jumpHandler(event) { jump = true; } /**** GRAB ****/ var grabElement = document.getElementById("grab_container"); grabElement.addEventListener("click", grabHandler, false); var grab = false; function grabHandler(event) { grab = true; } /**** START ****/ var startElement = document.getElementById("start_container"); startElement.addEventListener("click", startHandler, false); var start = false; function startHandler(event) { start = true; } /**** Run this code every 100ms (10fps) ****/ setInterval(function(){ var message = { "joystick": { "message": { "x": joystick.deltaX(), "y": joystick.deltaY() } }, "jump": jump, "grab": grab, "start": start }; airconsole.message(0, message); jump = false; grab = false; start = false; }, 1/10 * 1000); </script> </body> </html>

3. Creating the Javascript: Advanced concepts

To be able to create a maintainable controller I had a couple of demands:

No javascript within the HTML file

No new javascript needed for every button/page

The controller doesn’t decide on anything

Every page/view should be visible without running AirConsole

In order to achieve this I came up with the idea to create the following html tags: air-page, air-btn & air-joystick. Using this the below HTML are 4 different controller views that are being used in the game. That look maintainable right?

<div id="Join" air-page="Join" class="page"> <img id="join_btn" air-btn="claim" class="join" src="images/Join_Button.png"> </div> <div id="MaxPlayers" air-page="MaxPlayers" class="page"> <img class="max-players" src="images/MaxPlayers.png"> </div> <div id="Loading" air-page="Loading" class="page"> <h1>Loading</h1> </div> <div id="Init" air-page="Init" class="page" style="background-image: url('loading_background.png');"> </div>

All the javascript needed to init the code:

var controller = new AirConsoleController(); controller.init("Init");

Since I don’t want to turn this series into a Javascript tutorial I won’t go through creating the actual javascript, but below is the basic setup that we used in pseudocode.

class AirConsoleController { Init (page) { // search for all air-page tags FindAllPages(); } ShowPage (page) { page.Register(); } } class Page { Init () { // search for all air-btn tags FindAllButtons(); // search for all air-joystick tags FindAllJoysticks(); // set display:hidden HidePage(); } Register () { // register all child buttons and joysticks } Unregister () { // unregister all child buttons an joystics } } class Button { Register () { // register for callbacks } Unregister () { // remove callbacks } } class Joystick { Register () { // register for callbacks } Unregister () { // remove callbacks } }

4. Extra notes

Don’t let the controller decide anything

The reason for this is that the controller isn’t directly connected and isn’t aware of the current gamestate in realtime. To minimize ‘desyncs’ and sending the wrong data to your game always let unity decide what can happen next, just use the controller as a general controller (only send input).

Send a class from Unity to the controller

The easiest way I found of being able to create a custom controller for each player, as well as changing 1 or 2 buttons on different game states is to send a variable from Unity to each controller containing different CSS classes. If you apply these classes to the root HTML you can easily use CSS to change the controller. Withing Basher Beatdown we always send a class for the color of the player (color0, color1, color2 etc) after which you can do this:

.color0 .hexagon-character-gradient { background-color: #00AAFF; } .color1 .hexagon-character-gradient { background-color: #FF0000; }

Doing it like this is the most flexible way and makes it a lot easier to adjust multiple objects at the same time.

click vs touchstart

Within this blog you might have noticed that I used “click” as the registered event, the reason being that this works on both a computer during AirConsole development with virtual test devices as well as on mobile devices. There are reasons that you don’t want to use the click event on a mobile device, so it might be worth using code like this:

var isEventSupported = (function(){ var TAGNAMES = { 'select':'input','change':'input', 'submit':'form','reset':'form', 'error':'img','load':'img','abort':'img' } function isEventSupported(eventName) { var el = document.createElement(TAGNAMES[eventName] || 'div'); eventName = 'on' + eventName; var isSupported = (eventName in el); if (!isSupported) { el.setAttribute(eventName, 'return;'); isSupported = typeof el[eventName] == 'function'; } el = null; return isSupported; } return isEventSupported; })();

Which can then be used like this:

this.register = function register() { if (isEventSupported('touchstart')) { this.eventType = "touchstart"; } else { this.eventType = "click"; } document.getElementById(this.elementId).addEventListener(this.eventType, this.eventHandler.bind(this), false); }

Extra resources

AirConsole blog on using smartphones as controller

AirConsole API

AirConsole Libs, containing many usefull libraries