Move with the Mouse, Aim with the Keyboard

by Michael James Williams on May 28, 2009 · 14 comments

in Avoider Game Extras,Tutorial

Only being able to shoot in one direction is soooo 1978. Let’s add three more to choose from.

Snapshot_O06.png
Mouse to move, WSAD to shoot

This tutorial is based on my avoider game tutorial, and follows on directly from Turn Your Avoider Game into a Shoot-’Em-Up. You’ll find this easier to understand if you went through both of those, but if you’ve got a decent grasp of AS3, don’t worry — just download this zip file, it’s got all the files you need, and they’re all set up ready for this post.

Move with the Keyboard, Shoot with… the Keyboard

Before we get on to using the mouse, I want to start with something simpler: move with the arrow keys, aim and fire with the WSAD keys. So pressing W will shoot a bullet upwards, A will shoot one left, and so on. (Apologies if you’re using a keyboard layout that happens to place these keys in weird positions!)

This means adding all the relevant Boolean variables, like wKeyIsBeingPressed. I know, I know, this is a pain. In a future tutorial I will explain a much simpler way of dealing with keyboard input. I’ll rush through these as we’ve done it all before.

AvoiderGame.as class-level variables:

?View Code ACTIONSCRIPT3
28
29
30
31
public var wKeyIsBeingPressed:Boolean;
public var sKeyIsBeingPressed:Boolean;
public var aKeyIsBeingPressed:Boolean;
public var dKeyIsBeingPressed:Boolean;

AvoiderGame() constructor function:

?View Code ACTIONSCRIPT3
59
60
61
62
wKeyIsBeingPressed = false;
sKeyIsBeingPressed = false;
aKeyIsBeingPressed = false;
dKeyIsBeingPressed = false;

onKeyPress() event listener:

?View Code ACTIONSCRIPT3
else if ( keyboardEvent.keyCode == 87 )
{
	wKeyIsBeingPressed = true;
}
else if ( keyboardEvent.keyCode == 83 )
{
	sKeyIsBeingPressed = true;
}
else if ( keyboardEvent.keyCode == 65 )
{
	aKeyIsBeingPressed = true;
}
else if ( keyboardEvent.keyCode == 68 )
{
	dKeyIsBeingPressed = true;
}

(The onKeyRelease() event listener is almost the same, except true is changed to false. If you’re wondering where I got the numbers 87, 83, 65 and 68 from, the answer is this page. Don’t be put off that it says “ActionScript 2.0″ at the top — the key codes have stayed the same in AS3.)

Now for the actual shooting code. As a reminder, here’s our current, always-shoot-upwards code:

?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
if ( spaceBarIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet();
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}

Well, it’s easy to change this to make the W key shoot upwards:

?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
if ( wKeyIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet();
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}

What do we do with the other three shooting keys though? We could copy and paste this code, once for each key, and just change the direction somehow, but that’s messy and irritating.

First, let’s figure out how we’d shoot to the left (i.e. what should happen when we press A). Presumably, we’d start by changing the above code like so:

?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
if ( aKeyIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet();
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}

Then what? There’s no code to say in which direction to fire the bullet. If you look further down in the onTick() function, you’ll remember this code:

?View Code ACTIONSCRIPT3
234
235
236
237
238
239
240
241
242
243
244
245
246
var j:int = bullets.length - 1;
var bullet:Bullet;
while ( j > -1 )
{
	bullet = bullets[j];
	bullet.moveABit();
	if ( bullet.y < -10 )
	{
		removeChild( bullet );
		bullets.splice( j, 1 );
	}
	j = j - 1;
}

The movement logic for the bullets is completely contained in the bullet.moveABit() function. So let’s open Bullet.as and take a look:

?View Code ACTIONSCRIPT3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Bullet extends MovieClip 
{
	public var speed:Number;	//pixels moved per tick
 
	public function Bullet() 
	{
		speed = 3;
	}
 
	public function moveABit():void 
	{
		y = y - speed;
	}
}

Not much to it.

I’m going to adapt a technique from Mushyrulez’s Enemies tutorial for controlling the bullet’s direction. (Check out his post if you want to see a full explanation.)

