There are a lot of games out there with jerky, unrealistic movements and that can do only one thing to your product: make it unappealing to the audience. But smooth movement is not hard to achieve - let's get to work!
Final Result Preview
Let's take a look at the final result we will be working towards:
Step 1: Set Up the Environment
This is a straight forward tutorial, so the setting up will also be straight forward.
Create a New ActionScript 3.0 Flash Project. The stage size and color don't matter, just use what you are confortable with.
I use FlashDevelop for coding, but also this could be done in any AS editor, like Flash Pro (or any text editor, maybe Notepad ;) ). So, create a Class file, make sure your code looks pretty much like mine; see below. I called mine "Movement". (If you're using Flash Pro, check out this guide to creating a class.)
package { import flash.display.Sprite; public class Movement extends Sprite { public function Movement():void { } } }
After you're done, make sure your Class is linked to the Flash project as the Main Class.
Step 2: Create the Square and Variables
So after you've linked the Movement Class to your document, define the variables as I did below
package { import flash.display.Sprite; import flash.events.Event; public class Movement extends Sprite { //The object that will move private var square:Sprite; //The maximum speed private var _max:Number = 10; //The variables that are going to be applied to move the square private var dx:Number = 0; private var dy:Number = 0; public function Movement():void { //Listen for added to stage event addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, init); //Creating a new Sprite and draw inside a square square = new Sprite(); square.graphics.beginFill(0x333333); square.graphics.drawRect(0, 0, 30, 30); square.x = stage.stageWidth / 2 - square.width / 2; square.y = stage.stageHeight / 2 - square.height / 2; addChild(square); } } }
This is pretty much all that we'll do for creating the object. You can use your own object but for this simple movement tutorial I used this simple square.
Step 3: Introducing the Input.as Class
Hi guys, this is the Input.as Class; Input.as Class these are the guys I told you about - be nice to them! :)
So what is this class about, you may wonder. Basically it does your key handling job for you. It adds a listener to ENTER_FRAME events - with low priority - and a key listener which fills some private Dictionaries. Also it uses another Class for key codes. You can take a look inside and see for yourself how is working.
Note: The Input.as Class does not belong to me. It was created by Matthew Bush, who ported Box2D to Flash.
//Example of Input.as Class usage //You have to always initialize it as this with the stage parameter Input.initialize(stage); //After initializing, you can use kd(), kp() or ku() methods, which //return a Boolean value if the conditions are met. //These methods accept multiple arguments, //so for one event you can use multiple keys. //This makes it a lot easier to give a boost of accessibility to your app. //e.g See below as I use one call for detecting UP arrow or W for going up. Input.kd("UP", "W");
Step 4: Importing the Classes
So now that you are familiar with the Input.as Class, we are going to import it in our Movement Class and initialize it.
package { import flash.display.Sprite; import flash.events.Event; import Input; public class Movement extends Sprite { //The object that will move private var square:Sprite; //The maximum speed private var _max:Number = 10; //The variables that are going to be applied to move the square private var dx:Number = 0; private var dy:Number = 0; public function Movement():void { //Listen for added to stage event addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, init); //Creating a new Sprite and draw inside a square square = new Sprite(); square.graphics.beginFill(0x333333); square.graphics.drawRect(0, 0, 30, 30); square.x = stage.stageWidth / 2 - square.width / 2; square.y = stage.stageHeight / 2 - square.height / 2; addChild(square); //Initialize the Input.as Class with handler on stage Input.initialize(stage); //Add the refresh loop addEventListener(Event.ENTER_FRAME, refresh); } private function refresh(e:Event):void { } } }
Step 5: Handling the Key Inputs
I use an ENTER_FRAME-based loop for detecting the key inputs; below is the refresh()
method which is the handler function for this event.
private function refresh(e:Event):void { //Key Handler if (Input.kd("A", "LEFT")) { //Move to the left } if (Input.kd("D", "RIGHT")) { //Move to the right } if (!Input.kd("A", "LEFT", "D", "RIGHT")) { //If there is no left/right pressed } if (Input.kd("W", "UP")) { //Move up } if (Input.kd("S", "DOWN")) { //Move down } if (!Input.kd("W", "UP", "S", "DOWN")) { //If there is no up/down action } }
Step 6: Explaining the Calculations - Handling the Velocity
This is pretty straight forward. Detect whether any of the keys are pressed, then act accordingly.
I use the ternary operator a lot: value = condition ? true : false;
This is basically an if-statement that's been condensed to a single line.
For every key detection, I use this method: if the value is bigger than _max
then set it equal to _max
; otherwise, increment or decrement that particular value as appropriate. This way, it's kept within certain bounds. Simple, right?
Below you can study the conditions:
private function refresh(e:Event):void { //Key Handler if (Input.kd("A", "LEFT")) { //Move to the left dx = dx < 0.5 - _max ? _max * -1 : dx - 0.5; } if (Input.kd("D", "RIGHT")) { //Move to the right dx = dx > _max - 0.5 ? _max : dx + 0.5; } if (!Input.kd("A", "LEFT", "D", "RIGHT")) { //If there is no left/right pressed if (dx > 0.5) { dx = dx < 0.5 ? 0 : dx - 0.5; } else { dx = dx > -0.5 ? 0 : dx + 0.5; } } if (Input.kd("W", "UP")) { //Move up dy = dy < 0.5 - _max ? _max * -1 : dy - 0.5; } if (Input.kd("S", "DOWN")) { //Move down dy = dy > _max - 0.5 ? _max : dy + 0.5; } if (!Input.kd("W", "UP", "S", "DOWN")) { //If there is no up/down action if (dy > 0.5) { dy = dy < 0.5 ? 0 : dy - 0.5; } else { dy = dy > -0.5 ? 0 : dy + 0.5; } } //After all that, apply these to the object square.x += dx; square.y += dy; }
If you're unfamiliar with the ternary operator, grab a piece of paper and a pen and write out a few of them in if...else format; it's a great exercise to get to grips with that's going on.
Keep in mind I manipulate the dx
and dy
variables, and only set the actual x and y values at the end. This helps us make the motion fluid; it's not jerking around as we alter their values directly throughout the function..
Go on, test it! See how nicely it's moving?
Step 7: Handling Boundary Collisions
Okay. Everything is right, moving fluidly - but OUT of the stage! Below I added the collision detection conditions.
private function refresh(e:Event):void { //Key Handler if (Input.kd("A", "LEFT")) { //Move to the left dx = dx < 0.5 - _max ? _max * -1 : dx - 0.5; } if (Input.kd("D", "RIGHT")) { //Move to the right dx = dx > _max - 0.5 ? _max : dx + 0.5; } if (!Input.kd("A", "LEFT", "D", "RIGHT")) { //If there is no left/right pressed if (dx > 0.5) { dx = dx < 0.5 ? 0 : dx - 0.5; } else { dx = dx > -0.5 ? 0 : dx + 0.5; } } if (Input.kd("W", "UP")) { //Move up dy = dy < 0.5 - _max ? _max * -1 : dy - 0.5; } if (Input.kd("S", "DOWN")) { //Move down dy = dy > _max - 0.5 ? _max : dy + 0.5; } if (!Input.kd("W", "UP", "S", "DOWN")) { //If there is no up/down action if (dy > 0.5) { dy = dy < 0.5 ? 0 : dy - 0.5; } else { dy = dy > -0.5 ? 0 : dy + 0.5; } } //Boundary detection if (square.x - dx < 0 || square.x + dx + square.width > stage.stageWidth) { //x axis detection } if (square.y - dy < 0 || square.y + dy + square.height > stage.stageHeight) { //y axis detection } //After all that, apply these to the object square.x += dx; square.y += dy; }
It's looking for boundaries in a more precise fashion, by checking whether the edges of the square hit the boundaries (before this, it was just checking the center of the square against the boundaries).
Great. Now we need to add the code to make the square bounce off the boundaries. What I do for that is multiply by -1 the axis value dx
or dy
. But that is not enough! If the speed is quite fast, then the square will get through the margins or just go nuts. So before we multiply we need to set the x or y of the object to be the same as the boundary that it meets.
So if x object.x = 0; and then multiply the dx
by -1.
//Margin detection if (square.x - dx < -dx || square.x + dx + square.width > stage.stageWidth) { //x axis detection square.x = square.x - dx < -dx ? 0 : stage.stageWidth - square.width; dx *= -1; } if (square.y - dy < -dy || square.y + dy + square.height > stage.stageHeight) { //y axis detection square.y = square.y - dy < -dy ? 0 : stage.stageHeight - square.height; dy *= -1; }
Test it now! Bouncy right? :)
To make it even better, go on experimenting with different values - like instead of multiplying by -1, try -0.7 and see the results.
Conclusion
So you met the Input.as Class, got to know how to work with it, and made a nice fluid movement in just a few minutes. I think this counts as a great time!
Please leave your comments below and any other questions, I will gladly answer.
But if you encounter any problem please check twice your code, compare it with the source file and then if you can't make it work, feel free to post a question.
Comments