Quick Links

Education Edu

Simulations Sims

Math Tools Math

Games Games

Generative ArtArt

Actinoscript Prog

Farmville Farm

 

 


Alkanes

Game Breakout

Click the above-left picture of the Breakout applet to start the game full screen.

 
 

Breakout

Ancient History

I think that the first ever video game, Pong is a pretty reasonable one to attempt for a first time Actionscript game writer. If nothing else, it provides an opportunity to create moving paddles and a ball. But it does require two concurrent players and that's not as easy to accomplish on a PC's keyboard. So forget that.

Instead, Breakout, another game that also has a paddle and ball, extends the concept with a field of bricks. Fancier variations have more interesting displays of bricks, multiple balls, and power-up options. This version just goes for the gameplay basics.

The Result

The finished product, without bells or whistles, weighs in at a paltry 7,555 bytes. The score is displayed only after the game is finished. Clicking, starts it up again.

Content on this page requires a newer version of Adobe Flash Player.

Get Adobe Flash player

The Approach

Looking to be object oriented, but not over do it in terms of classes, I start with classes for paddle, ball, and the block (brick). Each of these draw a simple static shape. The Ball requires a few variables so is extended from a Sprite rather than a Shape.

Paddle

This Shape extended class just draws a rectangle based on the parameters passed in to it. Pretty straightforward.

package {
import flash.display.Shape;
public class PaddleHorz extends Shape {
public function PaddleHorz(w:Number, h:Number, c:Number)
{
with (graphics)
{
beginFill(c);
drawRect(0,0,w,h);
endFill();
}
}
}
}

Block

Each Block is a randomly colored rectangle with a thin half-opaqued edge. Hmmm, a few magic numbers that I should have cleaned up. The base color, maxC, is slightly bluish green. The contortions used in choosing the random color: maxC*(Math.random()+3)/4, permit only one quarter of the full range to vary.

package {
import flash.display.Shape;
public class Block extends Shape {
public var alive=true;
private var maxC:Number=0x08FF88;
public function Block() { with (graphics)
{
lineStyle(0.1,0xFFFFFF,0.5);
beginFill(maxC*(Math.random()+3)/4);
drawRect(0,0,50,20);
endFill();
}
}
}
}

Ball

The Ball class, extending a Sprite, begins with the drawing of its simple shape, a circle, but also has an update function which nudges its position by dX & dY.

package {
import flash.display.Sprite;
public class Ball extends Sprite {
public var dX:Number=1;
public var dY:Number=1;
public var radius:Number=10;
public function Ball()
{
with (graphics)
{
beginFill(0x00FFFF);
drawEllipse(-radius,-radius,2*radius,2*radius);
endFill();
}
}
public function update():void
{
x+=dX;
y+=dY;
}
}
}

Everything Else

The remainder of the code resides in the Breakout class. Additional logical classes could have been applied here, but this is a pretty simple game, so it hardly overtaxes a single class. Besides importing MovieClp, Event, MouseEvent, and text related classes, the PaddleHorz, Block, and Ball classes are needed, clearly demonstrating my lazy and non-canonical approach to placing my created classes in the project's root directory.

The Package

package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.filters.BlurFilter;

import PaddleHorz;
import Block;
import Ball;

Variables

Besides the instances of PaddleHorz, pad, and Ball, b, an array, blocks, contains the blocks. Blip, Bounce, & Bleep, are the three sounds used. remainingBlocks lets us know when a level is finished. Position of blocks is assisted with bw (block width), maxBlocks, and bpr (blocks per row). Shorthand notations of sw, and sh provide values for the often referenced width and height of the stage; maxX the maximum horizontal paddle position. A textfield is created for my blurred touchspin.com logo, along with a multitasked TextFormat, tff.

