Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Creating a Virtual Joystick for Touch Devices

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

This post is part of a series called GreenSock Tweening Platform.
Use the Flash Project Panel‏ to Build a Dynamic AS3 Menu
Using Native Multitouch Gestures in ActionScript 3.0

A lot of Tablet Computers and Touch Screen devices have emerged, and it's time to create games that have virtual joystick support on the screen for easier gameplay. Read on and discover how you can create a virtual joystick for your games on touch enabled devices..


Prerequisite

Before you can compile the source files provided with this tutorial, download GreenSock's TweenLite library for AS3 into the "greensock" subfolder in your project folder. (Discussed in Step 18).


Final Result Preview

Let's take a look at the final result we will be working towards:

If you have a touch enabled device that supports Flash, open this page in it and try it out! Otherwise, use the mouse to drag the joystick or click the button.


Step 1: Create a New AS3 Document

Let us begin by creating a new AS3 Document in Flash Professional CS5.

New AS3 Document

Go to the Document Settings in the Properties panel and make sure the document size (550x400) and background color (#000000) are as shown in the below image:

Document Properties

On the Properties panel, specify a document class name of "com.MyApp". This is the class that we will create that will represent the main application (more info here). We are going to create other classes specific to the joystick later so we can include the joystick in any other application with a couple of lines of code.

Save the file as "JoystickApp.fla".


Step 2: Create the Document Class

Document Class

Now, click on the pencil icon beside the Class name that will trigger the editing of that class. Since there is no class, we will have to create it.

Edit Document Class

When asked to choose the software that you want to use to edit the class definition, choose Flash Professional. You should see the new class definition pre-populated in a new ActionScript file:

package com {
	
	import flash.display.MovieClip;
	
	
	public class MyApp extends MovieClip {
		
		
		public function MyApp() {
			// constructor code
		}
	}
	
}

Save this .as file as "MyApp.as" in the same folder as the .fla file under the subfolders "\com\". Note that you may give your own names and folder structures. The structure of the folder refers to the structure of your AS package.


Step 3: Drawing the Basic Joystick and Knob

In this step, we will draw a basic set of circles to represent our Joystick. In later steps, we shall enhance the UI to fit our jazzy needs. Go ahead a draw a circle on the stage of your .fla file. Give it the following properties. Width: 160, Height: 160, Color: #CCCCCC. Place it at x: 100, y: 300.

Convert this circle to a MovieClip and name the MovieClip "Joystick". Also don't forget to choose the registration point as center while converting (Registration Point).

Place the MovieClip "Joystick" on stage in the bottom left corner as shown in the image below. This is just for a reference. We will add the MovieClips dynamically on the stage from "MyApp" class later.

Joystick MovieClip on Stage

Draw another circle (smaller) on the stage with the values Width: 50px, Height: 50px. Convert the same to a new MovieClip and name it "JoystickKnob". Place it on top of the "Joystick" MovieClip as shown in the below image.

JoystickKnob MovieClip

This is how the basic version of Joystick will look like once we complete all the steps. We will dynamically place it in the position shown above. Now that we have an idea of how it would look, let's go ahead and write the script for the same. You may now delete the two MovieClips from the stage.


Step 4: Tie the UI to the Custom Classes

Let us now tie the UI we drew to their custom classes. Go to Library and right-click on the "Joystick" MovieClip. Choose "Properties".

Check the check box that says "Export for ActionScript". Change your Class name to "com.controls.Joystick". We will create a subfolder in the "com" folder and have code related to controls in the "com.controls" package.

Joystick MovieClip Class

Click on the pencil icon beside your Class name. Choose "Flash Professional" if asked for the editor. A new .as file is created with the class name "Joystick", extended from the "MovieClip" class.

Repeat the same process for the other MovieClip "JoystickKnob". Provide the class name "com.controls.JoystickKnob".

Save the two class files as "Joystick.as" and "JoystickKnob.as" respectively.

This is how your library should look, with the two MovieClips attached to their custom classes:

Library

Step 5: Joystick Knob Class

It is recommended to set the Knob's origin with respect to the Joystick. This will help us to bring back the Knob to its initial location once the knob is dragged elsewhere and released.

For this, we will use two simple properties in the "JoystickKnob" class.

package com.controls {
	
	import flash.display.MovieClip;
	
	
	public class JoystickKnob extends MovieClip {		
		private var _origin_x:Number;
		private var _origin_y:Number;		
		
		public function JoystickKnob() {
			// constructor code
		}
	}
	
}

Let us now write the getter and setter methods to read and write the _origin_x and _origin_y properties. Add the following methods in the "JoystickKnob" class.

public function get origin_x():Number {
    return _origin_x;
}

public function set origin_x(o_x:Number):void {
    _origin_x = o_x;
}

public function get origin_y():Number {
    return _origin_x;
}

public function set origin_y(o_y:Number):void {
    _origin_y = o_y;
}

Observe that the function names are not having "_" (underscore) in the beginning of their names. This is because we just wanted the names to external classes to be origin_x and origin_y.


Step 6: Basic Joystick Class

Let us start by adding parameters to the constructor of the "Joystick" class. We will accept 2 parameters - left_margin:Number and bottom_margin:Number. This will help us place the Joystick wherever we want when we instantiate the MovieClip.

Later, we shall assign the two parameters to the private variables of "Joystick" class. Your code should look similar to this:

package com.controls {
	
	import flash.display.MovieClip;
	
	public class Joystick extends MovieClip {
		private var my_x:Number;
		private var my_y:Number;
		
		public function Joystick(margin_left:Number, margin_bottom:Number) {
			my_x = margin_left;
			my_y = margin_bottom;			
		}
	}
	
}

We now need to write the "initialize()" method to set the position of the Joystick on screen. We will also add the "JoystickKnob" MovieClip dynamically in this method. This method contains an "Event" type parameter. Don't worry about it just yet. We will come to it in a minute.

Right before you add the "JoystickKnob" MovieClip dynamically, we need to import the "JoystickKnob" class into this class. Add the following import statement:

import com.controls.JoystickKnob;

Now that we have imported the "JoystickKnob" class, declare a variable in your class to represent the knob.

private var knob:JoystickKnob;

Now, add the following function to your class.

private function init(e:Event=null):void {
	this.x = my_x + this.width / 2;
	this.y = stage.stageHeight - my_y - this.height / 2;

	knob = new JoystickKnob();
	knob.x = 0;
	knob.y = 0;

	knob.origin_x = 0;
	knob.origin_y = 0;

	addChild(knob);

	// knob.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
	// stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);
	
	knob.buttonMode = true;
}

As you can see, we have positioned the "Joystick" MovieClip on stage according to the margins the constructor accepted and we have added the "knob" on the stage. We have also added some event listeners to the "knob" and "stage" for MOUSE_DOWN and MOUSE_UP respectively. We shall write the methods mouseDown() and mouseReleased() later in the class to enable the dragging and dropping of the knob. The event listeners are currently commented out; DO NOT FORGET to uncomment the two lines after we write the methods.

Note that the MouseEvent.<EVENT_NAMES> translate to touch events on a touch tablet/device. So it is completely OK to use mouse events in place of touch.

We need to now call this method from the constructor. Instead of calling the method blindly, it is a good practice to check for the existence of "stage" and only then call this. If the "stage" property for "Joystick" MovieClip is not yet initialized by this time (which is a corner case), the init() method needs to get called after it can determine the "stage" property. Hence we call the init() method in the below shown manner (add this set of lines inside your constructor):

if (stage) {
    init();
} else {
    addEventListener(Event.ADDED_TO_STAGE,init);
}

We shouldn't forget to remove the added EventListener. Hence let's add a couple of lines inside the (beginning of) init() method:

if (hasEventListener(Event.ADDED_TO_STAGE)) {
    removeEventListener(Event.ADDED_TO_STAGE,init);
}

Step 7: Add Joystick to the Stage

Before we execute this code and check the output, we need to instantiate the "Joystick" class from inside the "MyApp" class. Add the following code to your constructor of the "MyApp" class. Also import the "Joystick" class and declare a property to represent the "Joystick" MovieClip. So your "MyApp" class should look like this at this time:

package com {
	
	import flash.display.MovieClip;
	import com.controls.Joystick;
	
	public class MyApp extends MovieClip {
		private var joystick:Joystick;
		
		public function MyApp() {
			joystick = new Joystick(30, 30);
			addChild(joystick);
		}
	}	
}

You may now execute and test the app. This would just dynamically add the "Joystick" MovieClip on the stage leaving 30px margin on the left and bottom as passed to the constructor. Here is the screenshot of the SWF:

Basic Swf

Step 8: Adding Interaction to Knob

Now is the time to add interaction to the "JoystickKnob" MovieClip, make it draggable around the Joystick's perimeter. In the "Joystick" class, uncomment the two event listeners that we commented before. We shall now write the mouseDown() and mouseReleased() methods for the "knob".

We will just add a startDrag() method inside the mouseDown() method. Give the properties to specify the boundary of the "Joystick" MovieClip. Don't forget to import the classes flash.geom.Rectangle and flash.events.MouseEvent (if you haven't).

