Advertisement

Deploy a Tank on a Mission in an Isometric War Zone

by

In this lesson we're going to create a little tank moving application. The core of this particular tutorial is to make a tank aim at the the mouse pointer and drive towards a mouse click.


Final Result Preview

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

Move the mouse to make the turret aim at it, and click anywhere to get the tank to drive to that point.

Step 1: Graphics Intro

Well, let's get started.. For this tutorial we'll need tank and turret graphics. I used Google SketchUp 7 to make mine.

First I created a tank as a whole. Then I turned the program's view mode to "parallel projection", positioning my camera at a 45° angle to the tank to make an illusion of isometrics in the game.

Please, don't judge me for horrible graphics, I really can do much better but this is just for the sake of explanation :)

Then I hid the turret leaving only the tank body visible and rotated the body by 45° eight times, rendering each position separately. So I achieved 8 different views of the body. I did the same thing to the turret while the tank was hidden. I saved all 16 pics as PNG 24 with alpha channel. You can see the final result below. (This is not actually enough for this kind of movement, it would be better to have twice as many views of the tank but it's enough for the purposes of this tutorial.)

.

You don't necessarily need to use Google SketchUp of course, you may wanna use 3ds max or Maya or whatever you like, you may also use my graphics to practice if you don't want to or cannot create your own graphics.

Step 2: New Flash Project

Now open up Flash (I use Adobe Flash CS4) and choose Create New Flash File (Actionscript 3.0). We will only need it to make a tank MovieClip and to connect the Game class to it later.

.

Step 3: Set the Document Properties

Right-click the stage and go to the Document Properties item. Make it 600 x 400 (or whatever you like) and 21 frames per second (this frame rate seems to be optimal to me so I almost always use it. It doesn't really matter, though).

Click OK. Go to File > Save as and save this file as tank.fla in some folder on your computer (I recommend you create a Tank folder and put the file inside).

Step 4: Import Your Tank Images to Library

Now go to File > Import > Import to library. Find the folder where you saved your eight tank images, select them all and import to library.

.

Step 5: New Symbol

Create a new empty symbol by clicking this little icon in the library:

Choose MovieClip type and call the symbol "tank". Put the registration point in the center (the registration point is that tiny black square in that grid of white squares, for those who don't know).

Step 6: Edit the Symbol

After the symbol is created it appears in the library (its name "tank" is just a cosmetic name which is used solely in the library so it doesn't matter what you call it). Open it by double-clicking its icon. Then take 1.png from the library and drag it onto the symbol's stage. Align the image to the symbol's registration point like in this screenshot:

You want the registration point to be in the center of the tank's turning circle.

Step 7: Create Seven Blank Key Frames

Go to the symbol's timeline. Rename the "Layer 1" to "Tank". Your tank's body image (1.png) should be on the first frame of this layer. Click the second frame and without releasing your mouse button drag the cursor to the 8th frame selecting frames 2 through 8. Then right-click this selected area and choose "Convert to blank keyframes".

Step 8: Drag All Images Onto the Stage

When 7 blank key frames are created, select the second frame and drag 2.png to it aligning the image with the registration point (just like you did with 1.png); imagining that the registration point is an axis that the tank will use to spin around when it's turning. Do the same to the rest of the frames. You don't have to align it very precisely, but the more precisely you align it the better the movement will look when the tank tries to follow the cursor as the game runs.

Step 9: Prevent the Timeline from Playing

After you dragged every image to the symbol's stage and aligned them select the first frame and push F9 to open the actions panel. Type a single method "stop();" in it and close the panel. This is an important step because we don't want our tank to automatically start spinning around as soon as it's added to the stage.

Step 10: Create a Turret MovieClip

Go to the library and create a new MovieClip symbol. Call it turret. Double-click the symbol's icon and do the same you did with the tank MovieClip (Steps 4. through 8.) to the turret but using turret images. Align it to the registration point this way, at the point around which the turret would naturally turn:

Notice that I've named the turret images like this: 1-.png, 2-.png and so on, so that they wouldn't replace the tank images when imported to library. You may want to use folders to put your images into. In this case your turret and tank images may have the same names.

Step 11: Assembling the Tank

After we've finished with turret symbol, open up the tank symbol again, create a new layer above the "Tank" layer and drag the turret movie clip from the library onto this layer. Align the turret with the tank body. The result should look like this:

Step 12: Give the Turret an Instance Name

Click once on the turret to select it (blue rectangle around it will indicate that it's selected):

...then go to properties and give it and instance name mTurret (for MovieClip turret):

Click "Scene 1" link to exit symbol editing mode.

Step 13: Export Your Tank for ActionScript

Right-click the tank symbol in the library, choose Properties (or Linkage if you're using Flash CS3) from the dropdown menu (make sure you're in an Advanced mode), check "Export for Actionscript", and for the class' name type in "Tank" (with capital T). This step will allow us to connect our Tank MovieClip to the instances created using our Tank class in future.

The graphics part is over for now; you may delete the tank from the stage. We'll add it programmatically later.

Step 14: Create a New ActionScript File

In your Flash Authoring tool go to File > New and create a new ActionScript file.

Step 15: Save Your File

Go to the folder where you saved your tank.fla file and create a new folder called "main" inside. Save your ActionScript file in this folder as TankMaker.as. The location of this file should therefore be: tank/main/TankMaker.as

Step 16: Let's Write Some Code

First of all, create a package and import the necessary classes into it:


package main {	

	import flash.display.*;
	import flash.events.*;

}

The word "main" here means the location of this file relative to our tank.fla

We will need "display" classes to show our assets on the screen and "events" to keep track of the mouse events.

Step 17: Public Class and Variables

Now we have to declare a public class TankMaker (according to the Actionscript file's name) and the necessary variables:

package main {
	
	import flash.display.*;
	import flash.events.*;

	public class TankMaker extends Sprite {
		
		// the instance of our Tank class (which we exported for actionscript before)
		private var tank:Tank;

		// the vars to keep the coordinates of the point where we want our tank to move
		private var moveTankToX:Number;
		private var moveTankToY:Number;

		// coordinates of the point at which the tank aims its turret
		private var turnTurretToX:Number;
		private var turnTurretToY:Number;

		// current position of the tank
		private var currentTankPositionX:Number;
		private var currentTankPositionY:Number;
		
		private var tankSpeed:Number = 2;

		// the point at which the tank moves after a mouse click
		private var clickPoint:Object;
		
		// the angle of tank and turret rotation in radians
		private var Atan2:Number;
		private var turretAtan2:Number;
	}
}

I declared all of the variables as "private" because I don't want them to be accessible from anywhere outside this class.

Step 18: Create a Constructor Method

Directly below the last variable create a public function and call it TankMaker. It's going to be the constructor of the class. Then add an event listener for Event.ADDED_TO_STAGE to it so that we can create our tank object only after the stage is. Then pass this event to the method called addStage.

public function TankMaker() {
	
	addEventListener(Event.ADDED_TO_STAGE, addStage);

}

Step 19: Create a Tank Instance

After the stage is created and the ADDED_TO_STAGE event has triggered the addStage() method it's about time to create a tank instance.

Declare a private function and call it addStage. The data type of the function will be: Event. And the return type will be "void" because we're not going to return anything from it.

private function addStage (e:Event):void {
	
	//create a new tank instance of the movie clip 
	// which you had exported for actionscript
	tank = new Tank();
	
	// now a little trick, I scaled my tank down
	// to the size of about one fourth of its original size
	// so that is wasn't so huge
	tank.scaleX = 0.25;
	tank.scaleY = 0.25;
	
	// set the initial position of the tank right
	// in the center of the stage
	tank.x = stage.stageWidth / 2;
	tank.y = stage.stageHeight / 2;
	// and add it to the display list
	addChild(tank);
	
	// now add event listeners for mouse down and mouse move
	// events to the stage
	stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownListener);
	stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveListener);
	
}

Step 20: Handling Mouse Movements

Create a private function mouseMoveListener() with a data type of mouse event to handle mouse movements across the stage. The event listener to trigger this function was created in the last Step.

private function mouseMoveListener(e:MouseEvent):void {
	
	// create a variable to keep the relative angle 
	// between mouse's current position in degrees
	// and the tank's current position
	var angle:Number;

	// the next 2 variables are gonna be 
	// equal to the the mouse's current position
	turnTurretToX = e.stageX;
	turnTurretToY = e.stageY;
	
	// calculate the relative angle in radians 
	// between the mouse's curent position 
	// and the tank's current position
	
	turretAtan2 = Math.atan2(turnTurretToY - currentTankPositionY, turnTurretToX - currentTankPositionX);


	// calculate the same angle in degrees.
	// to get degrees out of radians we have to
	// multiply radians by 180 and divide the result by PI
	// But here are some specifics of Flash
	// it calculates the angle not from 0 thru 360
	// but from 0 thru 180 and from - 180 thru 0
	
	// so let's add some conditional to get rid of this problem
	if (Math.round(turretAtan2 * 180 / Math.PI) < 0) {
		// if angle is between 0 and -180 add 360 to it
		angle = Math.round(turretAtan2 * 180 / Math.PI) + 360;
	} else {
		// if not, calculate it as usual
		angle = Math.round(turretAtan2 * 180 / Math.PI);
	}
	

	// having our angle value stored in a variable
	// let's rotate our turret towards the mouse pointer
	
	// I've done a lot of calculations trying to 
	// figure it all out, so you may try to undestand my logic
	
	// I'm just picking the right frame of the turret MC depending
	// on the angle of the mouse from the turret
	
	if (angle > 240 && angle < 300) {
		// go inside the tank MovieClip then inside mTurret
		// go to the frame 1 and stop  
		tank.mTurret.gotoAndStop(1);
	}
	if (angle > 300 && angle < 340) {
		tank.mTurret.gotoAndStop(2);
	}
	if ((angle >= 0 && angle < 20) || (angle > 340 && angle <= 360)) {
		tank.mTurret.gotoAndStop(3);
	}
	if (angle > 20 && angle < 60) {
		tank.mTurret.gotoAndStop(4);
	}
	if (angle > 60 && angle < 120) {
		tank.mTurret.gotoAndStop(5);
	}
	if (angle > 120 && angle < 160) {
		tank.mTurret.gotoAndStop(6);
	}
	if (angle > 160 && angle < 200) {
		tank.mTurret.gotoAndStop(7);;
	}
	if (angle > 200 && angle < 240) {
		tank.mTurret.gotoAndStop(8);
	} // end
	
}

Step 21: Mouse Click Handler

Below the end of our mouseMoveListener() function create a private function called mouseDownListener() with data type of MouseEvent and pass it to the moveTankOnEnterFrame method:

private function mouseDownListener(e:MouseEvent):void {
	
	// when click occurs assign the next variable 
	// the coordinates of the mouse pointer's current position
	moveTankToX = e.stageX;
	moveTankToY = e.stageY;
	// and add event listener to move the tank frame by frame
	addEventListener(Event.ENTER_FRAME, moveTankOnEnterFrame);
	
}

Step 22: Move Tank Frame By Frame

Create a new private function called moveTankOnEnterFrame() and inside it call a method moveToCoordinates() with 3 parameters:

private function moveTankOnEnterFrame(e:Event) {
	moveToCoordinates(tank, moveTankToX, moveTankToY);
}

This will, eventually, cause the tank to drive towards the clicked point, every frame.

Step 23: Teach the Tank To Move

Now we have called moveToCoordinates() method but we haven't created it yet. Let's do that now.

The tX and tY won't be used but we must create as many parameters as we called. This is how it looks:

private function moveToCoordinates(tank_mc:Tank, tX:Number, tY:Number) {
	
	// create a variable to keep the relative angle 
	// between mouse's current position in degrees
	// and the tank's current position
	// this variable exists only inside this function so 
	// you we can use the same name as we used before
	var angle:Number; 
	
	// calculate the angle (remember moveMouseListener?
	// we do the same here but with the tank itself)
	if (Math.round(Atan2 * 180 / Math.PI) < 0) {
		angle = Math.round(Atan2 * 180 / Math.PI) + 360;
	} else {
		angle = Math.round(Atan2 * 180 / Math.PI);
	}

	if (angle > 240 && angle < 300) {
		tank.gotoAndStop(1);
	}
	if (angle > 300 && angle < 340) {
		tank.gotoAndStop(2);
	}
	if ((angle >= 0 && angle < 20) || (angle > 340 && angle <= 360)) {
		tank.gotoAndStop(3);
	}
	if (angle > 20 && angle < 60) {
		tank.gotoAndStop(4);
	}
	if (angle > 60 && angle < 120) {
		tank.gotoAndStop(5);
	}
	if (angle > 120 && angle < 160) {
		tank.gotoAndStop(6);
	}
	if (angle > 160 && angle < 200) {
		tank.gotoAndStop(7);
	}
	if (angle > 200 && angle < 240) {
		tank.gotoAndStop(8);
	}
	
	// give the clickPoint a value of a current mouse position
	// when a click occurs
	clickPoint = {x:moveTankToX, y:moveTankToY};
	
	// calculate the angle in radians between the pointer's 
	// current position and tank
	Atan2 = Math.atan2(clickPoint.y - tank_mc.y, clickPoint.x - tank_mc.x);

	// now add a value of Atan2 cosine to the tank's X position
	// and sine to its Y position every frame
	tank_mc.x += Math.cos(Atan2) * tankSpeed;
	tank_mc.y += Math.sin(Atan2) * tankSpeed;
	
	
	// now give the values to currentTankPositionX and 
	// currentTankPositionY which we used in our mouseMoveListener
	currentTankPositionX = tank_mc.x;
	currentTankPositionY = tank_mc.y;
	
	// and finally a little trick. Since we don't want our tank to
	// start chaotically vibrating when it reaches its destination
	// we must calculate the distance between the tank's position and
	// the click point, and if it's less than 15, remove the ENTER_FRAME listener
	if (Math.abs(tank_mc.x - clickPoint.x) < 15 && Math.abs(tank_mc.y - clickPoint.y) < 15) {
		removeEventListener(Event.ENTER_FRAME,moveTankOnEnterFrame);
	}
}

Why use a distance of 15? Well, I found out that it never goes beyond 15 by simply tracing it like this:

trace(Math.abs(tank_mc.x - clickPoint.x));

...you can do the same if you want ;)

Step 24: Overviewing The Whole Class

As our class is ready, let's take a look at it as a whole:

package main {
	
	import flash.display.*;
	import flash.events.*;

	public class TankMaker extends Sprite {

		private var tank:Tank;

		private var moveTankToX:Number;
		private var moveTankToY:Number;

		private var turnTurretToX:Number;
		private var turnTurretToY:Number;
		private var currentTankPositionX:Number;
		private var currentTankPositionY:Number;
		
		private var tankSpeed:Number = 2;

		private var clickPoint:Object;
		
		private var Atan2:Number;
		private var turretAtan2:Number;

		public function TankMaker() {
			
			addEventListener(Event.ADDED_TO_STAGE, addStage);

		}
		
		private function addStage (e:Event):void {
			
			tank = new Tank();
			tank.scaleX = 0.25;
			tank.scaleY = 0.25;
			tank.x = stage.stageWidth / 2;
			tank.y = stage.stageHeight / 2;
			addChild(tank);
			
			stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownListener);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveListener);
			
		}

		private function mouseMoveListener(e:MouseEvent):void {
			
			var angle:Number;

			turnTurretToX = e.stageX;
			turnTurretToY = e.stageY;
			
			turretAtan2 = Math.atan2(turnTurretToY - currentTankPositionY, turnTurretToX - currentTankPositionX);

			
			if (Math.round(turretAtan2 * 180 / Math.PI) < 0) {
				angle = Math.round(turretAtan2 * 180 / Math.PI) + 360;
			} else {
				angle = Math.round(turretAtan2 * 180 / Math.PI);
			}

			
			if (angle > 240 && angle < 300) {
				tank.mTurret.gotoAndStop(1);
			}
			if (angle > 300 && angle < 340) {
				tank.mTurret.gotoAndStop(2);
			}
			if ((angle >= 0 && angle < 20) || (angle > 340 && angle <= 360)) {
				tank.mTurret.gotoAndStop(3);
			}
			if (angle > 20 && angle < 60) {
				tank.mTurret.gotoAndStop(4);
			}
			if (angle > 60 && angle < 120) {
				tank.mTurret.gotoAndStop(5);
			}
			if (angle > 120 && angle < 160) {
				tank.mTurret.gotoAndStop(6);
			}
			if (angle > 160 && angle < 200) {
				tank.mTurret.gotoAndStop(7);;
			}
			if (angle > 200 && angle < 240) {
				tank.mTurret.gotoAndStop(8);
			} 
			
		}
		
		
		private function mouseDownListener(e:MouseEvent):void {
			
			moveTankToX = e.stageX;
			moveTankToY = e.stageY;
			addEventListener(Event.ENTER_FRAME,moveTankOnEnterFrame);
			
		}
		
		private function moveTankOnEnterFrame(e:Event) {
			
			moveToCoordinates(tank, moveTankToX, moveTankToY);
		}

		private function moveToCoordinates(tank_mc:Tank, tX:Number, tY:Number) {
			
			var angle:Number; 
		
			if (Math.round(Atan2 * 180 / Math.PI) < 0) {
				angle = Math.round(Atan2 * 180 / Math.PI) + 360;
			} else {
				angle = Math.round(Atan2 * 180 / Math.PI);
			}

			if (angle > 240 && angle < 300) {
				tank.gotoAndStop(1);
			}
			if (angle > 300 && angle < 340) {
				tank.gotoAndStop(2);
			}
			if ((angle >= 0 && angle < 20) || (angle > 340 && angle <= 360)) {
				tank.gotoAndStop(3);
			}
			if (angle > 20 && angle < 60) {
				tank.gotoAndStop(4);
			}
			if (angle > 60 && angle < 120) {
				tank.gotoAndStop(5);
			}
			if (angle > 120 && angle < 160) {
				tank.gotoAndStop(6);
			}
			if (angle > 160 && angle < 200) {
				tank.gotoAndStop(7);
			}
			if (angle > 200 && angle < 240) {
				tank.gotoAndStop(8);
			}

			clickPoint = {x:moveTankToX, y:moveTankToY};
			Atan2 = Math.atan2(clickPoint.y - tank_mc.y, clickPoint.x - tank_mc.x);

			tank_mc.x += Math.cos(Atan2) * tankSpeed;
			tank_mc.y += Math.sin(Atan2) * tankSpeed;
			
			currentTankPositionX = tank_mc.x;
			currentTankPositionY = tank_mc.y;
			
			if (Math.abs(tank_mc.x - clickPoint.x) < 15 && Math.abs(tank_mc.y - clickPoint.y) < 15) {

				removeEventListener(Event.ENTER_FRAME,moveTankOnEnterFrame);
			}
		}
	}
}

Step 25: Final Strokes

Go to File > New, and create a new ActionScript file. Save it as "Game.as" in the same folder as your tank.fla file. This will be our document class.

Here's the code for Game.as class:

package {
	
	import flash.display.*;
    // import the contents of our main folder
	import main.*;
	
	public class Game extends MovieClip {
		
		private var newTank:TankMaker;
		
		public function Game () {
			
            // create a new instance of our tank maker class
			newTank = new TankMaker();
            // and add it to the stage of course
			addChild(newTank);
		}
	}
}

Save the file. All it does is create a tank and add it to the main stage, ready to control.

Step 26: Attach Game Class to the FLA

Open up tank.fla and go to the Properties panel. For Class type in "Game" and save the file.

Aha! There we go. You can now press CTRL + ENTER on PC (CMD + RETURN on Mac) and test the Game.

I hope you enjoyed this tutorial, thanks for reading :)

Advertisement