topical media & game development
mobile-game-ch27-appmobi-invasion-engine.js / js
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
var Game = new function() {
var boards = [];
// Game Initialization
this.initialize = function(canvasElementId,sprite_data,callback) {
var ctx = this.ctx = AppMobi.canvas.getContext("2d");
//wrap in try/catch because XDK does not currently support this property
try {
ctx.HTML5CompatibilityMode = true;
} catch(e){}
this.ctx.width = 320;
this.ctx.height = 480;
this.playerOffset = 10;
this.canvasMultiplier= 1;
this.mobile = true;
this.width = 320;
this.height= 480;
this.loop();
if(this.mobile) {
this.setBoard(4,new TouchControls());
}
SpriteSheet.load(sprite_data,callback);
};
this.setKeys = function(l,r,fire) {
Game.keys['left'] = l;
Game.keys['right'] = r;
Game.keys['fire'] = fire;
};
var lastTime = new Date().getTime();
var maxTime = 1/30;
// Game Loop
this.loop = function() {
var curTime = new Date().getTime();
requestAnimationFrame(Game.loop);
var dt = (curTime - lastTime)/1000;
if(dt > maxTime) { dt = maxTime; }
for(var i=0,len = boards.length;i<len;i++) {
if(boards[i]) {
boards[i].step(dt);
boards[i].draw(Game.ctx);
}
}
Game.ctx.present();
lastTime = curTime;
};
// Change an active game board
this.setBoard = function(num,board) { boards[num] = board; };
return this;
};
var SpriteSheet = new function() {
this.map = { };
this.load = function(spriteData,callback) {
this.map = spriteData;
this.image = new Image();
this.image.onload = callback;
this.image.src = 'mobile-game-ch27-appmobi-invasion-images-sprites.png';
};
this.draw = function(ctx,sprite,x,y,frame) {
var s = this.map[sprite];
if(!frame) frame = 0;
ctx.drawImage(this.image,
s.sx + frame * s.w,
s.sy,
s.w, s.h,
Math.floor(x), Math.floor(y),
s.w, s.h);
};
return this;
};
var TitleScreen = function TitleScreen(title,subtitle,callback) {
var up = false;
this.step = function(dt) {
if(!Game.keys['fire']) { up = true; }
if(up && Game.keys['fire'] && callback) {
Game.keys['fire'] = false;
callback();
}
};
this.draw = function(ctx) {
ctx.fillStyle = "#FFFFFF";
ctx.textAlign = "center";
ctx.font = "bold 40px bangers";
ctx.fillText(title,Game.width/2,Game.height/2);
ctx.font = "bold 20px bangers";
ctx.fillText(subtitle,Game.width/2,Game.height/2 + 40);
};
};
var GameBoard = function() {
var board = this;
// The current list of objects
this.objects = [];
this.cnt = {};
// Add a new object to the object list
this.add = function(obj) {
obj.board=this;
this.objects.push(obj);
this.cnt[obj.type] = (this.cnt[obj.type] || 0) + 1;
return obj;
};
// Mark an object for removal
this.remove = function(obj) {
var idx = this.removed.indexOf(obj);
if(idx == -1) {
this.removed.push(obj);
return true;
} else {
return false;
}
};
// Reset the list of removed objects
this.resetRemoved = function() { this.removed = []; };
// Removed an objects marked for removal from the list
this.finalizeRemoved = function() {
for(var i=0,len=this.removed.length;i<len;i++) {
var idx = this.objects.indexOf(this.removed[i]);
if(idx != -1) {
this.cnt[this.removed[i].type]--;
this.objects.splice(idx,1);
}
}
};
// Call the same method on all current objects
this.iterate = function(funcName) {
var args = Array.prototype.slice.call(arguments,1);
for(var i=0,len=this.objects.length;i<len;i++) {
var obj = this.objects[i];
obj[funcName].apply(obj,args);
}
};
// Find the first object for which func is true
this.detect = function(func) {
for(var i = 0,val=null, len=this.objects.length; i < len; i++) {
if(func.call(this.objects[i])) return this.objects[i];
}
return false;
};
// Call step on all objects and them delete
// any object that have been marked for removal
this.step = function(dt) {
this.resetRemoved();
this.iterate('step',dt);
this.finalizeRemoved();
};
// Draw all the objects
this.draw= function(ctx) {
this.iterate('draw',ctx);
};
// Check for a collision between the
// bounding rects of two objects
this.overlap = function(o1,o2) {
return !((o1.y+o1.h-1<o2.y) || (o1.y>o2.y+o2.h-1) ||
(o1.x+o1.w-1<o2.x) || (o1.x>o2.x+o2.w-1));
};
// Find the first object that collides with obj
// match against an optional type
this.collide = function(obj,type) {
return this.detect(function() {
if(obj != this) {
var col = (!type || this.type & type) && board.overlap(obj,this);
return col ? this : false;
}
});
};
};
var Sprite = function() { };
Sprite.prototype.setup = function(sprite,props) {
this.sprite = sprite;
this.merge(props);
this.frame = this.frame || 0;
this.w = SpriteSheet.map[sprite].w;
this.h = SpriteSheet.map[sprite].h;
};
Sprite.prototype.merge = function(props) {
if(props) {
for (var prop in props) {
this[prop] = props[prop];
}
}
};
Sprite.prototype.draw = function(ctx) {
SpriteSheet.draw(ctx,this.sprite,this.x,this.y,this.frame);
};
Sprite.prototype.hit = function(damage) {
this.board.remove(this);
};
var Level = function(levelData,callback) {
this.levelData = [];
for(var i =0; i<levelData.length; i++) {
this.levelData.push(Object.create(levelData[i]));
}
this.t = 0;
this.callback = callback;
};
Level.prototype.step = function(dt) {
var idx = 0, remove = [], curShip = null;
// Update the current time offset
this.t += dt * 1000;
// Start, End, Gap, Type, Override
// [ 0, 4000, 500, 'step', { x: 100 } ]
while((curShip = this.levelData[idx]) &&
(curShip[0] < this.t + 2000)) {
// Check if we've passed the end time
if(this.t > curShip[1]) {
remove.push(curShip);
} else if(curShip[0] < this.t) {
// Get the enemy definition blueprint
var enemy = enemies[curShip[3]],
override = curShip[4];
// Add a new enemy with the blueprint and override
this.board.add(new Enemy(enemy,override));
// Increment the start time by the gap
curShip[0] += curShip[2];
}
idx++;
}
// Remove any objects from the levelData that have passed
for(var i=0,len=remove.length;i<len;i++) {
var remIdx = this.levelData.indexOf(remove[i]);
if(remIdx != -1) this.levelData.splice(remIdx,1);
}
// If there are no more enemies on the board or in
// levelData, this level is done
if(this.levelData.length === 0 && this.board.cnt[OBJECT_ENEMY] === 0) {
if(this.callback) this.callback();
}
};
Level.prototype.draw = function(ctx) { };
var TouchControls = function() {
var gutterWidth = 10;
var unitWidth = Game.width/5;
var blockWidth = unitWidth-gutterWidth;
this.drawSquare = function(ctx,x,y,txt,on) {
ctx.globalAlpha = on ? 0.9 : 0.6;
ctx.fillStyle = "#CCC";
ctx.fillRect(x,y,blockWidth,blockWidth);
ctx.fillStyle = "#FFF";
ctx.textAlign = "center";
ctx.globalAlpha = 1.0;
ctx.font = "bold " + (3*unitWidth/4) + "px arial";
ctx.fillText(txt,
x+blockWidth/2,
y+3*blockWidth/4+5);
};
this.draw = function(ctx) {
ctx.save();
var yLoc = Game.height - unitWidth;
this.drawSquare(ctx,gutterWidth,yLoc,"◀");
this.drawSquare(ctx,unitWidth + gutterWidth,yLoc,"▶");
this.drawSquare(ctx,4*unitWidth,yLoc,"A");
ctx.restore();
};
this.step = function(dt) {
};
Game.playerOffset = unitWidth + 20;
};
var GamePoints = function() {
Game.points = 0;
var pointsLength = 8;
this.draw = function(ctx) {
ctx.save();
ctx.font = "bold 18px arial";
ctx.fillStyle= "#FFFFFF";
var txt = "" + Game.points;
var i = pointsLength - txt.length, zeros = "";
while(i-- > 0) { zeros += "0"; }
ctx.fillText(zeros + txt,10,20);
ctx.restore();
};
this.step = function(dt) { };
};
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.