Tutorial: Building A Game In Edge Animate (Offender)

Introduction:

I’ve decided to build this first Tetrageddon Games “mini-game” tutorial, Offender, in Adobe Edge Animate because the program is still very new, and there isn’t much out there on it in terms of game development.

I think this will make a great “getting started” resource for newcomers, as well as demonstrate the capabilities of Edge Animate.

In this tutorial I hope to offer a solid example of building a game with the program.
This is the first part of a series discussing HTML5 game development.

* DOWNLOAD the source files here: Offender_HTML.zip

* Play the finished game here: Offender HTML

* Visit the website for it here: http://offender.alienmelon.com/

Note, Edge Animate is being used in some game projects already. A few interesting links I’ve stumbled across while working with it are:

* Space Invaders RPG. Building a Game in Adobe Edge Animate

* Adobe’s Episode 10: Controllable characters in an interactive environment

* Create animated game HUD with Adobe Edge Animate and Coherent UI (Part 1)

In terms of HTML5 game development, there are many frameworks out there for making HTML5 games. A list slowly growing some of the most popular are is:

* Kiwi.js
Kiwi.js is a fun and friendly Open Source HTML5 Game Engine. Some people call it the WordPress of HTML5 game engines…

* seen.js
Library – Render 3D scenes into SVG or HTML5 Canvas.

* Isomer
An isometric graphics library for HTML5 canvas. Isomer is available for free under the MIT License, but you can also pay for it.

* KineticJS
KineticJS is an HTML5 Canvas JavaScript framework that enables high performance animations, transitions, node nesting, layering, filtering, caching, event handling for desktop and mobile applications, and much more.

* createjs.com/
A suite of Javascript libraries & tools for building rich, interactive experiences with HTML5.

* https://github.com/photonstorm/phaser
Phaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.

* http://html5quintus.com/
Quintus is an easy-to-learn, fun-to-use JavaScript HTML5 game engine for mobile, desktop and beyond!

* https://github.com/GoodBoyDigital/pixi.js
Super fast HTML 5 2D rendering engine that uses webGL with canvas fallback

* http://www.pandajs.net/
Free HTML5 game engine for mobile and desktop games

* http://spelljs.com/
The SpellJS framework enables you to develop games for the web and mobile platforms. It hides the additional effort associated with the development of cross-platform applications so that you can concentrate on creating games instead of building cross-platform infrastructure. The integrated development enviroment SpellEd assists with the creation of content.

* http://www.babylonjs.com/
Babylon.js is a 3D engine based on webgl and javascript. It supports many features, and I’m drooling over it.

* Blend4Web
Blend4Web is a software framework for authoring and interactive rendering of three-dimensional graphics and audio in browsers, i.e. a three-dimensional engine. The platform is intended for creating visualizations, presentations, online-shops, games and other rich internet applications.
The Blend4Web framework is integrated tightly with 3D modeling and animation tool Blender (hence the name). The content is rendered by the means of WebGL and other browser technologies, without the use of plugins.
Technically Blend4Web is a library for web pages, a Blender addon and some tools for debugging and optimization.

* Canvas Query
Canvas for 2d gamedevelopers. Out of box canvas, keyboard, mouse, events.
http://canvasquery.com

* Lightweight Java Game Library 3
LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL), audio (OpenAL) and parallel computing (OpenCL) applications. This access is direct and high-performance, yet also wrapped in a type-safe and user-friendly layer, appropriate for the Java ecosystem.

* Construct 2
This is the perfect tool for beginners or non-technical artists. Construct 2 is a powerful ground breaking HTML5 game creator designed specifically for 2D games. It allows anyone to build games — no coding required! It’s almost entirely drag-and-drop.

* CopperLicht
Free and fast WebGL JavaScript 3D Engine with World Editor. CopperLicht is an open source WebGL library and JavaScript 3D engine for creating games and 3D applications in the webbrowser. It uses the WebGL canvas supported by modern browsers and is able to render hardware accelerated 3d graphics without any plugins.

* pixijs
Pixi.js is a devoted rendering engine. There are a host of other engines covering game, sound and physics etc. and they all work beautifully with Pixi.

* cocos2d
Cross platform open source free 2D game engine for mobile gamedev, that is fast and stable, easy to learn and use.

* PuzzleScript
PuzzleScript is an open-source HTML5 puzzle game engine.

Edge Animate Resources:

* Edge Animate Docs

* Adobe Edge Animate JavaScript API

* Edgehero.js

*************

Setting up the Game Stage:

When first working with Edge scope can be a surprise to beginners — presents an issue. A great article on the subject is this post (HERE). To avoid problems, actions are typically added to document.compositionReady.

In this case “global” variables, and sounds, are created in document.compositionReady.

Basic keyboard events are in document.keydown, and document.keyup, which reference document.compositionReady‘s “global” variables.
The actual game is in the stage’s trigger (see label “game“).
The game takes place on label “game“. I thought it most convenient to manage scope by keeping the bulk of the game engine in that trigger.
Variables, states, arrays, etc… can set/reset without conflicting with other areas.

Animated/transitions that will change as the game is played are grouped in symbols.
It’s a good idea to plan this carefully, and use symbols (and their timeline) more than sending the “playhead” back and forth on the stage’s timeline (keep things manageable and grouped up). It will be easier to manage when you code.
Control symbols, and use the stage’s timeline for serious transitions (like changing from the main menu, to the highscores area).

In this case we have 3 “screens”. The “play area”, the “game over” screen, and the “enter your name” screen (succeeding “game over”).
The above image shows the play area. There are 4 important factors here.

1) The “Nightmare” symbol. This is an “extra” that shows/hides as you progress. While you are playing, the bunny is dreaming/thinking of all the terrible things that will happen to him if he gets abducted. These show in the corner of the screen in little comic book like “star” panels. There are two versions of a nightmare, and a random one is selected, and randomly plays.

2) The “current level” indicated by how many bunnies are left in bed. This is like a countdown before winning.
Whenever the player reaches a certain score, a bunny is pulled (abducted) out of its bed, until the player reaches the last bunny.

Progress is indicated in this manner, so I grouped all the bunnies in a symbol (Beds), and the animated bunnies (level 2, and 3) are also symbols where the animation takes place. When the level is to progress, the game will simply play that transition…

3) Your score. Each bunny you abduct with your ship adds a point. Each bunny that escapes off screen deducts a point.

4) The play area. This is the field. Bunnies created via sym.createChildSymbol are placed here (total of 10). The ship, the ships ray, and enemy ships are also added (created dynamically).

The Game Over Screen

Is a symbol, containing the buttons, and the player’s score. This shows when the player is killed by an enemy ship. “Surrender” will call up the “enter your name” screen. “Overachieve” is the try again option, and will re-start the game.

Enter Your Name Screen

The player will enter their name here. Saving score, and name is done using the HTML5 localStorage feature. More notes bellow.

*************

Sound:

There are a number of ways to add sound to your project. None of them are 100% reliable, due to browser support. See: http://caniuse.com/audio
My recommendation for Edge Animate is to either use Edge Commons’s Dirty Little Helpers: http://www.edgedocks.com/edgecommons
Here is the tutorial for setting it up: Sound playback with volume control in Animate with Edge Commons

Or Buzz!
http://buzz.jaysalvat.com/

Buzz is a small but powerful Javascript library that allows you to easily take advantage of the new HTML5 audio element. It degrades silently on non-modern browsers.

I will be using Buzz.

Add the script via Library > Scripts + tab, and navigate to where you have it.

Then in document.compositionReady (to add the compositionReady event click on the + next to Stage). The following adds a sound. To play call Aud_GameTrack.play();

Aud_GameTrack = new buzz.sound("audio/mainmenu-loop", {formats: ["wav", "ogg", "mp3"],
    preload: true,
    autoplay: true,
    loop: true
});

Formats = Various formats for browsers. Not all browsers support the same sound format, so you need to include the most common ones. See: http://buzz.jaysalvat.com/documentation/sound/
The best combination is OGG + MP3 formats (according to buzz).

Also, mobile devices will not load/play sound without user intervention (no autoplay). The sound must be triggered via user input (a button press, for example). iPhone and iPad only play one sound at a time, and you cannot change the volume dynamically.
See Overcoming iOS HTML5 audio limitations And Making HTML5 audio actually work on mobile, for some notes on that.

After you have created your sound (above), in document.compositionReady, make sure you call:

Aud_GameTrack.load();

Buzz is nice, and has a number of settings.
The above (Aud_GameTrack.load) is a loop, and the main “soundtrack” for the game.
I manually made it longer, so it appears to loop. You will notice a “loop gap”. It takes a bit more hacking around to get seamless audio looping (Flash does it well, but it’s trickier in javascript/html). Here are some notes for those interested in getting that to work:
Stackexchange – MP3 gapless looping help?
HTML5 Audio Loops
But this is a simple game, so I’m keeping the sound aspect simple.

All the sounds in the game are as follows:

//SOUND

