SimpleAVRG/Part 5

This is part five of the SimpleAVRG tutorial. It details Mobs.

While working on an AVRG, you may want to keep browser tabs open to these pages:
 * http://www.whirled.com/code/asdocs/
 * http://www.whirled.com/code/asdocs/com/whirled/avrg/AVRGameControl.html
 * http://www.whirled.com/code/asdocs/com/whirled/avrg/AVRServerGameControl.html
 * http://livedocs.adobe.com/flex/3/langref/index.html

Adding Interactive Mobs to Represent the Monsters
Instead of abstract monsters represented by the room's numbers ticking up, we're going to add simple interactive monsters that appear in the room and can be clicked on and caught. We will build these interactive monsters using Mobs.

This will require changes on both the client and the server. On the server side, the rooms' monster inventory will need to be a list of monster mob ids instead of just a count of monsters.

The room controllers on the server side will be responsible for spawning and despawning the mobs. The handlers for a player entering or leaving a room will now need trigger some mob related activity if it is the first player into or the last player out of the room.

Server.as
...

// This is called when a player joins the game. protected function handlePlayerJoinedGame (event :AVRGameControlEvent) :void {       // Retrieve the player id from the event. var playerId :int = int(event.value);

// Give the player a basket. _basketMonsters[playerId] = 0;

// Listen for the player moving from room to room so that after the last // player leaves a room, the system can turn out the lights. _control.getPlayer(playerId).addEventListener(AVRGamePlayerEvent.ENTERED_ROOM, handlePlayerEnteredRoom); _control.getPlayer(playerId).addEventListener(AVRGamePlayerEvent.LEFT_ROOM, handlePlayerLeftRoom); }

// This is called when a player enters a room. protected function handlePlayerEnteredRoom (event :AVRGamePlayerEvent) :void {       var playerId :int = event.playerId; var roomId :int = int(event.value); // If the room isn't already set up, set it up now. if (_roomMonsters[roomId] == null) { setupRoom(roomId); }   }

// This is called when a player leaves a room (or the game). protected function handlePlayerLeftRoom (event :AVRGamePlayerEvent) :void {       // Retrieve the player id and the room id from the event. var playerId :int = event.playerId; var roomId :int = int(event.value);

// Check whether any players remain in the room. if (_control.getRoom(roomId).getPlayerIds.length == 0) { emptyRoom(roomId); }   }

...

// Set up a new room for players who have bravely gone where no player has gone before // ...or at least where no other player was at the moment. protected function setupRoom (roomId :int) :void {       var room :RoomSubControlServer = _control.getRoom(roomId); room.addEventListener(AVRGameRoomEvent.ROOM_UNLOADED, handleRoomUnloaded); room.addEventListener(AVRGameRoomEvent.MOB_CONTROL_AVAILABLE, handleMobControlAvailable);

_roomMonsters[roomId] = new HashSet; }

...

We introduce the _nextMobId member to track the next mob id that's available for use. The addMonster method instructs the room to spawn a new mob with the next mob id.

Server.as ...

protected function addMonster (roomId :int) :void {       // Add a monster to the room. var room :RoomSubControlServer = _control.getRoom(roomId);

var mobId :String = String(_nextMobId);

_log.info("Adding monster #" + mobId + " to room #" + roomId); if (!_roomMonsters[roomId].contains(_nextMobId)) {

// Create the mob at a random location, but on the floor. room.spawnMob(mobId, MonsterMob.GENERIC_MONSTER, Math.random, 0, Math.random); _nextMobId += 1;

} else { _log.error("Attempted to add monster #" + mobId + " to room #" + roomId                     + ", but the monster was already there!"); }   }

...

Most of the work of actually adding the monster needs to happen after the server has spawned it and its mob controller has become available. The room.spawnMob(...) method described above will result in a AVRGameRoomEvent.MOB_CONTROL_AVAILABLE event being fired, so each room in the game needs to listen for these events and handle them appropriately.

