Pushbutton Engine – Handling User Input

In the last couple of articles I’ve covered how you set up your workspace (be that Flash Professional or Flash Builder) and how to create a scene and add your player entity – commonly referred to as the hero entity. In this article I am going show you how to wire up basic keyboard input, so you can actually move your player around the screen.

Before you look at the code, it will help to understand a little about the various options you have open to you when adding input control. You can either directly interact with the InputManager instance held by the main PBE class or you can use an input map. Direct interaction is easy to do, but it does require a tight relationship between your entities and the InputManager.

For example, imagine you want to allow the user to define the keys that they use to play your game (this is one close to my own heart, because I’m left handed and as such I’m less dexterous with my right when it comes to manipulating keys on my keyboard). It also makes using PBE’s support for XML based descriptor files difficult to implement as they are expecting an alternative mechanism to assign their inputs – an input map. This input map is an alternative (more of an extension really) to directly interacting with the InputManager.

The advantages of using an input map are that it abstracts out the association of the key mapping from the InputManager. This makes it easier for you to alter the key mapping at any point. Plus input maps also allow easy configuration from within the XML based .pbelevel files (I’ll cover that in a future article). For now let’s look at how to map keyboard input via both mechanisms so you can see the approaches involved.

Keyboard Control With InputManager
The simplest way to add keyboard controls to your game is by accessing the isKeyDown() method of the InputManager class. However, as you will see this can lead to overly complex and unwieldy mapping information. That said, you have to start somewhere so let’s use this as the basis of something you can improve upon. As with everything you need to create a class to manage the input operations. This should inherit from TickedComponent so that it gets called every time there is a game tick. The basic structure is to override the onTick() method and apply your controller code in it.

To interact with various aspects of your entity you need to pass the relevant property reference in. You can do this by declaring the required variable (of type PropertyReference) and then access it as needed via the owner reference. So you can see in the example code below that there is a variable named positionReference (of type PropertyReference), and to access it you just invoke the owner.getProperty() method and pass in the reference you wish to access as the method signature.

1
2
3
4
5
6
7
8
9
10

public var positionReference:PropertyReference;
 
override public function onTick(deltaTime:Number):void
{
     super.onTick(deltaTime);
 
     var _position:Point = owner.getProperty(positionReference);
 
     owner.setProperty(positionReference, _position);
}

To set any changes to a property you just call the counter method to getProperty(), which is setProperty(). This method takes two parameters though – the property that is being referenced and the value you wish to set it to (as shown at the bottom of the code above).

Mapping your keyboard inputs is really easy because the PBE developers have already added constants for pretty much every key you could possibly wish to use (you can find the list here).

Below is the complete KeyboardController class

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

package com.flashgen.gaming.controllers
{
     import com.pblabs.engine.PBE;
     import com.pblabs.engine.components.TickedComponent;
     import com.pblabs.engine.core.InputKey;
     import com.pblabs.engine.entity.PropertyReference;
 
     import flash.geom.Point;
 
     publicclass KeyboardController extends TickedComponent
     {
          publicvar positionReference:PropertyReference;
 
          publicfunction KeyboardController()
          {
               super();
          }
 
          overridepublicfunction onTick(deltaTime:Number):void
          {
               super.onTick(deltaTime);
 
               var _position:Point = owner.getProperty(positionReference);
 
               if(PBE.isKeyDown(InputKey.UP))
                    _position.y -= 5;
 
               if(PBE.isKeyDown(InputKey.DOWN))
                    _position.y += 5;
 
               if(PBE.isKeyDown(InputKey.LEFT))
                    _position.x -= 5;
 
               if(PBE.isKeyDown(InputKey.RIGHT))
                    _position.x += 5;
 
               if(_position.x > 190)
                    _position.x = 190;
 
               if(_position.x < -190)
                    _position.x = -190;
 
               if(_position.y > 105)
                    _position.y = 105;
 
               if(_position.y < -105)
                    _position.y = -105;
 
               owner.setProperty(positionReference, _position);
          }
     }
}

Adding the controller to your actual entity is fairly straightforward. You just need to declare the component – in this case your KeyboardController and assign it the required properties. Finally, make sure it is added to the entity itself; as the code below illustrates:

1
2
3

var _input:SimpleKeyboardController = new SimpleKeyboardController();
_input.positionProperty = new PropertyReference("@Spatial.position");
_hero.addComponent(_input, "Input");

This is just building upon the player, or hero, entity you saw in the previous article – Getting Started With Pushbutton Engine. While this code is clean and succinct – given that I am only mapping four keys, it does tightly couple the input controls to those defined in the actual controller class. A better approach is to abstract this information out so that it can be defined by the user or an external configuration. To do that you can use the InputMap class instead.

Flexible Keyboard Mapping With Input Maps
Using the InputMap class does require a bit of reverse thinking, mainly because it is best to start with the controller and work back to the entity that is going to use it. Now the InputMap class has a few handy methods that you will no doubt become very familiar with:

  • mapActionToHandler()
  • mapKeyToAction()
  • mapKeyToHandler()

The last one in the list is fairly self-explanatory and allows you to pass in an InputKey assignment and the name of a respective handler and when the key is depressed the handler is invoked. One thing you do need to know about these methods is that they don’t pass an event object to the handler. They simply pass the values 1 (one), if the key is down. Or 0 (zero), if the key up.

