AS3 example Keyboard control

From EduTech Wiki
Jump to: navigation, search

Draft

1 Introduction

This example belongs to the AS3 Tutorials Novice and is part of our Actionscript 3 tutorial series.

You will learn how to move a sprite accross the screen with keyboard arrows.


2 ActionScript Program

package {
  import flash.display.Sprite;
  import flash.display.Stage;
  import flash.events.Event;
  import flash.events.KeyboardEvent;
  import flash.ui.Keyboard;

  public class KeyboardDemo extends Sprite { 
    private var player:Sprite;

    public function KeyboardDemo() {

      player = createAvatar(0xFFFF00) // size, color yellow
      player.x = 200;
      player.y = 100;
      addChild(player);

      stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressedDown);

    }

    private function keyPressedDown(event:KeyboardEvent):void {
      var key:uint = event.keyCode;
      var step:uint = 5
      switch (key) {
          case Keyboard.LEFT :
            player.x -= step;
            break;
          case Keyboard.RIGHT :
            player.x += step;
            break;
          case Keyboard.UP :
            player.y -= step;
            break;
          case Keyboard.DOWN :
            player.y += step;
            break;
      }
    }

    private function createAvatar(bgColor:uint):Sprite {
      var s:Sprite = new Sprite();
      s.graphics.beginFill(bgColor);
      s.graphics.drawCircle(0, 0, 40);
      s.graphics.endFill();
      s.graphics.beginFill(0x000000);
      s.graphics.drawCircle(-15, -10, 5);
      s.graphics.drawCircle(+15, -10, 5);
      s.graphics.endFill();
      s.graphics.lineStyle(2, 0x000000, 100);
      s.graphics.moveTo(-20,15);
        //this will define the start point of the curve
      s.graphics.curveTo(0,35, 20,15); 
        //the first two numbers are your control point for the curve
        //the last two are the end point of the curve
      return s;
    }
  }
}

3 Walkthrough

---

package {
  import flash.display.Sprite;
  import flash.display.Stage;
  import flash.events.Event;
  import flash.events.KeyboardEvent;
  import flash.ui.Keyboard;

  public class KeyboardDemo extends Sprite { 

package declaration, import statements, class definition.

---

    private var player:Sprite;

we define the sprite that will be moved around the screen. It needs to be declared before the constructor because the variable will be used in different functions.

---

    public function KeyboardDemo() {

      player = createAvatar(0xFFFF00) // size, color yellow
      player.x = 200;
      player.y = 100;
      addChild(player);

      stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressedDown);

    }

Constructor function. The novelty here is the use of stage. This time, we will listen to events that take place anywhere on the stage rather than a given sprite.

Because we refer to the stage variable, to know what the variable corresponds, we need to import the Stage class. Make sure import flash.display.Stage; is listed among the import statements.

---

    private function keyPressedDown(event:KeyboardEvent):void {
      var key:uint = event.keyCode;
      var step:uint = 5
      switch (key) {
          case Keyboard.LEFT :
            player.x -= step;
            break;
          case Keyboard.RIGHT :
            player.x += step;
            break;
          case Keyboard.UP :
            player.y -= step;
            break;
          case Keyboard.DOWN :
            player.y += step;
            break;
      }
    }

This function defines what happens when a keydown event takes place on the stage. Any key down event would trigger that function. This could be pressing on a "a", pressing on a "shift key". Any key pressed will cause this function to execute. We need therefore to write some code that will trigger visible actions only when arrow keys (left, up, right, down) are pressed.

If you have used different computers, at home vs at the lab, desktop vs laptop computer, PC vs Mac, you know that the keyboard can be layed out differently in these different environments. A numeric code is used to provide a unique identifier across environments. Because us, humans, are not very good at remembering a large number of numeric codes, flash kindly provides us with a Keyboard class that has for main function to provide us with easy to read Keyboard information (Keyboard.LEFT, Keyboard.UP, Keyboard.RIGHT, Keyboard.DOWN).

The left, up, right, down variable *must* be written in uppercase because Flash is case sensitive (right, Right, and RIGHT are tree different varibles). They are in uppercase because the convention is to write this way program constants, that is values that don't get to be modified at any point of the program.

We define a step variable for ease of maintenance. For a second, let's imagine we had used player.x -= 5;. If we were to change our mind and change the distance to move by to 40 pixels rather than 5, we would have to change the value in 4 different places. The slightest distraction and we may forget to update all four values, meaning that we will need to compile a first time, identify the error, edit the program file to correct it, re-compile, re-test. It is far less troublesome to create a variable to hold the step value and use that variable when it comes to updating the x and y values of the player.

---

    private function createAvatar(bgColor:uint):Sprite {
      var s:Sprite = new Sprite();
      s.graphics.beginFill(bgColor);
      s.graphics.drawCircle(0, 0, 40);
      s.graphics.endFill();
      s.graphics.beginFill(0x000000);
      s.graphics.drawCircle(-15, -10, 5);
      s.graphics.drawCircle(+15, -10, 5);
      s.graphics.endFill();
      s.graphics.lineStyle(2, 0x000000, 100);
      s.graphics.moveTo(-20,15);
        //this will define the start point of the curve
      s.graphics.curveTo(0,35, 20,15); 
        //the first two numbers are your control point for the curve
        //the last two are the end point of the curve
      return s;
    }

We have been playing with graphics a lot, already. This creates a smiley face. A yellow circle in the background, two small black circles for the eyes, a curve for the smile.

---

  }
}

