As mentioned, the single “controversial” part of my talk at Flash on the Beach this year was in questioning polling for input in Flash games. In truth, it was hardly controversial. No death threats. No twitter-based lynch mobs. Just that a couple of guys came up to me and politely expressed disagreement later, and we had a conversation about it. But, as said conversations were done later in the evening at the Old Ship, I thought it might be worth discussing in a clearer state of mind.
So the idea is that I said I thought it was better, i.e. more efficient, to use events for keyboard and mouse input, rather than polling. A few people have made keyboard manager classes which allow you to check which keys are down. You can then poll this class to see if the navigation / action keys you are interested in are currently down, and act accordingly. If you are doing this in the game loop, this is going to happen on every frame or interval, and to me, this does not make sense.
To demonstrate this kind of setup, here is a bare bones game class. It’s using Richard Lord‘s KeyPoll class.
[as3]package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.ui.Keyboard;
import uk.co.bigroom.input.KeyPoll;
public class KeyboardGame extends Sprite
{
private var keyPoll:KeyPoll;
// view
private var character:Sprite;
// model
private var xpos:Number = 200;
private var speed:Number = 0;
private var direction:Number = 0;
public function KeyboardGame()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
keyPoll = new KeyPoll(stage);
character = new Sprite();
character.graphics.lineStyle(0);
character.graphics.drawCircle(0, 0, 10);
character.graphics.lineTo(20, 0);
character.x = xpos;
character.y = 200;
addChild(character);
addEventListener(Event.ENTER_FRAME, gameLoop);
}
protected function gameLoop(event:Event):void
{
input();
update();
render();
}
protected function input():void
{
if(keyPoll.isDown(Keyboard.LEFT))
{
direction = 180;
speed = -5;
}
else if(keyPoll.isDown(Keyboard.RIGHT))
{
direction = 0;
speed = 5;
}
else
{
speed = 0;
}
}
protected function update():void
{
xpos += speed;
}
protected function render():void
{
character.x = xpos;
character.rotation = direction;
}
}
}[/as3]
For the sake of simplicity, this is all in one class, with the view being a sprite with some graphics, and the “model” being a few class variables. The game loop runs on every frame and polls for input, updates the model, and renders the view.
The input method polls the keyPoll class, checking to see if the left or right cursor keys are pressed. If so, it adjusts the direction and speed in the “model”. If neither is pressed, direction is unchanged and speed is 0.
The update method simply updates the xpos based on the speed and the render method moves and rotates the character based on the model. Run it, press the left and right keys and the character turns and moves in the right direction. Yay.
So what was I proposing instead? To cut out the polling part. The idea being that the only time you need to handle input is when a key goes down or up. Not on every single frame. So you do something like this:
[as3]package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
public class KeyboardGameNoPoll extends Sprite
{
// view
private var character:Sprite;
// model
private var xpos:Number = 200;
private var speed:Number = 0;
private var direction:Number = 0;
public function KeyboardGameNoPoll()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
character = new Sprite();
character.graphics.lineStyle(0);
character.graphics.drawCircle(0, 0, 10);
character.graphics.lineTo(20, 0);
character.x = xpos;
character.y = 200;
addChild(character);
addEventListener(Event.ENTER_FRAME, gameLoop);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
protected function onKeyDown(event:KeyboardEvent):void
{
if(event.keyCode == Keyboard.LEFT)
{
direction = 180;
speed = -5;
}
else if(event.keyCode == Keyboard.RIGHT)
{
direction = 0;
speed = 5;
}
}
protected function onKeyUp(event:KeyboardEvent):void
{
if(event.keyCode == Keyboard.LEFT || event.keyCode == Keyboard.RIGHT)
{
speed = 0;
}
}
protected function gameLoop(event:Event):void
{
// input by events
update();
render();
}
protected function update():void
{
xpos += speed;
}
protected function render():void
{
character.x = xpos;
character.rotation = direction;
}
}
}[/as3]
Here, we add event listeners for key up and key down. On key down, we check which keys is being pressed, and update the model accordingly. When either of the special keys is released, set speed to 0.
So, no input method, but update and render are exactly the same.
Now, when a key is being pressed, it’s going to get multiple, repeated key down events, so you might say this is basically just polling anyway, even if only when the key is down. Yes, but it’s not like that is just a substitution for the polling in the first version. Actually, if you look in the KeyPoll class, or any other similar keyboard manager class, you are going to find an event listener for key up and key down. And just like this version, those are going to be run repeatedly when a key is held down. So the polling in the first version is ON TOP of that repeated key event stuff, which is going to happen regardless.
Problems, and a Comprimise
Even in this simple example, however, a problem can soon arise: in the non-polling version, you could run into this situation:
This would not happen in the polling version.
If this kind of problem happens in such a simple example, you are bound to run into it in many other forms in more complex input schemes. So it’s likely that you will have to start adding some additional logic to address it. My initial response was to create a leftKeyDown and rightKeyDown property, set these to true in the key down handler, and false in the key up handler and check the value of both to see if speed should be 0:
[as3]protected function onKeyUp(event:KeyboardEvent):void
{
if(event.keyCode == Keyboard.LEFT)
{
leftKeyDown = false;
}
else if(event.keyCode == Keyboard.RIGHT)
{
rightKeyDown = false;
}
if(!leftKeyDown && !rightKeyDown)
{
speed = 0;
}
}[/as3]
Unfortunately, this still breaks. If the user presses and holds right, and taps and releases left, the character will continue to move left. So you could do something ridiculous like this:
[as3]protected function onKeyUp(event:KeyboardEvent):void
{
if(event.keyCode == Keyboard.LEFT)
{
leftKeyDown = false;
}
else if(event.keyCode == Keyboard.RIGHT)
{
rightKeyDown = false;
}
if(leftKeyDown)
{
direction = 180;
speed = -5;
}
else if(rightKeyDown)
{
direction = 0;
speed = 5;
}
else
{
speed = 0;
}
}[/as3]
But this is just duplicating what’s going on in the key down handler. So I could extract the duplicated code into another method, but I’m starting to feel like I’m unnecessarily complicating the code for the sole reason of avoiding key polling. I don’t want to be that guy. There’s a beautiful simplicity in the polling version. My main concern is over performance, since the keyboard managers I’ve seen usually involve array lookups. But looking at Richard’s, it’s using byte arrays and liberal bitwise operators (almost to the point of obfuscation). So my initial guess is that’s not too inefficient. Even so, something about it doesn’t sit well with me.
What seems to make sense to me is to create a sort of custom keyboard handler for each game. I’ve done this in other games and it worked out pretty well. The thing about keyboard managers is they are generic and reusable, and thus have to be able to take note of, store, and retrieve the state of any possible key. So some type of an array or collection is always needed. But for your specific game, there probably at the most a half dozen keys you are really interested in. These can be stored as class properties with getters (or public properties if that’s not too taboo for you). These properties can also be named something logical to the game, such as moveLeft, moveRight, jump, shoot, etc. rather than Keyboard.LEFT, Keyboard.SPACE, etc. which makes for more readable code. This also abstracts away lower level stuff into whatever you are interested in. If you wanted to change your keyboard mappings you could do it right there, without changing your external code. Something like this:
[as3]package
{
import flash.display.Stage;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
public class InputLayer
{
private var stage:Stage;
public var movingLeft:Boolean = false;
public var movingRight:Boolean = false;
public function InputLayer(stage:Stage)
{
this.stage = stage;
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
protected function onKeyDown(event:KeyboardEvent):void
{
if(event.keyCode == Keyboard.LEFT)
{
movingLeft = true;
}
else if(event.keyCode == Keyboard.RIGHT)
{
movingRight = true;
}
}
protected function onKeyUp(event:KeyboardEvent):void
{
if(event.keyCode == Keyboard.LEFT)
{
movingLeft = false;
}
else if(event.keyCode == Keyboard.RIGHT)
{
movingRight = false;
}
}
}
}[/as3]
And the final class implementing it:
[as3]package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.ui.Keyboard;
public class KeyboardGameCustom extends Sprite
{
private var inputLayer:InputLayer;
// view
private var character:Sprite;
// model
private var xpos:Number = 200;
private var speed:Number = 0;
private var direction:Number = 0;
public function KeyboardGameCustom()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
inputLayer = new InputLayer(stage);
character = new Sprite();
character.graphics.lineStyle(0);
character.graphics.drawCircle(0, 0, 10);
character.graphics.lineTo(20, 0);
character.x = xpos;
character.y = 200;
addChild(character);
addEventListener(Event.ENTER_FRAME, gameLoop);
}
protected function gameLoop(event:Event):void
{
input();
update();
render();
}
protected function input():void
{
if(inputLayer.movingLeft)
{
direction = 180;
speed = -5;
}
else if(inputLayer.movingRight)
{
direction = 0;
speed = 5;
}
else
{
speed = 0;
}
}
protected function update():void
{
xpos += speed;
}
protected function render():void
{
character.x = xpos;
character.rotation = direction;
}
}
}[/as3]
Furthermore, you could perform other basic logic on the input before setting the input layer properties. For example, shift plus left key might set a runLeft property to true. Not the greatest example, but the point being you don’t need a one to one mapping of keys to Boolean values like you do in a generic manager.
So, am I eating crow? Yes, I guess I am. I still think the event method I originally described would be best, but the complexity you’d need to introduce to make it robust enough to handle all the intricacies would soon outweigh the potential performance benefits. And I’m also saving a bit of face by maintaining my opinion that generic keyboard managers are not a good idea, and offering a bit of a better solution.
Let the discussion begin.