private function mouseDown(event:MouseEvent):void {
    knob.startDrag(false,new Rectangle( -  this.width / 2, -  this.height / 2,this.width,this.height));
}

mouseReleased() is a simple stopDrag() for now. We shall add more code later to "animate" back the knob to its "origin_x" and "origin_y". Right now, it just snaps back when you release the mouse.

private function mouseReleased(event:MouseEvent):void {
    knob.stopDrag();
	
    knob.x = knob.origin_x;
    knob.y = knob.origin_y;
}

You may now execute the basic interaction of the Joystick. Compile and run! Here is how it should behave:


Step 9: Detecting Knob Movement

Let us try and detect the "knob" movement in the Joystick and determine how we want to use it for a simple top-down view sprite.

When the "knob" is being dragged around, we need to read the position of it with respect to the "Joystick" MovieClip and modify the sprite. We shall create a sprite later. For now, let us just read the value of the knob's position.

To read the position of the knob while dragging, we need to include an ENTER_FRAME method. Add the below method to the "Joystick" class:

private function knobMoved(event:Event):void {
    trace(knob.x + ", " + knob.y);
}

Add the ENTER_FRAME event listener just before the startDrag() happens inside the mouseDown() method:

private function mouseDown(event:MouseEvent):void {
    this.addEventListener(Event.ENTER_FRAME, knobMoved);
    knob.startDrag(false,new Rectangle( -  this.width / 2, -  this.height / 2,this.width,this.height));
}

