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();
}
- Line 71 – Hello Starling Juggler. We meet again, you pain in the ass. Starling’s Juggler class handles anything that you need animated. If you have something moving on stage, it’s gotta go in the Juggler. Why is Juggler a pain in the ass? Because it’s a freaking unstoppable machine! Every single tick that Juggler will juggle your images and animate your pngs and sparkle your particles without fail. Why is that bad? Because it can’t. be. stopped. There is no way to “pause” the Juggler right now. I propose they change the class name to Jugglernaut. After googling forever and finding 6 ways that didn’t work to effectively Pause your game, I came across this one way that does. Yo dawg, I herd u leik Jugglers. Sorry, yeah, essentially, you create your own Juggler class (“zfJuggler”) that you’re going to put Every animated game object into. Note that on Line 20 this is a static class so that I can access from other places using Play.zfJuggler. We’ll see what I do with it later, but basically, zfJuggler gets added to Starling’s main Juggler and you “pause” the game by removing zfJuggler from the main Juggler. Yeah…
- Line 73 – Why not Zoidb– vectors? Eh, this could be an Array if you made sure you only put classes implementing IZFManager into it. But, then, that’s the point of a Vector. It’s a Typed Array so I know as I iterate over it, every element of that Vector will implement the classes set forth by the IZFManager implementation. I’m initializing it here
- Line 76 – I don’t think we’ve been over the Config class yet. I think we’ll look at that in this post too as Config is found in pretty much every class. Config is basically my global holding place for data, vars, and anything “stateless” that I might need. Spanning the States, not beholden to one State or the other. That’s what you’ll find in Config. It really is a shiftless hive of scum and villainy. We’ll visit soon…
- Line 78-79 – very inefficient flag vars for if the game ended because of waves expiring (you beat everyone yay) or if the enemies overwhelmed your defenses and sent you packing.
- Line 81 – standard… make sure Play is on the Stage before doing anything
- Line 85 – remove the listener
- Line 87 – Hello Starling Juggler… we meet… ah yeah yeah. Here is where I add my manually created Juggler class to the actual main Starling.juggler. This line effectively means that anything I place into
- Line 89 – Flash.display.Stage?! Is that you! Oh something familiar! Starling.current.nativeStage is a direct reference to your good old fashioned Stage from flash.display.Stage. This is how you have to go about getting a reference to the stage so that we can take advantage of… which device boys and girls? That’s right… the Keyboard! Yeah, Starling does not handle your keyboard at all. So, I grab a reference to the Stage and pass that along to my KeyboardManager later for shortcutty type things.
- Line 91 – speaking of the KeyboardManager, let’s instantiate one and pass it our nativeStage object so it can listen for keyboard events
- Line 100 – pressing “P” on your keyboard in the game will pause the game. We’ll see at a later post how the KeyboardManager works, but just know that if the player presses “P” then an onPause Signal will dispatch. I want Play to listen for that so it can instruct all the other Managers that the game is paused.
- Line 94-107 – I am defining 5 different Sprite “layers” and adding them to stage. This is in a very specific order because I want my game map to be the furthest thing from the player. Then enemies will be on top of the map, then towers, then the HUD elements, then “topLayer” which really means my Game Options panel which needs to go over everything.
- Line 109-113 – create EnemyManager, push it into _zfMgrs. Create hudManager, push it into _zfMgrs. A quick note, unfortunately some of these Managers are more tightly coupled than I’d like them to be. They are not completely independent managing classes. So there actually is an order to how these Manager classes are created.
- Line 115 – create WaypointManager. wpMgr does not implement IZFManager so I don’t push that to my _zfMgrs Vector.
- Line 117-118 – create TowerManager, push to _zfMgrs
- Line 120 – Hey there’s our Map class! Let’s create one of those. Config.currentMapData is where I stored the map JSON data from the MapLoad State. So now in Play, I’m passing the currentMapData into the Map class so it can do its thing. I’m also passing in the WaypointManager and the mapOffsetX and Y. Map Offset X and Y are the pixel values that the map is set off from the top-left corner of the Flash Player. This is to account for the fact that in the demo, there are UI elements along the top 36px and empty space along the left 36px. That offset has to be accurate for math later down the line. It affects everything from enemy waypoint x/y coordinates, to if you can place a tower wherever your mouse is. We’ll look at the Map class later in this post.
- Line 121-122 – Map extends Sprite, so use those offsets to set the actual x/y of the Map Sprite and add it to the mapLayer Sprite that has already been added to stage.
- Line 125-126 – create the BulletManager, push to _zfMgrs. BulletManager is in charge of taking orders from towers, “Hey I fired a bullet of this type” and the BulletManager creates the Bullet Sprites and handles that jazz. We’ll look at this in a later post.
- Line 128-129 – create the CollisionManager, push to _zfMgrs. I didn’t want to keep typing collisionMgr as a var name, so I shortened it to hitMgr. It handles hit testing between enemies and towers. We’ll look at that in a later post.
- Line 132 – now that all of my Managers have been created, Map’s been created, let’s get EnemyManager initializing its enemies. call enemyMgr.handleNewMapData and pass in just the enemyData portion of currentMapData. We’ll look at this is a minute.
- Line 137 – EnemyManager has a Signal it dispatches when it “spawns” or adds a new enemy to the stage. hitMgr needs to listen for those spawns so it knows to account for another enemy in its calculations.
- Line 138 – EnemyManager has a Signal it dispatches when it removes an enemy from the stage. hitMgr needs to listen for that so it knows to remove the enemy from its loops.
- Line 139 – TowerManager dispatches onTowerAdded Signal when the player adds a new Tower to the stage. hitMgr needs to add the tower to its arrays.
- Line 140 – TowerManager dispatches onTowerRemoved Signal when the player removes (“sells”) a Tower from the stage. hitMgr needs to remove the tower from its arrays.
- Line 141 – I wrote a post about wasting bullets, a huge TD game pet peeve of mine. Before a Tower fires at a target Enemy, it does a quick check with that Enemy to see if this next shot the Tower is about to fire will kill the Enemy. If the Enemy is going to be killed by the next shot from a Tower, that Tower dispatches a Signal to the TowerManager so the TowerManager loops through every other Tower on stage, and removes that enemy from every other Towers’ _enemies array. This makes sure that no other Tower can try to waste a bullet on an Enemy that is going to be killed by the next shot of some other Tower. TowerManager dispatches onTowerManagerRemovingEnemy Signal when a Tower is going to kill an Enemy so the hitMgr can also remove that Enemy from its loops. If no Tower is looking to shoot the Enemy, and the CollisionManager isn’t measuring that Enemy’s distance from every Tower, then that last Tower can fire off the last Bullet and we waste. no. bullets.
- Line 143 – HUDManager dispatches an endOfHP Signal when it has updated the UI to 0 or less Hit Points. Play needs to stop the game when that happens, so listen for that Signal.
- Line 144 – EnemyManager dispatches an endOfEnemies Signal when it has run out of enemies to manage. That occurs when all waves and all groups and all enemies inside those groups have spawned. EnemyManager checks every time an Enemy is killed, or escapes, to see if there are 0 enemies left. This lets Play know that we beat all the enemies and as long as endOfHP hasn’t been dispatched, we know we have enough HP left that we actually won the map. Huzzah.
- Line 147 – Config holds my big map variables, HP, waves, gold, etc. Tell Config to set the current hit points to whatever the map JSON file says this map should start with.
- Line 150 – same thing but with Gold
- Line 152 – the HUDManager will display the pulsating next wave buttons now
- Line 155 – HUDManager’s updateUI breaks out just the UI elements from HUD’s update() method. This way I can update specific UI elements without, say, updating the WaveTileBar which would start the enemies spawning.
- Line 158 – the game starts initially paused. However, when you save or cancel from the Game Options panel, that triggers the game to resume. So I needed this extra flag so that if the player went to the Options menu at the start of the game, they didnt accidentally begin spawning enemies just because they saved some sound settings.
- Line 161 – pause the game.
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);
}
- Line 1 - onPauseEvent is a public function that other classes should use when they need to pause/resume the game. The Play State keeps track of if the game is paused or not, so other classes can just call the function and Play should be able to handle pausing or resuming the game.
- Line 2 - if this function gets called and our initial game-start pause is in effect, don't do anything. Only handle this pause event as a real pause/resume toggle if this the player has already started spawning waves, or if the _isGameStartPause is still active but we are calling onPauseEvent from somewhere other than from the Game Options.
- Line 5 - make sure we unset the game start pause flag
- Line 7 - if isPaused == true, this will make it false. If it is false, it makes it true. Flip the isPaused Boolean.
- Line 9 - if isPaused == true (now that we just flipped it), call onGamePaused
- Line 11 - if isPaused == false (now that we just flipped it), call onGameResumed
- Line 16 - onGamePaused handles what to do to pause the game
- Line 18 - set the current gameState to paused
- Line 20 - here is how we make all of the on-screen animations "pause". We remove the zfJuggler from Starling.juggler and since all of our enemies and towers and bullets were added to Play.zfJuggler, by removing that from the main juggler, there's nothing left to animate.
- Line 24 - loop through my IZFManagers and tell them the game is paused. They'll handle their minions.
- Line 28-38 - exactly the reverse of onGamePaused(). Set the gameState to play, add my zfJuggler back to the main juggler resuming animations, tell the managers to resume their minions.
- Line 41 - this gets called if the user clicks the Quit game button from the Game Options panel, call handleGameOver, tell it we quit.
- Line 44 - The Official Play State update() Function. People have waited in lines in tents for days to get to this moment. And here you are.
- Line 45 - endGameOnNextFrame is a Play State var that gets set when we're out of HP or enemies. Instead of trying to cut things off when Managers may be mid-update-loop themselves, we just set that Boolean so that when we get here, to line 45, no Manager has even begun to update yet. Now's the perfect time to end the game.
- Line 47 - we set the gameOverCondition on Config so that after Play has destroy()'d itself (probably through copious amounts of booze... you would too after always watching enemyies get slaughtered) the GameOver State can know the outcome of the map and why the game is over so we can display different messages.
- Line 48 - else... if the game is not paused and the gameState is not game over...
- Line 50 - Pro Tip #36: if you were going to add an In-Game Time achievement to your game, i.e. Achievement Unlocked 30Minutes of Game Time!, this is where you'd be adding that up. If update() happens and we do not pass into this part of the if/else, then the game is paused and we dont want to update that timer, or the game is over and we don't want to give those pesky players even 1/60th of a second more time on their clocks. No one gets our game time achievements without earning every 1/60th of a second along the way. Nobody.
- Line 52-56 - I could set up a loop and iterate through _zfMgrs, but this lets me very explicitly control what is about to update first. Should the enemies update before the bullets? Should the towers scan before the enemies move? Here's the way I've set up my Managers to update themselves.
- Line 52 - My enemies spawn, move, and advance unwaveringly onwards
- Line 53 - Bullets inch pixels closer to enemies. Bullets that reach their intended victim dispatch Signals out to let all concerned parties know of an untimely death.
- Line 54 - CollisionManager, the math nerd over in the corner with pleated khakis and white socks updates all the distances between every enemy and every tower. If an enemy is in range of a tower, CManager passes the Tower a reference to the Enemy for later. If an enemy was in range of a Tower, but now no longer is, CManager tells the Tower to forget that loser, the Tower was too good for it, there are other Enemies in the array of possibilities!
- Line 55 - TowerManager tells each of its Towers to begin their scan for enemies. Since CollisionManager was so kind as to go before us, now each tower ONLY has a reference to enemies that are inside it's range. This means that each and every Tower doesn't have to do a scan of each and every Enemy, we just handle that once in CollisionManager, then each of the Towers get to handle only those enemies in range.
- Line 56 - update the HUD! If any enemies were killed and gold was added, let's update that. Same with if enemies escaped and lives were lost, or if the EnemyManager spawned a new wave, update the wave counter. This handles any HUD/UI related things. The carnage of the last tick has occured, now we provide the UI feedback for the player for this game tick.
- Line 59 - if the game is paused, but the game is not over yet, start adding up paused time. I don't know why I added this. Maybe you want to add a Paused Game Achievement or something... there you go. Mission Accomplished!
- Line 63-82 - the following two functions are strange here in Play. I needed a central point for HUDManager and TowerManager to communicate and for some reason I used functions here instead of setting up Signals like a normal intelligent person would do. Oh well, here we go.
- Line 64 - canCreateTower() is called when the player has clicked a Tower icon on the right-hand side Towers area. HUDManager calls play.canCreateTower() and passes it the Tower's ID and level (which isnt really implemented, so just set to 0)
- Line 65 - check with TowerManager to get the cost of this Tower at this level (0)
- Line 68 - if the cost is less than or equal to our current gold supply, we can create the Tower!
- Line 78 - then HUDManager calls play.createTower() to actually create the Tower. But Play is the bag-man here. Play doesn't create Towers. But play knows we need to remove gold off of Config to pay for the Tower that TowerManager will make. Deduct the gold here.
- Line 79 - updateUI means our current gold value gets updated so players can see their gold has been reduced by the cost of the Tower.
- Line 81 - now that we've handled the money, tell TowerManager to really actually create the tower.
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);
}
}
- Line 1 - handleGameOver() takes the gameOverCondition param and then handles what should happen depending on how the game ended.
- Line 4 - if the game was over because we ran out of HP, do anything special here.
- Line 9 - if the game was over because the player killed all the enemies, update Totals on Config by adding a mapsWon = 1
- Line 11 - this deals with adding 1 to our total amount of upgrade points because the player won. Now after leaving the map and going to the Upgrades screen, the player will have one more upgrade point to spend.
- Line 17 - if the game was over because the player quit, we can do anything special we need to here before we...
- Line 22 - update our current game SharedObject data with the current totals the player racked up during this map. This adds things like bulletsFired and enemiesEscaped to the player's running lifetime totals.
- Line 25 - since the totals have been updated now, we call Game's SharedObjectManager instance (Game.so) and setGameData to our currentGameSOData. This saves the current lifetime totals out to the SO.
- Line 27 - update gameOverState with gameOverCondition for later use
- Line 28 - change the game state to the GameOver State
- Line 33-34 - if HudManager tells us that we've run out of HP, set the gameOverCondition to GAME_OVER_HP, and set endGameOnNextFrame to true so the game will end next time Play calls update(). Again, this allows all Managers to continue in their loops so no crazy stuff happens.
- Line 38-40 - same thing but if there are no more enemies, set the gameOverCondition and end on next frame.
- Line 45 - helps Play manage its own internal game staes
- Line 46-48 - if changing internal game states to the game over State, let Game know to change the main State to GameOver, which will call destroy() on Play and we'll clean everything up until next time.
- Line 51-59 - destroys the state, it calls _removeListeners(), _removeManagers(), then _removeLayers() to clean up the State.
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);
}
}
}
- Line 10-15 - different path constants for ZFLoader to use
- Line 18-25 - different extension/data type constants used by ZFLoader and a few other classes
- Line 27-28 - constants to use if the game speed is being changed up or down (faster or slower)
- Line 31-34 - the different Signals that Config might dispatch
- Line 37 - the current game SharedObject Id to use for this current game
- Line 38 - ZFGameData is an Object class I wrote to handle game data and make it easier to convert into the SharedObject for saving
- Line 41-42 - active and paused game time counters
- Line 45-47 - the current music and sound effect volume to use. These get set by shared object data if the player is loading a game.
- Line 50-52 - the selectedMap id, currentMapNumber (I'm not sure if this gets used or not?), and the currentMapData from the map's JSON file
- Line 64 - Totals object that I wrote as well, handles keeping track of different totals.
- Line 67-68 - debugging settings. if debugMode == false, then nothing should get traced out to the console if everything goes thru Config.log(). debugVerbose is a setting I implemented so if that is true, the larger files (map JSON etc) would be traced out, but if that is off, the big files won't get traced.
- Line 71-73 - current gold/hp/wave settings
- Line 76 - available game speeds to use are 0.5 (half), 1 (normal), and 2 (double) currently.
- Line 77 - _speedIndex pertains to the index in _gameSpeeds. So _speedIndex == 1 means _gameSpeeds[1] which is 1. This way you can just bump the index up or down, or change the gameSpeed values and the index functionality stays the same.
- Line 80 - currentUID is a unique ID counter that I implemented with the getUID() function later
- Line 86 - resets some variables on Config to get ready for a new map
- Line 99 - increments _currentUID and returns that to the calling function. This way Towers, Enemies, Bullets, etc can all have a unique ID when they're created. This could easily lead into a entity management system that would assign unique ids.
- Line 102 - gets called when the user changes the game speed. Pressing "+" or "-" will change the game speed. There's no UI way to do it yet. And at present, it may actually break things.
- Line 105 - if the speed was changed to faster
- Line 108 - if the speed index is less than the length of possible game speeds
- Line 110-111 - increment the game speed index and set that we changed the speed
- Line 119-120 - else if the speed was changed to slower and the speed index is greater than 0 already, decrement the index and set that we changed the speed
- Line 127 - if we changed speeds earlier, get the new currentGameSpeed
- Line 130 - dispatch a Signal letting listeners know that we've changed the game speed. Listening classes will then know to check config for the current game speed
- Line 135-140 - this pattern is used for gold/HP/waves. If addToCurrent is true, that means that the value passed in will be added to the current value. If addToCurrent is false, that means the value passed in should SET the current value.
- Line 151-158 - same thing for HP
- Line 168-174 - same thing for waves
- Line 185-188 - this is a quick helper function called from Play that quickly updates the mapsAttempted counter and then saves that game data. This happens every time a player clicks a map to play so we keep track of how many maps they started.
- Line 190-193 - quick helper function that increments the upgrade points by one and saves that game data so when the player gets back to the Upgrades screen they'll have one more upgrade point to spend.
- Line 195-201 - creates an Object with a musicVolume and sfxVolume property, setting those to whatever is on Config for the volume settings then saves that to the SO
- Line 203 - my global log function. Ideally I shouldn't use trace anywhere so I can immediately turn off all tracing, or send log messages to a file, db, or some other place.
- Line 222 - wrapper for log() that sets the level to error
- Line 226 - wrapper for log() that sets the level to warning
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
1 Comment