Last post we got into the various game States that I use in my AS3 Starling Tower Defense game demo. I left out Play because it’s pretty big and needed some cleaning up. Now I’ve got it all cleaned up and ready to go! Also, I had intended this post to get into Map Tiling at the end, but this ran long and I wanted to show the Config file since that’s used in so many places. So Map Tiling will come later. This is the Play State and my Config class.

[toc]

Quick note… I’ve removed all of the doc blocks from functions to conserve space here… Comment your code!

Also, feel free to check out the finished product of my AS3 Starling TD Demo.
You can also find all of the code used in srcview.
Or you can download the whole project zipped up.
Or check out the repo on Bitbucket

Play.as

The Play State (com.zf.states.Play.as) is the main point of the game. It’s the State where a map gets drawn, enemies spawn, towers get placed, and the player hopefully has a great time. After a player has selected a map from the MapSelect State, we know the map ID they chose and the MapLoad state actually loads in the map JSON file and gets it ready for Play to do something with it.

Play is also the creator and maintainer of most all of the “Manager” classes used in the game. We’ll start off with the constructor and the addedToStage functions. Since this is such an important class, I included pretty much everything here.

package com.zf.states
{
   import com.zf.core.Assets;
   import com.zf.core.Config;
   import com.zf.core.Game;
   import com.zf.managers.*;
   import com.zf.objects.map.Map;
	
   import flash.display.Stage;
   import flash.geom.Point;
   import flash.utils.getTimer;
	
   import starling.animation.Juggler;
   import starling.core.Starling;
   import starling.display.Sprite;
   import starling.events.Event;
	
public class Play extends Sprite implements IZFState
{
   public static var zfJuggler:Juggler;
   
   public static const GAME_OVER_HP	 : int = 0;
   public static const GAME_OVER_ENEMIES : int = 1;
   public static const GAME_OVER_QUIT	 : int = 2;
				
   public static const GAME_STATE_PAUSE : int = 0;
   public static const GAME_STATE_PLAY	: int = 1;
   public static const GAME_STATE_END	: int = 2;
   public static const GAME_STATE_OVER	: int = 3;

   public static var gameState	   : int = GAME_STATE_PLAY;
   public static var gameOverState : int = -1;

   // Public Managers & Objects
   public var wpMgr	: WaypointManager;
   public var towerMgr	: TowerManager;
   public var keyMgr	: KeyboardManager;
   public var bulletMgr : BulletManager;
   public var enemyMgr	: EnemyManager;
   public var hitMgr	: CollisionManager;
   public var hudMgr    : HudManager;  
   public var map	: Map;
   public var ns	: Stage;
		
   public var mapOffsetX:int = 36;
   public var mapOffsetY:int = 36;
   public var endGameOnNextFrame:Boolean = false;

   //Public state values
   public var isPaused:Boolean = false;
   public var currentGold:int = 100;
		
   public var mapLayer:Sprite;
   public var enemyLayer:Sprite;
   public var towerLayer:Sprite;
   public var hudLayer:Sprite;
   public var topLayer:Sprite;
   
   private var _game:Game;

   // Game Conditions
   private var _gameCondEndOfWaves:Boolean;
   private var _gameCondEndOfEnemies:Boolean;
		
   private var _zfMgrs:Vector.;
   private var _isGameStartPause:Boolean;

   public function Play(g:Game) {
      _game = g;

      zfJuggler = new Juggler();

      _zfMgrs = new Vector.();

      // reset Config variables for new game
      Config.resetForNewGame();

      _gameCondEndOfWaves = false;
      _gameCondEndOfEnemies = false;

      addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
   }

   private function onAddedToStage(evt:Event):void {
      removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);

      Starling.juggler.add(zfJuggler);

      ns = Starling.current.nativeStage;

      keyMgr = new KeyboardManager(this, ns);
      keyMgr.onPause.add(onPauseEvent);

      mapLayer = new Sprite();
      addChild(mapLayer);

      enemyLayer = new Sprite();
      addChild(enemyLayer);

      towerLayer = new Sprite();
      addChild(towerLayer);

      hudLayer = new Sprite();
      addChild(hudLayer);

      topLayer = new Sprite();
      addChild(topLayer);

      enemyMgr = new EnemyManager(this);
      _zfMgrs.push(enemyMgr);

      hudMgr = new HudManager(this);
      _zfMgrs.push(hudMgr);
			
      wpMgr = new WaypointManager(this);

      towerMgr = new TowerManager(this , Assets.towerData);
      _zfMgrs.push(towerMgr);

      map = new Map(Config.currentMapData, wpMgr, mapOffsetX, mapOffsetY);
      map.x = mapOffsetX;
      map.y = mapOffsetY;
      mapLayer.addChild(map);

      bulletMgr = new BulletManager(this);
      _zfMgrs.push(bulletMgr);

      hitMgr = new CollisionManager(this);
      _zfMgrs.push(hitMgr);

      // Set up enemy data
      enemyMgr.handleNewMapData(Config.currentMapData.enemyData);

      /**
       * Set up signals listeners
       */
      enemyMgr.onEnemyAdded.add(hitMgr.onEnemyAdded);
      enemyMgr.onEnemyRemoved.add(hitMgr.onEnemyRemoved);
      towerMgr.onTowerAdded.add(hitMgr.onTowerAdded);
      towerMgr.onTowerRemoved.add(hitMgr.onTowerRemoved);
      towerMgr.onTowerManagerRemovingEnemy.add(hitMgr.onEnemyRemoved);

      hudMgr.endOfHP.add(onEndOfHP);
      enemyMgr.endOfEnemies.add(onEndOfEnemies);

      // set the current start HP
      Config.changeCurrentHP(map.startHP, false);

      // set the current start Gold from map data
      Config.changeCurrentGold(map.startGold, false);

      hudMgr.showNextWaveButtons();

      // update hud ui
      hudMgr.updateUI();
			
      // this is the initial pause at the start of the game
      _isGameStartPause = true;
			
      // pause the game so nothing happens until we resume
      onGamePaused();
   }

Alrighty… about halfway done with Play. It sets up the whole playable fun part so the setup is pretty intense. I’m also taking a little break here because I imagine if you’re trying to find the notes on a line number and you have to scroll down, then scroll all the way back up for the next line of code, then back down for the commentary, I bet that’s pretty irritating. I’ll try to keep the sections short. The following is still from com.zf.states.Play.as.

   public function onPauseEvent(fromOptions:Boolean = false):void {
      if(!_isGameStartPause || (_isGameStartPause && !fromOptions)) {
         Config.log('Play', 'onPauseKeyPressed', "Play.onPauseKeyPressed() == " + isPaused);
         // the game has started from a means other than options saving so set this to false
         _isGameStartPause = false;

         isPaused = !isPaused;
         if(isPaused) {
            onGamePaused();
         } else {
            onGameResumed();
         }
      }
   }

   public function onGamePaused():void {
      isPaused = true;
      gameState = GAME_STATE_PAUSE;
      Config.log('Play', 'pauseGame', "Removing juggler");
      Starling.juggler.remove(zfJuggler);

      var len:int = _zfMgrs.length;
      for(var i:int = 0; i < len; i++) {
         _zfMgrs[i].onGamePaused();
      }
   }

   public function onGameResumed():void {
      isPaused = false;
      gameState = GAME_STATE_PLAY;
      Config.log('Play', 'resumeGame', "Adding Juggler");
      Starling.juggler.add(zfJuggler);
			
      var len:int = _zfMgrs.length;
      for(var i:int = 0; i < len; i++) {
         _zfMgrs[i].onGameResumed();
      }
   }

   public function onQuitGameFromOptions():void {
      handleGameOver(GAME_OVER_QUIT);
   }

   public function update():void {
      if(endGameOnNextFrame) {
         // handle game over before getting back into another round of updates
         handleGameOver(Config.gameOverCondition);
      } else if(!isPaused && gameState != GAME_STATE_OVER) {
         // keeps track of how long the game has been active
         Config.activeGameTime = getTimer() - Config.pausedGameTime;
	
         enemyMgr.update();
         bulletMgr.update();
         hitMgr.update();
         towerMgr.update();
         hudMgr.update();
      } else if(isPaused && gameState != GAME_STATE_OVER) {
         // keeps track of how long the game has been paused
         Config.pausedGameTime = getTimer() - Config.activeGameTime;
      }
   }

   public function canCreateTower(towerID:String, level:int = 0):Boolean {
      var canCreate:Boolean = false,
          towerCost:int = towerMgr.getTowerCost(towerID, level);
			
      // CHECK CRITERIA FOR CREATING A TOWER
      if(towerCost <= Config.currentGold) {
         canCreate = true;
      }
			
      return canCreate;
   }

   public function createTower(towerID:String, pos:Point, level:int = 0):void {
      // HANDLE ACTUALLY CREATING THE TOWER
      // remove gold value
      Config.changeCurrentGold(-towerMgr.getTowerCost(towerID, level))
      hudMgr.updateUI();

      towerMgr.createNewTower(towerID, pos);
   }

Whew... almost done with com.zf.states.Play.as

   public function handleGameOver(gameOverCondition:int):void {
      switch(gameOverCondition) {
         case GAME_OVER_HP:
            Config.log('Play', 'handleGameOver', "LOST GAME LOSER!");
            break;

         case GAME_OVER_ENEMIES:
            // set that we won
            Config.totals.mapsWon = 1;
            // Add an upgrade point for the Upgrades screen
            Config.addTotalUpgradePoint();
            Config.log('Play', 'handleGameOver', "GAME WON WINNER!");
            break;

         case GAME_OVER_QUIT:
            // player quit
            Config.log('Play', 'handleGameOver', 'Quit Game');
            break;
      }

      // Add map totals to currentGameSOData
      Config.currentGameSOData.updateFromTotals(Config.totals);

      // Set SharedObject with game data and save
      Game.so.setGameData(Config.currentGameSOData, '', true);

      gameOverState = gameOverCondition;
      _changeGameState(GAME_STATE_OVER);
   }
		
   public function onEndOfHP():void {
      Config.log('Play', 'onEndOfHP', "Play onEndOfHP");
      Config.gameOverCondition = GAME_OVER_HP;
      endGameOnNextFrame = true;
   }
		
   public function onEndOfEnemies():void {
      Config.log('Play', 'onEndOfEnemies', "Play onEndOfEnemies");
      Config.gameOverCondition = GAME_OVER_ENEMIES;
      endGameOnNextFrame = true;
   }
		
   private function _changeGameState(st:int):void {
      Config.log('Play', '_changeGameState', "Game Changing State to: " + st);
      gameState = st;
      if(gameState == GAME_STATE_OVER) {
         _game.changeState(Game.GAME_OVER_STATE);
      }
   }

   public function destroy():void {
      Config.log('Play', 'destroy', "Play.destroy()");

      // remove all added listeners first
      _removeListeners();
      _removeManagers();
      _removeLayers();
      trace(Config.totals.toString());
   }

   private function _removeListeners():void {
      Config.log('Play', '_removeListeners', "Play._removeListeners()");
      keyMgr.onPause.remove(onPauseEvent);
      enemyMgr.onEnemyAdded.remove(hitMgr.onEnemyAdded);
      enemyMgr.onEnemyRemoved.remove(hitMgr.onEnemyRemoved);
      towerMgr.onTowerAdded.remove(hitMgr.onTowerAdded);
      towerMgr.onTowerRemoved.remove(hitMgr.onTowerRemoved);
      towerMgr.onTowerManagerRemovingEnemy.remove(hitMgr.onEnemyRemoved);
      hudMgr.endOfHP.remove(onEndOfHP);
      enemyMgr.endOfEnemies.remove(onEndOfEnemies);
   }

   private function _removeManagers():void {
      Config.log('Play', '_removeManagers', "Play._removeManagers()");
      map.destroy();
      map = null;

      keyMgr.destroy();
      keyMgr = null;

      wpMgr.destroy();
      wpMgr = null;

      // Handles Bullet, Collision, Enemy, Hud, Tower Managers
      var len:int = _zfMgrs.length;
      for(var i:int = len - 1; i >= 0; i--) {
         _zfMgrs[i].destroy();
         _zfMgrs[i] = null;
         _zfMgrs.splice(i, 1);
      }
   }		

   private function _removeLayers():void {
      Config.log('Play', '_removeLayers', "Play._removeLayers()");
      Starling.juggler.remove(zfJuggler);
      zfJuggler = null;
      removeChild(mapLayer);
      removeChild(enemyLayer);
      removeChild(towerLayer);
      removeChild(hudLayer);
   }
}

Ok, we're done with Play. We're not going to get to Map Tiling in this post as I'd originally hoped, but I will post Config.as here so we can take a quick peek at that.

Config.as

package com.zf.core
{
   import com.zf.utils.ZFGameData;

   import org.osflash.signals.Signal;

   public class Config
   {
      //PATH CONSTANTS
      public static const PATH_ASSETS         : String = "assets/";
      public static const PATH_JSON           : String = Config.PATH_ASSETS + "json/";
      public static const PATH_XML            : String = Config.PATH_ASSETS + "xml/";
      public static const PATH_SCENES         : String = Config.PATH_JSON + "scenes/";
      public static const PATH_IMG            : String = Config.PATH_ASSETS + "images/";
      public static const PATH_SOUNDS         : String = Config.PATH_ASSETS + "sounds/";
		
      // dataType constants
      public static const DATA_XML            : String = 'xml';
      public static const DATA_JSON           : String = 'json';
      public static const DATA_PEX            : String = 'pex';
      public static const DATA_FNT            : String = 'fnt';
      public static const DATA_MP3            : String = 'mp3';
      public static const IMG_PNG             : String = 'png';
      public static const IMG_JPG             : String = 'jpg';
      public static const IMG_GIF             : String = 'gif';

      public static const GAME_SPEED_UP	  : String = 'up';
      public static const GAME_SPEED_DOWN : String = 'down';

      // Signals
      public static var currentWaveChanged : Signal = new Signal(int);
      public static var currentHPChanged   : Signal = new Signal(int);
      public static var currentGoldChanged : Signal = new Signal(int);
      public static var onGameSpeedChange  : Signal = new Signal();

      // Game Data
      public static var currentGameSOID   : String = 'game1';
      public static var currentGameSOData : ZFGameData = new ZFGameData();

      // Timer Config
      public static var activeGameTime		: Number = 0;
      public static var pausedGameTime		: Number = 0;
	
      // Volume Config
      public static var musicVolume		: Number = 0.75;
      public static var sfxVolume		: Number = 0.75;
      public static const DEFAULT_SOUND_VOLUME 	: Number = 0.75;
		
      // Map Config
      public static var selectedMap	 : String = 'map1';
      public static var currentMapNumber : int = 1;
      public static var currentMapData	 : Object = {};
		
      // Game Speed Config
      public static var currentGameSpeed : Number;

      // Maximum number of waves this map
      public static var maxWave	: int;

      // What is the game over condition code
      public static var gameOverCondition : int;

      // Keeps track of total number of things
      public static var totals : Totals;

      // Debugger Config
      public static var debugMode      : Boolean = false;
      public static var debugVerbose   : Boolean = true;
		
      // Current Map Stats
      private static var _currentGold  : int;
      private static var _currentHP    : int;
      private static var _currentWave  : int;

      // Valid game speeds config
      private static var _gameSpeeds : Array = [0.5, 1, 2];
      private static var _speedIndex : uint = 1;

      // Unique ID
      private static var _currentUID:int = 0;
		
      public function Config() {
         Config.resetForNewGame();
      }

      public static function resetForNewGame():void {
         totals = new Totals();

         _currentGold = 0;
         _currentHP = 0;
         _currentWave = 0;

         // Speeds
         _speedIndex = 1;
         currentGameSpeed = 1;
      }

      public static function getUID():int {
         return ++_currentUID;
      }

      public static function changeGameSpeed(speedChangeDir:String):void {
         var speedChanged:Boolean = false;

         if(speedChangeDir == GAME_SPEED_UP) 
         {
            // check if we can speed up any
            if(_speedIndex < _gameSpeeds.length - 1) 
            {
               _speedIndex++;
               speedChanged = true;
            }
         } 
         else if(speedChangeDir == GAME_SPEED_DOWN) 
         {
            // check if we can slow down any
            if(_speedIndex > 0) 
            {
               _speedIndex--;
               speedChanged = true;
            }
         }
			
         // only dispatch event if the speed changed
         if(speedChanged) {
            // set currentGameSpeed
            currentGameSpeed = _gameSpeeds[_speedIndex];
            trace('currentGameSpeed changed: ' + currentGameSpeed);
            // dispatch new speed
            onGameSpeedChange.dispatch();
         }
      }
		
      public static function changeCurrentGold(g:int, addToCurrent:Boolean = true):void {
         if(addToCurrent) {
            _currentGold += g;
         } else {
            _currentGold = g;
         }
         currentGoldChanged.dispatch(_currentGold);
      }

      public static function get currentGold():int { 
         return _currentGold 
      }

      public static function set currentGold(g:int):void { 
         _currentGold = g;
      }

      public static function changeCurrentHP(hp:int, addToCurrent:Boolean = true):void {
         if(addToCurrent) {
            _currentHP += hp;
         } else {
            _currentHP = hp;
         }
         currentHPChanged.dispatch(_currentHP);
      }

      public static function get currentHP():int { 
         return _currentHP; 
      }

      public static function set currentHP(hp:int):void { 
         _currentHP = hp;
      }
		
      public static function changeCurrentWave(w:int, addToCurrent:Boolean = true):void {
         if(addToCurrent) {
            _currentWave += w;
         } else {
            _currentWave = w;
         }
         currentWaveChanged.dispatch(_currentWave);
      }

      public static function get currentWave():int { 
         return _currentWave; 
      }
		
      public static function set currentWave(w:int):void { 
         _currentWave = w;
      }

      public static function addGameAttempted():void {
         currentGameSOData.mapsAttempted++;
         Game.so.setGameDataProperty(currentGameSOData.mapsAttempted, 'mapsAttempted', '', true);
      }

      public static function addTotalUpgradePoint():void {
         currentGameSOData.upgrades.ptsTotal++;
         Game.so.setGameDataProperty(currentGameSOData.upgrades.ptsTotal, 'upgrades.ptsTotal', '', true);
      }

      public static function saveGameOptions():void {
            var opts:Object = {
               musicVolume: musicVolume,
               sfxVolume: sfxVolume
            };
         Game.so.setGameOptions(opts, true);
      }

      public static function log(klass:String, fn:String, msg:String, level:int = 0, verbose:Boolean = false):void  {
         if(Config.debugMode)  {
            var levelText:String = '';
            if(level == 0) {
               levelText = 'INFO';
            } else if(level == 1) {
               levelText = 'WARNING';
            } else if(level == 2) {
               levelText = 'ERROR';
            }
            var classNameFn:String = "[" + klass + "." + fn + "]";
            levelText = "[" + levelText + "]";

            if(!verbose || (Config.debugVerbose && verbose)) {
               trace(classNameFn + ' ' + levelText + ' => ' + msg);
            }
         }
      }

      public static function logError(klass:String, fn:String, msg:String):void  {
         Config.log(klass, fn, msg, 2);
      }

      public static function logWarning(klass:String, fn:String, msg:String):void  {
         Config.log(klass, fn, msg, 1);
      }
   }
}

So, there it is... the Play State! This post was originally going to include Map Tiling, but we'll look at that next time. This was pretty hefty and it's a good place to stop for now. Until next time, thanks for reading and I hope this was helpful!

Also, feel free to check out the finished product of my AS3 Starling TD Demo.
You can also find all of the code used in srcview.
Or you can download the whole project zipped up.
Or check out the repo on Bitbucket

-Travis

Categories:

1 Comment

Leave a Reply

Avatar placeholder