//I manually made the sound longer so it appears to loop -- nevermind the gap
//it is a bit more hacking around to get seamless audio looping (Flash does it well)
//here are some notes for those interested in getting it to work
//http://sound.stackexchange.com/questions/8916/mp3-gapless-looping-help
//http://forestmist.org/blog/html5-audio-loops/
//the main soundrack of the game:
Aud_GameTrack = new buzz.sound("audio/mainmenu-loop", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
//plays at the end of the game:
Aud_GameTrack_2 = new buzz.sound("audio/mainmenu-under-loop2", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
Aud_ScoresTrack = new buzz.sound("audio/scores-loop", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
Aud_KeysTrack = new buzz.sound("audio/Keys-loop", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
//Menu sounds
Aud_MenuPreLoop = new buzz.sound("audio/Mainmenu-preloop", {formats: ["wav", "ogg", "mp3"], preload: true});
Aud_MenuLoop = new buzz.sound("audio/mainmenu-loop3", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});

//Intro
//Aud_LogoMoron = new buzz.sound("audio/morons-blb", {formats: ["wav", "ogg", "mp3"]});
Aud_Intro = new buzz.sound("audio/Intro", {formats: ["wav", "ogg", "mp3"], preload: true});

Aud_Nightmare = new buzz.sound("audio/JAPAN-3", {formats: ["wav", "ogg", "mp3"]});
Aud_Explode = new buzz.sound("audio/explode", {formats: ["wav", "ogg", "mp3"],preload: true});
Aud_End = new buzz.sound("audio/End", {formats: ["wav", "ogg", "mp3"],preload: true});
Aud_Hit1_ = new buzz.sound("audio/daaaam-1", {formats: ["wav", "ogg", "mp3"]});
Aud_Hit2_ = new buzz.sound("audio/daaaam-2", {formats: ["wav", "ogg", "mp3"]});
Aud_Hit3_ = new buzz.sound("audio/daaaam-3", {formats: ["wav", "ogg", "mp3"]});

Aud_BeamLoop = new buzz.sound("audio/beam-loop", {formats: ["wav", "ogg", "mp3"],
    loop: true
});

Aud_Laser = new buzz.sound("audio/LASER", {formats: ["wav", "ogg", "mp3"]});

//load them with Edge
Aud_GameTrack.load();
Aud_GameTrack_2.load();
Aud_ScoresTrack.load();
Aud_KeysTrack.load();
Aud_MenuPreLoop.load();
Aud_MenuLoop.load();
Aud_Intro.load();
Aud_Nightmare.load();
Aud_Explode.load();
Aud_End.load();
Aud_Hit1_.load();
Aud_Hit2_.load();
Aud_Hit3_.load();

//stop all currently playing "hits" for menu and anywhere else they trigger on button overs
sym.stopHits = function(){
    Aud_Hit1_.stop();
    Aud_Hit2_.stop();
    Aud_Hit3_.stop();
}

The last one is a function that stops the “hits” for the menu. When you mouse over buttons a hit plays. To keep it from piling up, I stop all of them, before playing the button mouseover sound.

*************

Fonts:

The original Bitmap font was from: 04.jp

But in this version I am using the Google Font, Press Start 2P.

To get web fonts working in Edge Animate follow Adobe’s walkthrough: Web fonts and Edge, together at last.

*************

All The “Global” Variables Of document.compositionReady

Aside from sound, there are a slew of variables in document.compositionReady. These are “global” variables that I will need to access throughout the game, in all the different places. Like document.keydown, or button events. I put them here so that scope will not be an issue.

The first, and most important is sym.SCENE. I save the current “scene” that the game is on here. For example, if you are viewing the highscore area it is set to “scores”, on the main menu it’s “menu”, in the actual game it’s “game”. I do this for the keyboard. If you are playing the game, the keyboard for the “game” unlocks by checking against sym.SCENE (if sym.SCEME=="game"). If I where to have custom keyboard events for menu, the keyboard for “menu” would unlock. This gives me a platform to build on if I where to make this game keyboard only, or expand the keyboard functionality. More on that in the Keyboard Controls section.

The first set of “global” variables ranges from numbers like scores, time, enemyAmount, to various sprite speeds, to the current level.
Booleans serve as flags, these are for the keyboard (down/up).

The “//symbol references” chunk saves symbol names (so they can be more easily referred to through code).

The two arrays are where created symbols (enemies and bunnies) are pushed.

The “//X Y positions” bit is where I save starting positions for created sprites, as well as where the centerX/Y of the stage is.

Finally there are “//Timers - intervals“. I saved the names of all setIntervals here to manage the scope for them. They can be stopped better this way.

//"GLOBAL" VARIABLES
//what label? 
//will check against this and if it is "game" then game functionality will unlock
//if it is menu then keyboard for menu will unlock, etc...
sym.SCENE; 

sym.score = 100;    
sym.time = 1;
sym.enemyAmount = 1;//current amount
sym.enemyMax = 3;//maximum amount of enemies alowed at once
sym.timeElapsed = 1;
sym.playerSpeed = 600;//5
sym.playerSpeed_left = 600;//5
sym.playerSpeed_right = 600;//5
sym.bunnySpeed = 5000;//will be random + itself, this is the default minimum value
//sym.bunnyAbAmnt = amount of abducted bunnies, if it's above a certain number then enemies will start appearing
//this keeps the game from generating enemies right away -- gives the player time to adjust
sym.bunnyAbAmnt = 0;
//goals (before leveling up)
sym.lvl1_goal = 300;
sym.lvl2_goal = 530;
//targeted time (how long you have been playing before you can go to next level)
sym.lvl1_time = 300;
sym.lvl2_time = 500;
//above but for winning
sym.win_time = 700;
sym.win_goal = 800;

sym.bNum = 10;//maximum number of bunnies on stage

//Booleans - flags
sym.bool_left = false;
sym.bool_right = false;

//.data to keep it .play() called once
//may only play once if it is true then the transition to next level may play
sym.mc_b1_data = false;
sym.mc_b2_data = false;

//symbol references
sym.stage = sym.$("Stage");
sym.mc_b1 = sym.getSymbol("Beds").getSymbol("IMG_Sprite_Bed02");
sym.mc_b2 = sym.getSymbol("Beds").getSymbol("IMG_Sprite_Bed03");
sym.mc_nightmare = sym.getSymbol("Nightmare");
sym.mc_transition = sym.getSymbol("Transition_Blink");
sym.mc_gameover = sym.getSymbol("Screen_End");
sym.mc_score = sym.getSymbol("Screen_Score");
sym.mc_ship;
sym.mc_ray;
sym.txt_score = sym.$("txt_score");

//Arrays
sym.arr_bunnies = [];
sym.arr_enemies = [];

//X Y positions
sym.shipY = 70;//25
sym.enemyY = 88;
sym.rayY = 66;//66
sym.bunnyY = 180;
//centering
sym.centerX = sym.stage.width()/2;
sym.centerY = sym.stage.height()/2;
//left/right bounds
sym.maxX = sym.stage.width();
sym.minX = 10;//minimum placement for ship
//player X (incremented/decremented via keyboard input, initial value is center)
sym.shipX = sym.centerX;

//Timers - intervals
sym.timerInt;
sym.timeElapsedInt;
sym.nightmareInterval;
sym.enemyInterval;
sym.shipHitInterval;

*************

Collision detection – The Bunnies And Enemies

Because Edge Animate was never meant for games (according to general sentiment) this is very limited.
There are a number of discussions out there. For example both jquery – Detecting div collision, and Please recommend a JQuery plugin that handles collision detection for draggable elements are good summaries of how to tackle it, and this thread on Adobe Community which has some good tips. Another great 2D Collision Detection tutorial is:
HTML5 Canvas Game: 2D Collision Detection

The best solution out there is JQuery Collision (which I highly recommend)
SourceForge: JQuery Collision
It is also being used in Adobe’s Episode 10: Controllable characters in an interactive environment tutorial (which is the most thorough breakdown of hit detection in Edge Animate that I could find).

I’m using simple object intersection collision detection because this is a small project.

But really, for more elaborate things consider JQuery Collision

Download it here: http://sourceforge.net/projects/jquerycollision/

And add it via Library > Scripts + tab. Navigate to where you have it.

Basic collision detection code for this project is as follows:

function hitTest(a, b){
    //
    aPos = {x:parseInt(a.css('left')), y:parseInt(a.css('top'))};
    bPos = {x:parseInt(b.css('left')), y:parseInt(b.css('top'))};
    //
    return aPos.x < bPos.x + b.width() && aPos.x + a.width() > bPos.x && aPos.y < bPos.y + b.height() && aPos.y + a.height() > bPos.y
}
//Usage
var Rectangle = sym.$("Rectangle");
var Rectangle2 = sym.$("Rectangle2");
console.log(hitTest(Rectangle, Rectangle2));

(Thanks Adobe forms for pointing me in the right direction here!)

Collision detection will take place in two cases.

1) When the player presses keyboard “space” and the ship’s beam should pick up bunnies.

Bunnies are dynamically created with createChildSymbol:

function makeBunny() {
    for (var i = 0; i<sym.bNum; i++) {
        var mc_bunny = sym.createChildSymbol("Bunny", "Stage").getSymbolElement();
        //push to array for access later and start moving it
        //bunnyM also takes care of placeing them
        //note: bunnies must be stored in array
        //for colision detection, and better access
        sym.arr_bunnies.push(mc_bunny);
        bunnyM(mc_bunny);//movement function
    }
}

After they are created bunnyM is called and each is passed as a parameter.

//Bunnies!
function bunnyM(clip) {
    //console.log(clip);
    //speed, and end variables
    var bEnd = sym.stage.width()-clip.width();
    var bSpeed = (Math.ceil(Math.random()*sym.bunnySpeed)+sym.bunnySpeed)-500;
    //place it randomly (x) on the stage
    clip.css({"position":"absolute", "top":sym.bunnyY + "px", "left":(Math.ceil(Math.random()*sym.stage.width())-clip.width()) + "px"});
    //call first to cancel queue -- just incase this could become a problem
    clip.stop();
    //
    clip.animate( {left: bEnd}, bSpeed, "linear", function(){
        sym.score -= 1;//"escapee" penalty
        bunnyM(clip);
    }
    );
}

bunnyM is responsible for moving them. They are positioned with css. clip.stop is called to stop possible previous animation queue, and finally .animate is called. When .animate is done it calls bunnyM again as the complete callback function. See: https://api.jquery.com/animate/ for more .animate options.

Reference to them is stored in an array. This is a good idea when you are dynamically creating symbols because it’s easier to reference them later. There are other ways of getting symbols (loop through stage), but I prefer this method.

When SPACE is pressed there are two functions associated with it.

//creating function with sym.
//to change scope so keyboard events can access these
sym.key_onKeyDown = function() {
    sym.mc_ray.show();
    Aud_BeamLoop.play();
    //hit detection
    for (var i = 0; i<sym.arr_bunnies.length; ++i){
          bunnyD(sym.arr_bunnies[i]);
    }
};
sym.key_onKeyUp = function() {
    Aud_BeamLoop.stop();
    sym.mc_ray.hide();
}

onKeyDown shows the ray, plays the sound, and loops through the bunnies calling bunnyD (the collision detection function), and passing each bunny as the function parameter.

AND FINALLY bunnyD is where the magic happens.

function bunnyD(clip) {
    if(hitTest(sym.mc_ray, clip)){
        sym.score += 1;
        sym.bunnyAbAmnt+=1;
        clip.stop();//stop current animation queue
        clip.animate( {top: 0}, 200, "linear", function(){
            bunnyM(clip);
        }
        );
    }
}

If the ray hits the clip (array element) increment the score, STOP the current animation (this is good practice because it prevents issues with the animation queue because they are animated with .animate, and that can get funky), and then .animate them to the top of the screen (abduct them). When the bunny reaches the top of the screen the bunnyM function is called again (start over).

AND collision detection case #2 is…
2) When an enemy ship hit the player.

This one is simpler.
There are two intervals in the game that manage enemies. enemyInit(); and sym.timerInt = setInterval(timer, 100);

Timer interval takes care of incrementing the time (how long player has played), and setting the score. It also increments enemies. If the score is past lvl1_goal, and the player has played long enough more enemies show. Up to 4 enemies can be created at a time (sym.enemyMax). If the max has been reached they reset back to 1, and start incrementing again. This simulates “waves of attacks”.

This is how timer() looks:

//game timer
//initiates enemies
function timer(){
    //set score
    sym.txt_score.html(sym.score);
    //increment time
    sym.time+=1;
    //increment enemies, if the score is past lvl1_goal
    if (sym.time>100 && sym.score>sym.lvl1_goal-100) {
        sym.time = 1;
        if (sym.enemyAmount<sym.enemyMax) {
            sym.enemyAmount++;
        }
        //no more than 4 enemies, reset it
        if (sym.enemyAmount == sym.enemyMax) {
            sym.enemyAmount = 1;
        }
    }
}

Then there is enemyInit(). It initiates the enemies (calls to create the enemy). As you play, and as you progress, the game gets more difficult (more enemies get created).
enemyInit is an interval that gets called at random intervals. Difficulty is set by shortening the interval. The game gets harder because enemyInit is called more often.

Here is how it looks:

//enemyInit calls to create the enemy
//as you play, and as you progress
//the game gets more difficutl -- more enemies
function enemyInit()
{
    clearInterval(sym.enemyInterval);
    //was (Math.random()*20000)+10000;
    var randNum = (Math.random()*9000)+10000;
    //
    if(sym.score>=sym.lvl1_goal-100){
        randNum = (Math.random()*8000)+9000;
    }
    if(sym.score>=sym.lvl2_goal-200){
        randNum = (Math.random()*5000)+7000;
    }
    if(sym.score>=sym.win_goal-400){
        randNum = (Math.random()*1000)+6000;
    }
    if(sym.score>=sym.win_goal-200){
        randNum = (Math.random()*1000)+5000;
    }
    //if sym.bunnyAbAmnt is above the goal
    //then you may start creating enemies
    if(sym.bunnyAbAmnt>10){
        makeEnemyFire();
        attachEnemyShip();
    };
    //
    sym.enemyInterval = setInterval(enemyInit, randNum);
}

Notice var randNum is fairly high at the beginning.
If the score is above the goal for level 1 shorten it, if the score is above the goal for level 2 shorten it, etc… When you are just about to win the game is hardest (if(sym.score>=sym.win_goal-200)).
The very last condition keep enemies from being created RIGHT AWAY. This will give the player time to adjust to the game. They must abduct at least 10 bunnies before enemies start appearing.
The final line (sym.enemyInterval = setInterval(enemyInit, randNum);) calls the interval after all that is set up…

The enemies are created with makeEnemyFire(); and attachEnemyShip();
makeEnemyFire() serves as a warning that an enemy is approaching. The fire does nothing (has no impact on the player). The Fire appears randomly somewhere under the ship. One shot is fired per enemy that approaches.

This is how it looks

function makeEnemyFire(){
    Aud_Laser.play();
    //make as much enemy fire as there are enemies, so going by sym.enemyAmount
    for (var i = 1; i<sym.enemyAmount+1; i++)
    {
        var mc_fire = sym.createChildSymbol("Fire", "Stage").getSymbolElement();
        enemyFire(mc_fire); 
    }
}

And enemyFire(...) is then called, passing the created symbol as a parameter.

function enemyFire(clip)
{
    var randSpeed = (Math.random()*20)+10;
    //enemy fire serves as a warning that an enemy is approaching
    //fire does nothing, it passes the ship,
    //and should appear randomly somewhere under the ship, and over the bunnies
    clip.css({"position":"absolute", "top":Math.ceil(Math.random()*40)+90 + "px", "left":-clip.width() + "px"});
    //
    clip.animate( {left: (sym.stage.width()+clip.width())}, 1000, "linear", function(){
        clip.remove();//get rid of it when it's passed
    }
    );
}

enemyFire(...) animates the fire. The ‘clip‘ is removed once it has animated to the side of the screen.
I am removing the symbol using .remove(). You can also use .deleteSymbol(). I am not certain what the difference is so I am sticking to .remove(); see: http://api.jquery.com/remove/

attachEnemyShip(); is what creates the enemy, and pushes it to the enemy array. Simply put, it looks like this

function attachEnemyShip()
{
    for (var i = 0; i<sym.enemyAmount; i++)
    {
        var mc_enemy = sym.createChildSymbol("Enemy_Ship", "Stage").getSymbolElement();
        sym.arr_enemies.push(mc_enemy);
        enemyShip(mc_enemy);
    }
}

enemyShip(...) positions, and moves the enemy. Enemies move at a random speed. Here’s the function

function enemyShip(clip)
{
    //position ship - somewhere off screen, at same Y
    var enemyStartX = (Math.random()*-500)-200;
    clip.css({"position":"absolute", "top":sym.enemyY + "px", "left":enemyStartX + "px"});
    //random speed
    var randNum = (Math.random()*10000)+5000;
    //move it
    clip.animate({
        left: (sym.stage.width()+clip.width())
        }, {
            duration: randNum, 
            queue: false,
            easing: "linear", 
            complete: function(){
                clip.remove();
            }
    });
    //start moving it up/down -- call the starting movement
    //at random -- either the top most or bottom most
    if(randNum>randNum/2){
        enemyShip_move2(clip);
    }else{
        enemyShip_move4(clip);
    }
}

I’m doing something different here for enemy movement. Enemies move in an “8 step” up/down pattern (separated into respective functions).
I use .animate to call the next function when a certain movement is done. This simulates the ship flying up/down exactly how I want them (it’s very specific). They fly up, and stay just above the player giving the player a window to dodge UNDER them, OR move down and stay under the player giving the player a short window to dodge OVER them. Each enemy should randomly start on the lower level, or upper level.

Here are all 8 of the movement functions:

//enemy ship comes in 8 steps using .anmiate to call the next when a movement is done
//this simulates the ship flying up/down exactly how I want it
//I'm certain there are better ways of doing this, if so let me know!
//
//the starting function is called at random -- in enemyShip(clip);
//so when there are multiple enemies there is a random flying pattern to doge
var enemyBounceSpeed = 500; //the speed of up/down movement -- should last 0.3 seconds
function enemyShip_move1(clip){
    clip.animate({
        top: 83
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeInCubic", 
            complete: function(){
                enemyShip_move2(clip);
            }
    });
}
function enemyShip_move2(clip){
    clip.animate({
        top: 120
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeOutCubic", 
            complete: function(){
                enemyShip_move3(clip);
            }
    });
}
function enemyShip_move3(clip){
    clip.animate({
        top: 60
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeInCubic", 
            complete: function(){
                enemyShip_move4(clip);
            }
    });
}
function enemyShip_move4(clip){
    clip.animate({
        top: 25
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeOutCubic", 
            complete: function(){
                enemyShip_move1(clip);
            }
    });
}

notice in the ending condition in enemyShip(...)

    //start moving it up/down -- call the starting movement
    //at random -- either the top most or bottom most
    if(randNum>randNum/2){
        enemyShip_move2(clip);
    }else{
        enemyShip_move4(clip);
    }

This is what calls the random top most or bottom most starting function. I did this because, when there are multiple enemies created, they will sometimes fly in zig-zag (like scissors) and the player has to time his movement carefully to dodge between them. It makes for a fun challenge. The player must pay attention.
I’m certain there are many better ways of doing this (managing enemy movement), but this seemed the best/simplest to me.

AND FINALLY enemy collision detection takes place as a setInterval. sym.shipHitInterval = setInterval(enemyHit , 100);
shipHitInterval calls enemyHit and this is what checks for ship vs. enemy colision. enemyHit looks like this


//DEATH
//was the player hit by an enemy ship?
function enemyHit(){
    //
    for(var i = 0; i<sym.arr_enemies.length; ++i){
    //
        if(hitTest(sym.mc_ship, sym.arr_enemies[i])){
            //create and place an explosion on the ship coords
            var mc_explode = sym.createChildSymbol("Explode", "Stage").getSymbolElement();
            mc_explode.css({"position":"absolute", "top":sym.mc_ship.position().top + "px", "left":sym.mc_ship.position().left+ "px"});
            //
            Aud_Explode.play();
            sym.arr_enemies[i].stop();
            sym.arr_enemies[i].remove();
            //you lose
            sym.SCENE = "lose";
            stopG();
        }
    }
}

Because all enemies are stored in an array I loop through the contents of this array, and run the hitTest(...) on player and enemies.
If a collision takes place the “death” explosion is placed where the player ship is, the explosion sound plays, and the current enemy is stopped and removed. You lose, and the game is stopped. (stopG() kills the game).

This is a simple game, so the above method will work, but it is inefficient in that every 100 milliseconds the game must loop through all enemies created vs. player to check if a collision took place. For something larger performance would suffer… but it’s ok for this because it’s little.

*************

Keyboard Controls, The Player Ship, and The Beam

The player ship, and the player beam are probably the simplest symbols here.
There are two functions that manage their creation, and removal.


//Player
//create and place ship and beam
function createSprites() {
    sym.mc_ship = sym.createChildSymbol("Ship", "Stage").getSymbolElement();
    sym.mc_ship.css({"position":"absolute", "top":sym.shipY + "px", "left":sym.shipX + "px"});
        //
    sym.mc_ray = sym.createChildSymbol("Ray", "Stage").getSymbolElement();
    sym.mc_ray.css({"position":"absolute", "top":sym.rayY + "px", "left":sym.shipX + "px"});
    sym.mc_ray.hide();
}

function removeSprites(){
    sym.mc_ship.stop();
    sym.mc_ray.stop();
    sym.mc_ship.remove();
    sym.mc_ray.remove();
}

The first creates it, the second removes it. Like I said, I am removing the symbols using .remove(). You can also use .deleteSymbol(). See: http://api.jquery.com/remove/ for more information on .remove.

After this, it becomes a matter of moving the ship left/right, and triggering the beam when you press SPACE.
We’ve already covered the SPACE keyboard event in the above collision detection section:

//creating function with sym.
//to change scope so keyboard events can access these
sym.key_onKeyDown = function() {
    sym.mc_ray.show();
    Aud_BeamLoop.play();
    //hit detection
    for (var i = 0; i<sym.arr_bunnies.length; ++i){
          bunnyD(sym.arr_bunnies[i]);
    }
};
sym.key_onKeyUp = function() {
    Aud_BeamLoop.stop();
    sym.mc_ray.hide();
}

The rest is taken care of in document.keydown, and document.keyup.

Taking a look at document.keydown, you will see

//GAME KEYBOARD
//add others according to behavior for desired "SCENE"
//example: if it is main menu then enable main menu keyboard functionality
//if it is game then enable game keyboard functionality

//GAME
if(sym.SCENE == "game"){
    //32 is SPACE
    if (e.which == 32) {
        //call the function on the stage's "game trigger"
        sym.getComposition().getStage().key_onKeyDown();
    }
    //37 is LEFT
    if (e.which == 37){
        //keeps it from "crawling" if key is pressed while ship is close to left of screen
        sym.playerSpeed_left = (sym.mc_ship.position().left);
        sym.bool_left = true;
    }
    //39 is RIGHT
    if (e.which == 39){
        //keeps it from "crawling" if key is pressed while ship is close to right of screen
        sym.playerSpeed_right = (sym.stage.width()-(sym.mc_ship.position().left));
        sym.bool_right = true;
    }
    //player movement
    //is an interval - target left/right location are the sides of the screen
    //if the keyboard key is released .anmiate is stopped/canceled (see keyup)
    //there are a number of ways to animate with css/js http://www.dehats.com/drupal/node/120
    //has some good examples. I've chosen .animate here, and because the target are the edges
    //of the screen it will continue to move there untill you release the key
    //it will also not go off the screen...
    if(sym.bool_left){
        sym.mc_ship.animate( {left: sym.minX}, {duration: sym.playerSpeed_left, easing: "linear"} );
        sym.mc_ray.animate( {left: sym.minX}, {duration: sym.playerSpeed_left, easing: "linear"} );
    }
    if(sym.bool_right){
        sym.mc_ship.animate( {left: sym.maxX}, {duration: sym.playerSpeed_right, easing: "linear"} );
        sym.mc_ray.animate( {left: sym.maxX}, {duration: sym.playerSpeed_right, easing: "linear"} );
    }
}

The two variables that are set:
sym.playerSpeed_left = (sym.mc_ship.position().left);
sym.playerSpeed_right = (sym.stage.width()-(sym.mc_ship.position().left));

Will make the ship keep a steady speed (duration) even when close to the sides of the screen. The ship moves using .animate (see the last two if conditions). If I don’t do this, then, when the ship is closest to its target left/right, it will crawl there. This is not good for a game, so I make sure to always update the speed according to ship position.

The last two conditions are what take care of moving (if the key is pressed — sym.bool_left and sym.bool_right). The .animate target is always the sides of the screen. This way I don’t have to write a collision detection to stop the ship, it will just never go further than that.
Both the ship and the ray are moved to the same location.

Stopping the ship and ray takes place in document.keyup. This is how it looks

//GAME
if(sym.SCENE == "game"){
    //32 is SPACE
    if (e.which == 32) {
        sym.getComposition().getStage().key_onKeyUp();
    }
    //37 is LEFT
    if (e.which == 37){
        sym.bool_left = false;
    }
    //39 is RIGHT
    if (e.which == 39){
        sym.bool_right = false;
    }
    //stop movement and cancel anything queue -- prevents animation queue buildup
    if(!sym.bool_left && !sym.bool_right){
        sym.mc_ship.animate( {left: sym.mc_ship.position().left}, {duration: sym.playerSpeed, easing: "linear"} );
        sym.mc_ship.finish();
        sym.mc_ray.finish();
        //force placement of ray back to ship's x...
        sym.mc_ray.css({"position":"absolute", "top":sym.rayY + "px", "left":sym.mc_ship.position().left + "px"});
    }
}

Condition 3 and 4 (if (e.which == 37) and if (e.which == 39)) set the booleans for if left/right is down to false.
The last bit “stops” the ship if these are false. It is stopped by setting the target .animate to the ships current/last position. I do this to avoid issues with the animation queue (which can present issues when working with jquery .animate see: StackOverflow – Can somebody explain jQuery queue to me? for explanation) .finish is called to clear the queue and jump to the end value (which would be: sym.mc_ship.animate( {left: sym.mc_ship.position().left}, {duration: sym.playerSpeed, easing: "linear"} ); ).
The very last bit places the ray right under the ship.

I’m certain there are many better ways of doing this, but this is how I chose to do it.

*************

HIGHSCORES – LEADERBOARDS

I won’t implement an elaborate version here. For those interested here are some good resources for making your own:

Using Playtomic API: http://gmc.yoyogames.com/index.php?showtopic=538699
Add online highscores to your HTML5 games: http://gmc.yoyogames.com/index.php?showtopic=525654

For simplicity’s sake, I will be using localStorage:
Local Storage And How To Use It On Websites
Saving data with localStorage

Highscores, and player name, will be stored locally. This will also give people a prototype of using localStorage with Edge Animate.

What I came up with is to use bootstrapCallback in the html file, do a check if the user’s browser supports localStorage, and create a setScore and getScore function assigned to stage ( AdobeEdge.getComposition(compId).getStage() ).
Here (underneath < ! -- Adobe Edge Runtime End -- > ):

<script type="text/javascript">
    //handle localStorage "highscores" here...
    var comp;
    //callback
    AdobeEdge.bootstrapCallback(function(compId) {
        comp = AdobeEdge.getComposition(compId).getStage();
        //check if local storage is supported (browser)
        if(typeof(Storage)!=="undefined"){
            console.log("localStorage is supported.");
            //if it's supported create this functions on/to stage
            //usage: sym.getComposition().getStage().setScore("50");
            //SCORE:
            comp.setScore = function(plyr_score){
                localStorage.setItem("score", plyr_score);
                console.log("setScore called. plyr_score: " + plyr_score);
            }
            comp.getScore = function(){
                return localStorage.getItem("score");
                console.log("getScore called.");
            }
            //NAME:
            comp.setName = function(plyr_name){
                localStorage.setItem("name", plyr_name);
                console.log("setName called. plyr_name: " + plyr_name);
            }
            comp.getName = function(){
                return localStorage.getItem("name");
                console.log("getName called.");
            }
        }else{
            // browser is too old -- or no web storage support
            window.alert("Your browser does not support web storage. Score will not be saved.");
        }
    });
</script>

Simple! Read up on web storage here: http://dev.w3.org/html5/webstorage/
Here: http://www.w3schools.com/html/html5_webstorage.asp
And: Using localStorage
And, again, here: http://hacks.mozilla.org/2009/06/localstorage/

In this instance I save only the most recent name and score (just one), but you could easily built on it and save more.

For an example of how this works in the game refer to the “name submission window” (Symbol: Screen_Score):

The text field is called “txt_name“.

The code that sets it up is as follows

// prepare input field
var namefield = sym.$("txt_name")
namefield.html("Name, plz: ");
inputName = $('<input />').attr({'type':'text', 'value':'x', 'id':'plyr_name'});//id must be unique
inputName.appendTo(namefield);

Basically this creates and appends an input text field, with id “plyr_name“, to txt_name.

The end result will look like this

The button (Btn_OK) is where all the action happens.
It has a click event, which looks like

//get the value and send to localStorage!
var _score = sym.getComposition().getStage().score;
var _name = plyr_name.value;
//
sym.getComposition().getStage().setScore(_score);
sym.getComposition().getStage().setName(_name);
//go to the "scores" scene
sym.getComposition().getStage().gotoScores();

The first two variables get the values to send to localStorage. _score gets the player’s score from stage, and _name gets what the user just input into the text field (the assigned id ‘plyr_name‘).
After that we call the two javascript functions created earlier in our AdobeEdge.bootstrapCallback.
Because we assigned them to ‘comp‘ (the stage) we can easily call them. We send the variables _score, and _name to their respective javascript functions. Done! Now the player name and score is saved!
The last one calls a function which just sends the timeline to go to the score screen.

//done submitting scores, and playing, go to the scores scene
//leaves the game -- called in Screen_Score
sym.gotoScores = function(){
    sym.SCENE = "scores";
    sym.stop("scores");
}

The very same thing happens a final time when the player wins.

Showing the scores is just as simple. There is a label, and trigger (with code) on the main timeline

It will look like this, live

The code for it is as follows

sym.SCENE = "scores";
sym.stop();

//sound
Aud_ScoresTrack.play();

//get and set record holder (player + score + funny little comment)
var scr_plyrName = sym.getComposition().getStage().getName();
var scr_plyrScore = sym.getComposition().getStage().getScore();

//the funny comment
var arr_scoreMsg = ["Such an excellent role model!", "It was a magnificent harvest!", "The harvest was great!", "It was a sight to see!", "Such excellence will not go unnoticed!"];
var rand_scoreNum = Math.ceil(Math.random()*arr_scoreMsg.length)-1;
var rand_msg = arr_scoreMsg[rand_scoreNum];
//if it returns null then set it to one
if(arr_scoreMsg[rand_scoreNum] == undefined){
    rand_msg = arr_scoreMsg[0];
}
//create the message
var msg_score = "The latest record holder is, " + scr_plyrName + ", who abducted, " + scr_plyrScore + " rabbits. " + rand_msg;
//if player hasn't played yet, and there is nothing to show in getName or getScore...
if(scr_plyrScore == undefined){
    msg_score = "Nothing here, move along."
}
//set the message
sym.$("txt_highscore").html(msg_score);

The variables scr_plyrName and scr_plyrScore make a call to our two AdobeEdge.bootstrapCallback javascript functions that return the saved name, and score.

The bit bellow that creates a random “funny comment” — chosen at random from an array and set as variable rand_msg.

Bellow that the message is created, stringing scr_plyrName, scr_plyrScore, and rand_msg together. The variable msg_score is set to that.
Then the text field is set to msg_score: sym.$("txt_highscore").html(msg_score);

Simple!

*************

TIMERS

There are 5 “timers” in the game. These are setIntervals, and run at varying intervals.
As I’ve already described, in document.compositionReady:

//Timers - intervals
sym.timerInt;
sym.timeElapsedInt;
sym.nightmareInterval;
sym.enemyInterval;
sym.shipHitInterval;

These are set in resumeG() in the “game” trigger:

    enemyInit();
    nightmareInit();  
    sym.timerInt = setInterval(timer, 100);
    sym.timeElapsedInt = setInterval(elapsed, 100);
    sym.shipHitInterval = setInterval(enemyHit , 100);

We already covered timer(), enemyHit(), and enemyInit() above in the Collision Detection section, but here is a rundown of what they all are.

enemyInit(); — Initiates the enemies, and sets the difficulty (the closer to winning you are the more enemies are created).

nightmareInit(); — Plays (hides/shows) the bunny’s nightmare. While you are playing a little comic book like star panel shows on the side depicting what the bunny is dreaming (all those dreadful things that will happen to him!).
It looks like this:

//Bunny nightmare pannel
//bad dreams ocasionally show up acompanied with a sound
function nightmareInit() {
    clearInterval(sym.nightmareInterval);
    var randNum = (Math.random()*10000)+10000;
    sym.mc_nightmare.play();
    Aud_Nightmare.play();
    //start again
    sym.nightmareInterval = setInterval(nightmareInit, randNum);
}

It shows at random by clearing the last interval, and calling itself again at a random interval.

timer();sym.timerInt = setInterval(timer, 100); — This updates the score textbox with the current score, and increments the time between enemy increments. If time hits 100 then the enemyAmount is incremented. There cannot be more than a certain amount of enemies (enemyMax), so the enemies, and counter are regularly reset when it reaches the maximum amount of enemies. This simulates “waves of attacks”.
Again, timer(); looks like this:

//game timer
//initiates enemies
function timer(){
    //set score
    sym.txt_score.html(sym.score);
    //increment time
    sym.time+=1;
    //increment enemies, if the score is past lvl1_goal
    if (sym.time>100 && sym.score>sym.lvl1_goal-100) {
        sym.time = 1;
        if (sym.enemyAmount<sym.enemyMax) {
            sym.enemyAmount++;
        }
        //no more than 4 enemies, reset it
        if (sym.enemyAmount == sym.enemyMax) {
            sym.enemyAmount = 1;
        }
    }
}

elapsed();sym.timeElapsedInt = setInterval(elapsed, 100); — This is the leveling system. It is also what triggers the “win”.
If your timeElapsed is above the win_time (to keep people from winning too fast), and the score is above the win_goal, the game is beat! It stops the game, clears everything, and plays the win animation.
Here is how it looks:

//leveling system
function elapsed() {
    sym.timeElapsed += 1;
    //win
    if (sym.timeElapsed>sym.win_time && sym.score>sym.win_goal) {
        console.log("You win.");
        sym.SCENE = "win";
        //play();
        stopG();
        sym.$("Screen_End").hide();//hide gameover
        sym.play();
    }
    //.data to keep it .play() called once
    if ((sym.timeElapsed>sym.lvl2_time && sym.score>sym.lvl2_goal) && sym.mc_b2_data != false) {
        randHit();
        sym.mc_transition.play("transition");//blink
        sym.mc_b2.play();
        sym.mc_b2_data = false;
    }
    if ((sym.timeElapsed>sym.lvl1_time && sym.score>sym.lvl1_goal) && sym.mc_b1_data != false) {
        randHit();
        sym.mc_transition.play("transition");//blink
        sym.mc_b1.play();
        sym.mc_b1_data = false;
    }
}

The other two conditions are what set “next level”. If you met your goal then the screen blinks, and the bunny gets pulled out of its bed. A random hit sound (randHit();) also plays with this event. Next level.

enemyHit();sym.shipHitInterval = setInterval(enemyHit , 100); — has already been covered in the Collision Detection section above. This is what continuously checks if the ship has been hit by an enemy.

*************

Starting & Stopping The Game

Starting and stopping the game takes place in two functions (on the “game” timeline trigger). Starting is responsible for setting or re-setting the game, and starting it up again.
It looks like this:

//resumeG functions both as init() and resuming
//if the player loses, the game is cleared in stopG(),
//and this would be called to start a fresh one
//so this is setting all values to their default state
//as well as creating all new sprites
function resumeG() {
    sym.SCENE = "game"; //set to game so game keyboard input will work
    sym.score = 1;    
    sym.mc_b1.stop(0);
    sym.mc_b2.stop(0);
    sym.mc_b1_data = true;
    sym.mc_b2_data = true;
    sym.bunnyAbAmnt = 0;
    //clear/reset arrays
    //there are a number of ways to do this, for example: 
    //http://stackoverflow.com/questions/1232040/how-to-empty-an-array-in-javascript
    //but keeping it simple...
    sym.arr_bunnies = [];
    sym.arr_enemies = [];
    //start the music
    Aud_GameTrack.play().fadeIn();
    sym.$("Screen_End").hide();//hide mc_gameover
    sym.$("Screen_Score").hide();//hide the "your name here" screen
    createSprites();
    makeBunny();
    enemyInit();
    nightmareInit();  
    sym.timerInt = setInterval(timer, 100);
    sym.timeElapsedInt = setInterval(elapsed, 100);
    sym.shipHitInterval = setInterval(enemyHit , 100);
    //
    //removeEnemyShip();
    //reset vars
    sym.time = 1;
    sym.enemyAmount = 1;
    sym.timeElapsed = 1;
    //set these values AFTER sprites have been created
    sym.maxX = sym.stage.width()-sym.mc_ship.width();
    sym.minX = 0;
}

//Start game
resumeG();

sym.SCENE is set to “game” again. Reset score, and all necessary symbols (like putting the bunnies back in their bed).
The arrays are also cleared out (reset).
The game soundtrack is started again, and the two screens (Screen_End, and Screen_Score) are both hidden. Incase they where open.
After arrays are cleared, sprites are created again.
All intervals/”timers” (as just described above) are also started.
Then reset the last variables (time, enemyAmount, and timeElapsed).
The last two maxX, and minX set the desired bounds for ship movement.

stopG() does the opposite. It serves as the “game over” function. It works with Screen_End:

This is how the function looks:

//stops the game
//this clears the current game
//to start it again call resumeG();
function stopG() {
    SoundMixer_stopAll();
    //set player score message for if this is gameover
    sym.mc_gameover.$("txt_plyr_score").html("You score "+sym.score+"!");
    //
    randHit();
    Aud_Explode.play();
    //show gameover
    sym.$("Screen_End").show();
    //
    removeEnemyShip();
    removeSprites();
    removeBunny();
    clearInterval(sym.timerInt);
    clearInterval(sym.nightmareInterval);
    clearInterval(sym.enemyInterval);
    clearInterval(sym.timeElapsedInt);
    clearInterval(sym.shipHitInterval);
}

The first 8 lines manage the game over event, as well as show the game over screen (Screen_End). In cases where I don’t want a game over, I call this same function, but just hide Screen_End (sym.$("Screen_End").hide()). You’ll have noticed this in elapsed();, above.
The final section removes all created sprites, and clears all intervals.

The remove functions look like this:

function removeEnemyShip()
{
    //remove all enemies
    for (var i = 0; i<sym.arr_enemies.length; ++i){
    //You can remove in two ways .remove() or .deleteSymbol()
    //I am not certain what the difference is so I am using .remove()
    //see: http://api.jquery.com/remove/
    //always .stop(); if you are removing a symbol with .animate()
        sym.arr_enemies[i].stop();
        sym.arr_enemies[i].remove();
        //sym.getSymbol(sym.arr_enemies[i]).deleteSymbol();
    }
}

function removeSprites(){
    sym.mc_ship.stop();
    sym.mc_ray.stop();
    sym.mc_ship.remove();
    sym.mc_ray.remove();
}

function removeBunny() {
    //delete bunnies
    //You can remove in two ways .remove() or .deleteSymbol()
    //I am not certain what the difference is so I am using .remove()
    //see: http://api.jquery.com/remove/
    //always .stop(); to cancel the .animate();
    for (var i = 0; i<sym.arr_bunnies.length; ++i){
        sym.arr_bunnies[i].stop();
        sym.arr_bunnies[i].remove();
        //sym.getSymbol(sym.arr_bunnies[i]).deleteSymbol();
    }
}

*************

Going The Extra Mile For Mobile

It would be simple, from this point on, to do a mobile check. If the player is on a mobile device, then enable touchscreen controls.
There are a number of ways to do mobile check. The simplest, and not always most reliable is: if(/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)){returns true or false};
The best would be:
Detect Mobile Browsers | Open source mobile phone detection
JS Mobile Redirection (Javascript mobile redirection library)
Or refer to this thread: http://stackoverflow.com/questions/11381673/javascript-solution-to-detect-mobile-browser

I’m not going to do this here. I’m going to be lazy because it’s Saturday! In the menu I run the simple check, and display a little window.alert telling them that the game is not yet compatible with touchscreen.

sym.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
//
if (sym.isMobile) { 
    //sym.stop();
    window.alert("Sorry mobile, but this game is not yet compatible for touchscreen devices.");
}else{
    //you may pass
}

I have it as an App in the App Store. Incase you’re wondering. :)

*************

ALL THE CODE:

That’s it! And, in conclusion, the entire game engine, as comfortably tucked away in Trigger @ 22000 ms is as follows:

sym.stop();

/* Score usage:
sym.getComposition().getStage().setScore("50");
console.log("SCORE: "+comp.getScore()); 
*/

//Collision Detection
//simple object intersection hit test because this is a small project
//consider using JQuery Collision http://sourceforge.net/projects/jquerycollision/
//for more elaborate things -- thanks to Adobe forums for pointing me in the right direction
function hitTest(a, b){
    //
    aPos = {x:parseInt(a.css('left')), y:parseInt(a.css('top'))};
    bPos = {x:parseInt(b.css('left')), y:parseInt(b.css('top'))};
    //
    return aPos.x < bPos.x + b.width() && aPos.x + a.width() > bPos.x && aPos.y < bPos.y + b.height() && aPos.y + a.height() > bPos.y
}

