ActionScript 3 - Flash Game Core Tutorial by Chris DeLeon

Difficulty: Beginner
Program: MXMLC (Free ActionScript 3 Command-Line Compilation)
Estimated Completion Time: 45 minutes

ActionScript 3 - Flash Game Core Tutorial

Written by Chris DeLeon
HobbyGameDev.com

My previous Free ActionScript 3 Command-Line Compilation introduction to using MXMLC ended by noting that only a few types of things are needed to make nearly any 2D game:

In this tutorial, we'll walk through each of those, creating a very simple 2D videogame as we go.


Final Result Preview - ActionScript 3 Game Core

Let's take a look at the final result we will be working towards:

Click on the smiley to make it beep! If you're not hearing anything, Spacebar should also produce the tone after clicking anywhere in that frame. (Otherwise, check volume and mute.)

This tutorial presumes that you are familiar with the material covered by my previous tutorial, which outlines how to use MXMLC to compile a Flash program.

If you have done C or Java programming before, what follows hopefully shouldn't seem too tricky. If you're completely new to programming, and would like some additional context (feel free to read ahead, and return to these links later), I have previously written both a conceptual introduction to programming as well as a breakdown of each major programming element, and their common uses in videogame programming. Browsing those won't result in immediately understanding programming (of course), but they should help provide understanding of what it is, and provide some mental context for the code used in this tutorial. In particular, it's useful to know that anything on the same line after // marks, or between any number of lines between the /* marks and */ marks is a text comment describing the code; comments are ignored by the compiler, and have no effect on the program.


Step 1: Create a New Project

If you aren't yet set up to compile with mxmlc, then follow the steps 1-4 from the previous tutorial. At this point, make a copy of ShortExample.as and rename it GameCore.as. In keeping with the new project's filename, its starter source should be adjusted to read like so, renaming each instance of "ShortExample" to read "GameCore" instead:

package { // no special packages needed to organize this single file project
  import flash.display.Sprite; // the application is built upon Sprite
  import flash.text.TextField; // needed to display the project's text
  
  public class GameCore extends Sprite // must match the file's name!
  {  
    // initialize text label
    public var ourExampleText:TextField = new TextField();

    public function GameCore() // program execution begins here
    {      
      ourExampleText.text = "Hello, world!"; // set the text
      addChild(ourExampleText); // add text to the application

    } // end of GameCore() function
        
  } // end of Gamecore class definition
    
} // end of file/program

Make sure that it compiles before we move forward, using whichever one of the following compilation lines fits your OS:

Mac or Linux compilation line:

./bin/mxmlc ./projects/GameCore.as

Windows PC compilation line:

.\bin\mxmlc projects\GameCore.as

This should create a GameCore.swf file in your projects folder that displays "Hello, world!" when opened with a browser or Flash Player:

Image of Hellow World sample program

Step 2: Display Images

Loading images with ActionScript 3 code is a bit strange. Fortunately, as with anything strange in programming, it follows a very clear pattern - once it's figured out once, it can be copy/pasted and modified as needed for every project that follows.

The first image that we'll be loading and using is this happy face ball that I made using GIMP, a free graphics editor available for Windows/Mac/Linux:

HappyBall image

Either create your own happyball.png file using an image editor, or right click on the above image and "Save As..."/"Save Image As..." to your flex_sdk_4/projects/ folder (the same directory as GameCore.as, to avoid specifying path in code for now). Note that I'm choosing to use png because png supports variable alpha transparency - e.g. area in the square image outside of the ball graphic isn't whie, it's transparent, so it appear as a circle in the game no matter what background is drawn under it.

Modify the code to read as follows (new code is noted in bold):

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.display.Bitmap; // variable type for images
  
  public class GameCore extends Sprite
  {  
    public var ourExampleText:TextField = new TextField();

    // The next lines must be together, one after another
    // for each image or sound that we want to reference in code
    [Embed(source="/happyball.png")] // filename of image to load
    public var ballImage:Class; // code name for that filename

    //  This is how we'll keep track of the image from "ballImage"
    public var ballBMP:Bitmap = new ballImage() as Bitmap;

    public function GameCore()
    {      
      ballBMP.x = 80; // position it 80 units from the left
      ballBMP.y = 50; // position it 50 units from the top
      addChild(ballBMP); // add image to the application

      ourExampleText.text = "Wow! An image!";
      addChild(ourExampleText);
    }
  }
}

