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

BOOK: Foundation Game Design with ActionScript 3.0, Second Edition
3.03Mb size Format: txt, pdf, ePub
      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;
        }
      }
    }
    private function keyUpHandler(event:KeyboardEvent):void
    {
      if (event.keyCode == Keyboard.LEFT
      || event.keyCode == Keyboard.RIGHT)
      {
        _character.vx = 0;
      }
      else if (event.keyCode == Keyboard.DOWN
      || event.keyCode == Keyboard.UP)
      {
        _character.vy = 0;
      }
    }
    private function addGameObjectToLevel
      (gameObject:Sprite, xPos:int, yPos:int):void
    {
      this.addChild(gameObject);
      gameObject.x = xPos;
      gameObject.y = yPos;
    }
  }
}
How the scrolling code works

To simplify the code a bit, I've declared all the variables and objects you need for scrolling as instance variables in the class definition. This means they can be accessed anywhere in the class by any method or object whenever they're needed.

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;

The
startGame
method then initializes 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);

The
enterFrameHandler
calculates the scroll velocity by first capturing the background's position in temporary variables, moving the background, and then calculating the scroll velocity.

_temporaryX = _background.x;
_temporaryY = _background.y;
//...scroll the background...
_scroll_Vx = _background.x - _temporaryX;
_scroll_Vy = _background.y - _temporaryY;

With the
_scroll_Vx
and
_scroll_Vy
variables now in the bag, you can scroll the game objects using the
scroll
method.

//Scroll the monsters
scroll(_monster1);
scroll(_monster2);
scroll(_monster3);
scroll(_monster4);
//Scroll the star
if(_star.launched)
{
  scroll(_star);
}

Here's the
scroll
method that does this work:

public function scroll(gameObject:Sprite):void
{
  gameObject.x += _scroll_Vx;
  gameObject.y += _scroll_Vy;
}

Because the game world is now so much bigger, the
checkStageBoundaries
method needs to compensate for this. Here's the updated
checkStageBoundaries
method that makes sure the game objects don't cross the edges of the playing field:

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

The
checkStarStageBoundaries
method is also modified to let the star move to the edge of the stage boundaries.

private function checkStarStageBoundaries(star:Star):void
{
  if (star.y < 0
  || star.x < 0
  || star.x > _stage.stageWidth
  || star.y > _stage.stageHeight)
  {
     _star.launched = false;
  }
}

As you can see, all this code just compensates for the larger game world and adds the scroll velocity to the game objects' velocities.

There's one exception that you need to take a special look at: how to scroll the explosions.

Scrolling the explosions

All the explosion objects are created locally in the
killMonster
method, like this:

var explosion:Explosion = new Explosion();
this.addChild(explosion);

That means they can't be accessed outside the
killMonster
method. This is a problem because the
enterFrameHandler
needs some sort of access to them so that they can be scrolled after they're added to the stage.

A simple solution to this is to create an instance variable that stores a reference to the current explosion. You'll recall that instance variables are any variables declared in the class definition. They're available to
all objects and all methods anywhere in the class. I created an instance variable called
_currentExplosion
in the class definition and gave it an initial value of null.

private var _currentExplosion:Sprite = null;

It's a Sprite, so it can contain any objects that extend the Sprite class, just as the explosion objects do.

When the
killMonster
class creates a temporary explosion object, all you need to do is copy it into the
_currentExplosion
variable, like this:

var explosion:Explosion = new Explosion();
this.addChild(explosion);
_currentExplosion = explosion;

This lets you hold a reference to the temporary explosion that can be used in other methods in the class. The
enterFrameHandler
can use this
_currentExplosion
object to scroll the explosion, like this:

if(_currentExplosion != null)
{
  scroll(_currentExplosion);
}

This technique is a quick and easy way to reference temporary objects and use them outside of the method where they were created. But it might not be the best way. It starts to become complicated to track and control temporary objects, especially if you need to access more than one at a time. In
Chapter 10
you'll learn how to use loops and arrays to precisely add, remove, and control temporary objects in a game.

Intelligent monsters

It's very easy to make the monsters actively hunt the character. All the code needs to do is figure out whether a monster is above, below, to the left, or to the right of the character. Once you know that, just change the monster's velocity so that it moves towards the player. The code you need to write follows this logic.

if(the character is to the left of the monster)
{
  move the monster to the left
}

Here's what the actual code could look like in Monster Mayhem:

if(_character.x < monster.x)
{
  monster.vx = -2;
  monster.vy = 0;
}

As you can see, it's just a simple matter of comparing the x position of the character to the x position of the monster and then moving the monster to the left.

