Porting to AirConsole: Creating the controller

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

2 Comments

  • Andrew February 19, 2017 Reply

    Do you have an example of what you have in your Unity scripts to call color0 or color1?

    • Peter Klooster February 20, 2017 Reply

      Without trying to explain the complete setup we have in Unity (i’ll probably do a blog on that in the future) this is what we do:

      When we update any states within unity we send each device a JSON string containing the view and classes that apply to that specific device.

      Then on the controller withing the airconsole.onDeviceStateChange function we only have to do this:

      document.body.className = classesFromTheJSON

      Hope that helps 🙂

Leave a Reply