?View Code ACTIONSCRIPT3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Bullet extends MovieClip 
{
	public var speed:Number;	//pixels moved in a direction per tick
	public var xDirection:Number;
	public var yDirection:Number;
 
	public function Bullet( startingXDirection:Number, startingYDirection:Number ) 
	{
		xDirection = startingXDirection;
		yDirection = startingYDirection;
		speed = 3;
	}
 
	public function moveABit():void 
	{
		x = x + ( speed * xDirection );
		y = y + ( speed * yDirection );
	}
}

See how that works? It’s actually very similar to the way we move both the enemies and the avatar. If we pass, say, startingXDirection = 1, startingYDirection = 0 then every tick, the x-coordinate of the bullet will increase by 3 pixels (because the speed is 3) — in other words, the bullet will move to the right.

Now head back to the code we were editing in AvoiderGame.as, and let’s get the A button shooting to the left:

?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
if ( aKeyIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet( -1, 0 );
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}

If passing ( 1, 0 ) would make the bullet move to the right, it stands to reason that ( -1, 0 ) would move it to the left.

Try it out:

Snapshot_O01.png
Click to play

It works! OK, we need to make the bullet face the correct direction, but it’s a start. Now for the other three keys.

Shooting Up, Down and Right

This is the simplest way I can think of to include the W, S and D keys:

?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
else if ( wKeyIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet( 0, -1 );
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}
else if ( sKeyIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet( 0, 1 );
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}
else if ( aKeyIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet( -1, 0 );
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}
else if ( dKeyIsBeingPressed )
{
	if ( ticksSinceLastShot >= 10 )
	{
		var newBullet = new Bullet( 1, 0 );
		newBullet.x = avatar.x;
		newBullet.y = avatar.y;
		addChild( newBullet );
		bullets.push( newBullet );
		ticksSinceLastShot = 0;
		sfxSoundChannel = shotSound.play();
	}
}

Horrible, isn’t it? Look at all that duplicated code! Imagine if we ever wanted to change, say, the sound that played; we’d have to do it in four places.

Let’s simplify it. First, note that we can move the if ( ticksSinceLastShot >= 10 ) to be outside the other if statements:

?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
if ( ticksSinceLastShot >= 10 )
{
	if ( wKeyIsBeingPressed )
	{
		//bullet creation code goes here
	}
	else if ( sKeyIsBeingPressed )
	{
		//bullet creation code goes here
	}
	else if ( aKeyIsBeingPressed )
	{
		//bullet creation code goes here
	}
	else if ( dKeyIsBeingPressed )
	{
		//bullet creation code goes here
	}
}

Now how about the code inside the lettered if statements?

Well, we can just stick this all in a new function, and use it as many times as we like :)

First, we take the bullet creation code, and we put it in a new function:

?View Code ACTIONSCRIPT3
335
336
337
338
339
340
341
342
343
344
public function createNewBullet():void
{
	var newBullet = new Bullet( 0, -1 );
	newBullet.x = avatar.x;
	newBullet.y = avatar.y;
	addChild( newBullet );
	bullets.push( newBullet );
	ticksSinceLastShot = 0;
	sfxSoundChannel = shotSound.play();
}

(Remember to put this outside the onTick() function, but inside the AvoiderGame class — otherwise, you’ll get an error.)

Because all the variables we use here are either class-level variables (avatar, bullets, ticksSinceLastShot, sfxSoundChannel, shotSound) or created by the function (newBullet), we don’t run into any problems.

Now we can simply call this one function from the onTick() class, like so:

?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
if ( ticksSinceLastShot >= 10 )
{
	if ( wKeyIsBeingPressed )
	{
		createNewBullet();
	}
	else if ( sKeyIsBeingPressed )
	{
		createNewBullet();
	}
	else if ( aKeyIsBeingPressed )
	{
		createNewBullet();
	}
	else if ( dKeyIsBeingPressed )
	{
		createNewBullet();
	}
}

Run it, to make sure it works:

Snapshot_O02.png
Click to play

It does, but of course it’s still only shooting in one direction, no matter which key you press. We have to pass the information about the key pressed to the new function, like so:

?View Code ACTIONSCRIPT3
311
312
313
314
315
316
317
318
319
320
public function createNewBullet( startingXDirection:Number, startingYDirection:Number ):void
{
	var newBullet = new Bullet( startingXDirection, startingYDirection );
	newBullet.x = avatar.x;
	newBullet.y = avatar.y;
	addChild( newBullet );
	bullets.push( newBullet );
	ticksSinceLastShot = 0;
	sfxSoundChannel = shotSound.play();
}
?View Code ACTIONSCRIPT3
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
if ( ticksSinceLastShot >= 10 )
{
	if ( wKeyIsBeingPressed )
	{
		createNewBullet( 0, -1 );
	}
	else if ( sKeyIsBeingPressed )
	{
		createNewBullet( 0, 1 );
	}
	else if ( aKeyIsBeingPressed )
	{
		createNewBullet( -1, 0 );
	}
	else if ( dKeyIsBeingPressed )
	{
		createNewBullet( 1, 0 );
	}
}

There. And check it out — no duplicated code!

Snapshot_O03.png
Click to play

Rotating the Bullets

It still looks a bit off, though, doesn’t it? We need the bullets to be pointing in the direction they’re moving.

Fortunately, all MovieClips — and therefore, all Bullets, as Bullet extends MovieClip — have a rotation property that controls the angle that the image is turned.

The way Flash handles these angles is a bit wacky; they range from -180 to 180 degrees, instead of from 0 to 360 degrees. If you want to read about why this is (or just want to know more about angles and degrees in general), check out my post Angles In Flash: Rotation, Degrees and Radians.

Basically, we just need to set the bullets rotation to:

  • 90 degrees, if it’s moving to the right
  • 180 degrees, if it’s moving downwards
  • -90 degrees, if it’s moving to the left
  • 0 degrees, if it’s moving upwards

I think the best place to do this is in the Bullet’s constructor function. Do you agree?

The code is simple, since we already know all the possible inputs:

?View Code ACTIONSCRIPT3
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public function Bullet( startingXDirection:Number, startingYDirection:Number ) 
{
	xDirection = startingXDirection;
	yDirection = startingYDirection;
	speed = 3;
 
	if ( xDirection == 0 )
	{
		if ( yDirection == 1 )
		{
			//moving downwards
			rotation = 180;
		}
		else if ( yDirection == -1 )
		{
			//moving upwards
			rotation = 0;
		}
	}
	else if ( yDirection == 0 )
	{
		if ( xDirection == 1 )
		{
			//moving right
			rotation = 90;
		}
		else if ( xDirection == -1 )
		{
			//moving left
			rotation = -90;
		}
	}
}

Now, this is a little sloppy, I’ll admit; if we change the bullet’s possible directions in AvoiderGame.as, we’ll also have to change the corresponding rotations in Bullet.as. That’s not very tidy. I’ll explain how to improve this in the next shoot-’em-up tutorial.

For now, though, it’s good enough. Take a look:

Snapshot_O04.png
Click to play

Mouse Movement

Actually, mouse movement is really easy! Remember back in Part 7 we added a boolean, useMouseControl, to make it easy to switch?

All we have to do is find this line:

?View Code ACTIONSCRIPT3
64
useMouseControl = false;

…and set it to this:

?View Code ACTIONSCRIPT3
64
useMouseControl = true;

Have a go:

Snapshot_O05.png
Click to play

Hmm. It doesn’t “feel” right. I think it’s because the avatar moves much faster than the bullets. Let’s change the bullets’ speed in Bullet.as:

Snapshot_O06.png
Click to play

Ah, now that’s much better :)

Wrapping Up

This makes the game much fairer, as you can now blast a path through enemies if they surround you.

You can download a zip of all the files I used in this tutorial (including the sound files) from this link.

In the next part of this mini-series, we’ll switch things round so that you can move with the keyboard and aim with the mouse. It’s harder than it sounds ;)

Until then, why not try incorporating Mushyrulez’s Enemies tutorial into your new shoot-’em-up? It makes sense, given that those enemies come from multiple directions and you can now shoot in multiple directions.

Also, just as with the character, I’ve not included the ability for diagonal movement. Will I never learn? Have a go at coding that, too :)

One last thing: garbage collection. At the minute, bullets are only being garbage collected if they move too far off the top of the play screen. Obviously this won’t do for bullets moving left, right, or downwards. See if you can fix that.

Click here to read the next part of the shoot-’em-up tutorial.