closing the class definition and then the package declaration. ---

4 Variants

(still to provide)

  • Adding a jump option triggered by the space letter key.

4.1 Using letter keys to control movement (using Keyboard class)

There may be several reasons why you would want to allow users to control movement with letters instead of arrow keys. Luckily, using a switch statement such as in this example makes adding alternate controls pretty straight-forward. This first method shows you how using Flash's Keyboard class. I'm using a standard American qwerty keyboard, so I'm choosing w, a, s, and d. You can find a full list of options in the Keyboard class documentation.

Here's the code to add letter key navigation:

private function keyPressedDown(e:KeyboardEvent):void {

        var key:uint = e.keyCode;
        var step:uint = 5;

        switch(key) {
            case Keyboard.LEFT:
            case Keyboard.A:
                player.x -= step;
                break;
            case Keyboard.RIGHT:
            case Keyboard.D:
                player.x += step;
                break;
            case Keyboard.UP:
            case Keyboard.W:
                player.y -= step;
                break;
            case Keyboard.DOWN:
            case Keyboard.S:
                player.y += step;
                break;
        }

}

If you compare the new code to original code you'll see that the only change was adding a second condition for each direction. Again, The letters need to be capitalized as Flash is case-sensitive.

4.2 Using letter keys to control navigation (using keyCodes)

As mentioned in the walkthrough, you can use keycodes for keyboard input. Adobe has a list of all keyboard keys and key code values.

This method isn't quite as easy to read, but I personally prefer it because it takes less code and doesn't require you to import "flash.ui.Keyboard", which seems to result in a swf that's a few bytes smaller than when using the Keyboard class. I'm including this to demonstrate an alternative to the Keyboard class. Chris Stout

Here's the code to implement navigation with the letter keys:

private function keyPressedDown(e:KeyboardEvent):void {

    var key:uint = e.keyCode;
    var step:uint = 5;
    /* keyCodes used in this example:
     * 37 = Left; 39 = Right; 38 = Up; 40 = Down
     * 65 = A; 68 = D; 87 = W; 83 = S
     */
        
    switch(key) {
        case 65:
        case 37:
            player.x -= step;
            break;
        case 39:
        case 68:
            player.x += step;
            break;
        case 38:
        case 87:
            player.y -= step;
            break;
        case 40:
        case 83:
            player.y += step;
            break;
    }
            
}

4.3 Adding jumping triggered by space

Adding the ability to jump required some significant revisions to the code, at least any way I could figure out did. For that reason, there will be a walk through after the code. The functional changes made were: adding jump, triggered by space; allowing diagonal movement; and smoother movement.

4.3.1 ActionScript Program