//Player
//create and place ship and beam
function createSprites() {
    sym.mc_ship = sym.createChildSymbol("Ship", "Stage").getSymbolElement();
    sym.mc_ship.css({"position":"absolute", "top":sym.shipY + "px", "left":sym.shipX + "px"});
        //
    sym.mc_ray = sym.createChildSymbol("Ray", "Stage").getSymbolElement();
    sym.mc_ray.css({"position":"absolute", "top":sym.rayY + "px", "left":sym.shipX + "px"});
    sym.mc_ray.hide();
}

function removeSprites(){
    sym.mc_ship.stop();
    sym.mc_ray.stop();
    sym.mc_ship.remove();
    sym.mc_ray.remove();
}

//Sound
//random "hits" for when bunnies get abducted or player dies
var randHitArr = [Aud_Hit1_, Aud_Hit2_, Aud_Hit3_];
function randHit() {
    var randHit = randHitArr[Math.floor(Math.random()*randHitArr.length)];
    if(randHit == undefined){
        randHit = randHitArr[0];
    }
    randHit.play();
}

//Bunnies!
function bunnyM(clip) {
    //console.log(clip);
    //speed, and end variables
    var bEnd = sym.stage.width()-clip.width();
    var bSpeed = (Math.ceil(Math.random()*sym.bunnySpeed)+sym.bunnySpeed)-500;
    //place it randomly (x) on the stage
    clip.css({"position":"absolute", "top":sym.bunnyY + "px", "left":(Math.ceil(Math.random()*sym.stage.width())-clip.width()) + "px"});
    //call first to cancel queue -- just incase this could become a problem
    clip.stop();
    //
    clip.animate( {left: bEnd}, bSpeed, "linear", function(){
        sym.score -= 1;//"escapee" penalty
        bunnyM(clip);
    }
    );
}