{ 12 comments… read them below or add one }

Snurre May 28, 2009 at 8:02 am

Great! and yes, you will learn, very soon :)

samuvagyok May 28, 2009 at 8:13 am

funny :)

Ramon Fritsch May 28, 2009 at 12:38 pm

great job man. you’re good.

DudexD January 20, 2010 at 9:27 pm

Something really weird happend to my game in the middle of the tut.

I’m not getting any errors or anything so I got no idea why this is happening.

if i could send you an E-mail or something with my avoidergame.as and the swf so you could look at it would be great :P

Michael Williams January 20, 2010 at 10:21 pm

Hey, sure thing — my email is on the About page.

DudexD January 21, 2010 at 6:39 pm

HEAWREHAWEH. am I a flash retard or what?

Even when you told me where the code is wrong is till can’t find it:S

Tho I found a mistake i’d done in the “public function onKeyRelease”
Forgot to set wasd controls to false:P but since thats not the thing i need to fix, i give up… almost;

So i tryed to change my onKeyPress function; with urs from the zip file, but its still not working as it should?!?!? it still take the focus away from the game and to the program instead:S:S:S

Michael Williams January 26, 2010 at 7:22 am

Oh! I thought that not setting WASD to false was the problem you were talking about!

Have you had any luck in the past few days?

DudexD January 26, 2010 at 4:05 pm

been looking over it a couple of times, but with no luck ;(

It maybe be a problem with my program or something because, i get no errors in the log,
but then whe i try to play the game it works fine until i try to shoot with Wasd,
what happens is that instead of shooting it sets the Focus back to the program itself instead of the game. so when i press wasd it starts changing tools in the tool panel :P
So i will try to do this tut over again, and hopefully it will work then…

BYT i like the new Design :P

Michael Williams January 28, 2010 at 5:15 am

Oh! Hit Disable keyboard shortcuts in the menu of the Flash player somewhere. I forget where at the moment.

Jos June 21, 2011 at 5:30 am

Please help!

I’m having problems when trying to add different enemy types. I’ve tried a few approaches and none seem to work for me. The game im working on is based on youre tuts and Ive done them all excpt for kong api(hoping to soon!).

I can create and add the new enemy type “miniBoss” to the stage no problem (i can also control the movement, spawn rate etc thanks to you!) but I run into problems when I try and implement hit-testing. They have they’re own class as they will have different behaviours, weapon load outs etc. If I try to push the new enemytype into the existing array I get the cant coerce mc to mc@ error. Is there a way to hit test items in an array more generally i.e. without naming the instance?

So I tried setting up a separate array but repeating the nested loop with the bullets within a when(i > -1) seemed far too repetitive and I couldn’t get it to work anyways. I never dialed in exactly why but I did manage to crash flash multiple times.

So what would be the best frame work for introducing new enemytypes? Do i need individual seperate arrays? Is there a way to have miniBoss extend the enemy class but add variables or something?

I feel like I’m right there. Any help would be hugely appreciated.

Jos June 21, 2011 at 11:15 am

Ha!

Figured it out using a class similar to collectibles.

Thanks again for all the great knowledge… on to cutscenes.

Jos June 23, 2011 at 6:03 am

Ok. I you happen to see this any help would be appreciated.

Im trying to implement enemies shooting back at the player and have hit a wall I can’t seem to find a way around.

Ideally I would like to instantiate the bullets within the enemy class (which would allow me to easily configure multiple variations for multiple enemy types i.e. multiple bullets multiple directions and even multiple bullet types).

I’ve done this and all was well until i tried to set up hittesting. I cant figure out how to either:

push the new instances into an array that is created in the avoidergame/playscreen class.

hit test within the bulletclass against and object/mc/avatar created in the avoidergame class.

On the otherhand if I create the bullets within the avoidergame class Im clueless how to position them on the x,y of an object within an array so it appears the ships are firing them.

Any thoughts? I feel like there’s something simple I’m missing about class communication but for all my googling im left at square one. bullets wizzing by at high speed:{

Leave a Comment

Writing code? Write <pre> at the start and </pre> at the end to keep it looking neat.

Anti-Spam Protection by WP-SpamFree

{ 2 trackbacks }

Previous post:

Next post: