Invaders
/**
* Copyright royi ( http://wonderfl.net/user/royi )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/1t2o
*/
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.filters.BlurFilter;
import flash.filters.DisplacementMapFilter;
import flash.filters.DisplacementMapFilterMode;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.Dictionary;
[SWF(width=465, height=465, frameRate=60, backgroundColor=0x000000)]
public class Invaders extends Sprite {
private static const PLAYER :String = "player",
ALIEN :String = "alien",
SHIELD :String = "shield",
UFO :String = "ufo",
BULLET :String = "bullet",
ALIEN_BULLET :String = "alienBullet";
private static const SCALE:int = 3;
private static const ALIEN_W:int = 9;
private static const ALIEN_H:int = 7;
private static const ALIEN_ROWS:int = 5;
private static const ALIEN_COLS:int = 11;
private static const PLAYER_W:int = 11;
private static const PLAYER_H:int = 7;
private static const SHIELD_W:int = 17;
private static const SHIELD_H:int = 12;
private var alienSpeedX:int = 4; // aliens move this many pixels each tick
private var alienSpeedY:int = 4; // aliens descend this many pixels
private var alienSpacingX:int = 2; // pixels of spaaace between aliens
private var alienSpacingY:int = 5;
private var alienVX:int = 0, alienVY:int = 0; // alien velocity
private var alienDrop:Boolean;
private var alienCluster:Rectangle = new Rectangle(); // matches the size of whole alien grid
private var gameBounds:Rectangle = new Rectangle(2, 20, 151, 125); // aliens can only move within bounds
private var particles:Array = [];
private var gravity:Number = .15; // only particles are affected
// gfx
private var baseColor:uint = 0xff00ff00;
private var canvas:BitmapData; // main canvas for drawing game
private var canvasBig:BitmapData; // visible canvas enlarged
private var sw:int, sh:int; // stage width/height
private var cw:int, ch:int; // canvas width/height
private var gfx:Object = { }; // list of all graphics (bitmapData)
private var floorRect:Rectangle = new Rectangle(2, 145, 151, 1); // floor size/position
private var scaleMatrix:Matrix = new Matrix();
private var alienColors:Array = [0xffff0000, 0xff00ff00, 0xff0000ff,
0xffffff00, 0xff00ffff, 0xffff00ff];
private var filter:DisplacementMapFilter;
// input
private var keyDown:Dictionary = new Dictionary(); // keeps track of keys pressed
private var paused:Boolean = true;
private var newGame:Boolean;
private var initTick:int; // init in startGame()
private var tick:int;
private var frame:int; // frame 0/1
private var lives:int;
private var score:int;
private var playerSpeed:int;
private var player:Entity;
private var playerBullet:Rectangle = new Rectangle(0, 0, 1, 4);
private var playerBulletSpeed:int;
private var playerBulletActive:Boolean = false;
private var aliensMoving:Boolean = true; // stops/starts all aliens
private var aliens:Array = [];
private var shields:Array = [];
private var numAliens:int;
private var menu:Boolean = true;
private var shieldGraphics:BitmapData;
private var numShields:int = 4;
private var shieldsY:int = 117;
private var alienBullet:Entity;
private var alienBulletFrame:int;
private var scanLines:BitmapData;
/* INIT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
public function Invaders() {
sw = stage.stageWidth, sh = stage.stageHeight;
cw = sw / SCALE, ch = sh / SCALE;
canvas = new BitmapData(cw, ch, false, 0x000000);
canvasBig = new BitmapData(sw, sh, false, 0x000000);
var bmp:Bitmap = new Bitmap(canvasBig);
addChild(bmp);
scaleMatrix.scale(SCALE, SCALE);
// displacement map filter
var mapBitmapData:BitmapData = new BitmapData(sw, sh, false, 0x888888);
var mapSprite:Sprite = new Sprite();
mapSprite.graphics.beginFill(0xff0000);
mapSprite.graphics.drawRect(0, 0, sw, sh);
mapSprite.graphics.endFill();
mapSprite.graphics.beginFill(0x800000);
mapSprite.graphics.drawRoundRect(2, 2, sw-4, sh-4, 100);
mapSprite.graphics.endFill();
mapBitmapData.draw(mapSprite);
var blur:BlurFilter = new BlurFilter(50, 50, 3);
mapBitmapData.applyFilter(mapBitmapData, mapBitmapData.rect, new Point(), blur);
var mapBitmap:Bitmap = new Bitmap(mapBitmapData);
//addChild(mapBitmap);
//DisplacementMapFilterMode.
//BitmapDataChannel.
filter = new DisplacementMapFilter(mapBitmapData, new Point(), 1, 1, 20, 20, "clamp", 0xff0000, 1);
scanLines = new BitmapData(sw, sh, true, 0x00000000);
for (var i:int = 0; i < sh; i += SCALE) {
scanLines.fillRect(new Rectangle(0, i, sw, 1), 0x22000000);
scanLines.fillRect(new Rectangle(0, i+1, sw, 1), 0x22ffffff);
}
// populate gfx object with generated graphics
gfx[PLAYER] = newPlayerGfx();
for (i = 1; i <= ALIEN_ROWS; i++)
gfx[ALIEN + i] = randomAlienGfx();
gfx[ALIEN_BULLET] = newAlienBulletGfx();
// create entities
var shieldsWidth:int = (gameBounds.width / numShields) * (numShields-1)+(SHIELD_W);
for (i = 0; i < numShields; i++) {
gfx[SHIELD + (i+1)] = newShieldGfx();
shields[i] = new Entity(SHIELD + (i+1));
shields[i].x = ((gameBounds.width / numShields) * i) + ((cw-shieldsWidth)*.5);
shields[i].y = shieldsY;
trace(shields[i].x);
}
alienBullet = new Entity(ALIEN_BULLET);
alienBullet.active = false;
player = new Entity(PLAYER);
player.y = floorRect.y - PLAYER_H - 1;
player.active = false;
numAliens = ALIEN_ROWS * ALIEN_COLS;
for (i = 0; i < numAliens; i++)
aliens[i] = new Entity(ALIEN + (i % ALIEN_ROWS + 1));
startGame();
tick = initTick;
addEventListener(Event.ENTER_FRAME, loop);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
/* START GAME
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function startGame():void {
menu = false;
paused = false;
newGame = true; // set state
player.active = false;
alienDrop = true;
alienBullet.active = false;
frame = 0;
lives = 2;
score = 0;
playerSpeed = 1;
playerBulletSpeed = 3;
initTick = numAliens;
with (alienCluster) { // TODO: set proper x/y
width = (ALIEN_COLS * (ALIEN_W + alienSpacingX)) - alienSpacingX;
height = (ALIEN_ROWS * (ALIEN_H + alienSpacingY)) - alienSpacingY;
x = alienSpacingX;
y = -alienCluster.height;
}
// position aliens
for (var i:int = 0; i < numAliens; i++) {
var col:int = i % ALIEN_ROWS;
var row:int = i % ALIEN_COLS;
aliens[i].active = true;
aliens[i].x = (ALIEN_W + alienSpacingX) * row + alienSpacingX;
aliens[i].y = (ALIEN_H + alienSpacingY) * col - alienCluster.height;
}
}
/* LOOP
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function loop(e:Event):void {
if (paused) return;
// draw background
canvas.fillRect(canvas.rect, 0x000000);
//canvas.fillRect(gameBounds, 0x000033);
//canvas.fillRect(alienCluster, 0x330033);
if (menu) return; // don't draw the rest if in menu
if (newGame) { // if new game state, play intro
alienDrop = false;
alienBullet.active = false;
player.x = (cw - PLAYER_W) * .5;
if (alienCluster.top >= gameBounds.top) {
newGame = false;
player.active = true;
alienDrop = true;
tick = initTick;
} else {
tick = 1; // tick repeatedly
}
}
if (tick == 0) {
tick = initTick;
if (frame == 0) frame = 1;
else frame = 0;
}
tick--;
// draw particles
for (i = 0; i < particles.length; i++) {
if (particles[i][6] == false) continue;
particles[i][2] *= .98;
particles[i][3] *= .96;
particles[i][3] += gravity;
particles[i][0] += particles[i][2];
particles[i][1] += particles[i][3];
if (particles[i][1] > floorRect.y - 1) {
particles[i][1] = floorRect.y-1;
if (particles[i][5] < 1) {
particles[i][3] = -particles[i][3];
particles[i][5]++;
} else {
particles[i][6] = false;
}
}
canvas.setPixel(particles[i][0], particles[i][1], particles[i][4]);
}
// move player
if (player.active) {
if (keyDown[37]) { // left
player.x -= playerSpeed;
}
if (keyDown[39]) { // right
player.x += playerSpeed;
}
if (player.x < gameBounds.left) {
player.x = gameBounds.left;
} else if (player.x > gameBounds.right - PLAYER_W) {
player.x = gameBounds.right - PLAYER_W;
}
if (playerBulletActive) {
playerBullet.y -= playerBulletSpeed;
if (playerBullet.y < gameBounds.top) {
playerBulletActive = false;
}
// draw bullet
canvas.fillRect(playerBullet, Math.random() * uint.MAX_VALUE);
} else {
if (keyDown[32]) { // space
playerBulletActive = true;
playerBullet.x = player.x + int(PLAYER_W * .5);
playerBullet.y = player.y;
}
}
}
// draw alien bullet
if (alienBullet.active && player.active) {
alienBullet.y++;
if (alienBulletFrame == 0) alienBulletFrame = 1;
else alienBulletFrame = 0;
if (alienBullet.y > gameBounds.bottom - 4) {
alienBullet.y > gameBounds.bottom - 4;
alienBullet.active = false;
}
canvas.copyPixels(gfx[ALIEN_BULLET][alienBulletFrame], gfx[ALIEN_BULLET][alienBulletFrame].rect, alienBullet.pos);
} else if (!alienBullet.active && player.active) {
var randAlien:int = Math.random() * aliens.length;
for (var k:int = randAlien; k < aliens.length; k++) {
if (!aliens[k].active) continue;
alienBullet.x = aliens[k].x + (ALIEN_W * .5) -1;
alienBullet.y = aliens[k].y + ALIEN_H;
alienBullet.active = true;
break;
}
}
// if bullet hit shield
for (i = 0; i < numShields; i++) {
var bulletHitShield:Boolean = gfx[SHIELD + (i+1)][0].hitTest(shields[i].pos,
255,
playerBullet);
if (bulletHitShield) {
damage(shields[i], new Point(playerBullet.x, playerBullet.top));
playerBulletActive = false;
trace("bullet hit shield");
}
var alienBulletHitShield:Boolean = gfx[SHIELD + (i + 1)][0].hitTest(shields[i].pos,
255,
gfx[ALIEN_BULLET][alienBulletFrame],
alienBullet.pos,
255)
if (alienBulletHitShield) {
damage(shields[i], new Point(alienBullet.x, alienBullet.y+2));
alienBullet.active = false;
}
}
if (tick == 0) {
if (alienCluster.right >= gameBounds.right) {
if (alienDrop == false) {
alienDrop = true;
alienVY = alienSpeedY;
} else {
alienDrop = false;
alienVY = 0;
}
alienVX = -alienSpeedX;
} else if (alienCluster.left <= gameBounds.left) {
if (alienDrop == false) {
alienDrop = true;
alienVY = alienSpeedY;
} else {
alienDrop = false;
alienVY = 0;
}
alienVX = alienSpeedX;
} else {
alienDrop = false;
alienVY = 0;
}
if (!alienDrop) alienCluster.x += alienVX;
else alienCluster.y += alienVY;
}
// draw floor
canvas.fillRect(floorRect, baseColor);
// draw shields
for (var i:int = 0; i < numShields; i++) {
canvas.copyPixels(gfx[SHIELD + (i+1)][0], gfx[SHIELD + (i+1)][0].rect, shields[i].pos);
}
// draw aliens
for (i = 0; i < numAliens; i++) {
if (!aliens[i].active) continue;
var bmpData:BitmapData = gfx[ ALIEN + (i % ALIEN_ROWS + 1) ][frame];
if (aliensMoving && tick == 0) {
if (!alienDrop) aliens[i].x += alienVX;
else aliens[i].y += alienVY;
}
// if alien hit player, game over
if (player.active) {
var alienHitPlayer:Boolean = gfx[aliens[i].gfxId][frame].hitTest(aliens[i].pos,
255,
gfx[PLAYER][0],
player.pos,
255);
if (alienHitPlayer) {
gameOver();
}
}
// if alien hit shield
for (var j:int = 0; j < numShields; j++) {
var alienHitShield:Boolean = gfx[aliens[i].gfxId][frame].hitTest(aliens[i].pos,
255,
gfx[SHIELD + (j+1)][0],
shields[j].pos,
255);
if (alienHitShield) {
subtractPixels(shields[j], aliens[i]);
}
}
// explode alien if hit by player bullet
var bulletHitAlien:Boolean = gfx[aliens[i].gfxId][frame].hitTest(aliens[i].pos,
255,
playerBullet);
if (playerBulletActive && bulletHitAlien) {
aliens[i].active = false;
playerBulletActive = false;
explode(aliens[i]);
recalculateAlienCluster();
if (initTick > 1)
initTick--;
else
trace("max speed reached");
}
canvas.copyPixels(bmpData,
bmpData.rect,
aliens[i].pos);
}
// draw player
if (player.active) {
canvas.copyPixels(gfx[PLAYER][0], gfx[PLAYER][0].rect, player.pos);
var alienBulletHitPlayer:Boolean = gfx[PLAYER][0].hitTest(player.pos,
255,
gfx[ALIEN_BULLET][alienBulletFrame],
alienBullet.pos,
255)
if (alienBulletHitPlayer) {
gameOver();
alienBullet.active = false;
}
}
// draw lives
for (i = 0; i < lives; i++)
canvas.copyPixels(gfx[PLAYER][0], gfx[PLAYER][0].rect, new Point((i * (PLAYER_W + 2) + gameBounds.x), floorRect.bottom + 1));
// scale and draw final image
canvasBig.draw(canvas, scaleMatrix);
canvasBig.draw(scanLines);
canvasBig.applyFilter(canvasBig, canvasBig.rect, new Point(), filter);
}
/* GAME OVER
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function gameOver():void {
lives--;
player.active = false;
explode(player, false);
aliensMoving = false;
if (lives < 0) {
lives = 0;
trace("GAME OVER");
}
trace("-1 LIFE");
}
/* RECALCULATE ALIEN CLUSTER BOUNDS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function recalculateAlienCluster():void {
var left:int = cw;
var right:int = 0;
var top:int = ch;
var bottom:int = 0;
for (var i:int = 0; i < numAliens; i++) {
if (aliens[i].active) {
if (aliens[i].x < left) left = aliens[i].x;
if (aliens[i].x > right) right = aliens[i].x + ALIEN_W;
if (aliens[i].y < top) top = aliens[i].y;
if (aliens[i].y > bottom) bottom = aliens[i].y + ALIEN_H;
}
}
alienCluster.x = left;
alienCluster.y = top;
alienCluster.width = right-left;
alienCluster.height = bottom-top;
}
/* SUBTRACT PIXELS AROUND DAMAGE POINT
* (ONLY FROM NON ANIMATED)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function damage(entity:Entity, point:Point):void {
var damageSquare:int = 4;
var relX:int = point.x - entity.x;
var relY:int = point.y - entity.y;
var rndX:int;
var rndY:int;
for (var x:int = 0; x < damageSquare; x++) {
for (var y:int = 0; y < damageSquare; y++) {
rndX = (Math.random() * damageSquare) - (damageSquare * .5);
rndY = (Math.random() * damageSquare) - (damageSquare * .5);
gfx[entity.gfxId][0].setPixel32(rndX+relX+x, rndY+relY+y, 0x00000000);
}
}
}
/* SUBTRACT PIXELS (ONLY FROM NON ANIMATED)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function subtractPixels(fromEntity:Entity, withEntity:Entity, withAnimated:Boolean = true):void {
var frame:int = 0;
if (withAnimated) frame = this.frame;
var relX:int = withEntity.x - fromEntity.x;
var relY:int = withEntity.y - fromEntity.y;
var width:int = gfx[withEntity.gfxId][frame].width;
var height:int = gfx[withEntity.gfxId][frame].height;
for (var x:int = 0; x < width; x++) {
for (var y:int = 0; y < height; y++) {
var color:uint = gfx[withEntity.gfxId][frame].getPixel(x, y);
if (color) {
gfx[fromEntity.gfxId][0].setPixel32(relX+x, relY+y, 0x00000000);
}
}
}
}
/* EXPLODE INTO PARTICLES
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function explode(obj:Object, animated:Boolean = true):void {
var frame:int = 0;
if (animated) frame = this.frame;
var width:int = gfx[obj.gfxId][frame].width;
var height:int = gfx[obj.gfxId][frame].height;
for (var i:int = 0; i < width; i++) {
for (var j:int = 0; j < height; j++) {
var color:uint = gfx[obj.gfxId][frame].getPixel(i, j);
if (color) {
if (Math.random() < .7)
particles.push([obj.pos.x+i, obj.pos.y+j, 0, 0, 0x333333, 0, true]);
else
particles.push([obj.pos.x+i, obj.pos.y+j, (Math.random() - .5) * 4, (Math.random() - .8) * 4, 0x333333, 0, true] );
}
}
}
}
/* GFX
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function newPlayerGfx():Array {
var bmpData:BitmapData = new BitmapData(15, 7, true, 0x00000000);
with (bmpData) {
setPixel32(5, 0, baseColor);
setPixel32(4, 1, baseColor);
setPixel32(5, 1, baseColor);
setPixel32(6, 1, baseColor);
fillRect(new Rectangle(1, 2, 9, 1), baseColor);
fillRect(new Rectangle(0, 3, 11, 4), baseColor);
}
return [bmpData];
}
private function newAlienBulletGfx():Array {
var color:uint = 0xffff0000;
var frame1:BitmapData = new BitmapData(2, 4, true, 0x00000000);
var frame2:BitmapData = new BitmapData(2, 4, true, 0x00000000);
with (frame1) {
setPixel32(0, 0, color);
setPixel32(1,1,color);
setPixel32(0,2,color);
setPixel32(1,3,color);
}
with (frame2) {
setPixel32(1,0,color);
setPixel32(0,1,color);
setPixel32(1,2,color);
setPixel32(0,3,color);
}
return [frame1, frame2];
}
private function newShieldGfx():Array {
var bmpData:BitmapData = new BitmapData(17, 12, true, 0x00000000);
with (bmpData) {
fillRect(new Rectangle(4, 0, 9, 1), baseColor);
fillRect(new Rectangle(3, 1, 4, 1), baseColor);
fillRect(new Rectangle(10, 1, 4, 1), baseColor);
fillRect(new Rectangle(2, 2, 4, 1), baseColor);
fillRect(new Rectangle(11, 2, 4, 1), baseColor);
fillRect(new Rectangle(1, 3, 5, 1), baseColor);
fillRect(new Rectangle(11, 3, 5, 1), baseColor);
fillRect(new Rectangle(0, 4, 7, 1), baseColor);
fillRect(new Rectangle(10, 4, 7, 1), baseColor);
fillRect(new Rectangle(0, 5, 8, 1), baseColor);
fillRect(new Rectangle(9, 5, 8, 1), baseColor);
fillRect(new Rectangle(0, 6, 17, 1), baseColor);
fillRect(new Rectangle(0, 7, 7, 1), baseColor);
fillRect(new Rectangle(10, 7, 7, 1), baseColor);
fillRect(new Rectangle(0, 8, 5, 1), baseColor);
fillRect(new Rectangle(12, 8, 5, 1), baseColor);
fillRect(new Rectangle(0, 9, 4, 3), baseColor);
fillRect(new Rectangle(13, 9, 4, 3), baseColor);
setPixel32(7, 2, baseColor);
setPixel32(9, 2, baseColor);
setPixel32(8, 4, baseColor);
}
return [bmpData];
}
// generate 2-frame alien graphic (picks random top/mid/bottom)
private function randomAlienGfx():Array {
var rndColor:int = Math.random() * alienColors.length;
var color:uint = alienColors[rndColor];
var tops:Array = [[2,0,6,0,3,1,5,1],
[2,0,6,0,3,1,5,1],
[4,0,3,1,4,1,5,1],
[4,0,3,1,4,1,5,1]];
var mids:Array = [[2,2,3,2,4,2,5,2,6,2,1,3,2,3,4,3,6,3,7,3,0,4,8,4,0,5,8,5],
[0,1,8,1,0,2,2,2,3,2,4,2,5,2,6,2,8,2,1,3,2,3,4,3,6,3,7,3],
[2,2,3,2,4,2,5,2,6,2,2,3,4,3,6,3],
[2,2,3,2,4,2,5,2,6,2,2,3,4,3,6,3]];
var btms:Array = [[2,4,3,4,4,4,5,4,6,4,2,5,6,5,3,6,5,6],
[2,4,3,4,4,4,5,4,6,4,2,5,6,5,1,6,7,6],
[3,4,5,4,2,5,4,5,6,5,2,6,6,6],
[3,4,4,4,5,4,2,5,6,5,3,6,5,6]];
var frames:Array = [];
frames[0] = new BitmapData(ALIEN_W, ALIEN_H, true, 0x00000000);
frames[1] = new BitmapData(ALIEN_W, ALIEN_H, true, 0x00000000);
function addPart(part:Array):void {
var rnd:int = int(Math.random() * (part.length / 2)) * 2;
for (var i:int = 0; i < part[rnd].length; i+=2) {
frames[0].setPixel32(part[rnd][i], part[rnd][i+1], color);
}
for (i = 0; i < part[rnd+1].length; i+=2) {
frames[1].setPixel32(part[rnd+1][i], part[rnd+1][i+1], color);
}
}
addPart(tops);
addPart(mids);
addPart(btms);
return frames;
}
/* KEYBOARD EVENTS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
private function onKeyDown(e:KeyboardEvent):void {
keyDown[e.keyCode] = true;
// start game
if (menu)
startGame();
}
private function onKeyUp(e:KeyboardEvent):void { delete keyDown[e.keyCode]; }
}
}
import flash.geom.Point;
class Entity extends Object {
public var pos:Point = new Point();
public var active:Boolean = true;
public var gfxId:String;
// pass the key for the graphics you want to use from the gfx dictionary
public function Entity(gfxId:String) {
this.gfxId = gfxId;
}
public function set x(value:Number):void { pos.x = value; }
public function get x():Number { return pos.x; }
public function set y(value:Number):void { pos.y = value; }
public function get y():Number { return pos.y; }
}