function makeBunny() {
    for (var i = 0; i<sym.bNum; i++) {
        var mc_bunny = sym.createChildSymbol("Bunny", "Stage").getSymbolElement();
        //push to array for access later and start moving it
        //bunnyM also takes care of placeing them
        //note: bunnies must be stored in array
        //for colision detection, and better access
        sym.arr_bunnies.push(mc_bunny);
        bunnyM(mc_bunny);
    }
}

function removeBunny() {
    //delete bunnies
    //You can remove in two ways .remove() or .deleteSymbol()
    //I am not certain what the difference is so I am using .remove()
    //see: http://api.jquery.com/remove/
    //always .stop(); to cancel the .animate();
    for (var i = 0; i<sym.arr_bunnies.length; ++i){
        sym.arr_bunnies[i].stop();
        sym.arr_bunnies[i].remove();
        //sym.getSymbol(sym.arr_bunnies[i]).deleteSymbol();
    }
}

function bunnyD(clip) {
    if(hitTest(sym.mc_ray, clip)){
        sym.score += 1;
        sym.bunnyAbAmnt+=1;
        clip.stop();//stop current animation queue
        clip.animate( {top: 0}, 200, "linear", function(){
            bunnyM(clip);
        }
        );
    }
}

//creating function with sym.
//to change scope so keyboard events can access these
sym.key_onKeyDown = function() {
    sym.mc_ray.show();
    Aud_BeamLoop.play();
    //hit detection
    for (var i = 0; i<sym.arr_bunnies.length; ++i){
          bunnyD(sym.arr_bunnies[i]);
    }
};
sym.key_onKeyUp = function() {
    Aud_BeamLoop.stop();
    sym.mc_ray.hide();
}

