AS Reference  :  Notes Index  :  Resources  :  About/Contact  :  Downloads

Creating the Dart Shooter Game with Classes

Get the latest Flash player here

As part of the Actionscript Basics class, we looked at creating a dart shooter game using movieclips and code, especially onEnterFrame handlers, in frame 1. To show how classes could be used instead, I decided to rewrite the game from scratch, using the same movieclips (shooter, dart, and bubble) but associating each with a class that defines its behavior and another class to manage the game interactions. It uses Flash's built-in EventDispatcher, including the Sender and Receiver structures described on this page, to provide necessary communication between the classes.

Coding with classes means writing a lot more code up front, but it is then easier to find where various parts of the functionality are located and change them, and maybe even reuse the classes for other projects.

Defining the Behaviors: Shooter Class and Game Manager Class

Knowing the basic operation of the game (bubbles float continuously across the top of the movie and may be shot at by a shooter, which is draggable horizontally and fires a dart when the mouse is released), the next step was to think about the behavior that would be associated with each of the parts. The shooter, for example, had to be horizontally draggable and fire a dart on mouse release. But the dart is a separate object, and I might want to have the shooter do something slightly different in another version, so I decided to just have it broadcast an event on mouse release that could be acted on (in any way desired) by the game manager. The event would be called shooterFired, and the game manager class would need to be set up to listen for and respond (in the desired way) to this event.

So, to use the terminology from the EventDispatcher page, Shooter would need to be a Sender and the Game Manager would be a Receiver. Pulling code from those two classes on that page, I created the Shooter class (file saved in Shooter.as) and DartShooterManager class. I opened my original dart shooter fla, deleted all objects from the stage except the background picture and the mask, and right-clicked the shooter movieclip in the Library, checking Export for Actionscript, and assigning Shooter as the class. With the following two classes, and a line of code in the fla to instantiate DartShooterManager and pass a reference to the game timeline (so it knows where to attach things):

/**
* @class        DartShooterManager
* @author       Helen Triolo
* @description  Manages the dart shooter game (in dartshooter.fla)
*               Attaches a shooter movieclip and sets up to respond to shooterFired
*                  messages from it in onShooterFired
*/

import mx.utils.Delegate;

class DartShooterManager {

   private var timeline:MovieClip;
   
   public function DartShooterManager($timeline:MovieClip) {
      timeline = $timeline;
      var shootermc:MovieClip = timeline.attachMovie("shooter", 
                                                     "shooter", 
                                                     2, 
                                                     {_x:26, 
                                                      _y:263, 
                                                      x1:0, 
                                                      x2:500
                                                     });
      shootermc.addEventListener("shooterFired", Delegate.create(this, onShooterFired));
   }
   
   private function onShooterFired(o:Object):Void {
   }
}
and
/**
* @class        Shooter
* @author       Helen Triolo
* @description  Defines the behavior of a shooter movieclip in the library which 
*                 has one frame with a shooter graphic.  The shooter can be dragged
*                 horizontally between x1 and x2 (set in initobject) when pressed
*               When the mouse is released, a "shooterFired" event is broadcast 
*                 using EventDispatcher
*               The class has no public methods
*/

import mx.events.EventDispatcher;

class Shooter extends MovieClip {
	
   private var x1:Number;		// set in initobject
   private var x2:Number;		// set in initobject
	
   public var addEventListener:Function;
   public var removeEventListener:Function;
   public var dispatchEvent:Function;	
	
   public function Shooter() {
      EventDispatcher.initialize(this);	
   }
   
   private function onPress() {
      this.startDrag(false, this.x1, this._y, this.x2, this._y);
   }
   
   private function onRelease() {
      this.stopDrag();
      // send coordinates for the dart to be centered on the shooter
      dispatchEvent({target:this, type:'shooterFired', x:this._x+this._width/2, y:this._y});   
   }