When a mob control becomes available to a room, it adds that monster's id to its collection of monster ids, increments the room's monster count property, and increments the global monster count property.

Server.as ...

// This is called when a mob has been spawned in a room. protected function handleMobControlAvailable (event :AVRGameRoomEvent) :void {       // Add the monster id to the local dictionary of monsters in rooms. var mobId :String = event.name; _roomMonsters[event.roomId].add(mobId);

// Set the room-level count. _control.getRoom(event.roomId).props.set(SimpleAVRGConstants.ROOM_MONSTER_COUNT,           _roomMonsters[event.roomId].size);

// Store the sum so we don't have to recalculate it. _numMonsters += 1;

// Transmit the new global total. _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, _numMonsters); }

...

Handling the capture of a monster is a little bit different now, too. The client sends a capture request message as before, but in addition to decrementing the room's monster count as before, the server also needs to remove the monster's id from the room's collection of monsters.

Server.as ...

// This is called when a game message has been received. protected function handleGameMessageReceived (event :MessageReceivedEvent) :void {       if (event.name == SimpleAVRGConstants.CATCH_MONSTER_REQUEST_MESSAGE) { // Find the player catching a monster and the room from which it is being caught. var playerId :int = event.senderId; var roomId :int = _control.getPlayer(event.senderId).getRoomId; var mobId :String = String(event.value);

_log.info("Player #" + playerId + " is attempting to take monster #" + mobId                    + " from room #" + roomId + "."); var monsters :Set = Set(_roomMonsters[roomId]); if (monsters != null && monsters.contains(mobId)) { // Remove the mob. _control.getRoom(roomId).despawnMob(mobId);

// Set the room-level count. monsters.remove(mobId); _control.getRoom(roomId).props.set(SimpleAVRGConstants.ROOM_MONSTER_COUNT,                   monsters.size);

// Set the global count. removeGlobalMonsters(1);

// Set the player's basket count. _basketMonsters[playerId] += 1; _control.getPlayer(playerId).props.set(SimpleAVRGConstants.BASKET_MONSTER_COUNT,                   _basketMonsters[playerId]); } else { _log.warning("Player #" + playerId + " attempted to remove monster #" + mobId                           + " from room #" + roomId + ", but the monster wasn't there!"); }       }    }

...

The client also needs to listen for AVRGameRoomEvent.MOB_CONTROL_AVAILABLE events to know that a monster has been added to a room. When that happens, it invokes its mob sprite exporter function to build a sprite representing the new monster.

SimpleAVRG.as
...

public function SimpleAVRG {       ...

// Set up a function for drawing mobs. _control.local.setMobSpriteExporter(createMobSprite);

...

// Listen for mobs appearing in the room. _control.room.addEventListener(AVRGameRoomEvent.MOB_CONTROL_AVAILABLE, handleMobControlAvailable);

...   }

/**    * This builds a new mob sprite. */   protected function createMobSprite (type :String) :DisplayObject {       return new MonsterSprite(type); }

...

/**    * This is called when a mob enters the room. */   protected function handleMobControlAvailable (event :AVRGameRoomEvent) :void {       // The mob sprite exporter will already have built the sprite. Fetch it       // by mob id and cast it into a MonsterSprite variable. var mobSprite :MonsterSprite = MonsterSprite(           _control.room.getMobSubControl(event.name).getMobSprite); mobSprite.mobId = event.name;

mobSprite.addEventListener(MouseEvent.CLICK, handleMobSpriteClicked); mobSprite.addEventListener(Event.UNLOAD, function (event :Event) :void {           removeEventListener(MouseEvent.CLICK, handleMobSpriteClicked);        }); }

...

Clicking on a monster catches it, removing it from the room and adding it to the player's basket. To achieve this, we put a click event handler on each monster sprite, as shown in the snippet above.

The handler simply determines which monster has been clicked and passes its id back to the server with a catch monster request message. On the server, the handleGameMessageReceived function is invoked as shown above. It calls the room controller's despawnMob(...) method (still on the server). The server then despawns the mob and sends a message to the client, which removes its sprite without any further code from us.

SimpleAVRG.as ...

/**    * This is called when a mob sprite has been clicked. */   protected function handleMobSpriteClicked(event :MouseEvent) :void {       if (event.target is MonsterSprite) { var monsterSprite :MonsterSprite = MonsterSprite(event.target); _control.agent.sendMessage(SimpleAVRGConstants.CATCH_MONSTER_REQUEST_MESSAGE, monsterSprite.mobId); }   }

...

MonsterMob contains monster mob behavior on the server. For now, it just contains a string constant for the only monster we've defined. This constant cannot go in the MonsterSprite class because the server side is unable to instantiate Sprite objects. The constant could go in our general constant class (SimpleAVRGConstants), but we hope to eventually add enough monster types to warrant special treatment.

The monster sprite itself is a simple extension of Sprite. For now, all of the monsters are drawn as pinkish spheres.

MonsterMob.as
...

/** * A monster! */ public class MonsterMob { public static const GENERIC_MONSTER :String = "genericMonster"; }

...

MonsterSprite.as
...

public class MonsterSprite extends Sprite {

public function MonsterSprite (type :String) {       super; graphics.beginGradientFill(GradientType.RADIAL, [0xff0000, 0xcc22cc, 0xff00ff], [1, 0.80, 0.50], [0, 150, 255]); graphics.drawCircle(25, 25, 50); graphics.endFill; }   public var mobId :String; }

...

Some of the other classes have small changes that can be gleaned from the source listing below. The capture monster button is removed from ScoreBoard.as, for example.

Source Files
The source now looks like this:

MonsterMob.as
// // MonsterMob.as // // Defines monster mob constants and behaviors.

package {

/** * A monster! */ public class MonsterMob { public static const GENERIC_MONSTER :String = "genericMonster"; }

}

MonsterSprite.as
// // MonsterSprite.as // // A sprite representing a monster in the game.

package {

import flash.display.GradientType; import flash.display.Sprite;

public class MonsterSprite extends Sprite {

public function MonsterSprite (type :String) {       super; graphics.beginGradientFill(GradientType.RADIAL, [0xff0000, 0xcc22cc, 0xff00ff], [1, 0.80, 0.50], [0, 150, 255]); graphics.drawCircle(25, 25, 50); graphics.endFill; }   public var mobId :String; }

}

ScoreBoard.as
// // ScoreBoard.as // // A game state data display for SimpleAVRG.

package {

import com.threerings.flash.SimpleTextButton; import com.threerings.util.Log; import flash.display.Sprite; import flash.events.MouseEvent; 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;

// The base X position of the text fields. public static const FIELD_LEFT :int = 5;

// The base Y position and height (including padding) of the text fields. public static const FIELD_TOP :int = 10; public static const FIELD_HEIGHT :int = 15;

/**    * 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 the display for the number of monsters in the room. */   public function setRoomMonsterCount (monsters :int) :void {       _roomMonsterCountDisplay.text = "Monsters in room: " + monsters; }

/**    * Set the display for the number of monsters in the player's basket. */   public function setBasketMonsterCount (monsters :int) :void {       _basketMonsterCountDisplay.text = "Monsters in basket: " + 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; initCounterDisplay(_globalMonsterCountDisplay, "Monsters in game");

_roomMonsterCountDisplay = new TextField; initCounterDisplay(_roomMonsterCountDisplay, "Monsters in room");

_basketMonsterCountDisplay = new TextField; initCounterDisplay(_basketMonsterCountDisplay, "Monsters in basket"); }

// Set the label and position of a counter display. protected function initCounterDisplay (field :TextField, label :String) :void {       field.autoSize = TextFieldAutoSize.LEFT; field.text = label + ": 0"; // This will usually be immediately updated. field.x = FIELD_LEFT; field.y = FIELD_TOP + FIELD_HEIGHT * _countDisplaysAdded++; addChild(field); }

// This is called when the "catch a monster" button has been clicked. protected function handleCatchMonsterClick (event :MouseEvent) :void {       dispatchEvent( new ScoreBoardEvent(ScoreBoardEvent.CATCH_MONSTER_REQUEST) ); }

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

// The display for the number of monsters in the room. protected var _roomMonsterCountDisplay :TextField;

// The display for the number of monsters in the player's basket. protected var _basketMonsterCountDisplay :TextField;

// A counter of the number of text fields added so far. protected var _countDisplaysAdded :int = 0;

// A button for catching monsters. protected var _catchMonsterButton :SimpleTextButton;

// The logger. protected var _log :Log = Log.getLog(ScoreBoard); }

}

ScoreBoardEvent.as
// // ScoreBoardEvent.as // // An event generated by the ScoreBoard.

package {

import flash.events.Event;

public class ScoreBoardEvent extends Event { public static const CATCH_MONSTER_REQUEST :String = "catchMonsterRequestEvent";

public function ScoreBoardEvent(type:String) { super(type); } }

}

Server.as
// // Server.as // // The server agent for SimpleAVRG

package {

import com.threerings.util.HashSet; import com.threerings.util.Log; import com.threerings.util.Set; import com.whirled.ServerObject; import com.whirled.avrg.AVRServerGameControl; import com.whirled.avrg.AVRGameControlEvent; import com.whirled.avrg.AVRGamePlayerEvent; import com.whirled.avrg.AVRGameRoomEvent; import com.whirled.avrg.RoomSubControlServer; import com.whirled.net.MessageReceivedEvent; import flash.events.Event; import flash.events.TimerEvent; import flash.utils.Dictionary; 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 * monsters and the odds that it will add a monster for each player it    * considers. */   public const ADD_MONSTER_DELAY :int = 1000;

public const ADD_MONSTER_CHANCE :Number = 0.05;

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

_control = new AVRServerGameControl(this);

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

// listen for players joining and quitting the game _control.game.addEventListener(AVRGameControlEvent.PLAYER_JOINED_GAME,           handlePlayerJoinedGame); _control.game.addEventListener(AVRGameControlEvent.PLAYER_QUIT_GAME,           handlePlayerQuitGame);

// start with no monsters _numMonsters = 0; _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, 0); // This dictionary will store the count of monsters in each room. _roomMonsters = new Dictionary;

// This dictionary will store the count of monsters in each player's basket. _basketMonsters = new Dictionary;

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

// listen for game-level messages _control.game.addEventListener(MessageReceivedEvent.MESSAGE_RECEIVED,           handleGameMessageReceived); }

// Consider adding monsters. protected function maybeAddMonsters (event :TimerEvent) :void {       // For each player in the game, consider adding a monster to that // player's location. This means that rooms with more players in them // have greater odds of receiving monsters. for each (var playerId :int in _control.game.getPlayerIds) { if (Math.random <= ADD_MONSTER_CHANCE) { addMonster( _control.getPlayer(playerId).getRoomId ); }       }    }

// Add a monster to a room and broadcast its addition. // This should only be called for a room that is known to contain at least // one player. protected function addMonster (roomId :int) :void {       // Add a monster to the room. var room :RoomSubControlServer = _control.getRoom(roomId);

var mobId :String = String(_nextMobId);

_log.info("Adding monster #" + mobId + " to room #" + roomId); if (!_roomMonsters[roomId].contains(_nextMobId)) {

// Create the mob at a random location, but on the floor. room.spawnMob(mobId, MonsterMob.GENERIC_MONSTER, Math.random, 0, Math.random); _nextMobId += 1;

} else { _log.error("Attempted to add monster #" + mobId + " to room #" + roomId                     + ", but the monster was already there!"); }   }

// Remove a number of monsters from the game and report the removal. protected function removeGlobalMonsters (removedMonsters :int) :void {       _log.info("Removing " + removedMonsters + " monsters from global count."); _numMonsters -= removedMonsters; _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, _numMonsters); }

// Set up a new room for players who have bravely gone where no player has gone before // ...or at least where no other player was at the moment. protected function setupRoom (roomId :int) :void {       var room :RoomSubControlServer = _control.getRoom(roomId); room.addEventListener(AVRGameRoomEvent.ROOM_UNLOADED, handleRoomUnloaded); room.addEventListener(AVRGameRoomEvent.MOB_CONTROL_AVAILABLE, handleMobControlAvailable);

_roomMonsters[roomId] = new HashSet; }

// Monsters flee an empty room. // (Maybe later, we'll have them flee the empty room into another room   // in the game. For now, they return to the Underwhirled.) protected function emptyRoom (roomId :int) :void {       _log.info("Emptying room #" + roomId); if (_roomMonsters[roomId] != null) { // Remove the spawned mobs from the room. var room :RoomSubControlServer = _control.getRoom(roomId); room.getSpawnedMobs.forEach( function (item:*, index:int, array:Array) :void {               var mobId :String = String(item);                room.despawnMob(mobId);            });

// Update the room property count. room.props.set(SimpleAVRGConstants.ROOM_MONSTER_COUNT, 0);

// Remove the monsters from the local dictionary. var removedMonsters :int = _roomMonsters[roomId].size; delete _roomMonsters[roomId];

// Remove the monsters from the global count. removeGlobalMonsters(removedMonsters); } else { _log.info("No monsters to remove from room #" + roomId); }   }

// This is called when a mob has been spawned in a room. protected function handleMobControlAvailable (event :AVRGameRoomEvent) :void {       // Add the monster id to the local dictionary of monsters in rooms. var mobId :String = event.name; _roomMonsters[event.roomId].add(mobId);

// Set the room-level count. _control.getRoom(event.roomId).props.set(SimpleAVRGConstants.ROOM_MONSTER_COUNT,           _roomMonsters[event.roomId].size);

// Store the sum so we don't have to recalculate it. _numMonsters += 1;

// Transmit the new global total. _control.game.props.set(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT, _numMonsters); }

// This is called when a player joins the game. protected function handlePlayerJoinedGame (event :AVRGameControlEvent) :void {       // Retrieve the player id from the event. var playerId :int = int(event.value);

// Give the player a basket. _basketMonsters[playerId] = 0;

// Listen for the player moving from room to room so that after the last // player leaves a room, the system can turn out the lights. _control.getPlayer(playerId).addEventListener(AVRGamePlayerEvent.ENTERED_ROOM,           handlePlayerEnteredRoom); _control.getPlayer(playerId).addEventListener(AVRGamePlayerEvent.LEFT_ROOM,           handlePlayerLeftRoom); }

// This is called when a player enters a room. protected function handlePlayerEnteredRoom (event :AVRGamePlayerEvent) :void {       var playerId :int = event.playerId; var roomId :int = int(event.value); // If the room isn't already set up, set it up now. if (_roomMonsters[roomId] == null) { setupRoom(roomId); }   }

// This is called when a player leaves a room (or the game). protected function handlePlayerLeftRoom (event :AVRGamePlayerEvent) :void {       // Retrieve the player id and the room id from the event. var playerId :int = event.playerId; var roomId :int = int(event.value);

// Check whether any players remain in the room. // TODO Confirm that this event is guaranteed to occur only after the player's id       //      has been removed from the room's player list. // FIXME This probably sets up a race condition between the event handler and the room //      object getting dropped, but I don't see a better way to clean up the room before //      it is unloaded. The ROOM_UNLOADED event also races the actual memory cleanup, //      and is even more likely to lose. if (_control.getRoom(roomId).getPlayerIds.length == 0) { emptyRoom(roomId); }   }

// This is called when a room is being unloaded. protected function handleRoomUnloaded (event :AVRGameRoomEvent) :void {       var room :RoomSubControlServer = _control.getRoom(event.roomId); room.removeEventListener(AVRGameRoomEvent.ROOM_UNLOADED, handleRoomUnloaded); room.removeEventListener(AVRGameRoomEvent.MOB_CONTROL_AVAILABLE,           handleMobControlAvailable); }

// This is called when a player quits the game. protected function handlePlayerQuitGame (event :AVRGameControlEvent) :void {       // Retrieve the player id from the event. var playerId :int = int(event.value);

// Take the player's basket away. delete _basketMonsters[playerId];

// Stop listening for the player to leave rooms. // TODO Check whether it's really necessary to explicitly remove this. It shouldn't be. _control.getPlayer(playerId).removeEventListener(AVRGamePlayerEvent.LEFT_ROOM,           handlePlayerLeftRoom); }

// This is called when a game message has been received. protected function handleGameMessageReceived (event :MessageReceivedEvent) :void {       if (event.name == SimpleAVRGConstants.CATCH_MONSTER_REQUEST_MESSAGE) { // Find the player catching a monster and the room from which it is being caught. var playerId :int = event.senderId; var roomId :int = _control.getPlayer(event.senderId).getRoomId; var mobId :String = String(event.value);

_log.info("Player #" + playerId + " is attempting to take monster #" + mobId                    + " from room #" + roomId + "."); var monsters :Set = Set(_roomMonsters[roomId]); if (monsters != null && monsters.contains(mobId)) { // Remove the mob. _control.getRoom(roomId).despawnMob(mobId);

// Set the room-level count. monsters.remove(mobId); _control.getRoom(roomId).props.set(SimpleAVRGConstants.ROOM_MONSTER_COUNT,                   monsters.size);

// Set the global count. removeGlobalMonsters(1);

// Set the player's basket count. _basketMonsters[playerId] += 1; _control.getPlayer(playerId).props.set(SimpleAVRGConstants.BASKET_MONSTER_COUNT,                   _basketMonsters[playerId]); } else { _log.warning("Player #" + playerId + " attempted to remove monster #" + mobId                           + " from room #" + roomId + ", but the monster wasn't there!"); }       }    }

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

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

// A counter that provides unique mob ids. protected var _nextMobId :int = 0;

// This is the number of monsters in each room. protected var _roomMonsters :Dictionary;

// This is the number of monsters in each player's basket. protected var _basketMonsters :Dictionary;

// The server side game control. protected var _control :AVRServerGameControl;

// The logger. protected var _log :Log = Log.getLog(Server); }

}

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

package {

import com.threerings.util.Log; import com.whirled.avrg.AVRGameControl; import com.whirled.avrg.AVRGamePlayerEvent; import com.whirled.avrg.AVRGameRoomEvent; import com.whirled.net.PropertyChangedEvent; import flash.display.DisplayObject; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent;

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

// Set up a function for drawing mobs. _control.local.setMobSpriteExporter(createMobSprite);

// 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( int(_control.game.props.get(SimpleAVRGConstants.GLOBAL_MONSTER_COUNT)) ); _scoreBoard.setRoomMonsterCount( int(_control.room.props.get(SimpleAVRGConstants.ROOM_MONSTER_COUNT)) );

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

// Listen for room level property changes. _control.room.props.addEventListener(PropertyChangedEvent.PROPERTY_CHANGED, handleRoomPropertyChanged);

// Listen for player level property changes. _control.player.props.addEventListener(PropertyChangedEvent.PROPERTY_CHANGED, handlePlayerPropertyChanged);

// Listen for mobs appearing in the room. _control.room.addEventListener(AVRGameRoomEvent.MOB_CONTROL_AVAILABLE, handleMobControlAvailable);

// Listen for the player moving into a new room. _control.player.addEventListener(AVRGamePlayerEvent.ENTERED_ROOM, handleEnteredRoom); }   }

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

_scoreBoard.addEventListener(ScoreBoardEvent.CATCH_MONSTER_REQUEST, handleCatchMonsterRequest); }

/**    * This builds a new mob sprite. */   protected function createMobSprite (type :String) :DisplayObject {       return new MonsterSprite(type); }

/**    * This is called when the player is attempting to catch a monster. */   protected function handleCatchMonsterRequest (event :ScoreBoardEvent) :void {       _log.info("Player attempted to catch a monster."); _control.agent.sendMessage(SimpleAVRGConstants.CATCH_MONSTER_REQUEST_MESSAGE); }   /**     * This is called when the player enters a new room, as well as when the * player first enters the game. */   protected function handleEnteredRoom (event :AVRGamePlayerEvent) :void {       _log.info("Entered room #" + _control.room.getRoomId); var roomCount :Object = _control.room.props.get(SimpleAVRGConstants.ROOM_MONSTER_COUNT); _scoreBoard.setRoomMonsterCount( int(roomCount) ); }

/**    * 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( int(event.newValue) ); }   }

/**    * This is called when a mob enters the room. */   protected function handleMobControlAvailable (event :AVRGameRoomEvent) :void {       // The mob sprite exporter will already have built the sprite. Fetch it       // by mob id and cast it into a MonsterSprite variable. var mobSprite :MonsterSprite = MonsterSprite(           _control.room.getMobSubControl(event.name).getMobSprite); mobSprite.mobId = event.name;

mobSprite.addEventListener(MouseEvent.CLICK, handleMobSpriteClicked); mobSprite.addEventListener(Event.UNLOAD, function (event :Event) :void {           removeEventListener(MouseEvent.CLICK, handleMobSpriteClicked);        }); }

/**    * This is called when a mob sprite has been clicked. */   protected function handleMobSpriteClicked(event :MouseEvent) :void {       if (event.target is MonsterSprite) { var monsterSprite :MonsterSprite = MonsterSprite(event.target); _control.agent.sendMessage(SimpleAVRGConstants.CATCH_MONSTER_REQUEST_MESSAGE, monsterSprite.mobId); }   }

/**    * This is called when a player property has changed. */   protected function handlePlayerPropertyChanged (event :PropertyChangedEvent) :void {       // If the player's basket monster count has changed, update the notice board. if (event.name == SimpleAVRGConstants.BASKET_MONSTER_COUNT) { _scoreBoard.setBasketMonsterCount( int(event.newValue) ); }   }

/**    * This is called when a room property has changed. */   protected function handleRoomPropertyChanged (event :PropertyChangedEvent) :void {       // If the room monster count has changed, update the notice board. if (event.name == SimpleAVRGConstants.ROOM_MONSTER_COUNT) { _scoreBoard.setRoomMonsterCount( int(event.newValue )); }   }

/**    * 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); _control.room.props.removeEventListener(PropertyChangedEvent.PROPERTY_CHANGED, handleRoomPropertyChanged); _control.player.removeEventListener(AVRGamePlayerEvent.ENTERED_ROOM, handleEnteredRoom); }   }

protected var _control :AVRGameControl; protected var _log :Log = Log.getLog(SimpleAVRG); protected var _scoreBoard :ScoreBoard; } }

SimpleAVRGConstants.as
// // SimpleAVRGConstants.as // // The property and message names for SimpleAVRG

package {

public class SimpleAVRGConstants { public static const GLOBAL_MONSTER_COUNT :String = "globalMonsterCount"; public static const ROOM_MONSTER_COUNT :String = "roomMonsterCount"; public static const BASKET_MONSTER_COUNT :String = "basketMonsterCount";

public static const CATCH_MONSTER_REQUEST_MESSAGE :String = "catchMonsterRequestMessage"; }

}

What have you done?
You have added mobs to the game and provided an interactive display for them.

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


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