In this chapter we are going to move our hero using arrow keys in 4 direction. In the most basic form, movement is simply changing the tile where hero stands. Try it here (you may need to click on the movie first):
Start by setting up the arrow keys detection. Sadly the useful key.isDown function has been removed from AS3 and we need to replace its behaviour with our own way. In prepareGame function set up keys object and fill it with 4 different objects, one for each arrow key:
keys = new Object(); keys[Keyboard.UP] = {down:false, dirx:0, diry:-1}; keys[Keyboard.DOWN] = {down:false, dirx:0, diry:1}; keys[Keyboard.LEFT] = {down:false, dirx:-1, diry:0}; keys[Keyboard.RIGHT] = {down:false, dirx:1, diry:0};
When keys are pressed or released the Flash detects them by their keycodes. Codes for arrow keys are: Left Arrow=37, Up Arrow=38, Right Arrow=39, Down Arrow=40. Keyboard class provides shortcuts for key codes so we dont have to remember the keycodes. We could write keys[37]={} but using keys[Keyboard.RIGHT] instead makes the code looks more readable.
Each key object has several properties. The down variable will have either value false (when key is currently not pressed) or true (when key is pressed). Variables dirx and diry are directions to move, for example with UP key we set dirx=0 and diry=-1 which means there is no horisontal movement and we move up by 1 tile.
Move on to buildMap function. After we have created the tiles in double loop we set up the hero and key listeners:
hero.bmp = getImageFromSheet (hero, hero.sprNum); moveObject (hero, {dirx:0, diry:0}); myParent.addChild (hero.bmp); myParent.addEventListener (KeyboardEvent.KEY_DOWN, downKeys); myParent.addEventListener (KeyboardEvent.KEY_UP, upKeys); myParent.addEventListener (Event.ENTER_FRAME, runGame);
Here we call the moveObject function where the hero will be moved into correct coordinates. We also add hero into screen.
Next we set up 3 listeners to capture key events and to control the game. Function downKeys is called every time some key is being pressed down (it happens for every key in the keyboard not just arrow keys), upKeys does similar thing for releasing keys. Function runGame runs constantly like endless loop until it is removed or movie is closed.
The key handling functions are very similar:
function downKeys (ev:KeyboardEvent) { if (keys[ev.keyCode] != undefined) { keys[ev.keyCode].down = true; } } function upKeys (ev:KeyboardEvent) { if (keys[ev.keyCode] != undefined) { keys[ev.keyCode].down = false; } }
If some key is being pressed, we can read the code of this key from ev.keyCode. Now we check if object with such code has been declared in the keys object, since we did set up only arrow keys, all other keys are ignored. In case object with keycode value does exists, we set its down property to true (in downKeys function) or false (in upKeys function).
Main loop running in the game is runGame function:
function runGame (ev:Event) { for each (var keyOb in keys) { if (keyOb.down == true) { tileName = "t_" + (hero.ytile + keyOb.diry) + "_" + (hero.xtile + keyOb.dirx); if (tiles[tileName].walkable == true) { moveObject (hero, keyOb); break; } } } }
Here we have for..each..in loop that goes through the keys object. At every step keyOb points to one of the 4 key objects. We can now check for its down property, which is true if the key is being currently pressed and false if the key is not pressed.
If the key is down, we check the tile hero is about to step on. Lets suppose hero is currently on the tile 2,1 so the property xtile=2 and ytile=1 and UP arrow is being pressed. Directions from UP arrow object were dirx=0 and diry=-1, adding these into tile properties of hero we can see that hero is about to step on the tile xtile=2+0=2 ytile=1-1=0. So the tile to check is t_0_2. We dont want the hero to walk into wall tiles so we check if the tile is floor (walkable==true). In case the tile is floor we call the moveObject function and break the loop. Breaking a loop after movement is good idea or else multiple keys being pressed at same time would mess up our movement.
To update coordinates of hero (or basically any moving thing) we use moveObject function:
function moveObject (ob:Object, moveOb:Object) { ob.xtile += moveOb.dirx; ob.ytile += moveOb.diry; ob.x = ob.xtile * ts; ob.y = ob.ytile * ts; ob.bmp.x = ob.x; ob.bmp.y = ob.y; }
The function will have 2 arguments: ob is the object trying to move (currently only the hero has weird idea of moving) and moveOb is object which knows where and how to move. We update the xtile and ytile properties in hero with the directions from moveOb. Then we calculate new coordinates on screen and place the image of hero in correct coordinates.
Note that in this example image of hero is not aligned to the center of tile, both hero and tile images are set from top left corner but since hero is smaller then tile, it looks slightly off. To fix this, find half the difference between size of hero and size of tiles and add it to the position of hero:
ob.bmp.x = ob.x + (ts - ob.ts) / 2; ob.bmp.y = ob.y + (ts - ob.ts) / 2;
You can download the source fla with all the code and movie set up here.
Surely jumping directly from one tile to next is sometimes good enough for hero, but there are times when hero needs to move smoothly:
To make the hero move smoothly from tile to tile this we will use 2 variables (set them up in prepareGame function):
hero.hasMoved = 0; hero.speed = 2;
The speed is obviously number of pixels hero will move at each step. It is good idea to use full numbers for speed and also use numbers which our tilesize can be divided with. Having speed of 1.23456 may look interesting, but it will create visible jumps when next tile is reached. Variable hasMoved is equal to 0 when hero is at the center of tile and it will have value >0 whenever hero has not yet reached the center.
Modify runGame function like this:
if (hero.hasMoved == 0) { for each (var keyOb in keys) { if (keyOb.down == true) { tileName="t_" + (hero.ytile + keyOb.diry) + "_" + (hero.xtile + keyOb.dirx); if (tiles[tileName].walkable == true) { hero.moveOb = keyOb; hero.hasMoved = ts; break; } } } } if (hero.hasMoved > 0) { moveObject (hero, hero.moveOb); }
The idea here is that keys are only detected if the hasMoved property is 0. And it is 0 only when hero is standing in the center of tile. Only then will we check for keys and if we have key pressed down, we will set the value of hasMoved to size of tiles so hero knows he has to move by full tilesize before he can stop again. We also save the current key as moveOb inside hero so it can be used to continue movement smoothly.
The moveObject function is being run only if the hasMoved is greater then 0:
function moveObject (ob:Object, moveOb:Object) { ob.x += moveOb.dirx * ob.speed; ob.y += moveOb.diry * ob.speed; ob.hasMoved -= ob.speed; if (ob.hasMoved <= 0) { ob.xtile += moveOb.dirx; ob.ytile += moveOb.diry; ob.x = ob.xtile * ts; ob.y = ob.ytile * ts; ob.hasMoved = 0; } ob.bmp.x = ob.x + (ts - ob.ts) / 2; ob.bmp.y = ob.y + (ts - ob.ts) / 2; }
Every time hero moves the hasMoved variable is reduced by the speed of hero. Once it reaches zero, hero has also reached center of next tile. Then we update the xtile and ytile properties, place the image exactly at the center and reset hasMoved variable.
You can download the source fla with all the code and movie set up here.
Next: More moving.