We don't want the ENTER_FRAME method to be running all the time. This could prove costly. Hence, we will add the listener only when dragging starts and remove the listener as soon as we stopDrag(). Add the removeEventListener right after stopDrag().

if (this.hasEventListener(Event.ENTER_FRAME)) {
    this.removeEventListener(Event.ENTER_FRAME, knobMoved);
}

Compile and execute the SWF now to test this out. Read the values in the output window.


Step 10: Snapping the Knob

Since it is the Touch Screens that we are trying to target, there is no physical feel of the controls. Hence, there is a clear possibility that the user might just touch outside the knob and try to drag the joystick around. It is unfair and inconvenient to expect the user to precisely touch and drag the knob. To overcome this confusion, it is a good idea to snap the knob to the place where the touch happens on the Joystick.

In this step, we will snap the "knob" MovieClip to the point on the "Joystick" where the actual touch might happen.

In the "Joystick" class, add the following private method:

private function snapKnob(event:MouseEvent):void {
    knob.x = this.mouseX;
    knob.y = this.mouseY;
    mouseDown(null);
}

In the above code, we are setting the "knob" MovieClip's x and y coordinates to "Joystick" MovieClip's mouseX and mouseY coordinates. We are also calling the mouseDown(null) method to start the dragging process.

This method will be called by adding an event listener. Let's do that in the init() method. Right around the event listener that calls the mouseDown() method. Add the following line in the init() method:

this.addEventListener(MouseEvent.MOUSE_DOWN, snapKnob);
knob.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);

