SimpleAVRG/Part 2

This is part two of the SimpleAVRG tutorial. It details the Game Property Space.

Adding a Global Monster Counter
AVRGs have three distinct property spaces: game, room and player. In this tutorial, we will add a game-wide monster counter that demonstrates the game property space.

In Server.as, add a counter for the number of monsters in the game and a system to periodically increment the value.

Server.as
...

public class Server extends ServerObject {   /**      * This is the interval at which the game decides whether or not to add a     * monster and the odds it will add a monster each time. */   public const ADD_MONSTER_DELAY :int = 1000;

public const ADD_MONSTER_CHANCE :Number = 0.25;

/**    * Constructs a new server agent. */   public function Server {       ...

// start with no monsters _numMonsters = 0; _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, 0);

// add monsters periodically _monsterTimer = new Timer(ADD_MONSTER_DELAY); _monsterTimer.addEventListener(TimerEvent.TIMER, maybeAddMonster); _monsterTimer.start; }

// Consider adding a monster. protected function maybeAddMonster (event :TimerEvent) :void {       if (Math.random <= ADD_MONSTER_CHANCE) { addMonster; }   }

...

// A timer for periodically adding monsters. protected var _monsterTimer :Timer;

...

For now, adding a monster just means incrementing a local (to the server) counter and updating a global property with the new value.

Server.as ...

// Add a monster to the game and broadcast its addition. protected function addMonster :void {       _numMonsters += 1; _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, _numMonsters); }

...

It's best to stop timers and detach listeners at unload time to help Flash clean up. This is done with a handler on the UNLOAD event:

Server.as ...

public function Server {       ...

// listen for an unload event _control.addEventListener(Event.UNLOAD, handleUnload);

...   }

...

// Don't pollute! Unload your resources. protected function handleUnload (event :Event) :void {       if (_monsterTimer != null) { _monsterTimer.stop; _monsterTimer.removeEventListener(TimerEvent.TIMER, maybeAddMonster); }   }

...

GLOBAL_MONSTER_COUNT is a string constant. This allows the compiler to check for typographical errors, and allows editors with code completion and refactoring support do their thing. It is defined in a class set aside for just such definitions:

SimpleAVRGConstants.as
// // $Id$ // // The property names for SimpleAVRG

package {

public class SimpleAVRGConstants { public static const GLOBAL_MONSTER_COUNT:String = "globalMonsterCount"; }

}

On the client side, we will need to build a display for the monster count then listen for changes to the number and update the display as necessary.

The constructor in SimpleAVRG will need to build the display and attach the listeners:

SimpleAVRG.as
...

import com.whirled.net.PropertyChangedEvent;

...

public function SimpleAVRG {       _control = new AVRGameControl(this);

// Set up the display. buildDisplay;

// listen for an unload event _control.addEventListener(Event.UNLOAD, handleUnload);

if (_control.isConnected) { // Set the initial values on the score board. _scoreBoard.setGlobalMonsterCount(_control.game.props.get(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT));

// listen for game level property changes _control.game.props.addEventListener(PropertyChangedEvent.PROPERTY_CHANGED, handleGamePropertyChanged); }   }

/**    * Build the display. */   protected function buildDisplay :void {       _scoreBoard = new ScoreBoard; _scoreBoard.x = 0; _scoreBoard.y = 0; addChild(_scoreBoard); }

/**    * This is called when a server property has changed. */   protected function handleGamePropertyChanged (event :PropertyChangedEvent) :void {       // If the global monster count has changed, update the notice board. if (event.name == SimpleAVRGConstants.GLOBAL_MONSTER_COUNT) { _scoreBoard.setGlobalMonsterCount(event.newValue as int); }   }

/**    * This is called when your game is unloaded. */   protected function handleUnload (event :Event) :void {       // stop any sounds, clean up any resources that need it. This specifically includes // unregistering listeners to any events - especially Event.ENTER_FRAME if (_control != null) { _control.game.props.removeEventListener(PropertyChangedEvent.PROPERTY_CHANGED, handleGamePropertyChanged); }   }

protected var _control :AVRGameControl; protected var _scoreBoard :ScoreBoard;

...

For now, the score board is just a Sprite with a rectangle and a text field drawn onto it. It's simple, but it works.

ScoreBoard.as
// // $Id$ // // ScoreBoard - A game state data display for SimpleAVRG.

package {

import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize;

public class ScoreBoard extends Sprite {   public static const WIDTH :int = 150; public static const HEIGHT :int = 100;

/**    * Create a new score board. */   public function ScoreBoard {       buildDisplay; }

/**    * Set the display for the total number of monsters in the game to the given value. */   public function setGlobalMonsterCount (monsters :int) :void {       _globalMonsterCountDisplay.text = "Monsters in game: " + monsters; }

// Set up the display objects. protected function buildDisplay :void {       graphics.beginFill(0x666666); graphics.drawRoundRect(0, 0, WIDTH, HEIGHT, 3, 3); graphics.endFill;

_globalMonsterCountDisplay = new TextField; _globalMonsterCountDisplay.autoSize = TextFieldAutoSize.LEFT; _globalMonsterCountDisplay.text = "Monsters in game: 0"; // This will usually be immediately updated. _globalMonsterCountDisplay.x = 5; _globalMonsterCountDisplay.y = 10; addChild(_globalMonsterCountDisplay); }

// The display for the total number of monsters in the game. protected var _globalMonsterCountDisplay :TextField; }

}

The project source directory now contains these files (and probably some build related artifacts):

simpleavrg/ ScoreBoard.as   Server.as    SimpleAVRG.as    SimpleAVRGConstants.as    build.bat build.xml

Source Files
Here are the source files' contents.

ScoreBoard.as
// // $Id$ // // ScoreBoard - A game state data display for SimpleAVRG.

package {

import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize;

public class ScoreBoard extends Sprite {   public static const WIDTH :int = 150; public static const HEIGHT :int = 100;

/**    * Create a new score board. */   public function ScoreBoard {       buildDisplay; }

/**    * Set the display for the total number of monsters in the game to the given value. */   public function setGlobalMonsterCount (monsters :int) :void {       _globalMonsterCountDisplay.text = "Monsters in game: " + monsters; }

// Set up the display objects. protected function buildDisplay :void {       graphics.beginFill(0x666666); graphics.drawRoundRect(0, 0, WIDTH, HEIGHT, 3, 3); graphics.endFill;

_globalMonsterCountDisplay = new TextField; _globalMonsterCountDisplay.autoSize = TextFieldAutoSize.LEFT; _globalMonsterCountDisplay.text = "Monsters in game: 0"; // This will usually be immediately updated. _globalMonsterCountDisplay.x = 5; _globalMonsterCountDisplay.y = 10; addChild(_globalMonsterCountDisplay); }

// The display for the total number of monsters in the game. protected var _globalMonsterCountDisplay :TextField; }

}

Server.as
// // $Id$ // // The server agent for SimpleAVRG - an AVR game for Whirled

package {

import com.whirled.ServerObject; import com.whirled.avrg.AVRServerGameControl; import flash.events.Event; import flash.events.TimerEvent; import flash.utils.Timer;

/** * The server agent for SimpleAVRG. Automatically created by the * whirled server whenever a new game is started. */ public class Server extends ServerObject {   /**      * This is the interval at which the game decides whether or not to add a     * monster and the odds it will add a monster each time. */   public const ADD_MONSTER_DELAY :int = 1000;

public const ADD_MONSTER_CHANCE :Number = 0.25;

/**    * Constructs a new server agent. */   public function Server {       trace("SimpleAVRG server agent reporting for duty!");

_control = new AVRServerGameControl(this);

// listen for an unload event _control.addEventListener(Event.UNLOAD, handleUnload);

// start with no monsters _numMonsters = 0; _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, 0);

// add monsters periodically _monsterTimer = new Timer(ADD_MONSTER_DELAY); _monsterTimer.addEventListener(TimerEvent.TIMER, maybeAddMonster); _monsterTimer.start; }

// Consider adding a monster. protected function maybeAddMonster (event :TimerEvent) :void {       if (Math.random <= ADD_MONSTER_CHANCE) { addMonster; }   }

// Add a monster to the game and broadcast its addition. protected function addMonster :void {       _numMonsters += 1; _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, _numMonsters); }

// Don't pollute! Unload your resources. protected function handleUnload (event :Event) :void {       if (_monsterTimer != null) { _monsterTimer.stop; _monsterTimer.removeEventListener(TimerEvent.TIMER, maybeAddMonster); }   }

// A timer for periodically adding monsters. protected var _monsterTimer :Timer;

// This is the total number of monsters in the whole game. protected var _numMonsters :int = 0;

protected var _control :AVRServerGameControl; }

}

SimpleAVRG.as
// // $Id$ // // SimpleAVRG - an AVR game for Whirled

package {

import flash.display.Sprite; import flash.events.Event; import com.whirled.avrg.AVRGameControl; import com.whirled.net.PropertyChangedEvent;

public class SimpleAVRG extends Sprite {   public function SimpleAVRG {       _control = new AVRGameControl(this);

// Set up the display. buildDisplay;

// listen for an unload event _control.addEventListener(Event.UNLOAD, handleUnload);

if (_control.isConnected) { // Set the initial values on the score board. _scoreBoard.setGlobalMonsterCount(_control.game.props.get(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT));

// listen for game level property changes _control.game.props.addEventListener(PropertyChangedEvent.PROPERTY_CHANGED, handleGamePropertyChanged); }   }

/**    * Build the display. */   protected function buildDisplay :void {       _scoreBoard = new ScoreBoard; _scoreBoard.x = 0; _scoreBoard.y = 0; addChild(_scoreBoard); }

/**    * This is called when a game property has changed. */   protected function handleGamePropertyChanged (event :PropertyChangedEvent) :void {       // If the global monster count has changed, update the notice board. if (event.name == SimpleAVRGConstants.GLOBAL_MONSTER_COUNT) { _scoreBoard.setGlobalMonsterCount(event.newValue as int); }   }

/**    * This is called when your game is unloaded. */   protected function handleUnload (event :Event) :void {       // stop any sounds, clean up any resources that need it. This specifically includes // unregistering listeners to any events - especially Event.ENTER_FRAME if (_control != null) { _control.game.props.removeEventListener(PropertyChangedEvent.PROPERTY_CHANGED, handleGamePropertyChanged); }   }

protected var _control :AVRGameControl; protected var _scoreBoard :ScoreBoard; } }

SimpleAVRGConstants.as
// // $Id$ // // The property names for SimpleAVRG

package {

public class SimpleAVRGConstants { public static const GLOBAL_MONSTER_COUNT:String = "globalMonsterCount"; }

}

As the project gets larger, it may become necessary to apply some structure. The classes could be split into packages by purpose and the source, resource and document files could be moved into their own directories to declutter the project root directory. This arrangement is good enough for now, though.

What have you done?
You have now added a game property and client-side listeners for changes to that property.

If you build and upload the results, you'll get something like this:

The next step will add a room property and listeners.


 * Return to the previous step
 * Proceed to the next step