public class Breakout extends MovieClip {

// Constants:
// Public padoperties:
// private properties:
private var pad:PaddleHorz;
private var maxX:Number;
private var blocks:Array=[];
private var bw:uint=50; // block width
private var maxBlocks:uint=88;
private var bpr:uint; // blocks per row
private var b:Ball;
private var maxBX:Number;
private var maxBY:Number;
private var padDX:Number;
private var ballDDY:Number=-0.25;
private var remainingBlocks:uint=0;
private var score:uint=0;
private var WHITE:uint=0xffffff;
private var blip:Blip = new Blip();
private var bounce:Bounce = new Bounce();
private var bleep:Bleep = new Bleep();
private var tff:TextFormat = new TextFormat();
private var tfTitle:TextField = new TextField();
private var sw:Number=stage.stageWidth;
private var sh:Number=stage.stageHeight;

All Those Resetting Functions

The Breakout.as constructor displays the game via resetGame and then puts it into a gameOver hold awaiting the player's starting click. This probably seems like an unnecessary number of similarly named functions: doResetGame, resetGame, newGame, resetBlocks -- but there is a bit of logic to it.

The event listener requires that it's handler receive an event parameter, but a similar call from the constructor does not. Also, the event handler, doResetGame, needs to remove the listener. The actual code for resetting the game appears in newGame because clearing the level of bricks during gameplay shouldn't reset the score as an actual reset does. Finally, resetBlocks removes, by name, all blocks from the displaylist and then regenerates them.

Within resetBlocks, the modulo provides the x coordinates and the floor of the dividend, the y. Embarrassingly, even though I've written this sort of thing several times in the past, it took me a bit to re-invent this wheel. I need to make some sort of mental note on this.

// Initialization:
public function Breakout()
{
resetGame();
gameOver();
}

// Public Methods:
// protected Methods:
public function gameOver()
{
removeEventListener(Event.ENTER_FRAME,update);
var tf:TextField = new TextField();
tf.text="Game Over\nScore: "+score;
tf.autoSize=TextFieldAutoSize.CENTER;
tf.multiline=true;
tf.wordWrap=true;
tf.selectable=false;
tf.width=sw;
tf.y=(sh-tf.height-100)/2;
tff.size=48;
addChild(tf);
tf.setTextFormat(tff);
var tf2:TextField = new TextField();
addChild(tf2);
tf2.text="Click to Restart";
tf2.autoSize=TextFieldAutoSize.CENTER;
tf2.width=sw;
tf2.y=sh-tf2.height;
tf2.selectable=false;
tff.size=16;
tf2.setTextFormat(tff);
b.visible=false;
pad.visible=false;
stage.addEventListener(MouseEvent.CLICK,doResetGame);
}
public function resetBlocks()
{
remainingBlocks=0;
for(var rb:uint=0;rb<blocks.length;rb++) { blocks[rb]=null; }
blocks=[];
for(var bt:uint=0;bt<maxBlocks;bt++)
{
var k:Block = new Block();
var bpos:Number=bt*k.width;
blocks.push(k);
addChild(k);
k.x=bw*(bt%bpr);
k.y=k.height*(3+Math.floor(bt/bpr));
k.name="B"+bt;
remainingBlocks++;
}
}
public function doResetGame(e:Event):void
{
stage.removeEventListener(MouseEvent.CLICK,doResetGame);
resetGame();
}
public function resetGame():void { score=0; newGame(); }
public function newGame():void
{
while (numChildren>0) { removeChildAt(0); };
tff.align=TextFormatAlign.LEFT;
tff.color=WHITE;
tff.size=24;
tfTitle.width=sw;
addChild(tfTitle);
tfTitle.text="touchspin.com";
tfTitle.filters=[new BlurFilter()];
tfTitle.setTextFormat(tff);
tff.align=TextFormatAlign.CENTER;

bpr=sw/bw;
pad = new PaddleHorz(50,10,WHITE);
addChild(pad);
maxX=sw-pad.width;
pad.y = sh-2*pad.height;
b = new Ball();
addChild(b);
b.x=sw/2;
b.y=sh*0.75;
maxBX=sw-b.radius;
maxBY=sh-b.radius;
resetBlocks();
addEventListener(Event.ENTER_FRAME,update);
}

Gameplay

All the movement of ball & paddle, and checking for collisions between ball and paddle, blocks, & walls occurs in the update function. This function handler for the ENTER_FRAME listener is created in newGame.

The original Pong game had players mistakenly believing they could place a spin on the ball by sliding the paddle at the moment it contacted the ball. However, this version of Breakout has variable padDX, effectively the difference between the last and current position, which affects horizontal motion. Since the paddle is repositioned to the mouse's x position it has a much peppier feel than if it just had a fixed velocity.

A few if-s keep the ball bouncing within game board region, making a "bleep" when it bounces off the walls.

Because the root FLA file operates at 33 frames/second, the innaccuracy of the hitTestObject between the rectangular paddle and the round ball is too fast to be seen. When such collision occurs, only one tenth of the padDX delta is added to the dX as "spin".

Finally, a loop iterating through the blocks checks for collisions with the ball. Rather than any fancy explosion, or fading out, a blip sound is produced, and the block is simply removed from the displayList.

It would have been easy enough to have displayed the updated score, but, by this time, I didn't have much screen real estate left and instead opted to only report the score when the game ends.

private function update(e:Event):void
{
// move the paddle
padDX=mouseX-pad.x-pad.width/2;
pad.x=mouseX-pad.width/2;
if (pad.x<0) pad.x=0;
else if (pad.x>maxX) pad.x=maxX;

// keep ball within bounds
b.update();
if (b.x<0) { b.dX*=-1; b.x=0; bleep.play(); }
else if (b.x>maxBX) { b.dX*=-1; b.x=maxBX; bleep.play(); }
if (b.y<0) { b.dY*=-1; b.y=0; bleep.play(); }
else if (b.y>maxBY) { gameOver(); }

// deal w/ball/paddle interaction
if (pad.hitTestObject(b))
{
b.dY*=-1;
b.y=pad.y-b.radius;
b.dY+=ballDDY;
b.dX+=padDX/10;
bounce.play();
}

// deal w/ball block interactions
for(var bt:uint=0;bt<blocks.length;bt++)
{
if (blocks[bt].alive && b.hitTestObject(blocks[bt]))
{
var blk:Block = Block(getChildByName("B"+bt));
if (blk==null) { trace("Null child for B"+bt); }
else
{
blocks[bt].visible=blocks[bt].alive=false;
removeChild(getChildByName("B"+bt));
remainingBlocks--;
if (remainingBlocks==0) resetBlocks();
b.dY*=-1;
score++;
blip.play()
break;
}
}
}
}
}

}

Conclusion

This was a fun little project, which took me about four hours to create. If I had a complete design prior to starting coding, it would have been faster. If I had elected to add standard game features such as intro screen, help page, top scores, etc., which are standard for most games, the time might possibly have stretched to four days! But, the ease of creating such a game in such a short time plainly demonstrates Actionscript 3's power to quickly deliver results.

If you modify and create something even more wonderful, please let me know.