//Enemy Ship
//And Enemy Laser

function enemyFire(clip)
{
    var randSpeed = (Math.random()*20)+10;
    //enemy fire serves as a warning that an enemy is approaching
    //fire does nothing, it passes the ship,
    //and should appear randomly somewhere under the ship, and over the bunnies
    clip.css({"position":"absolute", "top":Math.ceil(Math.random()*40)+90 + "px", "left":-clip.width() + "px"});
    //
    clip.animate( {left: (sym.stage.width()+clip.width())}, 1000, "linear", function(){
        clip.remove();//get rid of it when it's passed
    }
    );
}


function makeEnemyFire(){
    Aud_Laser.play();
    //make as much enemy fire as there are enemies, so going by sym.enemyAmount
    for (var i = 1; i<sym.enemyAmount+1; i++)
    {
        var mc_fire = sym.createChildSymbol("Fire", "Stage").getSymbolElement();
        enemyFire(mc_fire); 
    }
}

//enemy ship comes in 8 steps using .anmiate to call the next when a movement is done
//this simulates the ship flying up/down exactly how I want it
//I'm certain there are better ways of doing this, if so let me know!
//
//the starting function is called at random -- in enemyShip(clip);
//so when there are multiple enemies there is a random flying pattern to doge
var enemyBounceSpeed = 500; //the speed of up/down movement -- should last 0.3 seconds
function enemyShip_move1(clip){
    clip.animate({
        top: 83
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeInCubic", 
            complete: function(){
                enemyShip_move2(clip);
            }
    });
}
function enemyShip_move2(clip){
    clip.animate({
        top: 120
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeOutCubic", 
            complete: function(){
                enemyShip_move3(clip);
            }
    });
}
function enemyShip_move3(clip){
    clip.animate({
        top: 60
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeInCubic", 
            complete: function(){
                enemyShip_move4(clip);
            }
    });
}
function enemyShip_move4(clip){
    clip.animate({
        top: 25
        }, { 
            duration: enemyBounceSpeed, 
            queue: false, 
            easing: "easeOutCubic", 
            complete: function(){
                enemyShip_move1(clip);
            }
    });
}