package {

    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.events.Event;
    import flash.events.KeyboardEvent;

    public class KeyboardJump extends Sprite {

        private var player:Sprite;

//      private var moveUp:Boolean;
//      private var moveDown:Boolean;
        private var moveLeft:Boolean;
        private var moveRight:Boolean;
        private var isJumping:Boolean;

        private var jumpHeight:Number = 10;
        private var originalYPos:Number;
        private var gravity:Number = 0.5;
        private var jumpVelocity:Number;
        private var step:uint = 5;

        public function KeyboardJump():void {

            player = createAvatar(0xFFFF00);
            player.x = 200;
            player.y = 300;
            addChild(player);

            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPress);
            stage.addEventListener(KeyboardEvent.KEY_UP, keyRelease);
            stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);

        }

        private function createAvatar(bgColor:uint):Sprite {

            var s:Sprite = new Sprite();

            s.graphics.beginFill(bgColor);
            s.graphics.drawCircle(0, 0, 40);
            s.graphics.endFill();

            s.graphics.beginFill(0x000000);
            s.graphics.drawCircle(-15, -10, 5);
            s.graphics.drawCircle(+15, -10, 5);
            s.graphics.endFill();

            s.graphics.lineStyle(2);
            s.graphics.moveTo(-20, 15);
            s.graphics.curveTo(0, 35, 20, 15);

            return s;

        }

        private function keyPress(e:KeyboardEvent):void {

            var key:uint = e.keyCode;

            if (key == 37 || key == 65) {moveLeft = true;}
            if (key == 39 || key == 68) {moveRight = true;}
//          if (key == 38 || key == 87) {moveUp = true;}
//          if (key == 40 || key == 83) {moveDown = true;}
            if (key == 32 && !isJumping) {
                isJumping = true;
                originalYPos = player.y;
                jumpVelocity = jumpHeight;
            }

        }

        private function keyRelease(e:KeyboardEvent):void {

            var key:uint = e.keyCode;

            if (key == 37 || key == 65) {moveLeft = false;}
            if (key == 39 || key == 68) {moveRight = false;}
//          if (key == 38 || key == 87) {moveUp = false;}
//          if (key == 40 || key == 83) {moveDown = false;}

        }

        private function onEnterFrame(e:Event):void {

            if (moveLeft)  {player.x -= step;}
            if (moveRight) {player.x += step;}
//          if (moveUp)    {player.y -= step;}
//          if (moveDown)  {player.y += step;}

            if (isJumping) {
                player.y -= jumpVelocity;
                jumpVelocity -= gravity;
            }

            if(isJumping && player.y >= originalYPos) {
                isJumping = false;
            }

        }

    }

}

4.3.2 Walkthrough

As mentioned, this code is quite different from the original code. This walk through will only cover changes made to the original code. Some sections of code only have one line changed. I will denote this by placing [...] where unchanged code would be.

---

    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.events.Event;
    import flash.events.KeyboardEvent;

I chose to use the keyCodes rather than the Keyboard class, so there is no need to import it.

---

//      private var moveUp:Boolean;
//      private var moveDown:Boolean;
        private var moveLeft:Boolean;
        private var moveRight:Boolean;

moveUp, moveDown, moveLeft, and moveRight are Booleans which will be used to move the smiley face around. More about booleans.

These changes will allow for diagonal movement and smoother movement.

All references to moveUp/moveDown have been commented out because moving up/down while you are jumping can cause weirdness with jump height and landing. I have left them in the code to demonstrate how moving up/down would work in this example. Feel free to uncomment them, everything will still work, the jumping will just act a little odd.

---

        private var isJumping:Boolean;
        private var jumpHeight:Number = 10;
        private var originalYPos:Number;
        private var gravity:Number = 0.5;
        private var jumpVelocity:Number;
        private var step:uint = 5;

isJumping will be used to tell the appropriate functions that the face is in the middle of a jump. This will also be used to prevent double jumps.

jumpHeight will be used to determine how high the smiley face will jump. It is a Number rather than int or uint because we will need the flexibility of having it be both a decimal and negative. "int" types cannot be negative, "uint" can go negative, and neither can be a decimal.

originalYPos tracks the y position of the face when the jump was started. This will be used to make sure the face lands at the same y position it was at when the jump started.

gravity will be used to prevent the face from jumping infinitely high and will also bring it back down.

jumpVelocity is used to determine the height of the jump.

step has been moved here because it will be used in the onEnterFrame function rather than when a key is pressed. Since onEnterFrame runs every time there is a new frame it wouldn't make sense to keep re-declaring it 30 times a second. (At least I think the default frameRate is 30 fps.)

---

        public function KeyboardJump():void {
            
            [...]
            player.y = 300;
            [...]

            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPress);
            stage.addEventListener(KeyboardEvent.KEY_UP, keyRelease);
            stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);

        }

The name of the function, and class, has been changed. This is just to allow you to have both the original and the variant without any conflict.

player.y is 300 instead of 100 so that you don't jump off the screen.

The event listeners just execute a function every time the event happens. KEY_DOWN executes keyPress when you press a key down, KEY_UP executes keyRelease when you release the key, ENTER_FRAME executes onEnterFrame with each new frame.

KEY_UP will be used to both to allow diagonal movement and make the movement a little bit smoother. ENTER_FRAME will be used to move the sprite left/right and control the jump.

I changed the name of the function for the KEY_DOWN eventListener. I did this to demonstrate that the function name is arbitrary as long as it isn't a keyword or reserved word. If addEventListener is confusing to you, it's well worth reading it's Livedocs page.