To get this ActionScript 3 code to turn into a Flash SWF, we're going to start adding a new command-line argument when compiling the code: "-static-link-runtime-shared-libraries". Applications involving graphics and images that compiled fine in older versions of the Flex SDK now require that argument to compile nicely. This goes at the end of our normal compilation lines, which once again varies by operation system...

Mac or Linux compilation line:

./bin/mxmlc ./projects/GameCore.as -static-link-runtime-shared-libraries

Windows PC compilation line:

.\bin\mxmlc projects\GameCore.as -static-link-runtime-shared-libraries

(Most command-line prompts/terminals/shells let you use the up and down arrows to scroll through previously input commands. Try this after compiling with the line above - it can save a ton of typing when recompiling frequently between code edits!)

This will replace GameCore.swf file in your projects folder. Double click to open it in a browser or with Flash Player, and you should see this:

Image of Sample program with HappyBall shown

Step 3: Mouse Input

There are two types of mouse input: position, and click detection. Position is the easier of the two, but through updating position upon each mouse click, we'll be introducing both at once.

Modify the code to read as follows (new code is noted in bold):

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.display.Bitmap;
  import flash.events.MouseEvent; // needed for mouse click detection
  
  public class GameCore extends Sprite
  {  
    public var ourExampleText:TextField = new TextField();

    [Embed(source="/happyball.png")]
    public var ballImage:Class;

    public var ballBMP:Bitmap = new ballImage() as Bitmap;

    public function GameCore()
    {      
      ballBMP.x = 80;
      ballBMP.y = 50;
      addChild(ballBMP);

      ourExampleText.text = "The mouse works!";
      addChild(ourExampleText);
      
      // Attaches "clickRespond" function to mouse clicks:
      stage.addEventListener(MouseEvent.CLICK, clickRespond);
      
      /* "stage" is a special word that includes everything in 
      the game's window. If we forgot to include the "stage."
      before addEventListener, mouse clicks would only get
      caught if something attached by "addChild" were clicked
      on, such as the ourExampleText. This way, clicks on the
      plain background won't go ignored. */
    }
        
    // Function needs "(e:MouseEvent)" to work for mouse clicks
    public function clickRespond(e:MouseEvent):void
    {
      // mouseX and mouseY work anywhere in the program.
      // these are Sprite variables, which our game "extends"
      ballBMP.x = mouseX; // image horizontal to mouse X
      ballBMP.y = mouseY; // image vertical to mouse Y
    } // end of clickRespond() function 
  }
}

Re-compile with mxmlc in the same way shown from the previous step (keeping the "-static-link-runtime-shared-libraries" argument), then open the updated GameCore.swf.

You can now click anywhere to update the smiley face's position to where the mouse was clicked. Go ahead, try it. Is yours doing that, too? (If not please e-mail me at chris@gamedevlessons.com describing what's happening, and we'll try to get to the bottom of things!)

While in this case we're just using it to shuffle a smiley face around, this is the same way that we'll detect mouse clicks to do pretty much anything else in a Flash game: firing a laser, launching a ball, selecting a tank, flipping a switch, placing a gear, jumping, etc.

This is a big deal, every bit as important as figuring out how to handle gamepad input were we making a console game. Although how it's hooked up in code may look a bit quirky (to be fair, it kind of is), the important thing to remember is that there is no need to memorize this, or even understand it, really. You can hereafter copy, paste, and edit it as needed, freeing your mind to ponder other things about your game projects.


Step 4: Keyboard Input

That text box has been hanging around - let's use it to demonstrate basic keyboard input.

For the sake of videogame programming, we're usually not interested in the keyboard as a way to type letters. For most gameplay, we're interested in looking at the keyboard as a controller as a 100+ button controller. We don't care whether the player types lowercase 'b' or capital 'B', we care that the 'B' button needs to be counted on for a certain action regardless of Caps Lock. Speaking of capitalization: what letter is the Shift key? Or the Up Arrow? How about Control, a key traditionally used to fire in games?

Keycodes to the rescue! Different keys on the keyboard match up to different keycode numbers. We can use the program code that follows to check what different keycodes are, and then later check with numerical comparisons in code whether the key the player pressed corresponds to B, Shift, Up Arrow, Control, etc.

