In the first part of this article you saw how to put together the basic design and functional ‘building blocks’ that allowed you to drag the thumb stick of the controller around the orbit of the surround and return it back to the centre point in the controller once you released it. As the example below shows:
[kml_flashembed publishmethod=”dynamic” fversion=”10.0.0″ replaceId=”virtcontroller3″ movie=”http://blog.flashgen.com/swfs/VirtualControllers3.swf” width=”300″ height=”300″ targetclass=”flashmovie”]
[/kml_flashembed]
If you’ve not read the first part of this article yet, I suggest you go and have a read through it now (I’ll wait :p)
Hello… Is This Thing On?
With the ‘run off’ boundary in place all that is left to do is dispatch events to let the ‘outside world’ know what the controller is doing. As I mentioned in the first part of this article, I opted to just map keystrokes to the controller as most web-based Flash games tend to concentrate on keyboard controls over mouse movement (in some instances this is partly to do with the fact that the Flash runtime doesn’t provide support for Mouselook).
Now this sounds like a simple process. After all there are four arrow keys therefore I can just map those to Up, Down, Left, and Right on my controller. However that’s fine if I just want 4-way joystick control, (think Dig-Dug or Mr. Doo). However, most modern platformers tend to support 8-way movement at the very least. This poses a bit of a problem as it means you need to map combinations of key presses – for example Up and Left or Down and Right. This is painless when using a physical keyboard as the user just presses those keys in combination and they respond accordingly. We’ll have to emulate that and that means you need to do a bit more work in how you determine what your virtual controller is dispatching in the way of emulated key presses.
Mapping The Keys
The first thing to do is map the four arrow keys. That way you know that you are dispatching the events based on the position of the thumb stick. To do this you need to add in a simple internal loop (or tick if you prefer) that just updates the information about which key input you are emulating. So if you were to push the to the top you would like want to dispatch an event that represents the UP key being pressed for example.
Inverting Controls
One thing you may wish to implement in a future enhancement is allowing the user to invert the Y-axis of your controller (Up / Down). This is generally useful for games that relate to the flying of air- (or space-) based craft using a pitch and yaw approach. Therefore, as in a real aircraft, pushing up on the controller would in fact make the craft move down (or descend) and pulling the controller down would make it move up (or ascend).
You’re going to need another method to handle the checking and dispatching of the emulated key presses. So create a new protected method named dispatchKeyCombo() and put a call to it at the very bottom of the onThumbDrag() method. You should now have something that looks similar to the code below (note I’ve removed the main bulk of the code in the onThumbDrag() method for brevity):
1 2 3 4 5 6 7 8 9 10 11 | protected function onThumbDrag(e:Event):void { // Additional code removed for brevity dispatchKeyCombo(); } protected function dispatchKeyCombo():void { }
 |
Now every time the user moves the thumb stick and the onThumbDrag() method is invoked it will automatically invoke the dispatchKeyCombo() method as well. With the stub method in place let’s set about adding in the initial logic and event dispatching. The first thing you need to do is determine where the thumb stick is in relation to the surround. You may recall that in the onThumbDrag() method you had a variable named _degrees that stored the current positive or negative position of the thumb stick in degrees. At the time I skipped over its use and promised to explain its purpose later on. Well, now’s the time. You need the degree values so that you can use the current degree rotation as a basis for mapping the thumb stick location to the arrow keys.
Remember you are just adding in 4-way joystick support at this point. To achieve this you need to check what the value of the _degrees variable is and if it is equal to the required value for each of the four directions (Up, Down, Left and Right) you can dispatch the event. Based on the current setup, the degree values for each of the four thumb stick positions would be: Right = 0, Down = 90, Left = 180 and Up = -90. Now that you know what the values are it is a simple process of adding in the logical conditions to determine what values need to be dispatched.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | protected function dispatchKeyCombo():void { // Thumb stick position - Right if(_degrees == 0) { trace("Right"); } // Thumb stick position - Down else if(_degrees == 90) { trace("Down"); } // Thumb stick position - Left else if(_degrees == 180) { trace("Left"); } // Thumb stick position - Up else if(_degrees == -90) { trace("Up"); } }
 |
In theory this is exactly what you need. However if you test this in debug mode you’ll see that actually hitting that single value when moving the thumb stick is very difficult as you need to be very precise – it’s tricky with a Mouse imagine how hard it would be if you were using your thumb… The obvious course of action is to make use of a range of values to determine the position of the thumb stick. This way you can create ‘zones’ that relate to Up, Down, Left and Right. Below is the updated code that now uses zones to determine where the thumb stick is. Notice that the conditions are a little more complex as we are checking to see if the thumb stick is between values (some deal with both positive and negative values).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | protected function dispatchKeyCombo():void { // Thumb stick position - Right if(_degrees >= -22 && _degrees <= 22) { trace("Right"); } // Thumb stick position - Down else if (_degrees >= 68 && _degrees <= 112) { trace("Down"); } // Thumb stick position - Left else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158)) { trace("Left"); } // Thumb stick position - Up else if(_degrees >= -112 && _degrees <= -68) { trace("Up"); } }
 |
If you test this version in Debug mode you’ll see the traces are produced in a more consistent way – obviously you can tweak the range to vary the precision of the thumb stick output. However as you are targeting touch based devices don’t try and make the range too as user’s thumbs aren’t that precise – especially when they are in the heat of a fire fight.
Now that you have the basic structure in place you can move on to the next step. Mapping the keyboard input values to those of your thumb stick’s position. ActionSCript provides constants for all of the common key values, making implementing the inputs easy. Before you add any key input code to the dispatchKeyCombos() method you’ll need to declare four new variables. These are instance variables not method variables so just place them under all of the other instance variables at the top of the class.
1 2 3 4 5 | private var _primaryKeyCode :int; private var _secondaryKeyCode :int; private var _previousPrimaryKeyCode :int; private var _previousSecondaryKeyCode :int = 0;
 |
As with most things you don’t have to worry about all of them at this point in time; I’ll explain each one’s purpose as we add additional functionality. The only one you need to concern yourself with at this point is _primaryKeyCode, within this variable you’re going to store the current keyboard key code.
First up, replace each of the trace() statements with a reference to the _primaryKeyCode variable. Depending on which direction you are looking to monitor you can then assign the relevant keyboard input constant. Once these are in place you just need to dispatch the updated value of _primaryKeyCodeas the keyCode property of a new KeyboardEvent() as you can see in the following code block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | protected function dispatchKeyCombo():void { // Thumb stick position - Right if(_degrees >= -22 && _degrees <= 22) { _primaryKeyCode = Keyboard.RIGHT; } // Thumb stick position - Down else if (_degrees >= 68 && _degrees <= 112) { _primaryKeyCode = Keyboard.DOWN; } // Thumb stick position - Left else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158)) { _primaryKeyCode = Keyboard.LEFT; } // Thumb stick position - Up else if(_degrees >= -112 && _degrees <= -68) { _primaryKeyCode = Keyboard.UP; } dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _primaryKeyCode)); }
 |
The updated virtual controller is below, (I’ve added in an external event handler so you can see the current direction output in a text field above it)*.
[kml_flashembed publishmethod=”dynamic” fversion=”10.0.0″ replaceId=”virtcontroller4″ movie=”http://blog.flashgen.com/swfs/VirtualControllers4.swf” width=”300″ height=”300″ targetclass=”flashmovie”]
[/kml_flashembed]
*I’ve also set the alpha on the red boundary to 0 (zero) so it is no longer visible.
Expanding the Scope
Now that you have the basic structure for dispatching code let’s expand it so the virtual controller code allows you to use the controller as an 8-way controller as opposed to the 4-way you currently have. Considering that under the hood the controller is mapping to emulated keyboard events, dealing with the additional angles within your controller raises an additional requirement.
Remember those additional variables I made you declare: _primaryKeyCode, _secondaryKeyCode, and so on? Well, you are going to use another of those now – _secondaryKeyCode. Moving the thumb stick in the primary positions (as the code currently supports) requires only a single keyboard event to be dispatched. Moving the thumb stick to these off axis angles requires you to dispatch two keyboard events – for example if you move the thumb stick halfway between Up and Right you’re going to have to dispatch key codes that represent an Up Arrow key press as well as a Right Arrow key press.
Let’s look at the revised code and break down what is going on with the addition of the second event dispatcher and the inclusion of the new checks for those additional ‘zones’ that provide 8-way controller support.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | protected function dispatchKeyCombo():void { _secondaryKeyCode = 0; // Thumb stick position - Right if(_degrees >= -22 && _degrees <= 22) { _primaryKeyCode = Keyboard.RIGHT; } // Thumb stick position - Down else if (_degrees >= 68 && _degrees <= 112) { _primaryKeyCode = Keyboard.DOWN; } // Thumb stick position - Left else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158)) { _primaryKeyCode = Keyboard.LEFT; } // Thumb stick position - Up else if(_degrees >= -112 && _degrees <= -68) { _primaryKeyCode = Keyboard.UP; } // Thumb stick position - Up/Left else if(_degrees >= -157 && _degrees <= -113) { _primaryKeyCode = Keyboard.UP; _secondaryKeyCode = Keyboard.LEFT; } // Thumb stick position - Down/Left else if(_degrees <= 157 && _degrees >= 113) { _primaryKeyCode = Keyboard.DOWN; _secondaryKeyCode = Keyboard.LEFT; } // Thumb stick position - Up/Right else if(_degrees >=-67 && _degrees <= -21) { _primaryKeyCode = Keyboard.UP; _secondaryKeyCode = Keyboard.RIGHT; } // Thumb stick position - Down/Right else if(_degrees >= 23 && _degrees <= 67) { _primaryKeyCode = Keyboard.DOWN; _secondaryKeyCode = Keyboard.RIGHT; } if(_secondaryKeyCode > 0) dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _secondaryKeyCode)); dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _primaryKeyCode)); }
 |
You’ll notice that there have been a few changes to the dispatchKeyCombo() method. The biggest change is the one I mentioned prior to this code block: I’ve added support for key input mapping in relation to the full movement of the thumb stick, not just when it is moved Up, Down, Left, or Right. Secondly there is now a reference to the variable _secondaryKeyCode. This is where the application stores the secondary key input code. As I mentioned earlier if you push the thumb stick up and right then from a key input perspective you need to dispatch a KeyboardEvent() for both the Up key and the Right key. By default the _secondaryKeyCode variable is reset to 0 (zero) at the beginning of the method and only gets updated should the user move the virtual controller to a position that requires two key code values.
The last part is to add a check to see if the _secondaryKeyCode variable value is greater than 0 (zero) – if it is then dispatch the second KeyboardEvent().
1 2 | if(_secondaryKeyCode > 0) dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _secondaryKeyCode));
 |
Only one thing is left to do, and then the method will be finished. You may have noticed that you are dispatching KeyboardEvent.KEY_DOWN events, but how do you let any object listening to your controller know when you’ve changed direction. Well you need to dispatch the KeyboardEvent.KEY_UP event. This is where those final two variable come in. They are: _previousPrimaryKeyCode and _previousSecondaryKeyCode. These are used to store the (as the name implies) previous key codes dispatched. However to avoid unnecessary event dispatching they are only triggered if the new values for both the primary and secondary key codes have changed, like so:
1 2 3 4 5 6 7 8 9 10 11 | if(_primaryKeyCode != _previousPrimaryKeyCode) { dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousPrimaryKeyCode)); _previousPrimaryKeyCode = _primaryKeyCode; } if(_previousSecondaryKeyCode != _secondaryKeyCode) { dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousSecondaryKeyCode)); _previousSecondaryKeyCode = _secondaryKeyCode; }
 |
If the user changes the position of the thumb stick then the KeyboardEvent.KEY_UP event for the relevant input is dispatched. Because there’s a lot going on in this method, I’ve included the complete version below so you can see how it flows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | protected function dispatchKeyCombo():void { _secondaryKeyCode = 0; // Thumb stick position - Right if(_degrees >= -22 && _degrees <= 22) { _primaryKeyCode = Keyboard.RIGHT; } // Thumb stick position - Down else if (_degrees >= 68 && _degrees <= 112) { _primaryKeyCode = Keyboard.DOWN; } // Thumb stick position - Left else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158)) { _primaryKeyCode = Keyboard.LEFT; } // Thumb stick position - Up else if(_degrees >= -112 && _degrees <= -68) { _primaryKeyCode = Keyboard.UP; } // Thumb stick position - Up/Left else if(_degrees >= -157 && _degrees <= -113) { _primaryKeyCode = Keyboard.UP; _secondaryKeyCode = Keyboard.LEFT; } // Thumb stick position - Down/Left else if(_degrees <= 157 && _degrees >= 113) { _primaryKeyCode = Keyboard.DOWN; _secondaryKeyCode = Keyboard.LEFT; } // Thumb stick position - Up/Right else if(_degrees >=-67 && _degrees <= -21) { _primaryKeyCode = Keyboard.UP; _secondaryKeyCode = Keyboard.RIGHT; } // Thumb stick position - Down/Right else if(_degrees >= 23 && _degrees <= 67) { _primaryKeyCode = Keyboard.DOWN; _secondaryKeyCode = Keyboard.RIGHT; } if(_primaryKeyCode != _previousPrimaryKeyCode) { dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousPrimaryKeyCode)); _previousPrimaryKeyCode = _primaryKeyCode; } if(_previousSecondaryKeyCode != _secondaryKeyCode) { dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousSecondaryKeyCode)); _previousSecondaryKeyCode = _secondaryKeyCode; } if(_secondaryKeyCode > 0) dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _secondaryKeyCode)); dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _primaryKeyCode)); }
 |
So far the examples have been tested in the browser or on the desktop – however, the intention is that this controller will work on a touch based device (the title gives it away a bit :p). That said, if you were to put this on a device now it wouldn’t work.
Why? Well it’s because you need to tell your game that it is using multitouch events so it knows how to handle them. Now you could place this code within the virtual controller – making sure it always enables the multitouch events, or you can keep it externalized and place it in the actual game. Personally I tend to put this in the constructor of my controllers, mainly because in the heat of coding I tend to forget to put the multitouch code in and then sit their dumbfounded when it fails to work on the device. Obviously this could be conceived as repetitious if I have more than one controller (dual controllers, additional buttons etc), but it’s not a big bit of code and once it’s initialized, setting it again won’t cause any real additional overhead.
So in this final step just paste this code in to the constructor of your virtual controller just above where you added the event handlers and you are done.
1 | Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
 |
It’s worth pointing out that you need to use the MultitouchInputMode.TOUCH_POINT event as you are just monitoring for the press and release on the screen. If you needed to track multiple inputs so you could perform gestures (pinch zoom for example) then you’d need to use the MultitouchInputMode.GESTURE event instead. For controllers I would suggest you avoid mixing them otherwise you will get undesirable side effects.
Tidying Up
The last thing you need to do is tidy up a few loose strings. While your controller now dispatches the correct events if you remove your thumb from the controller they continue to get dispatched. The simple reason is that there is no catch all event to kill all of the events should the user remove their thumb from the controller. This is an easy fix. Just create a new method called killAllEvents() and within it place the following code:
1 2 3 4 5 6 7 8 | protected function killAllEvents():void { if(_primaryKeyCode) dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousPrimaryKeyCode)); if(_secondaryKeyCode > 0) dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousSecondaryKeyCode)); }
 |
Now place a call to it from within the resetThumb() method, right after you remove the event handler. Your resetThumb() method should now look like this:
1 2 3 4 5 6 7 8 9 10 | protected function resetThumb():void { removeEventListener(Event.ENTER_FRAME, onThumbDrag); killAllEvents(); _thumb.stopDrag(); _thumb.x = 0; _thumb.y = 0; }
 |
That’s it. If you test it now you’ll see the events are dispatched when the thumb stick is moved in any direction and if you remove your thumb from the controller the events will stop being broadcast.
Summary
In the final part of this article you have seen how you can easily emulate keyboard inputs through the virtual controller and dispatch them so that any external object listening for these events can react when they are received.
If you want to see this controller in action check out my Pushbutton engine article on integrating controls with Pushbutton Engine.
Related Files
VirtualController_pt2.zip
2 Comments