According to the code above, as soon as the "Joystick" MovieClip ("this") gets a MOUSE_DOWN event, it calls the snapKnob() method. Inturn, this method snaps "knob" MovieClip to the touched position. Later, calling the mouseDown(null) method, we force the startDrag() process so the user feels he has touched the knob.


Step 11: Creating a Hero MovieClip

Now that we have the basic Joystick working, we need to create a "Hero" MovieClip to start moving it according to the "knob" position values.

Move to the .fla file for now and draw a circle, as shown below. Convert it to MovieClip and use the center registration point (Registration Point) again. Give the MovieClip the name "Hero". You may choose to give the properties - Width: 30px, Height: 30px, Color: #0099CC.

Also, export to ActionScript while converting to MovieClip and give it the class name "com.Hero".

Export Hero MovieClip

This is how the MovieClip will look once it is converted.

Hero MovieClip

We should now remove the MovieClip from stage as we will add the "Hero" MovieClip dynamically on stage from the "MyApp" class.

After you have exported the MovieClip with the custom class name "com.Hero", don't forget to create the new class file and save it in the "com" folder.


Step 12: Adding Hero to the Stage

Let us open "MyApp.as" and add the "Hero" MovieClip dynamically. Since we have exported "Hero" MovieClip from the Library of "JoystickApp.fla", open "MyApp.as", import "com.Hero" class and declare a new property "hero" and add the following code before the "joystick" MovieClip is instantiated in the constructor. This is how your "MyApp" class should look like:

package com {
	
	import flash.display.MovieClip;
	import com.controls.Joystick;
	import com.Hero;
	
	public class MyApp extends MovieClip {
		private var joystick:Joystick;
		private var hero:Hero;
		
		public function MyApp() {
			hero = new Hero();
			hero.x = stage.stageWidth/2;
			hero.y = stage.stageHeight/2;
			addChild(hero);
			
			joystick = new Joystick(30, 30);
			addChild(joystick);
		}
	}
}

Note that we have also aligned the MovieClip to center of the stage. If you compile and run the SWF, you should see the "hero" MovieClip in the center of the stage.


Step 13: Passing Hero MovieClip to Joystick Class

Now, for the "joystick" MovieClip to access "hero" MovieClip that's added to the stage, we shall use an easy method of passing "hero" MovieClip to the constructor of "joystick" MovieClip along with the "margin_left" and "margin_bottom" properties.

Open the "com.controls.Joystick" class. Import "com.Hero" class and add a third parameter to the constructor as shown below:

public function Joystick(margin_left:Number, margin_bottom:Number, hero_mc:Hero) {

Now, since we want this parameter to be accessed across this class, we will create a private variable called "hero" in this class:

private var hero:Hero;

Later, assign the third parameter we just passed to this variable inside the constructor as shown below.

hero = hero_mc;

The final step is to pass the "hero" MovieClip from "MyApp" class after it's added, to the "joystick" MovieClip while it is being instantiated. Add (pass) the third parameter to the "joystick" MovieClip's constructor.

joystick = new Joystick(30, 30, hero);

At this point, the "hero" MovieClip on stage is accessible from the "joystick" MovieClip.


Step 14: Hero Class

Let us prepare the "Hero" class to be able to move around as dictated by the "joystick" MovieClip. We should have created a basic class by now that looks like this:

package com {
	
	import flash.display.MovieClip;	
	
	public class Hero extends MovieClip {		
		
		public function Hero() {
			
		}
	}	
}

Add the following properties to the class. We shall make use of them during the movement.

package com {
	
	import flash.display.MovieClip;	
	
	public class Hero extends MovieClip {		
		private var walk_speed = 2;
		
		public var move_left:Boolean = false;
		public var move_up:Boolean = false;
		public var move_right:Boolean = false;
		public var move_down:Boolean = false;
        
		public function Hero() {
			
		}
	}	
}

As the properties are self explanatory, you must have understood what they are used for. Let's move on to the next step and start animating the "hero" MovieClip.

The four Boolean properties we added are used to determine which direction the Hero is supposed to move. We will set these properties to true or false through the "Joystick" class. Before we do that, let us prepare "Hero" class with the capability to move.

Add an empty method called "heroMove()" in the "Hero" class.

private function heroMove(event:Event):void {
			
}

We shall call this method through an ENTER_FRAME event listener. Add the event listener in the constructor of this class.

public function Hero() {
    this.addEventListener(Event.ENTER_FRAME, heroMove);
}

Again, please don't forget to import generic classes like "flash.events.Event", etc.


Step 15: Understanding Knob Movement

Now, we have asked the "heroMove()" method in the "Hero" class to be called on ENTER_FRAME event. This is where we will check the 4 Boolean variables and move the Hero accordingly. But before we do that, let us add the specific code in the "Joystick" class. Open the "Joystick.as" file and go to the method called "knobMoved()".

We have the trace statement in the method that traces the x and y coordinates of the "knob" MovieClip. Replace the trace statement with the following highlighted lines and your "knobMoved()" method should look like this:

private function knobMoved(event:Event):void {
    // LEFT OR RIGHT
    if (knob.x > 20) {
        hero.move_right = true;
        hero.move_left = false;
    } else if (knob.x < -20) {
        hero.move_right = false;
        hero.move_left = true;
    } else {
        hero.move_right = false;
        hero.move_left = false;
    }
    
    // UP OR DOWN
    if (knob.y > 20) {
        hero.move_down = true;
        hero.move_up = false;
    } else if (knob.y < -20) {
        hero.move_down = false;
        hero.move_up = true;
    } else {
        hero.move_down = false;
        hero.move_up = false;
    }
}

What we have done in the above code is to separate the "Left or Right" and "Up or Down" movement related code as there won't be any scenario where the Hero would move left and right or move up and down. So, we set the "hero" MovieClip's 4 direction variables to true or false based on the x and y positions of the "knob" MovieClip with respect to the center of the "joystick" MovieClip.

The below figure will explain the simple logic:

Joystick Area Overview

The figure above shows the 2 red dashed lines intersecting in the center that represents the (0,0) or "origin" or "registration point" of the "joystick" MovieClip.

The 4 red shaded boxes represent the area in which the x and y values of the "knob" MovieClip will be passed to the "hero" MovieClip. Also the text on the red shaded boxes represent the directions.

The blue rectangles represent one simple direction. While the touch/knob is here, it just orders to move in one direction.

Also observe the 40x40 px square (gray) in the center. They represent the gutter area where if the "knob" MovieClip is moved, nothing happens. This gutter area is to provide a little finger movement on the screen when touched without disturbing the game's behavior. Since there is no physical surface for the user to feel, this gutter area is required, at least for some basic games, unless the game is supposed to be ultra sensitive to the Joystick.

Let us look at the "Left or Right" break-up:

Joystick Left or Right Break-up

Here is the "Up and Down" break-up:

Joystick Up and Down Break-up

Step 16: Animating Hero

Now, we have asked the "heroMove()" method in the "Hero" class to be called on ENTER_FRAME event. This is where we will check the 4 Boolean properties of "hero" MovieClip and move it accordingly.

Go ahead and include the following statements in the "heroMove()" method to make the "hero" MovieClip move according to basic knob movement.

private function heroMove(event:Event):void {
    if (move_left) {
        this.x -= walk_speed;
    }
    if (move_up) {
        this.y -= walk_speed;
    }
    if (move_right) {
        this.x += walk_speed;
    }
    if (move_down) {
        this.y += walk_speed;
    }
}

Lastly, don't forget to reset the 4 Boolean variables to "false" in the "mouseReleased()" method of the "Joystick" Class. Failing to do so will result in constant motion of the "hero" MovieClip even while not dragging the knob (this is triggered from the "heroMove()" method from "Hero" class through ENTER_FRAME event).

private function mouseReleased(event:MouseEvent):void {
    knob.stopDrag();
    
    hero.move_left = false;
    hero.move_up = false;
    hero.move_right = false;
    hero.move_down = false;
    
    if (this.hasEventListener(Event.ENTER_FRAME)) {
        this.removeEventListener(Event.ENTER_FRAME, knobMoved);
    }
    
    knob.x = knob.origin_x;
    knob.y = knob.origin_y;
}

This is how your SWF should behave:

Go ahead and move the Joystick Knob below.


Step 17: Hero Rotation & Direction

We are trying to have a top-down view game with a person depicted walking around. Keeping the complex artwork and animation apart, we would at least want to cover the direction or rotation of the Hero according to his movement. For this, we will make a slight addition to the "hero" MovieClip in the .fla file. We will add something that looks like a nose.

Open the "Hero" symbol in the Library and add a small circle that will represent the nose. Below is the zoomed in version of the object being drawn inside the "Hero" symbol and beside it is the actual size view of the MovieClip.

Hero Nose

Let us now change the "rotation" property of the "hero" MovieClip in the "Hero" class. Open the "Hero" class and add the following code in the beginning of the "heroMove()" method:

private function heroMove(event:Event):void {
    if (move_left && move_up) {
        this.rotation = -45;
    } else if (move_right && move_up) {
        this.rotation = 45;
    } else if (move_left && move_down) {
        this.rotation = 225;
    } else if (move_right && move_down) {
        this.rotation = 135;
    } else if (move_right) {
        this.rotation = 90;
    } else if (move_left) {
        this.rotation = -90;
    } else if (move_up) {
        this.rotation = 0;
    } else if (move_down) {
        this.rotation = 180;
    }
    
    if (move_left) {
        this.x -= walk_speed;
    }
    if (move_up) {
        this.y -= walk_speed;
    }
    if (move_right) {
        this.x += walk_speed;
    }
    if (move_down) {
        this.y += walk_speed;
    }
}

In the above code, we have made checks for all 8 directions and rotated the "hero" MovieClip accordingly. Compiling and running your code now should result in the following:

This concludes the basic behavior of the Joystick and Hero.


Step 18: Add TweenLite Support

In this step, we shall understand how TweenLite library works and how easily we can incorporate it in our demo so we can get a little better effect for animations.

We will choose to use a library called TweenLite developed by GreenSock. This is a very light weight tweening engine that is quick for a developer and has less impact on performance of your animations.

  1. Go to http://www.greensock.com/tweenlite/ and click on the "Download AS3" button on the right side.
  2. Read and accept the terms and conditions and once the download is done, you must have greensock-as3.zip.
  3. Extract it to a folder.
  4. Find the "com" folder inside the extracted folder. Navigate to inside the "com" folder.
  5. You should find a subfolder by name "greensock". This is the folder we need.

Now, there are 2 common ways of setting this library up for use in your projects.

  1. Copy this folder to your project folder.
  2. OR

  3. Link it dynamically from where ever it is to your project.

Linking dynamically has an advantage. You can end up using the same folder that exists on some other common location in many different projects. If you choose to copy the "greensock" folder into your project's folder, you will be able to use it only in that project. Doing this eventually for other project may lead to duplicates of GreenSock library and if the engine itself is released with bug fixes or new features, you will need to update your local folders for many different projects.

However, for this project, we will just copy the folder to our project's location.

  • Copy the "greensock" folder to the "com" folder of your project. The below image should help you understand the final location of the "greensock" folder.
GreenSock Library Folder

Now, we shall import the classes that we require to start animating the Knob. Open the "Joystick.as" file and add this import statement:

import com.greensock.*;

Step 19: Animating the Knob

Let us add a simple animation to the Knob when it is released and needs to come back to its origin. This is done in the "Joystick.as" file.

Add the following method to the class:

private function mover():void {
    TweenLite.to(knob, 0.5, {x: knob.origin_x, y:knob.origin_y});
}

Now, add the function call to "mover()" in the method "mouseReleased()" at the end:

mover();

Don't forget to REMOVE the following 2 lines in the "mouseReleased()" method:

knob.x = knob.origin_x
knob.y = knob.origin_y;

Now, try dragging the knob and releasing it away from the center. Notice the animation in the below swf:


Step 20: Knob Bounce Effect

Since a Joystick brings back the knob to its origin, it also adds a natural rebound effect. Luckily, we have very less work to do to achieve this using TweenLite.

Open the "Joystick.as" and Import a package of TweenLite that enables the easing effects:

import com.greensock.easing.*;

There is a property supported by TweenLite that will allow you to define the type of animation. In our case, it is the Bounce effect we need. So, go ahead and include the property "ease" in the "TweenLite.to()" method as shown below:

TweenLite.to(knob, 0.5, {x: knob.origin_x, y:knob.origin_y, ease:Bounce.easeOut});

Test the below SWF:

The Bug

You must be able to find a small glitch or bug in the above SWF. If you rapidly try to tap "around" the knob and release it, the knob doesn't respond. This is because of the animation. By the time you rapidly tap more than once, the animation that got triggered from the first tap isn't over. Without the animation complete, TweenLite is forcing the "knob" to animate back to its origin.

The Fix

To fix this, we need to force-stop the animation when there is a tap on the "joystick" MovieClip. To be able to stop an animation forcibly, we need to get the identifier that represents the tween/animation we are doing.

We start by creating an identifier (variable) of type "TweenLite". Create this new variable in "Joystick" class:

private var knob_tween:TweenLite;

Now, the animation we are performing using "TweenLite.to()" should be assigned to be identified by this property we created. We need to do a slight change to the tween statement. Remove the call for "to()" static method and change it to the following:

knob_tween = new TweenLite(knob, 0.5, {x: knob.origin_x, y:knob.origin_y, ease:Bounce.easeOut});

The new property, "knob_tween" is the one we use to force-stop the animation. Go to the method "mouseDown()" and add the following statements in the beginning of the method:

if (knob_tween) {
    knob_tween.kill();
}

Test and compare the SWF below to the previous one. Tap rapidly on the "joystick" MovieClip. Notice the change in behavior.

This covers the final behavior of Joystick.


Step 21: Joystick Re-design

Till now, we simply used 2 flat colored circles to represent a Joystick and Knob. I won't elaborate this section of the tutorial as it is subjective and has no limits to how creative it can become. I will show you some example designs that can be adapted into this logic to enhance the experience and look of your game.

Example 1 (screenshot)

Example Design 1

Example 2 (screenshot)

Example Design 2

Example 3 (screenshot)

Example Design 3

Step 22: Final Code

We shall review the final code for your reference of each .as file below.

MyApp.as

package com {
	
	import flash.display.MovieClip;
	import com.controls.Joystick;
	import com.Hero;
	
	public class MyApp extends MovieClip {
		private var joystick:Joystick;
		private var hero:Hero;
		
		public function MyApp() {
			hero = new Hero();
			hero.x = stage.stageWidth/2;
			hero.y = stage.stageHeight/2;
			addChild(hero);
			
			joystick = new Joystick(30, 30, hero);
			addChild(joystick);
		}
	}
}

JoystickKnob.as

package com.controls {
	
	import flash.display.MovieClip;
	
	public class JoystickKnob extends MovieClip {		
		private var _origin_x:Number;
		private var _origin_y:Number;		
		
		public function JoystickKnob() {
		}
		
		public function get origin_x():Number {
			return _origin_x;
		}

		public function set origin_x(o_x:Number):void {
			_origin_x = o_x;
		}

		public function get origin_y():Number {
			return _origin_x;
		}

		public function set origin_y(o_y:Number):void {
			_origin_y = o_y;
		}
	}
}

Hero.as

package com {
	
	import flash.display.MovieClip;	
	import flash.events.Event;
	
	public class Hero extends MovieClip {		
		private var walk_speed = 2;
		
		public var move_left:Boolean = false;
		public var move_up:Boolean = false;
		public var move_right:Boolean = false;
		public var move_down:Boolean = false;
		
		public function Hero() {
			this.addEventListener(Event.ENTER_FRAME, heroMove);
		}
		
		private function heroMove(event:Event):void {
			if (move_left && move_up) {
				this.rotation = -45;
			} else if (move_right && move_up) {
				this.rotation = 45;
			} else if (move_left && move_down) {
				this.rotation = 225;
			} else if (move_right && move_down) {
				this.rotation = 135;
			} else if (move_right) {
				this.rotation = 90;
			} else if (move_left) {
				this.rotation = -90;
			} else if (move_up) {
				this.rotation = 0;
			} else if (move_down) {
				this.rotation = 180;
			}
			
			if (move_left) {
				this.x -= walk_speed;
			}
			if (move_up) {
				this.y -= walk_speed;
			}
			if (move_right) {
				this.x += walk_speed;
			}
			if (move_down) {
				this.y += walk_speed;
			}
		}
	}	
}

Joystick.as

package com.controls {
	
	import flash.events.Event;
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	
	import com.controls.JoystickKnob;
	import com.Hero;
	
	import com.greensock.*;
	import com.greensock.easing.*;
	
	public class Joystick extends MovieClip {
		private var my_x:Number;
		private var my_y:Number;
		
		private var knob:JoystickKnob;
		private var hero:Hero;
		
		private var knob_tween:TweenLite;
		
		public function Joystick(margin_left:Number, margin_bottom:Number, hero_mc:Hero) {
			my_x = margin_left;
			my_y = margin_bottom;
			hero = hero_mc;
			
			if (stage) {
				init();
			} else {
				addEventListener(Event.ADDED_TO_STAGE,init);
			}		
		}
		
		private function init(e:Event=null):void {
			if (hasEventListener(Event.ADDED_TO_STAGE)) {
				removeEventListener(Event.ADDED_TO_STAGE,init);
			}
			
			this.x = my_x + this.width / 2;
			this.y = stage.stageHeight - my_y - this.height / 2;

			knob = new JoystickKnob();
			knob.x = 0;
			knob.y = 0;

			knob.origin_x = 0;
			knob.origin_y = 0;

			addChild(knob);

			this.addEventListener(MouseEvent.MOUSE_DOWN, snapKnob);
			knob.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);
			
			knob.buttonMode = true;
		}
		
		private function snapKnob(event:MouseEvent):void {
			knob.x = this.mouseX;
			knob.y = this.mouseY;
			mouseDown(null);
		}
		
		private function mouseDown(event:MouseEvent):void {
			if (knob_tween) {
				knob_tween.kill();
			}
			this.addEventListener(Event.ENTER_FRAME, knobMoved);
			knob.startDrag(false,new Rectangle( -  this.width / 2, -  this.height / 2,this.width,this.height));
		}
		
		private function knobMoved(event:Event):void {
			// LEFT OR RIGHT
			if (knob.x > 20) {
				hero.move_right = true;
				hero.move_left = false;
			} else if (knob.x < -20) {
				hero.move_right = false;
				hero.move_left = true;
			} else {
				hero.move_right = false;
				hero.move_left = false;
			}
			
			// UP OR DOWN
			if (knob.y > 20) {
				hero.move_down = true;
				hero.move_up = false;
			} else if (knob.y < -20) {
				hero.move_down = false;
				hero.move_up = true;
			} else {
				hero.move_down = false;
				hero.move_up = false;
			}
		}
		
		private function mouseReleased(event:MouseEvent):void {
			knob.stopDrag();
			
			hero.move_left = false;
			hero.move_up = false;
			hero.move_right = false;
			hero.move_down = false;
			
			if (this.hasEventListener(Event.ENTER_FRAME)) {
				this.removeEventListener(Event.ENTER_FRAME, knobMoved);
			}
			
			mover();
		}

		private function mover():void {
			knob_tween = new TweenLite(knob, 0.5, {x: knob.origin_x, y:knob.origin_y, ease:Bounce.easeOut});
		}
	}	
}

Step 27: Conclusion

This was a basic tutorial on how to make your games touch-devices-ready by adding an on-screen control.

Additionally, different types of games would need changes in the Hero movement logic. Some examples include top-down view car control (parking lot type game), platform game / side-scrolling game, puzzles, and other casual games. Hope you had fun learning to create this Virtual Joystick.

For more tutorials, tips on Flash development for devices and improvisations on the above code, visit http://www.hsharma.com/tech.

Advertisement