function enemyShip(clip)
{
    //position ship - somewhere off screen, at same Y
    var enemyStartX = (Math.random()*-500)-200;
    clip.css({"position":"absolute", "top":sym.enemyY + "px", "left":enemyStartX + "px"});
    //random speed
    var randNum = (Math.random()*10000)+5000;
    //move it
    clip.animate({
        left: (sym.stage.width()+clip.width())
        }, {
            duration: randNum, 
            queue: false,
            easing: "linear", 
            complete: function(){
                clip.remove();
            }
    });
    //start moving it up/down -- call the starting movement
    //at random -- either the top most or bottom most
    if(randNum>randNum/2){
        enemyShip_move2(clip);
    }else{
        enemyShip_move4(clip);
    }
}

//DEATH
//was the player hit by an enemy ship?
function enemyHit(){
    //
    for(var i = 0; i<sym.arr_enemies.length; ++i){
    //
        if(hitTest(sym.mc_ship, sym.arr_enemies[i])){
            //create and place an explosion on the ship coords
            var mc_explode = sym.createChildSymbol("Explode", "Stage").getSymbolElement();
            mc_explode.css({"position":"absolute", "top":sym.mc_ship.position().top + "px", "left":sym.mc_ship.position().left+ "px"});
            //
            Aud_Explode.play();
            sym.arr_enemies[i].stop();
            sym.arr_enemies[i].remove();
            //you lose
            sym.SCENE = "lose";
            stopG();
        }
    }
}


function attachEnemyShip()
{
    for (var i = 0; i<sym.enemyAmount; i++)
    {
        var mc_enemy = sym.createChildSymbol("Enemy_Ship", "Stage").getSymbolElement();
        sym.arr_enemies.push(mc_enemy);
        enemyShip(mc_enemy);
    }
}


function removeEnemyShip()
{
    //remove all enemies
    for (var i = 0; i<sym.arr_enemies.length; ++i){
    //You can remove in two ways .remove() or .deleteSymbol()
    //I am not certain what the difference is so I am using .remove()
    //see: http://api.jquery.com/remove/
    //always .stop(); if you are removing a symbol with .animate()
        sym.arr_enemies[i].stop();
        sym.arr_enemies[i].remove();
        //sym.getSymbol(sym.arr_enemies[i]).deleteSymbol();
    }
}

//enemyInit calls to create the enemy
//as you play, and as you progress
//the game gets more difficutl -- more enemies
function enemyInit()
{
    clearInterval(sym.enemyInterval);
    //was (Math.random()*20000)+10000;
    var randNum = (Math.random()*9000)+10000;
    //
    if(sym.score>=sym.lvl1_goal-100){
        randNum = (Math.random()*8000)+9000;
    }
    if(sym.score>=sym.lvl2_goal-200){
        randNum = (Math.random()*5000)+7000;
    }
    if(sym.score>=sym.win_goal-400){
        randNum = (Math.random()*1000)+6000;
    }
    if(sym.score>=sym.win_goal-200){
        randNum = (Math.random()*1000)+5000;
    }
    //if sym.bunnyAbAmnt is above the goal
    //then you may start creating enemies
    if(sym.bunnyAbAmnt>10){
        makeEnemyFire();
        attachEnemyShip();
    };
    //
    sym.enemyInterval = setInterval(enemyInit, randNum);
}

//Bunny nightmare pannel
//bad dreams ocasionally show up acompanied with a sound
function nightmareInit() {
    clearInterval(sym.nightmareInterval);
    var randNum = (Math.random()*10000)+10000;
    sym.mc_nightmare.play();
    Aud_Nightmare.play();
    //start again
    sym.nightmareInterval = setInterval(nightmareInit, randNum);
}

//game timer
//initiates enemies
function timer(){
    //set score
    sym.txt_score.html(sym.score);
    //increment time
    sym.time+=1;
    //increment enemies, if the score is past lvl1_goal
    if (sym.time>100 && sym.score>sym.lvl1_goal-100) {
        sym.time = 1;
        if (sym.enemyAmount<sym.enemyMax) {
            sym.enemyAmount++;
        }
        //no more than 4 enemies, reset it
        if (sym.enemyAmount == sym.enemyMax) {
            sym.enemyAmount = 1;
        }
    }
}

//leveling system
function elapsed() {
    sym.timeElapsed += 1;
    //win
    if (sym.timeElapsed>sym.win_time && sym.score>sym.win_goal) {
        console.log("You win.");
        sym.SCENE = "win";
        //play();
        stopG();
        sym.$("Screen_End").hide();//hide gameover
        sym.play();
    }
    //.data to keep it .play() called once
    if ((sym.timeElapsed>sym.lvl2_time && sym.score>sym.lvl2_goal) && sym.mc_b2_data != false) {
        randHit();
        sym.mc_transition.play("transition");//blink
        sym.mc_b2.play();
        sym.mc_b2_data = false;
    }
    if ((sym.timeElapsed>sym.lvl1_time && sym.score>sym.lvl1_goal) && sym.mc_b1_data != false) {
        randHit();
        sym.mc_transition.play("transition");//blink
        sym.mc_b1.play();
        sym.mc_b1_data = false;
    }
}

//stop all sounds :)
//better yet would be to use a groupd
//and buzz.all() to do this: http://buzz.jaysalvat.com/documentation/group/
function SoundMixer_stopAll(){
    Aud_GameTrack.stop();
    Aud_Hit1_.stop();
    Aud_Hit2_.stop();
    Aud_Hit3_.stop();
    Aud_BeamLoop.stop();
    Aud_Laser.stop();
    Aud_Nightmare.stop();
}

//"submit" your score window
//hide the current gameover window
//and open the submission one
//to call from screen: sym.getComposition().getStage().scorecall();
sym.scorecall = function() {
    sym.$("Screen_End").hide();//hide mc_gameover
    sym.$("Screen_Score").show();
}
//same as above but the "overachieve" option
//to call from screen: sym.getComposition().getStage().overachieve();
sym.overachieve = function(){
    resumeG();
}
//done submitting scores, and playing, go to the scores scene
//leaves the game -- called in Screen_Score
sym.gotoScores = function(){
    sym.SCENE = "scores";
    sym.stop("scores");
}

//stops the game
//this clears the current game
//to start it again call resumeG();
function stopG() {
    SoundMixer_stopAll();
    //set player score message for if this is gameover
    sym.mc_gameover.$("txt_plyr_score").html("You score "+sym.score+"!");
    //
    randHit();
    Aud_Explode.play();
    //show gameover
    sym.$("Screen_End").show();
    //
    removeEnemyShip();
    removeSprites();
    removeBunny();
    clearInterval(sym.timerInt);
    clearInterval(sym.nightmareInterval);
    clearInterval(sym.enemyInterval);
    clearInterval(sym.timeElapsedInt);
    clearInterval(sym.shipHitInterval);
}

//resumeG functions both as init() and resuming
//if the player loses, the game is cleared in stopG(),
//and this would be called to start a fresh one
//so this is setting all values to their default state
//as well as creating all new sprites
function resumeG() {
    sym.SCENE = "game"; //set to game so game keyboard input will work
    sym.score = 1;    
    sym.mc_b1.stop(0);
    sym.mc_b2.stop(0);
    sym.mc_b1_data = true;
    sym.mc_b2_data = true;
    sym.bunnyAbAmnt = 0;
    //clear/reset arrays
    //there are a number of ways to do this, for example: 
    //http://stackoverflow.com/questions/1232040/how-to-empty-an-array-in-javascript
    //but keeping it simple...
    sym.arr_bunnies = [];
    sym.arr_enemies = [];
    //start the music
    Aud_GameTrack.play().fadeIn();
    sym.$("Screen_End").hide();//hide mc_gameover
    sym.$("Screen_Score").hide();//hide the "your name here" screen
    createSprites();
    makeBunny();
    enemyInit();
    nightmareInit();  
    sym.timerInt = setInterval(timer, 100);
    sym.timeElapsedInt = setInterval(elapsed, 100);
    sym.shipHitInterval = setInterval(enemyHit , 100);
    //
    //removeEnemyShip();
    //reset vars
    sym.time = 1;
    sym.enemyAmount = 1;
    sym.timeElapsed = 1;
    //set these values AFTER sprites have been created
    sym.maxX = sym.stage.width()-sym.mc_ship.width();
    sym.minX = 0;
}