The following example shows how to use the first two: mapActionToHandler() and mapKeyToAction(). Think of these as two halves of the same object. You use one within the controller and the other within the entity (or wherever you want to set the input mappings). Take a look at the completed keyboard controller (which I’ve called InputMapKeyboardController) and break it down so you can get a better understanding of what’s going on.

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

package com.flashgen.gaming.controllers
{
     import com.pblabs.engine.PBE;
     import com.pblabs.engine.components.TickedComponent;
     import com.pblabs.engine.core.InputKey;
     import com.pblabs.engine.core.InputMap;
     import com.pblabs.engine.entity.PropertyReference;
 
     import flash.geom.Point;
 
     public class InputMapKeyboardController extends TickedComponent
     {
          private var _inputMap:InputMap;
 
          public var positionProperty:PropertyReference;
          private var _right:Number;
          private var _left:Number;
          private var _down:Number;
          private var _up:Number;
 
          public function InputMapKeyboardController()
          {
               super();
          }
 
          protected function onRight(value:Number):void
          {
               _right = value;
          }
 
          protected function onLeft(value:Number):void
          {
               _left = value;
          }
 
          protected function onDown(value:Number):void
          {
               _down = value;
          }
 
          protected function onUp(value:Number):void
          {
               _up = value
          }
 
          public override function onTick(deltaTime:Number):void
          {
               super.onTick(deltaTime);
 
               var _position:Point = owner.getProperty(positionProperty);
 
               if(Boolean(_up))
                    _position.y -= 5;
 
               if(Boolean(_down))
                    _position.y += 5;
 
               if(Boolean(_left))
                    _position.x -= 5;
 
               if(Boolean(_right))
                    _position.x += 5;
 
               if(_position.x > 190)
                    _position.x = 190;
 
               if(_position.x < -190)
                    _position.x = -190;
 
               if(_position.y > 105)
                    _position.y = 105;
 
               if(_position.y < -105)
                    _position.y = -105;
 
               owner.setProperty(positionProperty, _position);
          }
 
          public function get inputMap():InputMap
          {
               return _inputMap;
          }
 
          public function set inputMap(value:InputMap):void
          {
               _inputMap = value;
 
               if(_inputMap != null)
               { 
                    _inputMap.mapActionToHandler("Up", onUp);
                    _inputMap.mapActionToHandler("Down",onDown);
                    _inputMap.mapActionToHandler("Left", onLeft);
                    _inputMap.mapActionToHandler("Right",onRight);
               }
          }
     }
}

As you can see it isn’t a million miles away from the first controller class. However the big changes have to do with the getter / setter methods at the bottom. Within the setter I check to see if there is a valid InputMap object and if so create the mappings within the controller using mapActionToHandler(). This, as you can see, takes the string value and associates it with a predefined handler.

1
2
3
4
5
6
7
8
9
10
11
12

public function set inputMap(value:InputMap):void
{
     _inputMap = value;
 
     if(_inputMap != null)
     { 
          _inputMap.mapActionToHandler("Up", onUp);
          _inputMap.mapActionToHandler("Down",onDown);
          _inputMap.mapActionToHandler("Left", onLeft);
          _inputMap.mapActionToHandler("Right",onRight);
     }
}

Within these associated handlers I just have a variable that is set to the current numerical value passed to it – depending on whether the key is up or down (0 or 1). The final change is in the onTick() method. Unlike the previous controller example that checked each key explicitly to see if it was depressed or not, this example uses the values of the handler variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public override function onTick(deltaTime:Number):void
{
     super.onTick(deltaTime);
 
     var _position:Point = owner.getProperty(positionProperty);
 
     if(Boolean(_up))
          _position.y -= 5;
 
     if(Boolean(_down))
           _position.y += 5;
 
     if(Boolean(_left))
          _position.x -= 5;
 
     if(Boolean(_right))
          _position.x += 5;
     ...
}

Looking at the complete code again you have probably noticed that nowhere in the code do I declare the actual input keys; providing a suitably abstract approach to assigning your keyboard mapping. However, that’s all well and good for the controller class, but how do you actually assign those mappings? Well in this example you do it in the entity itself. Unlike the three lines of code you used to add your basic keyboard controller to your entity you have to add a few more parameters though:

1
2
3
4
5
6
7
8
9
10
11
12
13

var _input:InputMapKeyboardController = new InputMapKeyboardController();
 
_input.inputMap = new InputMap();
_input.inputMap.mapKeyToAction(InputKey.UP, "Up");
_input.inputMap.mapKeyToAction(InputKey.DOWN, "Down");
_input.inputMap.mapKeyToAction(InputKey.LEFT, "Left");
_input.inputMap.mapKeyToAction(InputKey.RIGHT, "Right");
 
_input.inputMap.
 
_input.positionProperty = new PropertyReference("@Spatial.position");
 
_hero.addComponent(_input, "Input");

It’s not that different but you can see that you map your input keys to our actions via the mapKeyToAction() method on the InputMap instance of our controller. Word to the wise, make sure you remember to instantiate InputMap otherwise it will fail – obvious I know but the simple things do tend to catch us out.

Summary
In this article you learned how to implement simple keyboard controls through the InputManager, firstly by explicitly defining the input keys in your controller class and then abstracting this further with the InputMap class, which acts as a facade to the InputManager. You also learned how you can use the InputMap class to easily decouple the actual input keys from your game – making it easy for the player to customize their keyboard configuration.

Related Files
PBE002_HandlingUserInput.zip

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *