Space Invaders RPG. Building a Game in Adobe Edge Animate

At the moment, Edge Animate is my absolute favorite environment for building HTML5 content.  It’s so much more powerful and flexible than you might think, and is a great environment for building casual or marketing HTML5 games with an extremely low barrier to entry.

Although this is a fairly simple game, I use a variety of techniques that you can use in your own, more robust game.  I breeze through a lot of Edge Animate fundamentals so if you are brand new to the software, you should check out this awesome book by Joseph Labrecque.

State Manager

We’ll be using Stage to load and unload our screen symbols.  We’ll start by writing a very simple state manager in our Stage symbol.  Start by opening the code editor (cmd / cntrl + E).  Navigate to the left pane and find Stage. Click the + sign, then Event -> creationComplete.  This is where we’ll write all of our logic for each symbol.  

// Stage creationComplete

var currentState;
window.game = {GLOBALS:{win:false}};

function init(){
sym.getSymbolElement().css('position','absolute');
changeState('TitleState')
}
function changeState(state){
var newState = sym.createChildSymbol(state, "Stage");
var el = newState.getSymbolElement();
el.css("position","absolute");
if(currentState != null){
currentState.deleteSymbol();
}
currentState = newState;
}

init();

sym.setVariable('changeState',changeState);

First we create a variable that will hold a reference to the current screen symbol on the stage.  Next, we write a simple global object where we can store various things that we may need throughout the game. In this case, we only need to know if the player won or lost when our GameOver screen loads - game.GLOBALS.win.

Our init function does a few things to kick off the game.  The first thing we need to do is set our stage to have a position style of `absolute`.  This is so the screens don’t stack when we add them from the Library.

What’s this sym?

You can think of it as this, and is a reference to our symbol.  But more like this in other, class-based languages.  It’s reference will always be to the symbol, regardless of where we are with in the closure.

So what we did with sym here is accessed the jQuery object that every symbol is based upon.  We can access this by the getSymbolElement() method, and when we do, we have the entire jQuery API at our disposal. 

Last in this function is a call to load our TitleState symbol by passing the string to our changeState function.

The changeState function handles the loading and unloading of our screens when a game state changes.  The createChildSymbol method is what we use to dynamically ‘attach’ a symbol from the Library.  We temporarily set it to a variable so we can deal with the previous state screen, if any.

Now grab that jQuery object out of our new symbol so we can also make it’s position absolute.  Again, we want to control it’s position with code.  Then we want to check if there is currently a screen on stage so we can delete it by using the deleteSymbol method.  Lastly set our new state to currentState.  Immediately after this function, call init to kick everything off.

This last piece of code is a crucial one.  The variables and functions we’ve written so far can control our symbol, but they are not actually properties of the symbol.  This causes a problem because we need access to them from other symbols.  So how can we make them public? By using the setVariable method on sym.

Now we can fire our changeState function via a variable we set to sym.  You can stick whatever you want in there to apply public access to anything in the symbol by using the getVariable() method. If it's a function your'e after, simply call it as so:

getVariable('func')(param).

Title State

First, create your TitleState symbol.  My screen here is very simple but as you can see, you have a timeline and a suite of filters and effects at your disposal.  After you lay out your symbol, delete it if it's on the stage.  Then follow the same steps we used in Stage to navigate to the creationComplete event in the code editor. 

// TitleState creationComplete

var game = sym.getComposition().getStage();

function init(){
sym.getSymbolElement().bind('click touchstart',function(){
game.getVariable('changeState')("PlayState");
});
}

init();

The first thing we do is hold a reference to our Stage.  We wrote our state manager on the stage so we need a reference to it so we can call on it’s now public ‘changeStage’ function.

You can use this approach anywhere in our composition to gain access to the ‘root’ of our application.

Next, we use jQuery to bind both click and touch events to the entire symbol.  Within the handler function we simply call changeState and pass it the string for the state we want next.  This string must match the name of the symbol in the Library.  Lastly , call init().

Note:  In this case you could have just as easily navigated to the click event in our code editor and wrote our changeState call in there.  There are a few reasons why I don’t do that.  First, if you plan on your application performing well on touch devices, the touch events work much better than click.  With jQuery, we can bind both events in one shot.  In Edge Animate, you’d have to write the same code twice, once for click and again for touch.  Second, this sticks the code elsewhere, creating it’s own closure and losing all references to anything we’ve written in our creationComplete code.  Of course we now know how to get around that, but that complicates things and it’s not necessary.  Plus, this approach is useless on dynamically creating symbols so as a rule of thumb, I just stay away from it.

Now we’ve successfully switched states.  But we need a PlayState to load, so lets do that now.  Create a PlayState symbol and the necessary UI in the upper left corner.

Play State

PlayState HUD symbols

waitBar symbol

We need a ‘dynamic’ text field for 3 HUD elements.  The player’s current power, hit points, and remaining potions.  Because we all know that potions restore spacecraft damage. We also have a wait bar that is used to show progress before we can attack again. This bar is common in turn-based RPGs.

For our text fields, there are a few ways to make these 'dynamic'.  We can always access a text object on stage by simply referencing the Title given to it in the top portion of its property panel.  This is very similar to instance name in Flash Pro.

sym.$('powerTxt').html(power);

Or we can assign any object a class name.  This is extremely handy in applications where we want to write one event handler function for several instances.  Like in a form or menu.  

sym.$('.power').html(power);

We’ll use this approach for no other reason than to show off the feature.  Assign each text field it’s own class, which will be used in our code to update.

Now that we’ve laid out our screen, lets get back to some code.  Get back in the code editor and dive into our PlayState creationComplete code.

//PlayState creationComplete

var game = sym.getComposition().getStage();
var aliens = [];
var alienStart = {x:80,y:190};
var alienSpacing = {x:170,y:170};
var power = 10;
var maxHP = 200;
var hp = 200;
var potion = 3;
var counter = 0;
var alienAttackCounter = 25;
var attackReady = false;
var gameTimer;

First we declare some variables.  Create a reference to stage like we did before.  Next is an array that will hold our alien symbols. We’ll then need some values to help us build our grid of aliens.  After that are some player variables to keep track of health, power, etc.  Our alienAttackCounter variable is the time it takes for the aliens to attack. This will actually decrease as time goes on, making the game more difficult as time progresses. The attackReady boolean is what we use to allow us to attack.  We can only attack when our wait bar is full.  Finally some variables to hold our gameTimer and a counter that we’ll use to determine when it’s time for aliens to attack us.

//PlayState cont.

function init(){
initPlayer();
spawnAliens();
initAliens();
initPotions();
startTimer();
}

Our init function calls several functions to start the game.  We’ll go through each function now.

function initPlayer(){
sym.$('.power').html(power);
sym.$('.hp').html(hp);
sym.$('.potion').html(potion);
}

First we’ll init our HUD values.  Assign each text field value to the appropriate variable values.

function spawnAliens(){
var alien;
var el;
var i;
var xPos = alienStart.x;
var yPos = alienStart.y;
var col = 0;
for(i = 0;i < 15;i++){
alien = sym.createChildSymbol("Alien", "Stage");
alien.play(Math.random() * 1000);
aliens.push(alien);
el = alien.getSymbolElement();
el.css({"position":"absolute", "top":yPos + "px", "left":xPos + "px"});
xPos += alienSpacing.x;
col++;
if(col === 5){
col = 0;
yPos += alienSpacing.y;
xPos = alienStart.x;
}
}
}

Now we spawn the aliens.  We’ve covered most of this already, but in a nutshell, we have a loop that will attach our Alien symbols in a nice grid.  You can adjust these position objects above until you have a level you like.  We are also offsetting the animations of the aliens by playing them at random timecodes in their timelines.

Before moving on in the PlayState game logic, let’s take a break and create our Alien symbol.

Alien

Our Alien symbol has a simple idle animation.  We have a trigger in the timeline to loop back to 0 so it doesn’t advance to our ‘explode’ sequence.  Again, you have a very nice toolset to play with here.  Your animations can be a lot more advanced than what I have here.  

We also have a text field that will display the current hit points of our Alien.  Now, lets look at the code for our Alien.  Again, in our creationComplete handler.

//Alien creationComplete

var hp;
var el = sym.getSymbolElement();
var hpTxt = sym.$('hpTxt');
var dead = false;

el.hide();

function init(points){
hp = points;
hpTxt.html(hp);
el.fadeIn();
}
function damage(points){
if(dead){
return false;
}
hp -= points;
if(hp <= 0){
dead = true;
hpTxt.html('');
sym.play('explode');
return true;
}
else{
hpTxt.html(hp);
if(hp <= 20){
hpTxt.css("color","red");
}
return false;
}
}
////PUBLIC
function isDead(){
return dead;
}
sym.setVariable('init',init);
sym.setVariable('damage',damage);
sym.setVariable('isDead',isDead);
/////////////

Declare a few variables at the top of our script.  Our hp variable holds the ‘health’ of our alien, and dead is a boolean we’ll need to determine a few things back in our game logic. Next are a few reference variables for quick access to the hp text field, as well as the jQuery element of our Alien.  Immediately after that, hide our Alien so we can fade him in when its properties are set.

Our init function is called from our game right after the Alien is loaded.  It accepts a parameter of points which be used to set our Aliens hp value and update the hp text field.  Here, we are simply using the Title property as opposed to an assigned class name.  Again, just a different way of doing the same thing.  Lastly, fade the sucker in.

Our damage function is also called from our game code and is used to inflict damage on the alien. If it’s already dead, just return out of the function.  If not, move on.  First deduct the value passed in from our hp value. If it’s less than 0, make it 0, i.e dead!  Get rid of the text field and play the explode sequence by jumping to the frame label ‘explode’.  

If it’s not dead yet, simply update the hp text field and make it red if it’s getting low.  I’m returning true or false in the function because the game needs to know if the attack killed the alien.

Now let’s open up some of this code so we can access it from the game code.  We’ve done this before.  Here we do something a little different though and create a special function to return our dead value.  We need to do this because dead is a variable and not a reference to a function.  Simply adding a dead variable to the symbol with a reference to dead will merely hold it’s initial value.  Think of isDead as a getter function for dead.  This approach can also be used for setting values.

That's enough death talk, now let's get back to our PlayState and move on with the game logic code.

//PlayState cont.

function initAliens(){
var el;
var killed;
$.each(aliens, function( count, a ){
a.getVariable('init')(Math.floor(Math.random() * 8) * 10 + 10);
el = a.getSymbolElement();
el.bind('touchstart click',function(){
if(!attackReady){
return;
}
attackReady = false;
killed = a.getVariable('damage')(power);
if(killed){
power += 5;
sym.$('.power').html(power);
}
sym.getSymbol('waitBar').play(0);
});
});
}

This is a fairly heavy function.  It’s all pretty straightforward but there is a crucial point to be made here and it has to do with referencing symbols after binding events to its element.  

We know that it’s easy to access a symbols element, but the other way around is not so straightforward.  Simply binding an event to a symbols element within the loop that creates it will lose a reference to each symbol within the element’s event handler.  This is not good since we need that symbol to invoke its instance code when attacking (tapping) it.  

We can achieve this by looping through our Alien symbol array, using a for each loop and closures.   Now we can safely reference each symbol instance in the handler function we create on them.

That being said, what we do here is loop through each Alien, invoke its init method by passing in a random hp value.  Then we bind the click / touch event.  We want to return out if we are not ready to attack.  If we are ready, we immediately set attackReady back to false to prevent further action.  Now we actually attack the Alien and store its returned boolean value to killed so we know if we wasted it. (remember it returns true if we killed it ).  If we did, we increase our own power (level up), and update the appropriate HUD element.  Lastly, restart the wait bar.

function initPotions(){
sym.$('potion').bind('click touchstart',function(){
if(!attackReady){
return;
}
else{
attackReady = false;
potion--;
sym.$('.potion').html(potion);
hp += 50;
hp = hp > maxHP ? maxHP : hp;
sym.$('.hp').html(hp);
var bar = sym.getSymbol('waitBar');
bar.play(0);
if(potion === 0){
sym.$('potion').unbind('click touchstart');
}
}
});
}

We might need a little help along the way so we are given 3 potions that will increase our HP by 50, never exceeding our max value so they need to be used wisely.  This should all look familiar to you by now so I won’t go into detail.  What’s new is our unbind call which simply prevents potion taps when we run out.

function startTimer(){
gameTimer = setInterval(run,300)
}
function run(){
counter++;
checkWaitBar();
checkForAlienAttack();
checkForAlienLife();
checkForLife();
}

Now we run a small game loop to periodically check game values.  This doesn’t need to be overly fast so every 300 milliseconds will serve us just fine.  Our run function first increases our counter variable then runs a series of check functions. Let's review these functions now.

function checkWaitBar(){    
var bar = sym.getSymbol('waitBar');
if(!bar.isPlaying()){
attackReady = true;
}
}

We need to see if we are in a state where we can attack, so calling the bars isPlaying() method will let us know if it has reached the end yet of its timeline.  If it has, it will stop and return false, allowing us to attack by updating the attachReady variable.

function checkForAlienAttack(){
if(counter === alienAttackCounter){
hp -= (Math.floor(Math.random() * 5) + 30);
hp = hp < 0 ? 0 : hp;
sym.$('.hp').html(hp);
var attackFlash = sym.createChildSymbol("Flash","Stage");
attackFlash.getSymbolElement().delay(1000).fadeOut('fast',function(){
attackFlash.deleteSymbol();
counter = 0;
alienAttackCounter--;
});
}
}

The next method determines if it’s time for the aliens to attack us.  If it’s time, we create a random, in-range value to damage us, make sure we don’t fall below zero, then update our HUD element for hit points.  After that we attach a simple animated symbol that loops through a series of alpha values on a full screen red rectangle.  After a second, we fade it out, delete it, set counter back to zero and decrease the time for the next alien attack.

function checkForAlienLife(){
var alien;
for(i = 0;i < aliens.length;i++){
alien = aliens[i];
if(!alien.getVariable('isDead')()){
return;
};
}
gameWin();
}
function checkForLife(){
if(hp === 0){
gameLose();
}
}

These next two functions check if the game should carry on.  We first loop through all aliens symbols and call on their isDead method.  If we get through the loop, we win!  We also need to check if we are dead and if so, we lose.

function gameWin(){
window.game.GLOBALS.win = true;
clearInterval(gameTimer);
setTimeout(function(){
game.getVariable('changeState')("GameOverState");
},1000);
}
function gameLose(){
clearInterval(gameTimer);
window.game.GLOBALS.win = false;
game.getVariable('changeState')("GameOverState");
}

Finally, these two functions wrap up of our game logic.  If we win the game, we set our global variable game.GLOBALS.win value to true, and false if we don’t.  We kill our game loop timer and finally change the game state.  We want a little padding if we win so we can see the last alien fade away into oblivion.

Lastly, call init() to kickstart everything that we just created.

GameOver State

var game = sym.getComposition().getStage();

function init(){
var msg = window.game.GLOBALS.win ? "YOU SAVED US ALL" : "YOU FAILED US ALL";
sym.$('messageTxt').html(msg);
sym.getSymbolElement().bind('click touchstart',function(){
game.getVariable('changeState')("PlayState");
});
}

init();

Let’s keep this last state nice and quick. We have symbol with some text for a few messages.  We access our global win value and set the text fields appropriately.  Bind a click / touch event to the entire symbol, which will then change our game state back to PlayState.  

Of course , call init() at the end of your script.


There you have it. As you can see, you can quickly create a game using Edge Animate.  This approach may not be best for large, intense games but it actually gets your pretty far.  It definitely makes for an amazing entry point to HTML5 games given the fact that Canvas can be pretty intimidating for beginners.  

Feel free to grab the source and add on to the game.  Maybe stick some potions in some of the aliens, etc.  Or even a few bombs?  The sky's the limit!