In a previous tutorial from Michael James Williams (MJW), you could move the skull with the mouse and aim with the keyboard. In this tutorial we will make the mouse movement a bit complex, but you will not be able to shoot.

image10.jpg

I am using the files from MJW’s final part of AS3 conversion of Frozen Haddock’s avoider game tutorial. If you do not have the files to follow the tutorial, you can grab them here.

Move with the mouse

Since we will start using the mouse, we do not need the keyboard verification functions and variables. I would comment all lines, but MJW made it simple with the useMouseControl variable. All we need to do is set it to true. Open AvoiderGame.as:

?View Code ACTIONSCRIPT3
public function AvoiderGame()
{
	currentLevelData = new LevelData( 1 );
	setBackgroundImage();
 
	backgroundMusic = new BackgroundMusic();
	bgmSoundChannel = backgroundMusic.play();
	bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished, false, 0, true );
	enemyAppearSound = new EnemyAppearSound();
 
	downKeyIsBeingPressed = false;
	upKeyIsBeingPressed = false;
	leftKeyIsBeingPressed = false;
	rightKeyIsBeingPressed = false;
 
	useMouseControl = true;
	Mouse.hide();
	army = new Array();
 
	avatar = new Avatar();
	addChild( avatar );
	if ( useMouseControl )
	{
		avatar.x = mouseX;
		avatar.y = mouseY;
	}
	else
	{
		avatar.x = 200;
		avatar.y = 250;
	}
 
	gameTimer = new Timer( 25 );
	gameTimer.addEventListener( TimerEvent.TIMER, onTick, false, 0, true );
	gameTimer.start();
 
	addEventListener( Event.ADDED_TO_STAGE, onAddToStage, false, 0, true );
}

I changed useMouseControl to true. Now we have the mouse movement working! Check it:

image11.jpg

Adding speed

When you play the game, you may find it easy, since the skull is placed where your mouse is, every frame. We need a harder game, so let’s put a fixed speed to the skull!

If we check the onTick function of AvoiderGame.as, we can see why the skull moves so fast:

?View Code ACTIONSCRIPT3
if ( useMouseControl )
{
	avatar.x = mouseX;
	avatar.y = mouseY;
}

This code only places the skull where our mouse is located, so we have to modify it.

Our skull needs to move in the horizontal and in the vertical, using a fixed speed. To do it, we need to know where our mouse is located and what is the angle between them. The reason for the angle will be explained later.

If we take a look into Avatar.as file, we can see only the moveABit function. We could change it to meet our needs, but instead of that, we will create a new function. Now, moveABit can still be used with the keyboard movement, in case you want to switch from mouse to keyboard and vice versa.

Let’s create the moveWithMouse function:

Avatar.as file:

?View Code ACTIONSCRIPT3
package
{
	import flash.display.MovieClip;
 
	public class Avatar extends MovieClip
	{
		public function Avatar()
		{
 
		}
 
		public function moveABit( xDistance:Number, yDistance:Number ):void
		{
			var baseSpeed:Number = 3;
			x += ( xDistance * baseSpeed );
			y += ( yDistance * baseSpeed );
		}
 
		public function moveWithMouse( angleBetween:Number ):void
		{
 
		}
	}
}

We need a speed variable to control our movement. Instead of creating one, I will turn the baseSpeed variable from moveABit into a class-level variable. Here is how I did it:

?View Code ACTIONSCRIPT3
package
{
	import flash.display.MovieClip;
 
	public class Avatar extends MovieClip
	{
		private var baseSpeed:Number;
 
		public function Avatar()
		{
			baseSpeed = 3;
		}
 
		public function moveABit( xDistance:Number, yDistance:Number ):void
		{
			x += ( xDistance * baseSpeed );
			y += ( yDistance * baseSpeed );
		}
 
		public function moveWithMouse( ):void
		{
 
		}
	}
}

The line 13 was removed, and baseSpeed is now a private variable.

As I said before, we need the angle between the skull and the mouse. We will calculate it into AvoiderGame.as and pass it as a parameter with a call to moveWithMouse. So let’s go back to onTick function of AvoiderGame.as.

?View Code ACTIONSCRIPT3
if ( useMouseControl )
{
	avatar.x = mouseX;
	avatar.y = mouseY;
}

I am removing the code inside that if statement, because we don’t need it.

Before writing any code, I am going to introduce you to Math.atan2 function. Math.atan2 computes the angle of a given point (in radians), measured counterclockwise from a circle’s x axis. The image below contributes to the understanding of the function:

Mathatan2

Now we can start coding the if statement:

?View Code ACTIONSCRIPT3
if ( useMouseControl )
{
	var angle:Number = Math.atan2( mouseY - avatar.y, mouseX - avatar.x );
	avatar.moveWithMouse(angle);
}

Be careful when using Math.atan2: you have to pass first the Y variable, then the X variable. The angle is measured in radians. We do not need to convert it to degrees – when we use it later, we need the angle in radians.

The reason for why I wrote (mouseY – avatar.y) and (mouseX – avatar.x) is because we are using avatar.x and avatar.y as the center of our reference circle. We have to decrease avatar.x and avatar.y of the values to make sure the center of the circle is the (0, 0) point.

We added a parameter to avatar’s moveWithMouse function, so let’s change it and work on the movement. Switch back to Avatar.as.

I’m adding the parameter to moveWithMouse:

?View Code ACTIONSCRIPT3
public function moveWithMouse( angleBetween:Number ):void
{
 
}

Before continuing, I have to talk about the uniform movement. Many of you will remember the physics classes. When something is moving, it has a x position modifier and a y position modifier. The vector resulting from the sum of these two other vectors is the final speed. Look at the image:

VectorSum

Guess what is the angle in the image? Exactly! The angle passed to moveWithMouse function. The Y speed and X speed vectors form an angle of 90º, so you can use sine and cosine to calculate them. The sine of the angle is (Y speed / Final speed), and cosine is (X speed / Final speed). In a better way, Y speed = Final speed * sine of angle and X speed = Final speed * cosine of angle.

Now, back to moveWithMouse function, we will use the two last formulas to make our skull move!

I will create an xSpeed and ySpeed variable, so you can easily work with them later.

?View Code ACTIONSCRIPT3
package
{
	import flash.display.MovieClip;
 
	public class Avatar extends MovieClip
	{
		private var baseSpeed:Number;
		private var xSpeed:Number;
		private var ySpeed:Number;
 
		public function Avatar()
		{
			baseSpeed = 3;
		}
 
		public function moveABit( xDistance:Number, yDistance:Number ):void
		{
			x += ( xDistance * baseSpeed );
			y += ( yDistance * baseSpeed );
		}
 
		public function moveWithMouse( angleBetween:Number ):void
		{
			xSpeed = Math.cos(angleBetween) * baseSpeed;
			ySpeed = Math.sin(angleBetween) * baseSpeed;
 
			x += xSpeed;
			y += ySpeed;
		}
	}
}

Here is how our game is looking.

image12.jpg

If you test the game, we can see it worked, but our mouse is not visible, making the game very hard! Also, when the skull reaches your mouse, it gets shaked. To fix the mouse issue, just change AvoiderGame.as:

?View Code ACTIONSCRIPT3
public function AvoiderGame()
{
	currentLevelData = new LevelData( 1 );
	setBackgroundImage();
 
	backgroundMusic = new BackgroundMusic();
	bgmSoundChannel = backgroundMusic.play();
	bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished, false, 0, true );
	enemyAppearSound = new EnemyAppearSound();
 
	downKeyIsBeingPressed = false;
	upKeyIsBeingPressed = false;
	leftKeyIsBeingPressed = false;
	rightKeyIsBeingPressed = false;
 
	useMouseControl = true;
	// Mouse.hide();
	army = new Array();
 
	avatar = new Avatar();
	addChild( avatar );
	if ( useMouseControl )
	{
		avatar.x = mouseX;
		avatar.y = mouseY;
	}
	else
	{
		avatar.x = 200;
		avatar.y = 250;
	}
 
	gameTimer = new Timer( 25 );
	gameTimer.addEventListener( TimerEvent.TIMER, onTick, false, 0, true );
	gameTimer.start();
 
	addEventListener( Event.ADDED_TO_STAGE, onAddToStage, false, 0, true );
}

I commented Mouse.hide() to make our mouse visible. Now you can see where you are moving.

For the shaking problem, we need to go to the onTick function of AvoiderGame.as. It is caused by the delay of the tick. It only asks our avatar to move to a position every tick. Sometimes our avatar exceeds the necessary distance to reach the mouse, and in the next tick it needs to go back, exceeding again the distance. This causes the shaking of our avatar.

Do you remember this post from MJW? We will use it now to calculate the distance between our mouse and our avatar. If the distance is less than or equal to 3 px, we only place the avatar in the mouse position.

?View Code ACTIONSCRIPT3
public function onTick( timerEvent:TimerEvent ):void
{
	gameClock.addToValue( 25 );
	if ( Math.random() < currentLevelData.enemySpawnRate )
	{
		var randomX:Number = Math.random() * 400;
		var newEnemy:Enemy = new Enemy( randomX, -15 );
		army.push( newEnemy );
		addChild( newEnemy );
		gameScore.addToValue( 10 );
		sfxSoundChannel = enemyAppearSound.play();
	}
	if ( useMouseControl )
	{
		var distance = Math.sqrt( (mouseX - avatar.x) * (mouseX - avatar.x) + (mouseY - avatar.y) * (mouseY - avatar.y));
 
		if(distance <= 3)
		{
			avatar.x = mouseX;
			avatar.y = mouseY;
		}
		else
		{
			var angle:Number = Math.atan2( mouseY - avatar.y, mouseX - avatar.x );
			avatar.moveWithMouse(angle);
		}
	}
	else
	{
		if ( downKeyIsBeingPressed )
		{
			avatar.moveABit( 0, 1 );
		}
		else if ( upKeyIsBeingPressed )
		{
			avatar.moveABit( 0, -1 );
		}
		else if ( leftKeyIsBeingPressed )
		{
			avatar.moveABit( -1, 0 );
		}
		else if ( rightKeyIsBeingPressed )
		{
			avatar.moveABit( 1, 0 );
		}
	}
 
	if ( avatar.x < ( avatar.width / 2 ) )
	{
		avatar.x = avatar.width / 2;
	}
	if ( avatar.x > 400 - ( avatar.width / 2 ) )
	{
		avatar.x = 400 - ( avatar.width / 2 );
	}
	if ( avatar.y < ( avatar.height / 2 ) )
	{
		avatar.y = avatar.height / 2;
	}
	if ( avatar.y > 300 - ( avatar.height / 2 ) )
	{
		avatar.y = 300 - ( avatar.height / 2 );
	}
 
	var avatarHasBeenHit:Boolean = false;
	var i:int = army.length - 1;
	var enemy:Enemy;
	while ( i > -1 )
	{
		enemy = army[i];
		enemy.moveABit();
		if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )
		{
			gameTimer.stop();
			avatarHasBeenHit = true;
		}
		if ( enemy.y > 350 )
		{
			removeChild( enemy );
			army.splice( i, 1 );
		}
		i = i - 1;
	}
	if ( avatarHasBeenHit )
	{
		bgmSoundChannel.stop();
		dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
	}
 
	if ( gameScore.currentValue >= currentLevelData.pointsToReachNextLevel )
	{
		currentLevelData = new LevelData( currentLevelData.levelNum + 1 );
		setBackgroundImage();
	}
}

I created the variable distance and calculated it, checking if it was less than or equal to 3. If true, we placed the avatar in the mouse’s position. If not, we just asked the avatar to move in the mouse’s direction. You can see the game now:

image10.jpg

Conclusion

This was just an introduction to our movement with the mouse. You can make it a lot better, adding acceleration and friction, for example.

You can work with the speed of our avatar, increasing it every level, or create a custom cursor. I will not tell you how to do it – go try it!

You can grab the final files of the tutorial here.

Thank you

4 Responses to “Avoider game tutorial – Moving with mouse and using speed”

  1. MichaelJW says:

    Hey Daniel, thanks for writing this! (Sorry about the comments being disabled earlier, I have no idea why that was.)

    It’s really great; proper mouse follow at last :D

  2. DanielSidhion says:

    Thank you MJW!

    For all readers: if you have any questions, feel free to ask here, I will try to help as soon as possible! =)

  3. [...] Click here to read Moving With the Mouse and Using Speed. [...]

  4. B says:

    Good tutorial.
    And LOL, I’ve scored 2010. :D

Leave a Reply