Read Foundation Game Design with ActionScript 3.0, Second Edition Online
Authors: Rex van der Spuy
This feature is very easy to implement by keeping track of which of the four arrow keys the player is pressing, and then changing the star's vx and vy properties based on that direction.
First,LevelTwo
uses a new String variable called_starDirection
to track which direction the star should move in. Note that_starDirection
is changed to “left”, “right”, “up”, or “down” depending on which key is pressed and it's only changed if the star hasn't already been launched. This prevents the star from changing direction in mid-flight if the player presses the arrow keys after the star has been launched.
private function keyDownHandler(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.LEFT)
{
_character.vx = -5;
if(!_star.launched)
{
_starDirection = "left";
}
}
else if (event.keyCode == Keyboard.RIGHT)
{
_character.vx = 5;
if(!_star.launched)
{
_starDirection = "right";
}
}
else if (event.keyCode == Keyboard.UP)
{
_character.vy = -5;
if(!_star.launched)
{
_starDirection = "up";
}
}
else if (event.keyCode == Keyboard.DOWN)
{
_character.vy = 5;
if(!_star.launched)
{
_starDirection = "down";
}
}
if(event.keyCode == Keyboard.SPACE)
{
if(!_star.launched)
{
_star.x = _character.x + _character.width / 2;
_star.y = _character.y + _character.width / 2;
_star.launched = true;
}
}
}
TheenterFrameHandler
then sets the star's velocity to move it in any of those four directions depending on what_starDirection
is set to.
if(_star.launched)
{
_star.visible = true;
//Set the star's velocity based on the
//_starDirection variable set in the
//keyDownHandler
if(_starDirection == "up")
{
_star.vy = -3;
_star.vx = 0;
}
else if(_starDirection == "down")
{
_star.vy = 3;
_star.vx = 0;
}
else if(_starDirection == "left")
{
_star.vy = 0;
_star.vx = -3;
}
else if(_starDirection == "right")
{
_star.vy = 0;
_star.vx = 3;
}
//Move and rotate the star
_star.x += _star.vx;
_star.y += _star.vy;
_star.rotation += 5;
//Check its stage boundaries
checkStarStageBoundaries(_star);
//Check for collisions with the monsters
starVsMonsterCollision(_star, _monster1);
starVsMonsterCollision(_star, _monster2);
starVsMonsterCollision(_star, _monster3);
starVsMonsterCollision(_star, _monster4);
}
else
{
_star.visible = false;
}
Those are the only changes that need to be made.
Optionally you could create a property called direction inside theStar
class itself. You could then change this direction property like this:
_star.direction = "up"
You could then use it to check in which direction the star should move like this:
if(_star.Direction == "up")
{…
The style you use to track the star's direction is entirely up to you.
In Monster Mayhem the character can't fire another star while the first one is still on the stage. In many games, however, characters can fire limitless numbers of projectiles, no matter how many are on the stage. To implement this, you need to know about
arrays
and
loops
. You'll learn about arrays and loops in
Chapter 9
, and you'll learn how to use them to fire multiple projectiles in
Chapter 10
.
Level two ends in the same way as level one. The code checks to see whether the monsters or the character has won and displays the appropriate message.
Monster Mayhem is a great model for a game project, but if you're making a similar type of game, there are probably a few more advanced things you'll want it to do.
You can implement both these features. Let's find out how.
In
Chapter 7
I showed you how to make a game with a large scrolling background. The trick to making this work is to calculate the
scroll velocity
and apply it to game objects. This keeps their positions synchronized with the scrolling background. You're going to use exactly the same technique to make the objects in Monster Mayhem scroll so make sure that you fully understand the code from
Chapter 7
before you work through this next section.
In Monster Mayhem, the monsters and the star weapon are moving across the stage. This means that they have to adjust their velocities to account for the moving background. This is, fortunately, very easy to do but requires a bit of thought and planning, so I'm going to walk you through the details of this code so you'll be more easily able to implement scrolling in your own games.
You'll find a scrolling version of Monster Mayhem in the ScrollingMonsters project folder. It only has one level with four monsters, and the collision detection has been disabled so that it's easier to test how the scrolling works. Run the SWF file and you'll see that the monsters can now meander freely all over the game world (
Figure 8-22
).
Figure 8-22.
Moving objects in a large scrolling game world
I've structured this example in exactly the same way as the original Monster Mayhem so that you can see another, simplified version your game structure system at work. The application class,ScrollingMonsters,
creates an object called_scrollingLevel
and adds it to the stage. All of the game logic is in theScrollingLevel
class. Here's theScrollingMonsters
application class that loads the game level:
package
{
import flash.display.Sprite;
import flash.events.Event;
[SWF(width="550", height="400",
backgroundColor="#FFFFFF", frameRate="60")]
public class ScrollingMonsters extends Sprite
{
private var _scrollingLevel:ScrollingLevel;
public function ScrollingMonsters()
{
_scrollingLevel = new ScrollingLevel(stage);
stage.addChild(_scrollingLevel);
}
}
}
So that you can see all the code in context, here's the entireScrollingLevel
class that contains all the game logic. Most of it is identical to the code in theLevelOne
andLevelTwo
classes that you looked at earlier in the chapter. I've highlighted all the new code that's relevant to scrolling, and I'll explain all in detail ahead.
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.ui.Keyboard;
import flash.utils.Timer;
public class ScrollingLevel extends Sprite
{
//Declare the variables to hold
//the game objects
private var _character:Character;
private var _background:BigBackground;
private var _gameOver:GameOver;
private var _monster1:Monster;
private var _monster2:Monster;
private var _monster3:Monster;
private var _monster4:Monster;
private var _star:Star;
private var _levelWinner:String;
private var _stage:Object;
private var _starDirection:String;
//The timers
private var _monsterTimer:Timer;
private var _gameOverTimer:Timer;
//Variables needed for scrolling
private var _temporaryX:int;
private var _temporaryY:int;
private var _scroll_Vx:int;
private var _scroll_Vy:int;
private var _rightInnerBoundary:uint;
private var _leftInnerBoundary:uint;
private var _topInnerBoundary:uint;
private var _bottomInnerBoundary:uint;
private var _currentExplosion:Sprite = null;
public function ScrollingLevel(stage:Object)
{
_stage = stage;
this.addEventListener
(Event.ADDED_TO_STAGE, addedToStageHandler);
}
private function addedToStageHandler(event:Event):void
{
startGame();
this.removeEventListener
(Event.ADDED_TO_STAGE, addedToStageHandler);
}
private function startGame():void
{
//Create the game objects
_character = new Character();
_star = new Star();
_background = new BigBackground();
_monster1 = new Monster();
_monster2 = new Monster();
_monster3 = new Monster();
_monster4 = new Monster();
_gameOver = new GameOver();
//Add the game objects to the stage
addGameObjectToLevel
(
_background,
-(_background.width - stage.stageWidth) / 2,
-(_background.height - stage.stageHeight) / 2
);
addGameObjectToLevel(_monster1, 400, 125);
addGameObjectToLevel(_monster2, 150, 125);
addGameObjectToLevel(_monster3, 400, 250);
addGameObjectToLevel(_monster4, 150, 250);
addGameObjectToLevel(_character, 250, 175);
addGameObjectToLevel(_star, 250, 300);
_star.visible = false;
addGameObjectToLevel(_gameOver, 140, 130);
//Initialize the monster timer
_monsterTimer = new Timer(1000);
_monsterTimer.addEventListener
(TimerEvent.TIMER, monsterTimerHandler);
_monsterTimer.start();
//Event listeners
_stage.addEventListener
(KeyboardEvent.KEY_DOWN, keyDownHandler);
_stage.addEventListener
(KeyboardEvent.KEY_UP, keyUpHandler);
this.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
//Define the inner boundary variables
_rightInnerBoundary
= (_stage.stageWidth / 2) + (_stage.stageWidth / 4);
_leftInnerBoundary
= (_stage.stageWidth / 2) - (_stage.stageWidth / 4);
_topInnerBoundary
= (_stage.stageHeight / 2) - (_stage.stageHeight / 4);
_bottomInnerBoundary
= (_stage.stageHeight / 2) + (_stage.stageHeight / 4);
}
private function enterFrameHandler(event:Event):void
{
//Move the game character
_character.x += _character.vx;
_character.y += _character.vy;
//Move the monsters and
//check their stage boundaries
if(_monster1.visible)
{
_monster1.x += _monster1.vx;
_monster1.y += _monster1.vy;
checkStageBoundaries(_monster1);
}
if(_monster2.visible)
{
_monster2.x += _monster2.vx;
_monster2.y += _monster2.vy;
checkStageBoundaries(_monster2);
}
if(_monster3.visible)
{
_monster3.x += _monster3.vx;
_monster3.y += _monster3.vy;
checkStageBoundaries(_monster3);
}
if(_monster4.visible)
{
_monster4.x += _monster4.vx;
_monster4.y += _monster4.vy;
checkStageBoundaries(_monster4);
}
//Has the star been launched?
if(_star.launched)
{
//If it has, make it visible
_star.visible = true;
//Set the star's velocity based on the
//_starDirection variable set in the
//keyDownHandler
if(_starDirection == "up")
{
_star.vy = -3;
_star.vx = 0;
}
else if(_starDirection == "down")
{
_star.vy = 3;
_star.vx = 0;
}
else if(_starDirection == "left")
{
_star.vy = 0;
_star.vx = -3;
}
else if(_starDirection == "right")
{
_star.vy = 0;
_star.vx = 3;
}
//Move and rotate the star
_star.x += _star.vx;
_star.y += _star.vy;
_star.rotation += 5;
//Check its stage boundaries
checkStarStageBoundaries(_star);
//Check for collisions with the monsters
starVsMonsterCollision(_star, _monster1);
starVsMonsterCollision(_star, _monster2);
starVsMonsterCollision(_star, _monster3);
starVsMonsterCollision(_star, _monster4);
}
else
{
_star.visible = false;
}
//Collision detection between the
//character and monsters
//Uncomment this code to re-enable the collisions
/*
characterVsMonsterCollision(_character, _monster1);
characterVsMonsterCollision(_character, _monster2);
characterVsMonsterCollision(_character, _monster3);
characterVsMonsterCollision(_character, _monster4);
*/
//Scroll the background
//Calculate the scroll velocity
_temporaryX = _background.x;
_temporaryY = _background.y;
//Check the inner boundaries
if (_character.x < _leftInnerBoundary)
{
_character.x = _leftInnerBoundary;
_rightInnerBoundary
= (_stage.stageWidth / 2) + (_stage.stageWidth / 4);
_background.x -= _character.vx;
}
else if
(_character.x + _character.width > _rightInnerBoundary)
{
character.x
= _rightInnerBoundary - _character.width
_leftInnerBoundary
= (_stage.stageWidth / 2) - (_stage.stageWidth / 4);
_background.x -= _character.vx;
}
if (_character.y < _topInnerBoundary)
{
_character.y = _topInnerBoundary;
_bottomInnerBoundary
= (_stage.stageHeight / 2) + (_stage.stageHeight / 4);
_background.y -= _character.vy;
}
else if
(_character.y + _character.height > _bottomInnerBoundary)
{
_character.y = _bottomInnerBoundary - _character.height;
_topInnerBoundary
= (_stage.stageHeight / 2) - (_stage.stageHeight / 4);
_background.y -= _character.vy;
}
//Background stage boundaries
if (_background.x > 0)
{
_background.x = 0;
_leftInnerBoundary = 0;
}
else if (_background.y > 0)
{
_background.y = 0;
_topInnerBoundary = 0;
}
else if
(_background.x < _stage.stageWidth - _background.width)
{
_background.x = _stage.stageWidth - _background.width;
_rightInnerBoundary = _stage.stageWidth;
}
else if
(_background.y < _stage.stageHeight - _background.height)
{
_background.y = _stage.stageHeight - _background.height;
_bottomInnerBoundary = _stage.stageHeight;
}
//Character stage boundaries
if (_character.x < 50)
{
_character.x = 50;
}
if (_character.y < 50)
{
_character.y = 50;
}
if(_character.x + _character.width > _stage.stageWidth - 50)
{
_character.x = _stage.stageWidth - _character.width - 50;
}
if(_character.y + _character.height > _stage.stageHeight -50)
{
_character.y = _stage.stageHeight - _character.height - 50;
}
//Calculate the scroll velocity
_scroll_Vx = _background.x - _temporaryX;
_scroll_Vy = _background.y - _temporaryY;
//1. Scroll the moving objects
//Scroll the monsters
scroll(_monster1);
scroll(_monster2);
scroll(_monster3);
scroll(_monster4);
//2. Scroll the star
if(_star.launched)
{
scroll(_star);
}
//3. Scroll the current explosion
if(_currentExplosion != null)
{
scroll(_currentExplosion);
}
}
public function scroll(gameObject:Sprite):void
{
gameObject.x += _scroll_Vx;
gameObject.y += _scroll_Vy;
}
private function characterVsMonsterCollision
(character:Character, monster:Monster):void
{
if(monster.visible
&& character.hitTestObject(monster))
{
character.timesHit++;
checkGameOver();
}
}
private function starVsMonsterCollision
(star:Star, monster:Monster):void
{
if(monster.visible
&& star.hitTestObject(monster))
{
//Call the monster's "openMouth"
//method to make it open its mouth
monster.openMouth();
//Deactivate the star
star.launched = false;
//Add 1 to the monster's
//timesHit variable
monster.timesHit++;
//Has the monster been hit
//3 times?
if(monster.timesHit == 3)
{
//call the "killMonster"
//method
killMonster(monster);
//Check to see if the
//game is over
checkGameOver();
}
}
}
private function killMonster(monster:Monster):void
{
//Make the monster invisible
monster.visible = false;
//Create a new explosion object
//and add it to the stage
var explosion:Explosion = new Explosion();
this.addChild(explosion);
_currentExplosion = explosion;
//Center the explosion over
//the monster
explosion.x = monster.x -21;
explosion.y = monster.y -18;
//Call the explosion's
//"explode" method
explosion.explode();
}
private function checkGameOver():void
{
if(_monster1.timesHit == 3
&& _monster2.timesHit == 3
&& _monster3.timesHit == 3
&& _monster4.timesHit == 3)
{
_levelWinner = "character"
_gameOverTimer = new Timer(2000);
_gameOverTimer.addEventListener
(TimerEvent.TIMER, gameOverTimerHandler);
_gameOverTimer.start();
_monsterTimer.removeEventListener
(TimerEvent.TIMER, monsterTimerHandler);
this.removeEventListener
(Event.ENTER_FRAME, enterFrameHandler);
}
if(_character.timesHit == 1)
{
_levelWinner = "monsters"
_character.alpha = 0.5;
_gameOverTimer = new Timer(2000);
_gameOverTimer.addEventListener
(TimerEvent.TIMER, gameOverTimerHandler);
_gameOverTimer.start();
_monsterTimer.removeEventListener
(TimerEvent.TIMER, monsterTimerHandler);
this.removeEventListener
(Event.ENTER_FRAME, enterFrameHandler);
}
}
private function checkStageBoundaries(gameObject:Sprite):void
{
if (gameObject.x < _background.x + 50)
{
gameObject.x = _background.x + 50;
}
if (gameObject.y < _background.y + 50)
{
gameObject.y = _background.y + 50;
}
if (gameObject.x + gameObject.width
> _background.x + _background.width - 50)
{
gameObject.x
= _background.x + _background.width
- gameObject.width - 50;
}
if (gameObject.y + gameObject.height
> _background.y + _background.height - 50)
{
gameObject.y
= _background.y + _background.height
- gameObject.height - 50;
}
}
private function checkStarStageBoundaries(star:Star):void
{
if (star.y < 0
|| star.x < 0
|| star.x > _stage.stageWidth
|| star.y > _stage.stageHeight)
{
_star.launched = false;
}
}
private function monsterTimerHandler(event:TimerEvent):void
{
changeMonsterDirection(_monster1);
changeMonsterDirection(_monster2);
changeMonsterDirection(_monster3);
changeMonsterDirection(_monster4);
}
private function changeMonsterDirection(monster:Monster):void
{
var randomNumber:int = Math.ceil(Math.random() * 4);
if(randomNumber == 1)
{
//Right
monster.vx = 2;
monster.vy = 0;
}
else if (randomNumber == 2)
{
//Left
monster.vx = -2;
monster.vy = 0;
}
else if(randomNumber == 3)
{
//Up
monster.vx = 0;
monster.vy = -2;
}
else
{
//Down
monster.vx = 0;
monster.vy = 2;
}
}
private function gameOverTimerHandler(event:TimerEvent):void
{
if(_levelWinner == "character")
{
if(_gameOverTimer.currentCount == 1)
{
_gameOver.levelComplete.visible = true;
}
if(_gameOverTimer.currentCount == 2)
{
_gameOverTimer.reset();
gameOverTimer.removeEventListener
(TimerEvent.TIMER, gameOverTimerHandler);
//dispatchEvent(new Event("levelOneComplete", true));
}
}
if(_levelWinner == "monsters")
{
_gameOver.youLost.visible = true;
gameOverTimer.removeEventListener
(TimerEvent.TIMER, gameOverTimerHandler);
}
}
private function keyDownHandler(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.LEFT)
{
_character.vx = -5;
if(!_star.launched)
{
_starDirection = "left";
}
}
else if (event.keyCode == Keyboard.RIGHT)
{
_character.vx = 5;
if(!_star.launched)
{
_starDirection = "right";
}
}
else if (event.keyCode == Keyboard.UP)
{
_character.vy = -5;
if(!_star.launched)
{
_starDirection = "up";
}
}
else if (event.keyCode == Keyboard.DOWN)
{
_character.vy = 5;
if(!_star.launched)
{
_starDirection = "down";
}
}