The latest version of the code (new code is again bolded):

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.display.Bitmap;
  import flash.events.MouseEvent;
  import flash.events.KeyboardEvent; // detect keystrokes
  
  /* Yes, we're starting to pile up lots of imports. In fact, 
  every time we're doing anything, we're adding an import.
  The thing to keep in mind though is that on each step, we're
  doing something totally different for the first time - that's
  why we're needing to add another import in order to do it!
  Once we have the main ones covered, you'll have a starting point
  for future projects that need images, mouse input, keyboard input,
  and (soon!) sound and real-time motion. Once these major areas
  are covered, you'll find it increasingly rare that new imports
  need to be added to do anything else for a basic 2D game. */

  public class GameCore extends Sprite
  {  
    public var ourExampleText:TextField = new TextField();

    [Embed(source="/happyball.png")]
    public var ballImage:Class;

    public var ballBMP:Bitmap = new ballImage() as Bitmap;

    public function GameCore()
    {      
      ballBMP.x = 80;
      ballBMP.y = 50;
      addChild(ballBMP);

      ourExampleText.text = "Click first!";
      addChild(ourExampleText);
      
      stage.addEventListener(MouseEvent.CLICK, clickRespond);
      
      // thankfully similar to how the mouse click is handled!
      stage.addEventListener(KeyboardEvent.KEY_DOWN, keyRespond);
    }
        
    public function clickRespond(e:MouseEvent):void
    {
      ourExampleText.text = "Press keys";
      ballBMP.x = mouseX;
      ballBMP.y = mouseY;
    }

    public function keyRespond(event:KeyboardEvent):void
    {
      // update in-game text with the code number for the key
      // knowing the keycode lets us test for arrow keys, etc.
      ourExampleText.text = "Key code: " + event.keyCode;
    } // end of keyRespond() function
  }
}

Re-compile with mxmlc once again, and the new GameCore.swf will give keycodes for each key pressed (you'll need to click on the swf first though):

Click in there, then press spacebar, and you should see it come up as Key code #32. That number will correspond to spacebar for everyone, allowing us to do a simple if comparison in the code if(event.keyCode==32) to handle the player pressing spacebar. We'll return to this in the next step.


Step 5: Play Audio

Audio files are loaded by ActionScript 3 code the same way that we load images.

File link for good.mp3 (8 kb download)

That's a plain beep tone I made with Audacity, a free sound editing program available for Windows/Mac/Linux. The new code follows:

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.display.Bitmap;
  import flash.events.MouseEvent;
  import flash.events.KeyboardEvent;
  import flash.media.Sound; // needed for sound effects
  
  public class GameCore extends Sprite
  {  
    public var ourExampleText:TextField = new TextField();

    [Embed(source="/happyball.png")]
    public var ballImage:Class;
    public var ballBMP:Bitmap = new ballImage() as Bitmap;

    [Embed(source="/good.mp3")] // filename of sound to load
    public var clickSound:Class; // code name for that filename
    
     // This is how we'll keep track of the sound from "clickSound"
    public var clickSND:Sound = new clickSound() as Sound;
    
    /* 2 notes about how sound is loaded:
         1- It's almost identical to how bitmaps are loaded
         2- Note that using Sound and SND in the sound variable name 
           (or using Image and BMP in the bitmap variable names) is
           only my own convention. It isn't necessary for the code
           to work. If we change "clickSound" in both places to say
           "CalloohCallay", and change "clickSND" in all locations
           to read "FrumiousBandersnatch" everything will work 
            the same. */

    public function GameCore()
    {      
      ballBMP.x = 80;
      ballBMP.y = 50;
      addChild(ballBMP);

      ourExampleText.text = "Sounds!";
      addChild(ourExampleText);
      
      stage.addEventListener(MouseEvent.CLICK, clickRespond);
      stage.addEventListener(KeyboardEvent.KEY_DOWN, keyRespond);      
    }
        
    public function clickRespond(e:MouseEvent):void
    {
      ourExampleText.text = "Press keys";
      clickSND.play(); // bloop!
      ballBMP.x = mouseX;
      ballBMP.y = mouseY;
    }

    public function keyRespond(event:KeyboardEvent):void
    {
      if(event.keyCode == 32) { // pressing spacebar?
        clickSND.play(); // bloop!
      }
      ourExampleText.text = "Key code: " + event.keyCode;
    }
  }
}