//Start game
resumeG();

document.compositionReady

//"GLOBAL" VARIABLES
//what label? 
//will check against this and if it is "game" then game functionality will unlock
//if it is menu then keyboard for menu will unlock, etc...
sym.SCENE; 

sym.score = 100;    
sym.time = 1;
sym.enemyAmount = 1;//current amount
sym.enemyMax = 3;//maximum amount of enemies alowed at once
sym.timeElapsed = 1;
sym.playerSpeed = 600;//5
sym.playerSpeed_left = 600;//5
sym.playerSpeed_right = 600;//5
sym.bunnySpeed = 5000;//will be random + itself, this is the default minimum value
//sym.bunnyAbAmnt = amount of abducted bunnies, if it's above a certain number then enemies will start appearing
//this keeps the game from generating enemies right away -- gives the player time to adjust
sym.bunnyAbAmnt = 0;
//goals (before leveling up)
sym.lvl1_goal = 300;
sym.lvl2_goal = 530;
//targeted time (how long you have been playing before you can go to next level)
sym.lvl1_time = 300;
sym.lvl2_time = 500;
//above but for winning
sym.win_time = 700;
sym.win_goal = 800;

sym.bNum = 10;//maximum number of bunnies on stage

//Booleans - flags
sym.bool_left = false;
sym.bool_right = false;

//.data to keep it .play() called once
//may only play once if it is true then the transition to next level may play
sym.mc_b1_data = false;
sym.mc_b2_data = false;

//symbol refferences
sym.stage = sym.$("Stage");
sym.mc_b1 = sym.getSymbol("Beds").getSymbol("IMG_Sprite_Bed02");
sym.mc_b2 = sym.getSymbol("Beds").getSymbol("IMG_Sprite_Bed03");
sym.mc_nightmare = sym.getSymbol("Nightmare");
sym.mc_transition = sym.getSymbol("Transition_Blink");
sym.mc_gameover = sym.getSymbol("Screen_End");
sym.mc_score = sym.getSymbol("Screen_Score");
sym.mc_ship;
sym.mc_ray;
sym.txt_score = sym.$("txt_score");

//Arrays
sym.arr_bunnies = [];
sym.arr_enemies = [];

//X Y positions
sym.shipY = 70;//25
sym.enemyY = 88;
sym.rayY = 66;//66
sym.bunnyY = 180;
//centering
sym.centerX = sym.stage.width()/2;
sym.centerY = sym.stage.height()/2;
//left/right bounds
sym.maxX = sym.stage.width();
sym.minX = 10;//minimum placement for ship
//player X (incremented/decremented via keyboard input, initial value is center)
sym.shipX = sym.centerX;

//Timers - intervals
sym.timerInt;
sym.timeElapsedInt;
sym.nightmareInterval;
sym.enemyInterval;
sym.shipHitInterval;

//SOUND

//I manually made the sound longer so it appears to loop -- nevermind the gap
//it is a bit more hacking around to get seamless audio looping (Flash does it well)
//here are some notes for those interested in getting it to work
//http://sound.stackexchange.com/questions/8916/mp3-gapless-looping-help
//http://forestmist.org/blog/html5-audio-loops/
//the main soundrack of the game:
Aud_GameTrack = new buzz.sound("audio/mainmenu-loop", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
//plays at the end of the game:
Aud_GameTrack_2 = new buzz.sound("audio/mainmenu-under-loop2", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
Aud_ScoresTrack = new buzz.sound("audio/scores-loop", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
Aud_KeysTrack = new buzz.sound("audio/Keys-loop", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});
//Menu sounds
Aud_MenuPreLoop = new buzz.sound("audio/Mainmenu-preloop", {formats: ["wav", "ogg", "mp3"], preload: true});
Aud_MenuLoop = new buzz.sound("audio/mainmenu-loop3", {formats: ["wav", "ogg", "mp3"], preload: true, loop: true});

//Intro
//Aud_LogoMoron = new buzz.sound("audio/morons-blb", {formats: ["wav", "ogg", "mp3"]});
Aud_Intro = new buzz.sound("audio/Intro", {formats: ["wav", "ogg", "mp3"], preload: true});

Aud_Nightmare = new buzz.sound("audio/JAPAN-3", {formats: ["wav", "ogg", "mp3"]});
Aud_Explode = new buzz.sound("audio/explode", {formats: ["wav", "ogg", "mp3"],preload: true});
Aud_End = new buzz.sound("audio/End", {formats: ["wav", "ogg", "mp3"],preload: true});
Aud_Hit1_ = new buzz.sound("audio/daaaam-1", {formats: ["wav", "ogg", "mp3"]});
Aud_Hit2_ = new buzz.sound("audio/daaaam-2", {formats: ["wav", "ogg", "mp3"]});
Aud_Hit3_ = new buzz.sound("audio/daaaam-3", {formats: ["wav", "ogg", "mp3"]});

Aud_BeamLoop = new buzz.sound("audio/beam-loop", {formats: ["wav", "ogg", "mp3"],
    loop: true
});

Aud_Laser = new buzz.sound("audio/LASER", {formats: ["wav", "ogg", "mp3"]});

//load them with Edge
Aud_GameTrack.load();
Aud_GameTrack_2.load();
Aud_ScoresTrack.load();
Aud_KeysTrack.load();
Aud_MenuPreLoop.load();
Aud_MenuLoop.load();
Aud_Intro.load();
Aud_Nightmare.load();
Aud_Explode.load();
Aud_End.load();
Aud_Hit1_.load();
Aud_Hit2_.load();
Aud_Hit3_.load();

//stop all currently playing "hits" for menu and anywhere else they trigger on button overs
sym.stopHits = function(){
    Aud_Hit1_.stop();
    Aud_Hit2_.stop();
    Aud_Hit3_.stop();
}

document.keydown

//GAME KEYBOARD
//add others acording to behavior for desired "SCENE"
//example: if it is main menu then enable main menu keyboard functionality
//if it is game then enable game keyboard functionality

//GAME
if(sym.SCENE == "game"){
    //32 is SPACE
    if (e.which == 32) {
        //call the function on the stage's "game trigger"
        sym.getComposition().getStage().key_onKeyDown();
        //sym.mc_ray.show();
        //Aud_BeamLoop.play();
    }
    //37 is LEFT
    if (e.which == 37){
        //keeps it from "crawling" if key is pressed while ship is close to left of screen
        sym.playerSpeed_left = (sym.mc_ship.position().left);
        sym.bool_left = true;
    }
    //39 is RIGHT
    if (e.which == 39){
        //keeps it from "crawling" if key is pressed while ship is close to right of screen
        sym.playerSpeed_right = (sym.stage.width()-(sym.mc_ship.position().left));
        sym.bool_right = true;
    }
    //player movement
    //is an interval - target left/right location are the sides of the screen
    //if the keyboard key is released .anmiate is stopped/canceled (see keyup)
    //there are a number of ways to animate with css/js http://www.dehats.com/drupal/node/120
    //has some good examples. I've chosen .animate here, and because the target are the edges
    //of the screen it will continue to move there untill you release the key
    //it will also not go off the screen...
    if(sym.bool_left){
        sym.mc_ship.animate( {left: sym.minX}, {duration: sym.playerSpeed_left, easing: "linear"} );
        sym.mc_ray.animate( {left: sym.minX}, {duration: sym.playerSpeed_left, easing: "linear"} );
    }
    if(sym.bool_right){
        sym.mc_ship.animate( {left: sym.maxX}, {duration: sym.playerSpeed_right, easing: "linear"} );
        sym.mc_ray.animate( {left: sym.maxX}, {duration: sym.playerSpeed_right, easing: "linear"} );
    }
}

//DEBUG
if(e.which==27){
//
}

document.keyup

//GAME KEYBOARD
//add others acording to behavior for desired "SCENE"
//example: if it is main menu then enable main menu keyboard functionality
//if it is game then enable game keyboard functionality

//GAME
if(sym.SCENE == "game"){
    //32 is SPACE
    if (e.which == 32) {
        //sym.mc_ray.hide();
        //Aud_BeamLoop.stop();
        sym.getComposition().getStage().key_onKeyUp();
    }
    //37 is LEFT
    if (e.which == 37){
        sym.bool_left = false;
    }
    //39 is RIGHT
    if (e.which == 39){
        sym.bool_right = false;
    }
    //stop movement and cancel anything queue -- prevents animation queue buildup
    if(!sym.bool_left && !sym.bool_right){
        sym.mc_ship.animate( {left: sym.mc_ship.position().left}, {duration: sym.playerSpeed, easing: "linear"} );
        sym.mc_ship.finish();
        sym.mc_ray.finish();
        //force placement of ray back to ship's x...
        sym.mc_ray.css({"position":"absolute", "top":sym.rayY + "px", "left":sym.mc_ship.position().left + "px"});
    }
}

*************

CONCLUSION

I hope this will be helpful to some, or that it will have provided some useful insight for creating a game in Edge Animate. In the next tutorial I’m going to cover creating HTML5 content in Flash.

Download all sourcefiles here, and take a look:
http://nathalielawhead.com/sourcefiles/Offender_HTML5_Tutorial/

You can play the end result here:
http://offender.alienmelon.com/thegame/

Visit the NEXT tutorial: Building A Game With Flash HTML5 Canvas & CreateJS (Haxatron 2000)