Foundation Game Design with ActionScript 3.0, Second Edition (85 page)

BOOK: Foundation Game Design with ActionScript 3.0, Second Edition
10.22Mb size Format: txt, pdf, ePub

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;
    }
  }
}

The
enterFrameHandler
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 the
Star
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.

More Monster Mayhem!

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.

  • A scrolling background
    : You'll want to make a big environment larger than the borders of the stage and have the character and monsters move through it.
  • Intelligent monsters
    : The monsters in Monster Mayhem move randomly around the stage. Wouldn't it be fun if they could figure out where the character is and move towards it?

You can implement both these features. Let's find out how.

Moving objects in a scrolling game world

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

The scrolling game code

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 the
ScrollingLevel
class. Here's the
ScrollingMonsters
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 entire
ScrollingLevel
class that contains all the game logic. Most of it is identical to the code in the
LevelOne
and
LevelTwo
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";
        }
      }

Other books

The Journeyer by Jennings, Gary
Scalpers by Ralph Cotton
Go With Me by Castle Freeman
Still Into You by Andrews, Ryleigh
Tied Up and Twisted by Alison Tyler
The Devil's Thief by Samantha Kane