Click to make a mildly annoying bloop sound, or press Spacebar after clicking to make the same sound (make sure your speakers are on and volume is up!):

Note that, as per our spacebar check in keyRespond, no other key besides spacebar causes the sound to play.


Step 6: Detect Collisions

Collisions are central to videogame programming. Detecting collisions is about much more than what it first sounds like. Yes, it's needed to determine when cars bump, or when a ball hits bricks - collisions in the literal sense - but it's also how we determine when a car is in the pit area, whether the player is close enough to a door to open it, what objects are in the blast radius of an explosion, and which menu button is being clicked on when the mouse is pressed.

Basic collision detection is about determining what's overlapping, and then deciding what to do about it. More advanced collision detection deals with optimizations to efficiently rule out which objects can't possibly be overlapping, but for a few dozen objects or less we don't need to be that fancy.

The two most common ways of testing collision checks are through rectangular bouncing boxes or circles. Which is better mostly comes down to what sort of game you're making, and what sort of objects are in it. Even though we're dealing with a circular object, we'll detect collision using rectangular bounding box - that's the most common approximation used, and for such a small ball it's close enough.

Did you notice in the above examples that the happy ball lined up the top-left corner when the app was clicked? That's because an image's pixels are lined up like so:

Diagram of corners on HappyBall

Those pairs like (0,11) are the (x,y) coordinates in the image. By convention, (0,0) is the top left corner of a picture. So when we place 12x12 image such that it's (x,y) coordinate is, for example, (256,42), then the image is covering a space that its corners in the main game area are:

See? It's so simple that... eh, it's actually sort of a mess. I wrote that, I've been programming videogames half my life, and it still looks wonky. Let's let the computer deal with the actual numbers instead, and just lay out through variables in the code what we need to happen. Computers are really, really good with keeping track of numbers, so we should let them handle that for us:

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.display.Bitmap;
  import flash.events.MouseEvent;
  import flash.events.KeyboardEvent;
  import flash.media.Sound;
  // No new imports are needed this time! Hooray!
  
  public class GameCore extends Sprite
  {  
    public var ourExampleText:TextField = new TextField();

    [Embed(source="/happyball.png")]
    public var ballImage:Class;
    public var ballBMP:Bitmap = new ballImage() as Bitmap;

    [Embed(source="/good.mp3")]
    public var clickSound:Class;
    public var clickSND:Sound = new clickSound() as Sound;

    public function GameCore()
    {      
      ballBMP.x = 80;
      ballBMP.y = 50;
      addChild(ballBMP);

      ourExampleText.text = "Click HappyBall!";
      addChild(ourExampleText);

      // by default, a TextField is crazy tall, like ~6 lines
      // this next line sets its height to be 1 line of text
      ourExampleText.height = 16;
      // if we don't do that, we'll get an "I" mouse cursor when 
      // mousing over over happyball. It would still work though.

      // likewise, its default length is 100, which is awkardly short
      // let's make it twice that:
      ourExampleText.width = 200;
      
      
      stage.addEventListener(MouseEvent.CLICK, clickRespond);
      stage.addEventListener(KeyboardEvent.KEY_DOWN, keyRespond);      
    }
        
    public function clickRespond(e:MouseEvent):void
    {
      // relative to the happyball image, is the mouse...
      if(mouseX < ballBMP.x || // left of the left side or
         mouseX > ballBMP.x+ballBMP.width || // right of the right
         mouseY < ballBMP.y || //  or above the top or
         mouseY > ballBMP.y+ballBMP.height) { // below the bottom?
         
         // The double bars || above mean "or", as in: if( [this] OR [that] )
         // In programming, (A || B) is true unless A and B are both false.
         // Compare to (A && B) which is true only if A and B are both true.
         
        ourExampleText.text = "Missed!"; // then we missed
        
      } else { // otherwise, we must have clicked on it!
      
        ourExampleText.text = "Ouch!"; // jerk! be nice!
        clickSND.play(); // make a sound
        
      }
    }

    public function keyRespond(event:KeyboardEvent):void
    {
      if(event.keyCode == 32) { // pressing spacebar?
        clickSND.play();
      }
      ourExampleText.text = "Key code: " + event.keyCode;
    }
  }
}

Click on or away from the smiley face, and watch for text and sound updates:

If you're testing your program, and not getting the sound you expect from clicking on the smiley, pressing Spacebar once the game has been clicked on should still play the sound effect, too. If you can't hear either, check your software and hardware volume, software and hardware mute state, speaker power, etc.


Step 7: Moving On Its Own

Poor ol' HappyBall didn't stand much of a chance in the above example. He just sits there until clicked on. So far, nothing in our example "games" has moved on their own - things have only changed when we clicked the mouse or pressed a key. That's not much fun. That's like a board game ;)

Videogames need to change constantly, not just waiting for player input. Conceptually, we do this in ActionScript 3 by creating a timer to call a function every certain number of milliseconds, as shown in the final version of the code:

package {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.display.Bitmap;
  import flash.events.MouseEvent;
  import flash.events.KeyboardEvent;
  import flash.media.Sound;
  import flash.utils.Timer; // used to call function ever n milliseconds
  import flash.events.TimerEvent; // responds to the game's Timer
  
  public class GameCore extends Sprite
  {  
    public var ourExampleText:TextField = new TextField();

    [Embed(source="/happyball.png")]
    public var ballImage:Class;
    public var ballBMP:Bitmap = new ballImage() as Bitmap;

    [Embed(source="/good.mp3")]
    public var clickSound:Class;
    public var clickSND:Sound = new clickSound() as Sound;
    
    public var constantAction:Timer; // to call a function regularly

    public function GameCore()
    {      
      ballBMP.x = 80;
      ballBMP.y = 50;
      addChild(ballBMP);

      ourExampleText.text = "Click HappyBall!";
      addChild(ourExampleText);
      ourExampleText.height = 16;
      
      stage.addEventListener(MouseEvent.CLICK, clickRespond);
      stage.addEventListener(KeyboardEvent.KEY_DOWN, keyRespond);    
      
      // Note: 1000 milliseconds = 1.0 second
      constantAction = new Timer(1500); // every 1.5 seconds...
      // if we set that to 1000/30, it would act 30 times per second :)
      
      // call our function called constantUpdate
      constantAction.addEventListener(TimerEvent.TIMER, constantUpdate);
      
      // kick off the timer. (Don't forget to start it after making it!)
      constantAction.start();
    }
        
    public function constantUpdate(evt:TimerEvent):void {
      // have the happyball dodge to a random new position
      
      // Note that Math.random() provides a random percentage from 0.0-1.0
      // so Math.random()*VALUE gives us anywhere from 0% to 100% of VALUE
      // stage.stageWidth and stage.stageHeight are the SWF's dimensions...

      ballBMP.x = Math.random()*stage.stageWidth;
      ballBMP.y = Math.random()*stage.stageHeight;

      // In the next tutorial we'll learn to set stage.stageWidth and stageHeight
      // Though for now (if you're curious) know that they default to 500 x 375
      
      ourExampleText.text = "Dodging...";
    }
    
    public function clickRespond(e:MouseEvent):void
    {
      if(mouseX < ballBMP.x ||
         mouseX > ballBMP.x+ballBMP.width ||
         mouseY < ballBMP.y ||
         mouseY > ballBMP.y+ballBMP.height) {
        ourExampleText.text = "Missed!";
      } else {
        ourExampleText.text = "Ouch!";
        clickSND.play();
      }
    }

    public function keyRespond(event:KeyboardEvent):void
    {
      if(event.keyCode == 32) { // pressing spacebar?
        clickSND.play();
      }
      ourExampleText.text = "Key code: " + event.keyCode;
    }
  }
}

The face is now harder to click on. It dodges on its own!

We now have our little videogame. With some tuning of the HappyBall's size and tweaks to how frequently HappyBall changes position, this can be made into a challenge for any player. By replacing the image and sound, the game can take on whatever theme you'd like (remember to compile after changing the assets - they get copied into the SWF file).


What We've Done, What We're Going to Do

This bite-sized tutorial took us from compiling a stale, text-only application, to an actual game of sorts, demonstrating all of the following building blocks:

Many 2D games are possible through proper rearranging of the code in this program.

Check back soon for my next tutorial, going through some of the common math and programming for movements that come up in 2D videogames.

UPDATE: Part 3 is now available



Written by Chris DeLeon

Part of his resources for
HobbyGameDev.com

If you liked this, consider checking out my other text resources at HobbyGameDev.com.
All the information there is free. I don't run ads on the site, I'm just into helping people.