---

        private function createAvatar(bgColor:uint):Sprite {

            [...]
            s.graphics.lineStyle(2);
            [...]

Originally, this line had the arguments (2, 0x000000, 100). I removed the last two arguments because lineStyle makes your line black with 100 alpha by default if you leave the arguments off.

---

        private function keyPress(e:KeyboardEvent):void {

            var key:uint = e.keyCode;

            if (key == 37 || key == 65) {moveLeft = true;}
            if (key == 39 || key == 68) {moveRight = true;}
//          if (key == 38 || key == 87) {moveUp = true;}
//          if (key == 40 || key == 83) {moveDown = true;}
            if (key == 32 && !isJumping) {
                isJumping = true;
                originalYPos = player.y;
                jumpVelocity = jumpHeight;
            }

        }

Using booleans here rather than the previously used switch statement provides three benefits: there's less code, the movement will be smoother when holding down the direction key, and it will also allow diagonal movement.

This function is run whenever a key is pressed. The following keyCodes are used: Left - 37, A - 65; Right - 39, D - 68; Up - 38, W - 87; Down - 40, S - 83; Space - 32.

If one of the keys for left/right/up/down movement is pressed, the moveLeft boolean is set to true. This boolean will be used to move the face in the onEnterFrame function.

If space is pressed and the face is not already in the middle of a jump, the isJumping boolean will be set to true, originalYPos will be set to equal the current player.y position, and jumpVelocity is set to equal the jumpHeight variable. These variables will be used by the onEnterFrame function to move the face.

"!isJumping" is another way of saying "isJumping == false".

Adding "!isJumping" to the condition prevents double jumping (jumping while still in air).

---

        private function keyRelease(e:KeyboardEvent):void {

            var key:uint = e.keyCode;

            if (key == 37 || key == 65) {moveLeft = false;}
            if (key == 39 || key == 68) {moveRight = false;}
//          if (key == 38 || key == 87) {moveUp = false;}
//          if (key == 40 || key == 83) {moveDown = false;}

        }

This function executes whenever you release a key that was pressed down. The keyCodes are the same as used in the keyPress function. When the appropriate key is released, it's corresponding boolean is set to false, this will stop the movement on the next frame.

Space isn't covered in this function. That's because the onEnterFrame function will handle the isJumping boolean.

---

        private function onEnterFrame(e:Event):void {

            if (moveLeft)  {player.x -= step;}
            if (moveRight) {player.x += step;}
//          if (moveUp)    {player.y -= step;}
//          if (moveDown)  {player.y += step;}

This function executes every time there is a new frame. This first part looks at each of the movement booleans we created earlier. If the boolean is set to true, it moves it in the appropriate direction by a number of pixels equal to the step variable.

---

            if (isJumping) {
                player.y -= jumpVelocity;
                jumpVelocity -= gravity;
            }

This part of the function checks if the isJumping boolean is set to true. This is the variable that's set by pressing space.

If you are jumping, the value of jump velocity is subtracted from player.y, moving the sprite upward. Then the value of gravity is subtracted from jumpVelocity. This both prevents the face from going infinitely high and also brings it back down to "the ground" it was originally on.

      • TODO: Provide a bit better explanation of how the gravity/jumpVelocities work. ***

The jumpVelocity/gravity relationship may seem a bit hard to grasp. For that reason I'm including a table to hopefully explain more clearly how the player.y changes frame-by-frame. Demonstrating this with a jumpHeight of 10 would take 41 frames, so I'm demonstrating it as if the jumpVelocity were 1.

Frame No. current player.y current jumpVelocity new player.y new jumpVelocity
1 300 1.0 299 0.5
2 299 0.5 298.5 0.0
3 298.5 0.0 298.5 \-0.5
4 298.5 \-0.5 299 \-1.0
5 299 \-1.0 300

At this point, the next part of the function (right below) would see that the sprite has returned to its original height and set the isJumping boolean to false.

---

            if(isJumping && player.y >= originalYPos) {
                isJumping = false;
            }

This statement checks two variables. First, it checks if the player is in the middle of a jump. Then, it checks if the face is either at it's original height, or lower (originalYPos was set in the keyPress function when space is pressed.)

If both conditions are met, isJumping is set to false. This will prevent the face from moving up/down on subsequent frames until space is pressed again.

5 Challenge

(incomplete, instructions not clear enough).

Design some dancing mat game type of game. Instructions on the sequence to repeat are given as a sequence of characters "U L R D U U D". There is a starting point and an end point. If the player followed the sequence exactly, he should arrive at the end point.