   private function onReleaseOutside() {
      this.stopDrag();
      dispatchEvent({target:this, type:'shooterFired', x:this._x+this._width/2, y:this._y});   
   }
   
}
and this in the fla:
var dsmgr:DartShooterManager = new DartShooterManager(this);

the shooter appears on stage and is draggable horizontally (though not under the mask). To fix the mask problem, I converted the mask in the fla to a MovieClip, unchecked Mask in the layer it's in, and added this code to the game manager class to put the mask above the shooter and use it as a mask:

      timeline.shooterMask.setDepth(GameVars.shooterDepth+1);
      shootermc.setMask(timeline.shooterMask);

So, by making Shooter an extension of the MovieClip class and assigning custom code to its onPress, onRelease and onReleaseOutside events, the shooter has the functionality it needs.

The one other change I would make right away to the DartShooterManager class is to take those hard-coded numbers out of there, put them into a new class called GameVars, so I know where to find whatever numbers define the numeric parameters of the game, like dart speed, bubble size variability, etc. This is the content of the GameVars class, and the change to DartShooterManager:

/**
* @class         GameVars
* @author        Helen Triolo
* @description   Contains static variables which define the parameters of the dart shooter game
*/
class GameVars {
   
   // depth to put shooter at when attached
   public static var shooterDepth:Number = 2;
   
   // position of shooter on stage when attached
   public static var shooterX:Number = 26;
   public static var shooterY:Number = 263;
   
   // left and right bounds of game (shooter and bubble)
   public static var leftBound:Number = 0;
   public static var rightBound:Number = 500;
   
}
and
// in the DartShooterManager constructor:
var shootermc:MovieClip = timeline.attachMovie("shooter", 
                                          "shooter", 
                                          GameVars.shooterDepth, 
                                          {_x:GameVars.shooterX, 
                                           _y:GameVars.shooterY, 
                                           x1:GameVars.leftBound, 
                                           x2:GameVars.rightBound
                                           });
Note that when static vars are referenced, it is by the class name, not an instance of the class (they are properties of the class, not an instance of it).

Defining the Behaviors: Bubble Class

A bubble in the game needs to do three things: float right across the stage, be poppable, and delete itself when it's popped or goes offstage. (In the previous version of the game, we used 5 bubble movieclips and reset them when needed, but with bubbles that need to be continuous, it seems a better idea to just keep creating them, and delete each one when no longer needed. Since there is a pop sequence in the bubble and we want it to be deleted afterwards, the easiest solution was to put code in the last frame of the movieclip (generally frowned upon, but at least it is documented in the Bubble class) to have the movieclip delete itself in the last frame of the pop sequence. The alternative would be to add some kind of check to see if the playhead is in the last frame of the clip and then delete it.

In this class, two properties may be set by the class that creates an instance of Bubble, by specifying them in an initObject (see the help files for attachMovie for more information on this). Default values are supplied if the caller does not provide them. The one other piece of functionality I gave the Bubble class was a public function allowing its scale to be set (so it could be set to random sizes). This is the Bubble class:

/**
* @class        Bubble
* @author       Helen Triolo
* @description  Defines the behavior of a bubble movieclip in the library 
*                  which has the following structure:
*                   stop(); in frame 1
*                   contains a frame labelled "explode", which begins a graphic 
*                       sequence of the bubble popping over the next several frames
*                   last frame contains this code: this.removeMovieClip(); 
*                       to delete the bubble mc after it pops
*                When a bubble is attached, it automatically starts moving 
*                       left-to-right across the stage, at a speed determined by 
*                    bubbleSpeed and bubbleInterval, set in initobject)
*                The class has two public methods: setScale and pop
*/   

class Bubble extends MovieClip {
	
   private var moveInt:Number;
   private var bubbleSpeed:Number;          // set in initobject
   private var bubbleInterval:Number;       // set in initobject
   
   public function Bubble() {
      // provide a default value in case undefined
      if (bubbleInterval == undefined) bubbleInterval = 30;
      if (bubbleSpeed == undefined) bubbleSpeed = 2;
      moveInt = setInterval(this, "floatRight", bubbleInterval);
   }
   
   public function setScale($scale:Number):Void {
      this._xscale = this._yscale = $scale;
   }
   
   private function floatRight():Void {
      this._x += this.bubbleSpeed;
      if (this._x > GameVars.rightBound) this.removeMovieClip();	  
   }
   
   public function pop():Void {
      this.gotoAndPlay("explode");
      clearInterval(moveInt);
   }
}

Notice the form of setInterval used. When creating an interval in a class, it's always a good idea to use the setInterval(this, "someMethodInTheClass", 100, var1, var2); form to make the scope of the function the current class.

That takes care of defining the bubble behavior; now bubbles just have to actually be continuously created (attached) in the movie, which is done by adding the following code to DartShooterManager. An array of bubbles currently on stage is kept for checking for collisions between dart and bubbles.

   // declare necessary variables
   private var bubbles:Array;
   private var bubbleInt:Number;
	
   // in constructor:
   bubbles = [];

   // additional method:
   private function createBubble():Void {
      var d:Number = timeline.getNextHighestDepth();
      var bubble:MovieClip;
      bubble = timeline.attachMovie("bubble",
                                    "bubble" + d,
                                    d,
                                   {_x:0,
                                    _y:20,
                                    bubbleInterval: 30,
                                    bubbleSpeed: 5
                                   });
      bubbles.push(bubble);
   }			
and setting the linkage for bubble in the library to class Bubble. With that code, I now have bubbles that appear continuously, but in a very boring way -- all the same size, the same spacing, the same y position.

Get the latest Flash player here

To make that variable, I again use the GameVars class to define the base value for bubble speed, as well as the amount of variability to be applied:

// in GameVars:
   public static var bubbleSpeed:Number = 5;
   public static var bubbleSpeedRandomness:Number = 5;
   public static var bubbleSizeRandomness:Number = 20;
and then apply it by changing the bubble attach code to:
      bubble = timeline.attachMovie("bubble",
                             "bubble" + d,
                             d,
                             {_x:0 - Math.random()*50,
                              _y:-20 + Math.random()*40,
                              bubbleInterval: 30,
                              bubbleSpeed: GameVars.bubbleSpeed - 
                                           GameVars.bubbleSpeedRandomness/2 + 
                                           Math.random()*GameVars.bubbleSpeedRandomness
                              });
      bubble.setScale(100 - GameVars.bubbleSizeRandomness/2 + Math.random()*GameVars.bubbleSizeRandomness);
      bubbles.push(bubble);

Now the bubbles appear at random x and y positions (within specified limits, x is between 0 and -50, y is between -20 and +20), at random sizes (between 80% and 120% of the size in the library), and move with random speeds.

Defining the Behaviors: Dart Class and Collision Checking

The dart only needs to do two things: fire (move upwards) and remove itself after popping a bubble or going offstage. Collision detecting will be done by the manager class. Moving the dart is much the same as moving the bubble; it is done with setInterval. I put in code to move the dart upward (used in this game) or to the right (not used here, just to show how it might be varied):

/**
* @class        Dart
* @author       Helen Triolo
* @description  Defines the behavior of a dart movieclip in the library which has one frame with a dart graphic
*               The class has two public methods: fire (moves itself in a specified direction) and remove (deletes itself)
*/

class Dart extends MovieClip {
	
   private var startDirection:String;	// direction to fire when dart is attached (optional, set in initobject)
	
   private var fireInt:Number;		// interval handle
	
   public function Dart() {
      if (startDirection != undefined) this.fire(startDirection);
   }
	
   public function fire($direction:String):Void {
      clearInterval(fireInt);
      fireInt = setInterval(this, "doFire", GameVars.dartInt, $direction);
   }
	
   private function doFire($direction:String):Void {
      switch ($direction) {
         case "up" :
            this._y -= GameVars.dartPixels;
            break;
         case "right" :
            this._x += GameVars.dartPixels;
            break;
      }
   }

   public function remove():Void {
      this.removeMovieClip();
      clearInterval(fireInt);
   }
}

A dart is attached and immediately fired (because a startDirection was passed) from within onShooterFired in DartShooterManager. Properties must also be added to that class to declare dart, which should be linked to the Dart class in the library, and collisionInt, used as an interval counter for detecting collisions.

   private function onShooterFired(o:Object):Void {
      // attach a dart behind the shooter and set it moving upwards 30 pixels every 50ms
      dart = timeline.attachMovie("dart",
                                    "dart",
                                    GameVars.shooterDepth-1,
                                    {_x:o.x,
                                     _y:o.y,
                                     startDirection:"up"
                                   });
      clearInterval(collisionInt);
      collisionInt = setInterval(this, "checkForCollision", 50);
   }

Note that the dart is reattached at the same depth every time the shooter is fired, which means that only one dart may exist on stage at any time. If you want to allow multiple darts, you'd need to create an array like the bubbles array and manage it similarly.

Because the dart can collide with any number of bubbles, collision detection is handled by DartShooterManager with this function, executed at intervals:

   // checks for collisions and also checks for bubbles gone offstage to delete them
   private function checkForCollision():Void {
      for (var i=0; i < bubbles.length; i++) {
         if (dart.hitTest(bubbles[i])) {
            clearInterval(collisionInt);
            dart.remove();
            // pop bubble (bubble will delete itself afterwards)
            bubbles[i].pop();
            // remove from bubbles array 
            bubbles.splice(i, 1);
            // and stop checking
            break;
         } else if (bubbles[i]._x > GameVars.rightBound) {
            // if bubble has gone offstage, remove it and take it out of the array
            bubbles[i].removeMovieClip();
            bubbles.splice(i, 1);
            break;
         }
      }
   }

You can download the class files and fla used to make the sample at the top of the page, with a subscription, from the link at upper right.

Intro
Flash: What & How
Example Sites
Create
Draw, Edit Shapes
Gradients
More Drawing Tips
Import
A Sample
Animate
Frames, Keyframes
Motion Tweens
More Motion Tweens
Shape Tweens
Masks
Control
Stop/Replay
Movieclips Intro
Movieclip Reference
Site Structure 1
Slideshow Movieclip
Contact Form
Scroll Resume
Preloader
Site Structure 2
Publish
Display Options
Player Detection
Optimize
AS 2.0 Basics
Intro to Syntax
Playhead Commands
Playhead Cmds 2
Coded Tween
onEnterFrame
Intro to Classes
Declare/Assign
Comments, Trace
Simple Data Types
Arrays & Objects
Code Blocks
Operators
Beyond Buttons
Code Structure
Toggle Controls
Group of Buttons
Drag and Hit
Distort Magnifier
Scroll Text
Bee Game
Dart Shooter
Sound Control
Easing Slider
Easing Slider 2
Components Intro
Timers & Delays
Dynamic Content
Intro
Drawing API
Create Text
Attach Movieclips
Easing Slider 3
Easing Slider 4
Load jpg/swf
Sliding Viewer
Preload swf
XML
Easing Slider 5
Server Comm
LoadVars (w/ PHP)
AS - PHP Lookup
Text File
Database 1:LoadVars
Database 2:Remoting
Read from directory
AS 2.0 Classes
Intro
Math
Key
Date
Color
EventDispatcher
New Samples
Pie Chart
Event-model Emailer
Tween Sequence
Fuse Sequence
SVG in Flash
Bitmap Topo
SWF as Data Holder
Two-level Menu
Yahoo! Flash Maps
Class-based Game
ASTB Samples
Disclaimer
3D Outlines
Bounce Collide
Address Book
Save Drawings
Home  :  Notes Index  :  Resources  :  About/Contact  :  Downloads