You can see all this code in action in the IntelligentMonsters example in the project's source files. Run the SWF and you'll notice that the monsters chase the character around the stage. Every second they decide whether they should move closer to the character by going left or right, or by going up or down. If you stop moving the character, you'll notice the monsters swarm around it.
Figure 8-23
illustrates the monsters' behavior.

Figure 8-23.
Monsters that chase the player

The code that does this is extremely simple. All it requires is a small change to the
changeMonsterDirection
method. Here's the updated method:

private function changeMonsterDirection(monster:Monster):void
{
  //Choose a random number between 1 and 2
  var randomNumber:int = Math.ceil(Math.random() * 2);
  //If the number is 1, move closer to the character
  //on the horizontal axis
  if(randomNumber == 1)
  {
    if(_character.x > monster.x)
    {
      //Right
      monster.vx = 2;
      monster.vy = 0;
    }
    if(_character.x < monster.x)
    {
      //Left
      monster.vx = -2;
      monster.vy = 0;
    }
  }
  //If the number is 2, move closer to the character
  //on the vertical axis
  if(randomNumber == 2)
  {
    if(_character.y < monster.y)
    {
      //Up
      monster.vx = 0;
      monster.vy = -2;
    }
    if(_character.y > monster.y)
    {
      //Down
      monster.vx = 0;
      monster.vy = 2;
    }
  }
}

The code first chooses a random number between 1 and 2.

var randomNumber:int = Math.ceil(Math.random() * 2);

If the number is 1, the monster decides to move closer to the character on the x axis.

if(randomNumber == 1)
{
   //Move left or right...
}

If the random number is 2, the monster moves closer to the character on the y axis.

if(randomNumber == 2)
{
   //Move up or down...
}

I've added this bit of randomness to make the game slightly easier to play. You could easily make the monsters very precise in their direction choices by making them move only in the direction that is closest to the character. To do this, compare the distance between the x and y positions and have the monster choose whichever distance is the longest. Use the
Math.abs
method to find out what the distances are without having to worry about whether the x and y positions of the objects are positive or negative.

//Find out whether the monster is closer to the
//character on the x axis or the y axis
var vx:Number = Math.abs(_character.x - monster.x);
var vy:Number = Math.abs(_character.y - monster.y);
//Move the character right or left if it's
//already closer on the y axis
if(vx>vy)
{
  if(_character.x > monster.x)
  {
    //Right
    monster.vx = 2;
    monster.vy = 0;
  }
  else
  {
    //Left
    monster.vx = -2;
    monster.vy = 0;
  }
}
//Move the character up or down if it's
//already closer on the x axis
else
{
  if(_character.y < monster.y)
  {
    //Up
    monster.vx = 0;
    monster.vy = -2;
  }
  else
  {
    //Down
    monster.vx = 0;
    monster.vy = 2;
  }
}

This modification lets the monsters zero in on the character with surgical precision. It would make for a very difficult game, but by experimenting with a bit of extra added randomness you'll be able find a good balance that will make for a fun and challenging game. You'll find all this working code in the comments of
changeMonsterDirection
method in the
IntelligentMonsters
class.

Obstacles and pathfinding

This has been your first introduction to area of game design called Artificial Intelligence (usually referred to as AI). It's a big topic, and if you're serious about game design (which you are!) you'll want to explore it in much more depth.

A further modification that you'll want to make to Monster Mayhem is to add obstacles, like a maze for the monsters to navigate around. You could do this by using the
Collision.block
method that you used in the previous two chapters and make the monsters change their directions when they hit a section of the wall. But because each monster will be checking for collisions between each and every section of the wall,
you'd have to manage a huge amount of repetitive code. In
Chapter 10
you'll learn how to handle multiple object collisions and I'll revisit this problem with a slice of very efficient and compact code.

You may also want to make your monsters find their way intelligently around a maze, possibly by the shortest path. To do this, you'll need to look at the
tile-based
approach to building games and implement a famous piece of code called
A-Star.
You can find out how to do all of this in
Advanced Game Design with Flash.

A little more about game structure

The structure for building games that you've used in this chapter will likely become the model for all the games you build from now on. Here's a quick view of how it works:

  • The application class loads the level classes and switches levels when a level dispatches an event that informs the game that the level is complete.
  • The level classes contain all the game logic and control the game objects.
  • Each object in the game is made from its own class. The game objects have properties that contain important data about those objects and manage their changes of state. They don't contain any code that's specific to the logic of the game. This makes it easy to reuse the same game object classes in other games.

Other books

In Medias Res by Yolanda Wallace
No Longer Mine by Shiloh Walker
Come as You Are by Emily Nagoski
Texas Wild by Brenda Jackson
Black Sun Reich by Trey Garrison
Road To Love by Brewer, Courtney
The Alpha King by Vicktor Alexander