mirror of
https://github.com/IllusionMan1212/adventures-of-kuroneko.git
synced 2026-10-24 02:12:36 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
ig.module(
|
||||
'impact.animation'
|
||||
)
|
||||
.requires(
|
||||
'impact.timer',
|
||||
'impact.image'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.AnimationSheet = ig.Class.extend({
|
||||
width: 8,
|
||||
height: 8,
|
||||
image: null,
|
||||
|
||||
init: function( path, width, height ) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
this.image = new ig.Image( path );
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.Animation = ig.Class.extend({
|
||||
sheet: null,
|
||||
timer: null,
|
||||
|
||||
sequence: [],
|
||||
flip: {x: false, y: false},
|
||||
pivot: {x: 0, y: 0},
|
||||
|
||||
frame: 0,
|
||||
tile: 0,
|
||||
loopCount: 0,
|
||||
alpha: 1,
|
||||
angle: 0,
|
||||
|
||||
|
||||
init: function( sheet, frameTime, sequence, stop ) {
|
||||
this.sheet = sheet;
|
||||
this.pivot = {x: sheet.width/2, y: sheet.height/2 };
|
||||
this.timer = new ig.Timer();
|
||||
|
||||
this.frameTime = frameTime;
|
||||
this.sequence = sequence;
|
||||
this.stop = !!stop;
|
||||
this.tile = this.sequence[0];
|
||||
},
|
||||
|
||||
|
||||
rewind: function() {
|
||||
this.timer.set();
|
||||
this.loopCount = 0;
|
||||
this.frame = 0;
|
||||
this.tile = this.sequence[0];
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
gotoFrame: function( f ) {
|
||||
// Offset the timer by one tenth of a millisecond to make sure we
|
||||
// jump to the correct frame and circumvent rounding errors
|
||||
this.timer.set( this.frameTime * -f - 0.0001 );
|
||||
this.update();
|
||||
},
|
||||
|
||||
|
||||
gotoRandomFrame: function() {
|
||||
this.gotoFrame( Math.floor(Math.random() * this.sequence.length) )
|
||||
},
|
||||
|
||||
|
||||
update: function() {
|
||||
var frameTotal = Math.floor(this.timer.delta() / this.frameTime);
|
||||
this.loopCount = Math.floor(frameTotal / this.sequence.length);
|
||||
if( this.stop && this.loopCount > 0 ) {
|
||||
this.frame = this.sequence.length - 1;
|
||||
}
|
||||
else {
|
||||
this.frame = frameTotal % this.sequence.length;
|
||||
}
|
||||
this.tile = this.sequence[ this.frame ];
|
||||
},
|
||||
|
||||
|
||||
draw: function( targetX, targetY ) {
|
||||
var bbsize = Math.max(this.sheet.width, this.sheet.height);
|
||||
|
||||
// On screen?
|
||||
if(
|
||||
targetX > ig.system.width || targetY > ig.system.height ||
|
||||
targetX + bbsize < 0 || targetY + bbsize < 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( this.alpha != 1) {
|
||||
ig.system.context.globalAlpha = this.alpha;
|
||||
}
|
||||
|
||||
if( this.angle == 0 ) {
|
||||
this.sheet.image.drawTile(
|
||||
targetX, targetY,
|
||||
this.tile, this.sheet.width, this.sheet.height,
|
||||
this.flip.x, this.flip.y
|
||||
);
|
||||
}
|
||||
else {
|
||||
ig.system.context.save();
|
||||
ig.system.context.translate(
|
||||
ig.system.getDrawPos(targetX + this.pivot.x),
|
||||
ig.system.getDrawPos(targetY + this.pivot.y)
|
||||
);
|
||||
ig.system.context.rotate( this.angle );
|
||||
this.sheet.image.drawTile(
|
||||
-this.pivot.x, -this.pivot.y,
|
||||
this.tile, this.sheet.width, this.sheet.height,
|
||||
this.flip.x, this.flip.y
|
||||
);
|
||||
ig.system.context.restore();
|
||||
}
|
||||
|
||||
if( this.alpha != 1) {
|
||||
ig.system.context.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,249 @@
|
||||
ig.module(
|
||||
'impact.background-map'
|
||||
)
|
||||
.requires(
|
||||
'impact.map',
|
||||
'impact.image'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.BackgroundMap = ig.Map.extend({
|
||||
tiles: null,
|
||||
scroll: {x: 0, y:0},
|
||||
distance: 1,
|
||||
repeat: false,
|
||||
tilesetName: '',
|
||||
foreground: false,
|
||||
enabled: true,
|
||||
|
||||
preRender: false,
|
||||
preRenderedChunks: null,
|
||||
chunkSize: 512,
|
||||
debugChunks: false,
|
||||
|
||||
|
||||
anims: {},
|
||||
|
||||
|
||||
init: function( tilesize, data, tileset ) {
|
||||
this.parent( tilesize, data );
|
||||
this.setTileset( tileset );
|
||||
},
|
||||
|
||||
|
||||
setTileset: function( tileset ) {
|
||||
this.tilesetName = tileset instanceof ig.Image ? tileset.path : tileset;
|
||||
this.tiles = new ig.Image( this.tilesetName );
|
||||
this.preRenderedChunks = null;
|
||||
},
|
||||
|
||||
|
||||
setScreenPos: function( x, y ) {
|
||||
this.scroll.x = x / this.distance;
|
||||
this.scroll.y = y / this.distance;
|
||||
},
|
||||
|
||||
|
||||
preRenderMapToChunks: function() {
|
||||
var totalWidth = this.width * this.tilesize * ig.system.scale,
|
||||
totalHeight = this.height * this.tilesize * ig.system.scale;
|
||||
|
||||
// If this layer is smaller than the chunkSize, adjust the chunkSize
|
||||
// accordingly, so we don't have as much overdraw
|
||||
this.chunkSize = Math.min( Math.max(totalWidth, totalHeight), this.chunkSize );
|
||||
|
||||
var chunkCols = Math.ceil(totalWidth / this.chunkSize),
|
||||
chunkRows = Math.ceil(totalHeight / this.chunkSize);
|
||||
|
||||
this.preRenderedChunks = [];
|
||||
for( var y = 0; y < chunkRows; y++ ) {
|
||||
this.preRenderedChunks[y] = [];
|
||||
|
||||
for( var x = 0; x < chunkCols; x++ ) {
|
||||
|
||||
|
||||
var chunkWidth = (x == chunkCols-1)
|
||||
? totalWidth - x * this.chunkSize
|
||||
: this.chunkSize;
|
||||
|
||||
var chunkHeight = (y == chunkRows-1)
|
||||
? totalHeight - y * this.chunkSize
|
||||
: this.chunkSize;
|
||||
|
||||
this.preRenderedChunks[y][x] = this.preRenderChunk( x, y, chunkWidth, chunkHeight );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
preRenderChunk: function( cx, cy, w, h ) {
|
||||
var tw = w / this.tilesize / ig.system.scale + 1,
|
||||
th = h / this.tilesize / ig.system.scale + 1;
|
||||
|
||||
var nx = (cx * this.chunkSize / ig.system.scale) % this.tilesize,
|
||||
ny = (cy * this.chunkSize / ig.system.scale) % this.tilesize;
|
||||
|
||||
var tx = Math.floor(cx * this.chunkSize / this.tilesize / ig.system.scale),
|
||||
ty = Math.floor(cy * this.chunkSize / this.tilesize / ig.system.scale);
|
||||
|
||||
|
||||
var chunk = ig.$new('canvas');
|
||||
chunk.width = w;
|
||||
chunk.height = h;
|
||||
chunk.retinaResolutionEnabled = false; // Opt out for Ejecta
|
||||
|
||||
var chunkContext = chunk.getContext('2d');
|
||||
ig.System.scaleMode(chunk, chunkContext);
|
||||
|
||||
var screenContext = ig.system.context;
|
||||
ig.system.context = chunkContext;
|
||||
|
||||
for( var x = 0; x < tw; x++ ) {
|
||||
for( var y = 0; y < th; y++ ) {
|
||||
if( x + tx < this.width && y + ty < this.height ) {
|
||||
var tile = this.data[y+ty][x+tx];
|
||||
if( tile ) {
|
||||
this.tiles.drawTile(
|
||||
x * this.tilesize - nx, y * this.tilesize - ny,
|
||||
tile - 1, this.tilesize
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ig.system.context = screenContext;
|
||||
|
||||
return chunk;
|
||||
},
|
||||
|
||||
|
||||
draw: function() {
|
||||
if( !this.tiles.loaded || !this.enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( this.preRender ) {
|
||||
this.drawPreRendered();
|
||||
}
|
||||
else {
|
||||
this.drawTiled();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
drawPreRendered: function() {
|
||||
if( !this.preRenderedChunks ) {
|
||||
this.preRenderMapToChunks();
|
||||
}
|
||||
|
||||
var dx = ig.system.getDrawPos(this.scroll.x),
|
||||
dy = ig.system.getDrawPos(this.scroll.y);
|
||||
|
||||
|
||||
if( this.repeat ) {
|
||||
var w = this.width * this.tilesize * ig.system.scale;
|
||||
dx = (dx%w + w) % w;
|
||||
|
||||
var h = this.height * this.tilesize * ig.system.scale;
|
||||
dy = (dy%h + h) % h;
|
||||
}
|
||||
|
||||
var minChunkX = Math.max( Math.floor(dx / this.chunkSize), 0 ),
|
||||
minChunkY = Math.max( Math.floor(dy / this.chunkSize), 0 ),
|
||||
maxChunkX = Math.ceil((dx+ig.system.realWidth) / this.chunkSize),
|
||||
maxChunkY = Math.ceil((dy+ig.system.realHeight) / this.chunkSize),
|
||||
maxRealChunkX = this.preRenderedChunks[0].length,
|
||||
maxRealChunkY = this.preRenderedChunks.length;
|
||||
|
||||
|
||||
if( !this.repeat ) {
|
||||
maxChunkX = Math.min( maxChunkX, maxRealChunkX );
|
||||
maxChunkY = Math.min( maxChunkY, maxRealChunkY );
|
||||
}
|
||||
|
||||
|
||||
var nudgeY = 0;
|
||||
for( var cy = minChunkY; cy < maxChunkY; cy++ ) {
|
||||
|
||||
var nudgeX = 0;
|
||||
for( var cx = minChunkX; cx < maxChunkX; cx++ ) {
|
||||
var chunk = this.preRenderedChunks[cy % maxRealChunkY][cx % maxRealChunkX];
|
||||
|
||||
var x = -dx + cx * this.chunkSize - nudgeX;
|
||||
var y = -dy + cy * this.chunkSize - nudgeY;
|
||||
ig.system.context.drawImage( chunk, x, y);
|
||||
ig.Image.drawCount++;
|
||||
|
||||
if( this.debugChunks ) {
|
||||
ig.system.context.strokeStyle = '#f0f';
|
||||
ig.system.context.strokeRect( x, y, this.chunkSize, this.chunkSize );
|
||||
}
|
||||
|
||||
// If we repeat in X and this chunk's width wasn't the full chunk size
|
||||
// and the screen is not already filled, we need to draw anohter chunk
|
||||
// AND nudge it to be flush with the last chunk
|
||||
if( this.repeat && chunk.width < this.chunkSize && x + chunk.width < ig.system.realWidth ) {
|
||||
nudgeX += this.chunkSize - chunk.width;
|
||||
maxChunkX++;
|
||||
}
|
||||
}
|
||||
|
||||
// Same as above, but for Y
|
||||
if( this.repeat && chunk.height < this.chunkSize && y + chunk.height < ig.system.realHeight ) {
|
||||
nudgeY += this.chunkSize - chunk.height;
|
||||
maxChunkY++;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
drawTiled: function() {
|
||||
var tile = 0,
|
||||
anim = null,
|
||||
tileOffsetX = (this.scroll.x / this.tilesize).toInt(),
|
||||
tileOffsetY = (this.scroll.y / this.tilesize).toInt(),
|
||||
pxOffsetX = this.scroll.x % this.tilesize,
|
||||
pxOffsetY = this.scroll.y % this.tilesize,
|
||||
pxMinX = -pxOffsetX - this.tilesize,
|
||||
pxMinY = -pxOffsetY - this.tilesize,
|
||||
pxMaxX = ig.system.width + this.tilesize - pxOffsetX,
|
||||
pxMaxY = ig.system.height + this.tilesize - pxOffsetY;
|
||||
|
||||
|
||||
// FIXME: could be sped up for non-repeated maps: restrict the for loops
|
||||
// to the map size instead of to the screen size and skip the 'repeat'
|
||||
// checks inside the loop.
|
||||
|
||||
for( var mapY = -1, pxY = pxMinY; pxY < pxMaxY; mapY++, pxY += this.tilesize) {
|
||||
var tileY = mapY + tileOffsetY;
|
||||
|
||||
// Repeat Y?
|
||||
if( tileY >= this.height || tileY < 0 ) {
|
||||
if( !this.repeat ) { continue; }
|
||||
tileY = (tileY%this.height + this.height) % this.height;
|
||||
}
|
||||
|
||||
for( var mapX = -1, pxX = pxMinX; pxX < pxMaxX; mapX++, pxX += this.tilesize ) {
|
||||
var tileX = mapX + tileOffsetX;
|
||||
|
||||
// Repeat X?
|
||||
if( tileX >= this.width || tileX < 0 ) {
|
||||
if( !this.repeat ) { continue; }
|
||||
tileX = (tileX%this.width + this.width) % this.width;
|
||||
}
|
||||
|
||||
// Draw!
|
||||
if( (tile = this.data[tileY][tileX]) ) {
|
||||
if( (anim = this.anims[tile-1]) ) {
|
||||
anim.draw( pxX, pxY );
|
||||
}
|
||||
else {
|
||||
this.tiles.drawTile( pxX, pxY, tile-1, this.tilesize );
|
||||
}
|
||||
}
|
||||
} // end for x
|
||||
} // end for y
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,274 @@
|
||||
ig.module(
|
||||
'impact.collision-map'
|
||||
)
|
||||
.requires(
|
||||
'impact.map'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.CollisionMap = ig.Map.extend({
|
||||
|
||||
lastSlope: 1,
|
||||
tiledef: null,
|
||||
|
||||
init: function( tilesize, data, tiledef ) {
|
||||
this.parent( tilesize, data );
|
||||
this.tiledef = tiledef || ig.CollisionMap.defaultTileDef;
|
||||
|
||||
for( var t in this.tiledef ) {
|
||||
if( t|0 > this.lastSlope ) {
|
||||
this.lastSlope = t|0;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
trace: function( x, y, vx, vy, objectWidth, objectHeight ) {
|
||||
// Set up the trace-result
|
||||
var res = {
|
||||
collision: {x: false, y: false, slope: false},
|
||||
pos: {x: x, y: y},
|
||||
tile: {x: 0, y: 0}
|
||||
};
|
||||
|
||||
// Break the trace down into smaller steps if necessary
|
||||
var steps = Math.ceil(Math.max(Math.abs(vx), Math.abs(vy)) / this.tilesize);
|
||||
if( steps > 1 ) {
|
||||
var sx = vx / steps;
|
||||
var sy = vy / steps;
|
||||
|
||||
for( var i = 0; i < steps && (sx || sy); i++ ) {
|
||||
this._traceStep( res, x, y, sx, sy, objectWidth, objectHeight, vx, vy, i );
|
||||
|
||||
x = res.pos.x;
|
||||
y = res.pos.y;
|
||||
if( res.collision.x ) { sx = 0; vx = 0; }
|
||||
if( res.collision.y ) { sy = 0; vy = 0; }
|
||||
if( res.collision.slope ) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
// Just one step
|
||||
else {
|
||||
this._traceStep( res, x, y, vx, vy, objectWidth, objectHeight, vx, vy, 0 );
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
|
||||
_traceStep: function( res, x, y, vx, vy, width, height, rvx, rvy, step ) {
|
||||
|
||||
res.pos.x += vx;
|
||||
res.pos.y += vy;
|
||||
|
||||
var t = 0;
|
||||
|
||||
// Horizontal collision (walls)
|
||||
if( vx ) {
|
||||
var pxOffsetX = (vx > 0 ? width : 0);
|
||||
var tileOffsetX = (vx < 0 ? this.tilesize : 0);
|
||||
|
||||
var firstTileY = Math.max( Math.floor(y / this.tilesize), 0 );
|
||||
var lastTileY = Math.min( Math.ceil((y + height) / this.tilesize), this.height );
|
||||
var tileX = Math.floor( (res.pos.x + pxOffsetX) / this.tilesize );
|
||||
|
||||
// We need to test the new tile position as well as the current one, as we
|
||||
// could still collide with the current tile if it's a line def.
|
||||
// We can skip this test if this is not the first step or the new tile position
|
||||
// is the same as the current one.
|
||||
var prevTileX = Math.floor( (x + pxOffsetX) / this.tilesize );
|
||||
if( step > 0 || tileX == prevTileX || prevTileX < 0 || prevTileX >= this.width ) {
|
||||
prevTileX = -1;
|
||||
}
|
||||
|
||||
// Still inside this collision map?
|
||||
if( tileX >= 0 && tileX < this.width ) {
|
||||
for( var tileY = firstTileY; tileY < lastTileY; tileY++ ) {
|
||||
if( prevTileX != -1 ) {
|
||||
t = this.data[tileY][prevTileX];
|
||||
if(
|
||||
t > 1 && t <= this.lastSlope &&
|
||||
this._checkTileDef(res, t, x, y, rvx, rvy, width, height, prevTileX, tileY)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t = this.data[tileY][tileX];
|
||||
if(
|
||||
t == 1 || t > this.lastSlope || // fully solid tile?
|
||||
(t > 1 && this._checkTileDef(res, t, x, y, rvx, rvy, width, height, tileX, tileY)) // slope?
|
||||
) {
|
||||
if( t > 1 && t <= this.lastSlope && res.collision.slope ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// full tile collision!
|
||||
res.collision.x = true;
|
||||
res.tile.x = t;
|
||||
x = res.pos.x = tileX * this.tilesize - pxOffsetX + tileOffsetX;
|
||||
rvx = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical collision (floor, ceiling)
|
||||
if( vy ) {
|
||||
var pxOffsetY = (vy > 0 ? height : 0);
|
||||
var tileOffsetY = (vy < 0 ? this.tilesize : 0);
|
||||
|
||||
var firstTileX = Math.max( Math.floor(res.pos.x / this.tilesize), 0 );
|
||||
var lastTileX = Math.min( Math.ceil((res.pos.x + width) / this.tilesize), this.width );
|
||||
var tileY = Math.floor( (res.pos.y + pxOffsetY) / this.tilesize );
|
||||
|
||||
var prevTileY = Math.floor( (y + pxOffsetY) / this.tilesize );
|
||||
if( step > 0 || tileY == prevTileY || prevTileY < 0 || prevTileY >= this.height ) {
|
||||
prevTileY = -1;
|
||||
}
|
||||
|
||||
// Still inside this collision map?
|
||||
if( tileY >= 0 && tileY < this.height ) {
|
||||
for( var tileX = firstTileX; tileX < lastTileX; tileX++ ) {
|
||||
if( prevTileY != -1 ) {
|
||||
t = this.data[prevTileY][tileX];
|
||||
if(
|
||||
t > 1 && t <= this.lastSlope &&
|
||||
this._checkTileDef(res, t, x, y, rvx, rvy, width, height, tileX, prevTileY) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t = this.data[tileY][tileX];
|
||||
if(
|
||||
t == 1 || t > this.lastSlope || // fully solid tile?
|
||||
(t > 1 && this._checkTileDef(res, t, x, y, rvx, rvy, width, height, tileX, tileY)) // slope?
|
||||
) {
|
||||
if( t > 1 && t <= this.lastSlope && res.collision.slope ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// full tile collision!
|
||||
res.collision.y = true;
|
||||
res.tile.y = t;
|
||||
res.pos.y = tileY * this.tilesize - pxOffsetY + tileOffsetY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// res is changed in place, nothing to return
|
||||
},
|
||||
|
||||
|
||||
_checkTileDef: function( res, t, x, y, vx, vy, width, height, tileX, tileY ) {
|
||||
var def = this.tiledef[t];
|
||||
if( !def ) { return false; }
|
||||
|
||||
var lx = (tileX + def[0]) * this.tilesize,
|
||||
ly = (tileY + def[1]) * this.tilesize,
|
||||
lvx = (def[2] - def[0]) * this.tilesize,
|
||||
lvy = (def[3] - def[1]) * this.tilesize,
|
||||
solid = def[4];
|
||||
|
||||
// Find the box corner to test, relative to the line
|
||||
var tx = x + vx + (lvy < 0 ? width : 0) - lx,
|
||||
ty = y + vy + (lvx > 0 ? height : 0) - ly;
|
||||
|
||||
// Is the box corner behind the line?
|
||||
if( lvx * ty - lvy * tx > 0 ) {
|
||||
|
||||
// Lines are only solid from one side - find the dot product of
|
||||
// line normal and movement vector and dismiss if wrong side
|
||||
if( vx * -lvy + vy * lvx < 0 ) {
|
||||
return solid;
|
||||
}
|
||||
|
||||
// Find the line normal
|
||||
var length = Math.sqrt(lvx * lvx + lvy * lvy);
|
||||
var nx = lvy/length,
|
||||
ny = -lvx/length;
|
||||
|
||||
// Project out of the line
|
||||
var proj = tx * nx + ty * ny;
|
||||
var px = nx * proj,
|
||||
py = ny * proj;
|
||||
|
||||
// If we project further out than we moved in, then this is a full
|
||||
// tile collision for solid tiles.
|
||||
// For non-solid tiles, make sure we were in front of the line.
|
||||
if( px*px+py*py >= vx*vx+vy*vy ) {
|
||||
return solid || (lvx * (ty-vy) - lvy * (tx-vx) < 0.5);
|
||||
}
|
||||
|
||||
res.pos.x = x + vx - px;
|
||||
res.pos.y = y + vy - py;
|
||||
res.collision.slope = {x: lvx, y: lvy, nx: nx, ny: ny};
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Default Slope Tile definition. Each tile is defined by an array of 5 vars:
|
||||
// - 4 for the line in tile coordinates (0 -- 1)
|
||||
// - 1 specifing whether the tile is 'filled' behind the line or not
|
||||
// [ x1, y1, x2, y2, solid ]
|
||||
|
||||
// Defining 'half', 'one third' and 'two thirds' as vars makes it a bit
|
||||
// easier to read... I hope.
|
||||
var H = 1/2,
|
||||
N = 1/3,
|
||||
M = 2/3,
|
||||
SOLID = true,
|
||||
NON_SOLID = false;
|
||||
|
||||
ig.CollisionMap.defaultTileDef = {
|
||||
/* 15 NE */ 5: [0,1, 1,M, SOLID], 6: [0,M, 1,N, SOLID], 7: [0,N, 1,0, SOLID],
|
||||
/* 22 NE */ 3: [0,1, 1,H, SOLID], 4: [0,H, 1,0, SOLID],
|
||||
/* 45 NE */ 2: [0,1, 1,0, SOLID],
|
||||
/* 67 NE */ 10: [H,1, 1,0, SOLID], 21: [0,1, H,0, SOLID],
|
||||
/* 75 NE */ 32: [M,1, 1,0, SOLID], 43: [N,1, M,0, SOLID], 54: [0,1, N,0, SOLID],
|
||||
|
||||
/* 15 SE */ 27: [0,0, 1,N, SOLID], 28: [0,N, 1,M, SOLID], 29: [0,M, 1,1, SOLID],
|
||||
/* 22 SE */ 25: [0,0, 1,H, SOLID], 26: [0,H, 1,1, SOLID],
|
||||
/* 45 SE */ 24: [0,0, 1,1, SOLID],
|
||||
/* 67 SE */ 11: [0,0, H,1, SOLID], 22: [H,0, 1,1, SOLID],
|
||||
/* 75 SE */ 33: [0,0, N,1, SOLID], 44: [N,0, M,1, SOLID], 55: [M,0, 1,1, SOLID],
|
||||
|
||||
/* 15 NW */ 16: [1,N, 0,0, SOLID], 17: [1,M, 0,N, SOLID], 18: [1,1, 0,M, SOLID],
|
||||
/* 22 NW */ 14: [1,H, 0,0, SOLID], 15: [1,1, 0,H, SOLID],
|
||||
/* 45 NW */ 13: [1,1, 0,0, SOLID],
|
||||
/* 67 NW */ 8: [H,1, 0,0, SOLID], 19: [1,1, H,0, SOLID],
|
||||
/* 75 NW */ 30: [N,1, 0,0, SOLID], 41: [M,1, N,0, SOLID], 52: [1,1, M,0, SOLID],
|
||||
|
||||
/* 15 SW */ 38: [1,M, 0,1, SOLID], 39: [1,N, 0,M, SOLID], 40: [1,0, 0,N, SOLID],
|
||||
/* 22 SW */ 36: [1,H, 0,1, SOLID], 37: [1,0, 0,H, SOLID],
|
||||
/* 45 SW */ 35: [1,0, 0,1, SOLID],
|
||||
/* 67 SW */ 9: [1,0, H,1, SOLID], 20: [H,0, 0,1, SOLID],
|
||||
/* 75 SW */ 31: [1,0, M,1, SOLID], 42: [M,0, N,1, SOLID], 53: [N,0, 0,1, SOLID],
|
||||
|
||||
/* Go N */ 12: [0,0, 1,0, NON_SOLID],
|
||||
/* Go S */ 23: [1,1, 0,1, NON_SOLID],
|
||||
/* Go E */ 34: [1,0, 1,1, NON_SOLID],
|
||||
/* Go W */ 45: [0,1, 0,0, NON_SOLID]
|
||||
|
||||
// Now that was fun!
|
||||
};
|
||||
|
||||
|
||||
// Static Dummy CollisionMap; never collides
|
||||
ig.CollisionMap.staticNoCollision = { trace: function( x, y, vx, vy ) {
|
||||
return {
|
||||
collision: {x: false, y: false, slope: false},
|
||||
pos: {x: x+vx, y: y+vy},
|
||||
tile: {x: 0, y: 0}
|
||||
};
|
||||
}};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,148 @@
|
||||
.ig_debug {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: #000;
|
||||
border-top: 2px solid #f57401;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
z-index: 1000;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.ig_debug_panel_menu {
|
||||
height: 28px;
|
||||
background: #222;
|
||||
background: -webkit-gradient(linear, left bottom, left top, color-stop(0,#000000), color-stop(1,#333));
|
||||
background: -moz-linear-gradient(center bottom, #000000 0%, #333 100%);
|
||||
background: -o-linear-gradient(#333, #000000);
|
||||
}
|
||||
|
||||
.ig_debug_panel_menu div {
|
||||
float: left;
|
||||
height: 22px;
|
||||
padding: 6px 8px 0 8px;
|
||||
border-right: 1px solid #333;
|
||||
}
|
||||
|
||||
.ig_debug_panel_menu .ig_debug_head {
|
||||
font-weight: bold;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.ig_debug_menu_item:hover {
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.ig_debug_menu_item.active, .ig_debug_menu_item.active:hover {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ig_debug_stats {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
float: right;
|
||||
color: #888;
|
||||
border-left: 1px solid #333;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ig_debug_stats span {
|
||||
width: 3em;
|
||||
display:inline-block;
|
||||
color: #fff !important;
|
||||
margin-right: 0.2em;
|
||||
margin-left: 0.3em;
|
||||
font-family: bitstream vera sans mono, courier new;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ig_debug_panel {
|
||||
height: 152px;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ig_debug_panel canvas {
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.ig_debug_panel .ig_debug_panel {
|
||||
padding: 8px;
|
||||
height: auto;
|
||||
float: left;
|
||||
background-color: #000;
|
||||
border-right: 2px solid #222;
|
||||
}
|
||||
|
||||
.ig_debug_option {
|
||||
padding: 2px 0 2px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ig_debug_option:first-child {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.ig_debug_option:hover {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
.ig_debug_graph_mark {
|
||||
position: absolute;
|
||||
color: #888;
|
||||
left: 4px;
|
||||
font-size: 10px;
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
.ig_debug_legend {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.ig_debug_label_mark {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 4px;
|
||||
-webkit-transition: 0.1s linear;
|
||||
-moz-transition: 0.1s linear;
|
||||
}
|
||||
|
||||
.ig_debug_legend_color {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 10px;
|
||||
margin-right: 4px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.ig_debug_legend_number {
|
||||
width: 3em;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
font-family: bitstream vera sans mono, courier new;
|
||||
color: #fff;
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
|
||||
.ig_debug_map_container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #888;
|
||||
}
|
||||
|
||||
.ig_debug_map_container canvas {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ig_debug_map_screen {
|
||||
position: absolute;
|
||||
border: 1px solid #f0f;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
ig.module(
|
||||
'impact.debug.debug'
|
||||
)
|
||||
.requires(
|
||||
'impact.debug.entities-panel',
|
||||
'impact.debug.maps-panel',
|
||||
'impact.debug.graph-panel'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
/* Empty module to require all debug panels */
|
||||
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
ig.module(
|
||||
'impact.debug.entities-panel'
|
||||
)
|
||||
.requires(
|
||||
'impact.debug.menu',
|
||||
'impact.entity'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
|
||||
ig.Entity.inject({
|
||||
colors: {
|
||||
names: '#fff',
|
||||
velocities: '#0f0',
|
||||
boxes: '#f00'
|
||||
},
|
||||
|
||||
draw: function() {
|
||||
this.parent();
|
||||
|
||||
// Collision Boxes
|
||||
if( ig.Entity._debugShowBoxes ) {
|
||||
ig.system.context.strokeStyle = this.colors.boxes;
|
||||
ig.system.context.lineWidth = 1.0;
|
||||
ig.system.context.strokeRect(
|
||||
ig.system.getDrawPos(this.pos.x.round() - ig.game.screen.x) - 0.5,
|
||||
ig.system.getDrawPos(this.pos.y.round() - ig.game.screen.y) - 0.5,
|
||||
this.size.x * ig.system.scale,
|
||||
this.size.y * ig.system.scale
|
||||
);
|
||||
}
|
||||
|
||||
// Velocities
|
||||
if( ig.Entity._debugShowVelocities ) {
|
||||
var x = this.pos.x + this.size.x/2;
|
||||
var y = this.pos.y + this.size.y/2;
|
||||
|
||||
this._debugDrawLine( this.colors.velocities, x, y, x + this.vel.x, y + this.vel.y );
|
||||
}
|
||||
|
||||
// Names & Targets
|
||||
if( ig.Entity._debugShowNames ) {
|
||||
if( this.name ) {
|
||||
ig.system.context.fillStyle = this.colors.names;
|
||||
ig.system.context.fillText(
|
||||
this.name,
|
||||
ig.system.getDrawPos(this.pos.x - ig.game.screen.x),
|
||||
ig.system.getDrawPos(this.pos.y - ig.game.screen.y)
|
||||
);
|
||||
}
|
||||
|
||||
if( typeof(this.target) == 'object' ) {
|
||||
for( var t in this.target ) {
|
||||
var ent = ig.game.getEntityByName( this.target[t] );
|
||||
if( ent ) {
|
||||
this._debugDrawLine( this.colors.names,
|
||||
this.pos.x + this.size.x/2, this.pos.y + this.size.y/2,
|
||||
ent.pos.x + ent.size.x/2, ent.pos.y + ent.size.y/2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_debugDrawLine: function( color, sx, sy, dx, dy ) {
|
||||
ig.system.context.strokeStyle = color;
|
||||
ig.system.context.lineWidth = 1.0;
|
||||
|
||||
ig.system.context.beginPath();
|
||||
ig.system.context.moveTo(
|
||||
ig.system.getDrawPos(sx - ig.game.screen.x),
|
||||
ig.system.getDrawPos(sy - ig.game.screen.y)
|
||||
);
|
||||
ig.system.context.lineTo(
|
||||
ig.system.getDrawPos(dx - ig.game.screen.x),
|
||||
ig.system.getDrawPos(dy - ig.game.screen.y)
|
||||
);
|
||||
ig.system.context.stroke();
|
||||
ig.system.context.closePath();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.Entity._debugEnableChecks = true;
|
||||
ig.Entity._debugShowBoxes = false;
|
||||
ig.Entity._debugShowVelocities = false;
|
||||
ig.Entity._debugShowNames = false;
|
||||
|
||||
ig.Entity.oldCheckPair = ig.Entity.checkPair;
|
||||
ig.Entity.checkPair = function( a, b ) {
|
||||
if( !ig.Entity._debugEnableChecks ) {
|
||||
return;
|
||||
}
|
||||
ig.Entity.oldCheckPair( a, b );
|
||||
};
|
||||
|
||||
|
||||
ig.debug.addPanel({
|
||||
type: ig.DebugPanel,
|
||||
name: 'entities',
|
||||
label: 'Entities',
|
||||
options: [
|
||||
{
|
||||
name: 'Checks & Collisions',
|
||||
object: ig.Entity,
|
||||
property: '_debugEnableChecks'
|
||||
},
|
||||
{
|
||||
name: 'Show Collision Boxes',
|
||||
object: ig.Entity,
|
||||
property: '_debugShowBoxes'
|
||||
},
|
||||
{
|
||||
name: 'Show Velocities',
|
||||
object: ig.Entity,
|
||||
property: '_debugShowVelocities'
|
||||
},
|
||||
{
|
||||
name: 'Show Names & Targets',
|
||||
object: ig.Entity,
|
||||
property: '_debugShowNames'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
ig.module(
|
||||
'impact.debug.graph-panel'
|
||||
)
|
||||
.requires(
|
||||
'impact.debug.menu',
|
||||
'impact.system',
|
||||
'impact.game',
|
||||
'impact.image'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
|
||||
ig.Game.inject({
|
||||
draw: function() {
|
||||
ig.graph.beginClock('draw');
|
||||
this.parent();
|
||||
ig.graph.endClock('draw');
|
||||
},
|
||||
|
||||
|
||||
update: function() {
|
||||
ig.graph.beginClock('update');
|
||||
this.parent();
|
||||
ig.graph.endClock('update');
|
||||
},
|
||||
|
||||
|
||||
checkEntities: function() {
|
||||
ig.graph.beginClock('checks');
|
||||
this.parent();
|
||||
ig.graph.endClock('checks');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.DebugGraphPanel = ig.DebugPanel.extend({
|
||||
clocks: {},
|
||||
marks: [],
|
||||
textY: 0,
|
||||
height: 128,
|
||||
ms: 64,
|
||||
timeBeforeRun: 0,
|
||||
|
||||
|
||||
init: function( name, label ) {
|
||||
this.parent( name, label );
|
||||
|
||||
this.mark16ms = (this.height - (this.height/this.ms) * 16).round();
|
||||
this.mark33ms = (this.height - (this.height/this.ms) * 33).round();
|
||||
this.msHeight = this.height/this.ms;
|
||||
|
||||
this.graph = ig.$new('canvas');
|
||||
this.graph.width = window.innerWidth;
|
||||
this.graph.height = this.height;
|
||||
this.container.appendChild( this.graph );
|
||||
this.ctx = this.graph.getContext('2d');
|
||||
|
||||
this.ctx.fillStyle = '#444';
|
||||
this.ctx.fillRect( 0, this.mark16ms, this.graph.width, 1 );
|
||||
this.ctx.fillRect( 0, this.mark33ms, this.graph.width, 1 );
|
||||
|
||||
this.addGraphMark( '16ms', this.mark16ms );
|
||||
this.addGraphMark( '33ms', this.mark33ms );
|
||||
|
||||
this.addClock( 'draw', 'Draw', '#13baff' );
|
||||
this.addClock( 'update', 'Entity Update', '#bb0fff' );
|
||||
this.addClock( 'checks', 'Entity Checks & Collisions', '#a2e908' );
|
||||
this.addClock( 'lag', 'System Lag', '#f26900' );
|
||||
|
||||
ig.mark = this.mark.bind(this);
|
||||
ig.graph = this;
|
||||
},
|
||||
|
||||
|
||||
addGraphMark: function( name, height ) {
|
||||
var span = ig.$new('span');
|
||||
span.className = 'ig_debug_graph_mark';
|
||||
span.textContent = name;
|
||||
span.style.top = height.round() + 'px';
|
||||
this.container.appendChild( span );
|
||||
},
|
||||
|
||||
|
||||
addClock: function( name, description, color ) {
|
||||
var mark = ig.$new('span');
|
||||
mark.className = 'ig_debug_legend_color';
|
||||
mark.style.backgroundColor = color;
|
||||
|
||||
var number = ig.$new('span');
|
||||
number.className = 'ig_debug_legend_number';
|
||||
number.appendChild( document.createTextNode('0') );
|
||||
|
||||
var legend = ig.$new('span');
|
||||
legend.className = 'ig_debug_legend';
|
||||
legend.appendChild( mark );
|
||||
legend.appendChild( document.createTextNode(description +' (') );
|
||||
legend.appendChild( number );
|
||||
legend.appendChild( document.createTextNode('ms)') );
|
||||
|
||||
this.container.appendChild( legend );
|
||||
|
||||
this.clocks[name] = {
|
||||
description: description,
|
||||
color: color,
|
||||
current: 0,
|
||||
start: Date.now(),
|
||||
avg: 0,
|
||||
html: number
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
beginClock: function( name, offset ) {
|
||||
this.clocks[name].start = Date.now() + (offset || 0);
|
||||
},
|
||||
|
||||
|
||||
endClock: function( name ) {
|
||||
var c = this.clocks[name];
|
||||
c.current = Math.round(Date.now() - c.start);
|
||||
c.avg = c.avg * 0.8 + c.current * 0.2;
|
||||
},
|
||||
|
||||
|
||||
mark: function( msg, color ) {
|
||||
if( this.active ) {
|
||||
this.marks.push( {msg:msg, color:(color||'#fff')} );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
beforeRun: function() {
|
||||
this.endClock('lag');
|
||||
this.timeBeforeRun = Date.now();
|
||||
},
|
||||
|
||||
|
||||
afterRun: function() {
|
||||
var frameTime = Date.now() - this.timeBeforeRun;
|
||||
var nextFrameDue = (1000/ig.system.fps) - frameTime;
|
||||
this.beginClock('lag', Math.max(nextFrameDue, 0));
|
||||
|
||||
|
||||
var x = this.graph.width-1;
|
||||
var y = this.height;
|
||||
|
||||
this.ctx.drawImage( this.graph, -1, 0 );
|
||||
|
||||
this.ctx.fillStyle = '#000';
|
||||
this.ctx.fillRect( x, 0, 1, this.height );
|
||||
|
||||
this.ctx.fillStyle = '#444';
|
||||
this.ctx.fillRect( x, this.mark16ms, 1, 1 );
|
||||
|
||||
this.ctx.fillStyle = '#444';
|
||||
this.ctx.fillRect( x, this.mark33ms, 1, 1 );
|
||||
|
||||
for( var ci in this.clocks ) {
|
||||
var c = this.clocks[ci];
|
||||
c.html.textContent = c.avg.toFixed(2);
|
||||
|
||||
if( c.color && c.current > 0 ) {
|
||||
this.ctx.fillStyle = c.color;
|
||||
var h = c.current * this.msHeight;
|
||||
y -= h;
|
||||
this.ctx.fillRect( x, y, 1, h );
|
||||
c.current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.ctx.textAlign = 'right';
|
||||
this.ctx.textBaseline = 'top';
|
||||
this.ctx.globalAlpha = 0.5;
|
||||
|
||||
for( var i = 0; i < this.marks.length; i++ ) {
|
||||
var m = this.marks[i];
|
||||
this.ctx.fillStyle = m.color;
|
||||
this.ctx.fillRect( x, 0, 1, this.height );
|
||||
if( m.msg ) {
|
||||
this.ctx.fillText( m.msg, x-1, this.textY );
|
||||
this.textY = (this.textY+8)%32;
|
||||
}
|
||||
}
|
||||
this.ctx.globalAlpha = 1;
|
||||
this.marks = [];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.debug.addPanel({
|
||||
type: ig.DebugGraphPanel,
|
||||
name: 'graph',
|
||||
label: 'Performance'
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
ig.module(
|
||||
'impact.debug.maps-panel'
|
||||
)
|
||||
.requires(
|
||||
'impact.debug.menu',
|
||||
'impact.game',
|
||||
'impact.background-map'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
|
||||
ig.Game.inject({
|
||||
loadLevel: function( data ) {
|
||||
this.parent(data);
|
||||
ig.debug.panels.maps.load(this);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.DebugMapsPanel = ig.DebugPanel.extend({
|
||||
maps: [],
|
||||
mapScreens: [],
|
||||
|
||||
|
||||
init: function( name, label ) {
|
||||
this.parent( name, label );
|
||||
this.load();
|
||||
},
|
||||
|
||||
|
||||
load: function( game ) {
|
||||
this.options = [];
|
||||
this.panels = [];
|
||||
|
||||
if( !game || !game.backgroundMaps.length ) {
|
||||
this.container.innerHTML = '<em>No Maps Loaded</em>';
|
||||
return;
|
||||
}
|
||||
|
||||
this.maps = game.backgroundMaps;
|
||||
this.mapScreens = [];
|
||||
this.container.innerHTML = '';
|
||||
|
||||
for( var m = 0; m < this.maps.length; m++ ) {
|
||||
var map = this.maps[m];
|
||||
|
||||
var subPanel = new ig.DebugPanel( m, 'Layer '+m );
|
||||
|
||||
var head = new ig.$new('strong');
|
||||
head.textContent = m +': ' + map.tiles.path;
|
||||
subPanel.container.appendChild( head );
|
||||
|
||||
subPanel.addOption( new ig.DebugOption('Enabled', map, 'enabled') );
|
||||
subPanel.addOption( new ig.DebugOption('Pre Rendered', map, 'preRender') );
|
||||
subPanel.addOption( new ig.DebugOption('Show Chunks', map, 'debugChunks') );
|
||||
|
||||
this.generateMiniMap( subPanel, map, m );
|
||||
this.addPanel( subPanel );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
generateMiniMap: function( panel, map, id ) {
|
||||
var s = ig.system.scale; // we'll need this a lot
|
||||
|
||||
// resize the tileset, so that one tile is 's' pixels wide and high
|
||||
var ts = ig.$new('canvas');
|
||||
var tsctx = ts.getContext('2d');
|
||||
|
||||
var w = map.tiles.width * s;
|
||||
var h = map.tiles.height * s;
|
||||
var ws = w / map.tilesize;
|
||||
var hs = h / map.tilesize;
|
||||
ts.width = ws;
|
||||
ts.height = hs;
|
||||
tsctx.drawImage( map.tiles.data, 0, 0, w, h, 0, 0, ws, hs );
|
||||
|
||||
// create the minimap canvas
|
||||
var mapCanvas = ig.$new('canvas');
|
||||
mapCanvas.width = map.width * s;
|
||||
mapCanvas.height = map.height * s;
|
||||
var ctx = mapCanvas.getContext('2d');
|
||||
|
||||
if( ig.game.clearColor ) {
|
||||
ctx.fillStyle = ig.game.clearColor;
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
}
|
||||
|
||||
// draw the map
|
||||
var tile = 0;
|
||||
for( var x = 0; x < map.width; x++ ) {
|
||||
for( var y = 0; y < map.height; y++ ) {
|
||||
if( (tile = map.data[y][x]) ) {
|
||||
ctx.drawImage(
|
||||
ts,
|
||||
Math.floor(((tile-1) * s) % ws),
|
||||
Math.floor((tile-1) * s / ws) * s,
|
||||
s, s,
|
||||
x * s, y * s,
|
||||
s, s
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mapContainer = ig.$new('div');
|
||||
mapContainer.className = 'ig_debug_map_container';
|
||||
mapContainer.style.width = map.width * s + 'px';
|
||||
mapContainer.style.height = map.height * s + 'px';
|
||||
|
||||
var mapScreen = ig.$new('div');
|
||||
mapScreen.className = 'ig_debug_map_screen';
|
||||
mapScreen.style.width = ((ig.system.width / map.tilesize) * s - 2) + 'px';
|
||||
mapScreen.style.height = ((ig.system.height / map.tilesize) * s - 2) + 'px';
|
||||
this.mapScreens[id] = mapScreen;
|
||||
|
||||
mapContainer.appendChild( mapCanvas );
|
||||
mapContainer.appendChild( mapScreen );
|
||||
panel.container.appendChild( mapContainer );
|
||||
},
|
||||
|
||||
|
||||
afterRun: function() {
|
||||
// Update the screen position DIV for each mini-map
|
||||
var s = ig.system.scale;
|
||||
for( var m = 0; m < this.maps.length; m++ ) {
|
||||
var map = this.maps[m];
|
||||
var screen = this.mapScreens[m];
|
||||
|
||||
if( !map || !screen ) { // Quick sanity check
|
||||
continue;
|
||||
}
|
||||
|
||||
var x = map.scroll.x / map.tilesize;
|
||||
var y = map.scroll.y / map.tilesize;
|
||||
|
||||
if( map.repeat ) {
|
||||
x %= map.width;
|
||||
y %= map.height;
|
||||
}
|
||||
|
||||
screen.style.left = (x * s) + 'px';
|
||||
screen.style.top = (y * s) + 'px';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.debug.addPanel({
|
||||
type: ig.DebugMapsPanel,
|
||||
name: 'maps',
|
||||
label: 'Background Maps'
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -0,0 +1,299 @@
|
||||
ig.module(
|
||||
'impact.debug.menu'
|
||||
)
|
||||
.requires(
|
||||
'dom.ready',
|
||||
'impact.system'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
|
||||
ig.System.inject({
|
||||
run: function() {
|
||||
ig.debug.beforeRun();
|
||||
this.parent();
|
||||
ig.debug.afterRun();
|
||||
},
|
||||
|
||||
setGameNow: function( gameClass ) {
|
||||
this.parent( gameClass );
|
||||
ig.debug.ready();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.Debug = ig.Class.extend({
|
||||
options: {},
|
||||
panels: {},
|
||||
numbers:{},
|
||||
container: null,
|
||||
panelMenu: null,
|
||||
activePanel: null,
|
||||
|
||||
debugTime: 0,
|
||||
debugTickAvg: 0.016,
|
||||
debugRealTime: Date.now(),
|
||||
|
||||
init: function() {
|
||||
// Inject the Stylesheet
|
||||
var style = ig.$new('link');
|
||||
style.rel = 'stylesheet';
|
||||
style.type = 'text/css';
|
||||
style.href = ig.prefix + 'lib/impact/debug/debug.css';
|
||||
ig.$('body')[0].appendChild( style );
|
||||
|
||||
// Create the Debug Container
|
||||
this.container = ig.$new('div');
|
||||
this.container.className ='ig_debug';
|
||||
ig.$('body')[0].appendChild( this.container );
|
||||
|
||||
// Create and add the Menu Container
|
||||
this.panelMenu = ig.$new('div');
|
||||
this.panelMenu.innerHTML = '<div class="ig_debug_head">Impact.Debug:</div>';
|
||||
this.panelMenu.className ='ig_debug_panel_menu';
|
||||
|
||||
this.container.appendChild( this.panelMenu );
|
||||
|
||||
// Create and add the Stats Container
|
||||
this.numberContainer = ig.$new('div');
|
||||
this.numberContainer.className ='ig_debug_stats';
|
||||
this.panelMenu.appendChild( this.numberContainer );
|
||||
|
||||
// Set ig.log(), ig.assert() and ig.show()
|
||||
if( window.console && window.console.log && window.console.assert ) {
|
||||
// Can't use .bind() on native functions in IE9 :/
|
||||
ig.log = console.log.bind ? console.log.bind(console) : console.log;
|
||||
ig.assert = console.assert.bind ? console.assert.bind(console) : console.assert;
|
||||
}
|
||||
ig.show = this.showNumber.bind(this);
|
||||
},
|
||||
|
||||
|
||||
addNumber: function( name, width ) {
|
||||
var number = ig.$new('span');
|
||||
this.numberContainer.appendChild( number );
|
||||
this.numberContainer.appendChild( document.createTextNode(name) );
|
||||
|
||||
this.numbers[name] = number;
|
||||
},
|
||||
|
||||
|
||||
showNumber: function( name, number, width ) {
|
||||
if( !this.numbers[name] ) {
|
||||
this.addNumber( name, width );
|
||||
}
|
||||
this.numbers[name].textContent = number;
|
||||
},
|
||||
|
||||
|
||||
addPanel: function( panelDef ) {
|
||||
// Create the panel and options
|
||||
var panel = new (panelDef.type)( panelDef.name, panelDef.label );
|
||||
if( panelDef.options ) {
|
||||
for( var i = 0; i < panelDef.options.length; i++ ) {
|
||||
var opt = panelDef.options[i];
|
||||
panel.addOption( new ig.DebugOption(opt.name, opt.object, opt.property) );
|
||||
}
|
||||
}
|
||||
|
||||
this.panels[ panel.name ] = panel;
|
||||
panel.container.style.display = 'none';
|
||||
this.container.appendChild( panel.container );
|
||||
|
||||
|
||||
// Create the menu item
|
||||
var menuItem = ig.$new('div');
|
||||
menuItem.className = 'ig_debug_menu_item';
|
||||
menuItem.textContent = panel.label;
|
||||
menuItem.addEventListener(
|
||||
'click',
|
||||
(function(ev){ this.togglePanel(panel); }).bind(this),
|
||||
false
|
||||
);
|
||||
panel.menuItem = menuItem;
|
||||
|
||||
// Insert menu item in alphabetical order into the menu
|
||||
var inserted = false;
|
||||
for( var i = 1; i < this.panelMenu.childNodes.length; i++ ) {
|
||||
var cn = this.panelMenu.childNodes[i];
|
||||
if( cn.textContent > panel.label ) {
|
||||
this.panelMenu.insertBefore( menuItem, cn );
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !inserted ) {
|
||||
// Not inserted? Append at the end!
|
||||
this.panelMenu.appendChild( menuItem );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
showPanel: function( name ) {
|
||||
this.togglePanel( this.panels[name] );
|
||||
},
|
||||
|
||||
|
||||
togglePanel: function( panel ) {
|
||||
if( panel != this.activePanel && this.activePanel ) {
|
||||
this.activePanel.toggle( false );
|
||||
this.activePanel.menuItem.className = 'ig_debug_menu_item';
|
||||
this.activePanel = null;
|
||||
}
|
||||
|
||||
var dsp = panel.container.style.display;
|
||||
var active = (dsp != 'block');
|
||||
panel.toggle( active );
|
||||
panel.menuItem.className = 'ig_debug_menu_item' + (active ? ' active' : '');
|
||||
|
||||
if( active ) {
|
||||
this.activePanel = panel;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
ready: function() {
|
||||
for( var p in this.panels ) {
|
||||
this.panels[p].ready();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
beforeRun: function() {
|
||||
var timeBeforeRun = Date.now();
|
||||
this.debugTickAvg = this.debugTickAvg * 0.8 + (timeBeforeRun - this.debugRealTime) * 0.2;
|
||||
this.debugRealTime = timeBeforeRun;
|
||||
|
||||
if( this.activePanel ) {
|
||||
this.activePanel.beforeRun();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
afterRun: function() {
|
||||
var frameTime = Date.now() - this.debugRealTime;
|
||||
var nextFrameDue = (1000/ig.system.fps) - frameTime;
|
||||
|
||||
this.debugTime = this.debugTime * 0.8 + frameTime * 0.2;
|
||||
|
||||
|
||||
if( this.activePanel ) {
|
||||
this.activePanel.afterRun();
|
||||
}
|
||||
|
||||
this.showNumber( 'ms', this.debugTime.toFixed(2) );
|
||||
this.showNumber( 'fps', Math.round(1000/this.debugTickAvg) );
|
||||
this.showNumber( 'draws', ig.Image.drawCount );
|
||||
if( ig.game && ig.game.entities ) {
|
||||
this.showNumber( 'entities', ig.game.entities.length );
|
||||
}
|
||||
ig.Image.drawCount = 0;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.DebugPanel = ig.Class.extend({
|
||||
active: false,
|
||||
container: null,
|
||||
options: [],
|
||||
panels: [],
|
||||
label: '',
|
||||
name: '',
|
||||
|
||||
|
||||
init: function( name, label ) {
|
||||
this.name = name;
|
||||
this.label = label;
|
||||
this.container = ig.$new('div');
|
||||
this.container.className = 'ig_debug_panel ' + this.name;
|
||||
},
|
||||
|
||||
|
||||
toggle: function( active ) {
|
||||
this.active = active;
|
||||
this.container.style.display = active ? 'block' : 'none';
|
||||
},
|
||||
|
||||
|
||||
addPanel: function( panel ) {
|
||||
this.panels.push( panel );
|
||||
this.container.appendChild( panel.container );
|
||||
},
|
||||
|
||||
|
||||
addOption: function( option ) {
|
||||
this.options.push( option );
|
||||
this.container.appendChild( option.container );
|
||||
},
|
||||
|
||||
|
||||
ready: function(){},
|
||||
beforeRun: function(){},
|
||||
afterRun: function(){}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.DebugOption = ig.Class.extend({
|
||||
name: '',
|
||||
labelName: '',
|
||||
className: 'ig_debug_option',
|
||||
label: null,
|
||||
mark: null,
|
||||
container: null,
|
||||
active: false,
|
||||
|
||||
colors: {
|
||||
enabled: '#fff',
|
||||
disabled: '#444'
|
||||
},
|
||||
|
||||
|
||||
init: function( name, object, property ) {
|
||||
this.name = name;
|
||||
this.object = object;
|
||||
this.property = property;
|
||||
|
||||
this.active = this.object[this.property];
|
||||
|
||||
this.container = ig.$new('div');
|
||||
this.container.className = 'ig_debug_option';
|
||||
|
||||
this.label = ig.$new('span');
|
||||
this.label.className = 'ig_debug_label';
|
||||
this.label.textContent = this.name;
|
||||
|
||||
this.mark = ig.$new('span');
|
||||
this.mark.className = 'ig_debug_label_mark';
|
||||
|
||||
this.container.appendChild( this.mark );
|
||||
this.container.appendChild( this.label );
|
||||
this.container.addEventListener( 'click', this.click.bind(this), false );
|
||||
|
||||
this.setLabel();
|
||||
},
|
||||
|
||||
|
||||
setLabel: function() {
|
||||
this.mark.style.backgroundColor = this.active ? this.colors.enabled : this.colors.disabled;
|
||||
},
|
||||
|
||||
|
||||
click: function( ev ) {
|
||||
this.active = !this.active;
|
||||
this.object[this.property] = this.active;
|
||||
this.setLabel();
|
||||
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Create the debug instance!
|
||||
ig.debug = new ig.Debug();
|
||||
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
ig.module(
|
||||
'impact.entity-pool'
|
||||
)
|
||||
.requires(
|
||||
'impact.game'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.EntityPool = {
|
||||
pools: {},
|
||||
|
||||
mixin: {
|
||||
staticInstantiate: function( x, y, settings ) {
|
||||
return ig.EntityPool.getFromPool( this.classId, x, y, settings );
|
||||
},
|
||||
|
||||
erase: function() {
|
||||
ig.EntityPool.putInPool( this );
|
||||
}
|
||||
},
|
||||
|
||||
enableFor: function( Class ) {
|
||||
Class.inject(this.mixin);
|
||||
},
|
||||
|
||||
getFromPool: function( classId, x, y, settings ) {
|
||||
var pool = this.pools[classId];
|
||||
if( !pool || !pool.length ) { return null; }
|
||||
|
||||
var instance = pool.pop();
|
||||
instance.reset(x, y, settings);
|
||||
return instance;
|
||||
},
|
||||
|
||||
putInPool: function( instance ) {
|
||||
if( !this.pools[instance.classId] ) {
|
||||
this.pools[instance.classId] = [instance];
|
||||
}
|
||||
else {
|
||||
this.pools[instance.classId].push(instance);
|
||||
}
|
||||
},
|
||||
|
||||
drainPool: function( classId ) {
|
||||
delete this.pools[classId];
|
||||
},
|
||||
|
||||
drainAllPools: function() {
|
||||
this.pools = {};
|
||||
}
|
||||
};
|
||||
|
||||
ig.Game.inject({
|
||||
loadLevel: function( data ) {
|
||||
ig.EntityPool.drainAllPools();
|
||||
this.parent(data);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,434 @@
|
||||
ig.module(
|
||||
'impact.entity'
|
||||
)
|
||||
.requires(
|
||||
'impact.animation',
|
||||
'impact.impact'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.Entity = ig.Class.extend({
|
||||
id: 0,
|
||||
settings: {},
|
||||
|
||||
size: {x: 16, y:16},
|
||||
offset: {x: 0, y: 0},
|
||||
|
||||
pos: {x: 0, y:0},
|
||||
last: {x: 0, y:0},
|
||||
vel: {x: 0, y: 0},
|
||||
accel: {x: 0, y: 0},
|
||||
friction: {x: 0, y: 0},
|
||||
maxVel: {x: 100, y: 100},
|
||||
zIndex: 0,
|
||||
gravityFactor: 1,
|
||||
standing: false,
|
||||
bounciness: 0,
|
||||
minBounceVelocity: 40,
|
||||
|
||||
anims: {},
|
||||
animSheet: null,
|
||||
currentAnim: null,
|
||||
health: 10,
|
||||
|
||||
type: 0, // TYPE.NONE
|
||||
checkAgainst: 0, // TYPE.NONE
|
||||
collides: 0, // COLLIDES.NEVER
|
||||
|
||||
_killed: false,
|
||||
|
||||
slopeStanding: {min: (44).toRad(), max: (136).toRad() },
|
||||
|
||||
init: function( x, y, settings ) {
|
||||
this.id = ++ig.Entity._lastId;
|
||||
this.pos.x = this.last.x = x;
|
||||
this.pos.y = this.last.y = y;
|
||||
|
||||
ig.merge( this, settings );
|
||||
},
|
||||
|
||||
reset: function( x, y, settings ) {
|
||||
var proto = this.constructor.prototype;
|
||||
this.pos.x = x;
|
||||
this.pos.y = y;
|
||||
this.last.x = x;
|
||||
this.last.y = y;
|
||||
this.vel.x = proto.vel.x;
|
||||
this.vel.y = proto.vel.y;
|
||||
this.accel.x = proto.accel.x;
|
||||
this.accel.y = proto.accel.y;
|
||||
this.health = proto.health;
|
||||
this._killed = proto._killed;
|
||||
this.standing = proto.standing;
|
||||
|
||||
this.type = proto.type;
|
||||
this.checkAgainst = proto.checkAgainst;
|
||||
this.collides = proto.collides;
|
||||
|
||||
ig.merge( this, settings );
|
||||
},
|
||||
|
||||
addAnim: function( name, frameTime, sequence, stop ) {
|
||||
if( !this.animSheet ) {
|
||||
throw( 'No animSheet to add the animation '+name+' to.' );
|
||||
}
|
||||
var a = new ig.Animation( this.animSheet, frameTime, sequence, stop );
|
||||
this.anims[name] = a;
|
||||
if( !this.currentAnim ) {
|
||||
this.currentAnim = a;
|
||||
}
|
||||
|
||||
return a;
|
||||
},
|
||||
|
||||
update: function() {
|
||||
this.last.x = this.pos.x;
|
||||
this.last.y = this.pos.y;
|
||||
this.vel.y += ig.game.gravity * ig.system.tick * this.gravityFactor;
|
||||
|
||||
this.vel.x = this.getNewVelocity( this.vel.x, this.accel.x, this.friction.x, this.maxVel.x );
|
||||
this.vel.y = this.getNewVelocity( this.vel.y, this.accel.y, this.friction.y, this.maxVel.y );
|
||||
|
||||
// movement & collision
|
||||
var mx = this.vel.x * ig.system.tick;
|
||||
var my = this.vel.y * ig.system.tick;
|
||||
var res = ig.game.collisionMap.trace(
|
||||
this.pos.x, this.pos.y, mx, my, this.size.x, this.size.y
|
||||
);
|
||||
this.handleMovementTrace( res );
|
||||
|
||||
if( this.currentAnim ) {
|
||||
this.currentAnim.update();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getNewVelocity: function( vel, accel, friction, max ) {
|
||||
if( accel ) {
|
||||
return ( vel + accel * ig.system.tick ).limit( -max, max );
|
||||
}
|
||||
else if( friction ) {
|
||||
var delta = friction * ig.system.tick;
|
||||
|
||||
if( vel - delta > 0) {
|
||||
return vel - delta;
|
||||
}
|
||||
else if( vel + delta < 0 ) {
|
||||
return vel + delta;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return vel.limit( -max, max );
|
||||
},
|
||||
|
||||
|
||||
handleMovementTrace: function( res ) {
|
||||
this.standing = false;
|
||||
|
||||
if( res.collision.y ) {
|
||||
if( this.bounciness > 0 && Math.abs(this.vel.y) > this.minBounceVelocity ) {
|
||||
this.vel.y *= -this.bounciness;
|
||||
}
|
||||
else {
|
||||
if( this.vel.y > 0 ) {
|
||||
this.standing = true;
|
||||
}
|
||||
this.vel.y = 0;
|
||||
}
|
||||
}
|
||||
if( res.collision.x ) {
|
||||
if( this.bounciness > 0 && Math.abs(this.vel.x) > this.minBounceVelocity ) {
|
||||
this.vel.x *= -this.bounciness;
|
||||
}
|
||||
else {
|
||||
this.vel.x = 0;
|
||||
}
|
||||
}
|
||||
if( res.collision.slope ) {
|
||||
var s = res.collision.slope;
|
||||
|
||||
if( this.bounciness > 0 ) {
|
||||
var proj = this.vel.x * s.nx + this.vel.y * s.ny;
|
||||
|
||||
this.vel.x = (this.vel.x - s.nx * proj * 2) * this.bounciness;
|
||||
this.vel.y = (this.vel.y - s.ny * proj * 2) * this.bounciness;
|
||||
}
|
||||
else {
|
||||
var lengthSquared = s.x * s.x + s.y * s.y;
|
||||
var dot = (this.vel.x * s.x + this.vel.y * s.y)/lengthSquared;
|
||||
|
||||
this.vel.x = s.x * dot;
|
||||
this.vel.y = s.y * dot;
|
||||
|
||||
var angle = Math.atan2( s.x, s.y );
|
||||
if( angle > this.slopeStanding.min && angle < this.slopeStanding.max ) {
|
||||
this.standing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.pos = res.pos;
|
||||
},
|
||||
|
||||
|
||||
draw: function() {
|
||||
if( this.currentAnim ) {
|
||||
this.currentAnim.draw(
|
||||
this.pos.x - this.offset.x - ig.game._rscreen.x,
|
||||
this.pos.y - this.offset.y - ig.game._rscreen.y
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
kill: function() {
|
||||
ig.game.removeEntity( this );
|
||||
},
|
||||
|
||||
|
||||
receiveDamage: function( amount, from ) {
|
||||
this.health -= amount;
|
||||
if( this.health <= 0 ) {
|
||||
this.kill();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
touches: function( other ) {
|
||||
return !(
|
||||
this.pos.x >= other.pos.x + other.size.x ||
|
||||
this.pos.x + this.size.x <= other.pos.x ||
|
||||
this.pos.y >= other.pos.y + other.size.y ||
|
||||
this.pos.y + this.size.y <= other.pos.y
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
distanceTo: function( other ) {
|
||||
var xd = (this.pos.x + this.size.x/2) - (other.pos.x + other.size.x/2);
|
||||
var yd = (this.pos.y + this.size.y/2) - (other.pos.y + other.size.y/2);
|
||||
return Math.sqrt( xd*xd + yd*yd );
|
||||
},
|
||||
|
||||
|
||||
angleTo: function( other ) {
|
||||
return Math.atan2(
|
||||
(other.pos.y + other.size.y/2) - (this.pos.y + this.size.y/2),
|
||||
(other.pos.x + other.size.x/2) - (this.pos.x + this.size.x/2)
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
check: function( other ) {},
|
||||
collideWith: function( other, axis ) {},
|
||||
ready: function() {},
|
||||
erase: function() {}
|
||||
});
|
||||
|
||||
|
||||
// Last used entity id; incremented with each spawned entity
|
||||
|
||||
ig.Entity._lastId = 0;
|
||||
|
||||
|
||||
// Collision Types - Determine if and how entities collide with each other
|
||||
|
||||
// In ACTIVE vs. LITE or FIXED vs. ANY collisions, only the "weak" entity moves,
|
||||
// while the other one stays fixed. In ACTIVE vs. ACTIVE and ACTIVE vs. PASSIVE
|
||||
// collisions, both entities are moved. LITE or PASSIVE entities don't collide
|
||||
// with other LITE or PASSIVE entities at all. The behaiviour for FIXED vs.
|
||||
// FIXED collisions is undefined.
|
||||
|
||||
ig.Entity.COLLIDES = {
|
||||
NEVER: 0,
|
||||
LITE: 1,
|
||||
PASSIVE: 2,
|
||||
ACTIVE: 4,
|
||||
FIXED: 8
|
||||
};
|
||||
|
||||
|
||||
// Entity Types - used for checks
|
||||
|
||||
ig.Entity.TYPE = {
|
||||
NONE: 0,
|
||||
A: 1,
|
||||
B: 2,
|
||||
BOTH: 3
|
||||
};
|
||||
|
||||
|
||||
|
||||
ig.Entity.checkPair = function( a, b ) {
|
||||
|
||||
// Do these entities want checks?
|
||||
if( a.checkAgainst & b.type ) {
|
||||
a.check( b );
|
||||
}
|
||||
|
||||
if( b.checkAgainst & a.type ) {
|
||||
b.check( a );
|
||||
}
|
||||
|
||||
// If this pair allows collision, solve it! At least one entity must
|
||||
// collide ACTIVE or FIXED, while the other one must not collide NEVER.
|
||||
if(
|
||||
a.collides && b.collides &&
|
||||
a.collides + b.collides > ig.Entity.COLLIDES.ACTIVE
|
||||
) {
|
||||
ig.Entity.solveCollision( a, b );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ig.Entity.solveCollision = function( a, b ) {
|
||||
|
||||
// If one entity is FIXED, or the other entity is LITE, the weak
|
||||
// (FIXED/NON-LITE) entity won't move in collision response
|
||||
var weak = null;
|
||||
if(
|
||||
a.collides == ig.Entity.COLLIDES.LITE ||
|
||||
b.collides == ig.Entity.COLLIDES.FIXED
|
||||
) {
|
||||
weak = a;
|
||||
}
|
||||
else if(
|
||||
b.collides == ig.Entity.COLLIDES.LITE ||
|
||||
a.collides == ig.Entity.COLLIDES.FIXED
|
||||
) {
|
||||
weak = b;
|
||||
}
|
||||
|
||||
|
||||
// Did they already overlap on the X-axis in the last frame? If so,
|
||||
// this must be a vertical collision!
|
||||
if(
|
||||
a.last.x + a.size.x > b.last.x &&
|
||||
a.last.x < b.last.x + b.size.x
|
||||
) {
|
||||
// Which one is on top?
|
||||
if( a.last.y < b.last.y ) {
|
||||
ig.Entity.seperateOnYAxis( a, b, weak );
|
||||
}
|
||||
else {
|
||||
ig.Entity.seperateOnYAxis( b, a, weak );
|
||||
}
|
||||
a.collideWith( b, 'y' );
|
||||
b.collideWith( a, 'y' );
|
||||
}
|
||||
|
||||
// Horizontal collision
|
||||
else if(
|
||||
a.last.y + a.size.y > b.last.y &&
|
||||
a.last.y < b.last.y + b.size.y
|
||||
){
|
||||
// Which one is on the left?
|
||||
if( a.last.x < b.last.x ) {
|
||||
ig.Entity.seperateOnXAxis( a, b, weak );
|
||||
}
|
||||
else {
|
||||
ig.Entity.seperateOnXAxis( b, a, weak );
|
||||
}
|
||||
a.collideWith( b, 'x' );
|
||||
b.collideWith( a, 'x' );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// FIXME: This is a mess. Instead of doing all the movements here, the entities
|
||||
// should get notified of the collision (with all details) and resolve it
|
||||
// themselfs.
|
||||
|
||||
ig.Entity.seperateOnXAxis = function( left, right, weak ) {
|
||||
var nudge = (left.pos.x + left.size.x - right.pos.x);
|
||||
|
||||
// We have a weak entity, so just move this one
|
||||
if( weak ) {
|
||||
var strong = left === weak ? right : left;
|
||||
weak.vel.x = -weak.vel.x * weak.bounciness + strong.vel.x;
|
||||
|
||||
var resWeak = ig.game.collisionMap.trace(
|
||||
weak.pos.x, weak.pos.y, weak == left ? -nudge : nudge, 0, weak.size.x, weak.size.y
|
||||
);
|
||||
weak.pos.x = resWeak.pos.x;
|
||||
}
|
||||
|
||||
// Normal collision - both move
|
||||
else {
|
||||
var v2 = (left.vel.x - right.vel.x)/2;
|
||||
left.vel.x = -v2;
|
||||
right.vel.x = v2;
|
||||
|
||||
var resLeft = ig.game.collisionMap.trace(
|
||||
left.pos.x, left.pos.y, -nudge/2, 0, left.size.x, left.size.y
|
||||
);
|
||||
left.pos.x = Math.floor(resLeft.pos.x);
|
||||
|
||||
var resRight = ig.game.collisionMap.trace(
|
||||
right.pos.x, right.pos.y, nudge/2, 0, right.size.x, right.size.y
|
||||
);
|
||||
right.pos.x = Math.ceil(resRight.pos.x);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ig.Entity.seperateOnYAxis = function( top, bottom, weak ) {
|
||||
var nudge = (top.pos.y + top.size.y - bottom.pos.y);
|
||||
|
||||
// We have a weak entity, so just move this one
|
||||
if( weak ) {
|
||||
var strong = top === weak ? bottom : top;
|
||||
weak.vel.y = -weak.vel.y * weak.bounciness + strong.vel.y;
|
||||
|
||||
// Riding on a platform?
|
||||
var nudgeX = 0;
|
||||
if( weak == top && Math.abs(weak.vel.y - strong.vel.y) < weak.minBounceVelocity ) {
|
||||
weak.standing = true;
|
||||
nudgeX = strong.vel.x * ig.system.tick;
|
||||
}
|
||||
|
||||
var resWeak = ig.game.collisionMap.trace(
|
||||
weak.pos.x, weak.pos.y, nudgeX, weak == top ? -nudge : nudge, weak.size.x, weak.size.y
|
||||
);
|
||||
weak.pos.y = resWeak.pos.y;
|
||||
weak.pos.x = resWeak.pos.x;
|
||||
}
|
||||
|
||||
// Bottom entity is standing - just bounce the top one
|
||||
else if( ig.game.gravity && (bottom.standing || top.vel.y > 0) ) {
|
||||
var resTop = ig.game.collisionMap.trace(
|
||||
top.pos.x, top.pos.y, 0, -(top.pos.y + top.size.y - bottom.pos.y), top.size.x, top.size.y
|
||||
);
|
||||
top.pos.y = resTop.pos.y;
|
||||
|
||||
if( top.bounciness > 0 && top.vel.y > top.minBounceVelocity ) {
|
||||
top.vel.y *= -top.bounciness;
|
||||
}
|
||||
else {
|
||||
top.standing = true;
|
||||
top.vel.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Normal collision - both move
|
||||
else {
|
||||
var v2 = (top.vel.y - bottom.vel.y)/2;
|
||||
top.vel.y = -v2;
|
||||
bottom.vel.y = v2;
|
||||
|
||||
var nudgeX = bottom.vel.x * ig.system.tick;
|
||||
var resTop = ig.game.collisionMap.trace(
|
||||
top.pos.x, top.pos.y, nudgeX, -nudge/2, top.size.x, top.size.y
|
||||
);
|
||||
top.pos.y = resTop.pos.y;
|
||||
|
||||
var resBottom = ig.game.collisionMap.trace(
|
||||
bottom.pos.x, bottom.pos.y, 0, nudge/2, bottom.size.x, bottom.size.y
|
||||
);
|
||||
bottom.pos.y = resBottom.pos.y;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
ig.module(
|
||||
'impact.font'
|
||||
)
|
||||
.requires(
|
||||
'impact.image'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
|
||||
ig.Font = ig.Image.extend({
|
||||
widthMap: [],
|
||||
indices: [],
|
||||
firstChar: 32,
|
||||
alpha: 1,
|
||||
letterSpacing: 1,
|
||||
lineSpacing: 0,
|
||||
|
||||
|
||||
onload: function( ev ) {
|
||||
this._loadMetrics( this.data );
|
||||
this.parent( ev );
|
||||
},
|
||||
|
||||
|
||||
widthForString: function( text ) {
|
||||
// Multiline?
|
||||
if( text.indexOf('\n') !== -1 ) {
|
||||
var lines = text.split( '\n' );
|
||||
var width = 0;
|
||||
for( var i = 0; i < lines.length; i++ ) {
|
||||
width = Math.max( width, this._widthForLine(lines[i]) );
|
||||
}
|
||||
return width;
|
||||
}
|
||||
else {
|
||||
return this._widthForLine( text );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_widthForLine: function( text ) {
|
||||
var width = 0;
|
||||
for( var i = 0; i < text.length; i++ ) {
|
||||
width += this.widthMap[text.charCodeAt(i) - this.firstChar] + this.letterSpacing;
|
||||
}
|
||||
return width;
|
||||
},
|
||||
|
||||
|
||||
heightForString: function( text ) {
|
||||
return text.split('\n').length * (this.height + this.lineSpacing);
|
||||
},
|
||||
|
||||
|
||||
draw: function( text, x, y, align ) {
|
||||
if( typeof(text) != 'string' ) {
|
||||
text = text.toString();
|
||||
}
|
||||
|
||||
// Multiline?
|
||||
if( text.indexOf('\n') !== -1 ) {
|
||||
var lines = text.split( '\n' );
|
||||
var lineHeight = this.height + this.lineSpacing;
|
||||
for( var i = 0; i < lines.length; i++ ) {
|
||||
this.draw( lines[i], x, y + i * lineHeight, align );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( align == ig.Font.ALIGN.RIGHT || align == ig.Font.ALIGN.CENTER ) {
|
||||
var width = this._widthForLine( text );
|
||||
x -= align == ig.Font.ALIGN.CENTER ? width/2 : width;
|
||||
}
|
||||
|
||||
|
||||
if( this.alpha !== 1 ) {
|
||||
ig.system.context.globalAlpha = this.alpha;
|
||||
}
|
||||
|
||||
for( var i = 0; i < text.length; i++ ) {
|
||||
var c = text.charCodeAt(i);
|
||||
x += this._drawChar( c - this.firstChar, x, y );
|
||||
}
|
||||
|
||||
if( this.alpha !== 1 ) {
|
||||
ig.system.context.globalAlpha = 1;
|
||||
}
|
||||
ig.Image.drawCount += text.length;
|
||||
},
|
||||
|
||||
|
||||
_drawChar: function( c, targetX, targetY ) {
|
||||
if( !this.loaded || c < 0 || c >= this.indices.length ) { return 0; }
|
||||
|
||||
var scale = ig.system.scale;
|
||||
|
||||
|
||||
var charX = this.indices[c] * scale;
|
||||
var charY = 0;
|
||||
var charWidth = this.widthMap[c] * scale;
|
||||
var charHeight = (this.height-2) * scale;
|
||||
|
||||
ig.system.context.drawImage(
|
||||
this.data,
|
||||
charX, charY,
|
||||
charWidth, charHeight,
|
||||
ig.system.getDrawPos(targetX), ig.system.getDrawPos(targetY),
|
||||
charWidth, charHeight
|
||||
);
|
||||
|
||||
return this.widthMap[c] + this.letterSpacing;
|
||||
},
|
||||
|
||||
|
||||
_loadMetrics: function( image ) {
|
||||
// Draw the bottommost line of this font image into an offscreen canvas
|
||||
// and analyze it pixel by pixel.
|
||||
// A run of non-transparent pixels represents a character and its width
|
||||
|
||||
this.height = image.height-1;
|
||||
this.widthMap = [];
|
||||
this.indices = [];
|
||||
|
||||
var px = ig.getImagePixels( image, 0, image.height-1, image.width, 1 );
|
||||
|
||||
var currentChar = 0;
|
||||
var currentWidth = 0;
|
||||
for( var x = 0; x < image.width; x++ ) {
|
||||
var index = x * 4 + 3; // alpha component of this pixel
|
||||
if( px.data[index] > 127 ) {
|
||||
currentWidth++;
|
||||
}
|
||||
else if( px.data[index] < 128 && currentWidth ) {
|
||||
this.widthMap.push( currentWidth );
|
||||
this.indices.push( x-currentWidth );
|
||||
currentChar++;
|
||||
currentWidth = 0;
|
||||
}
|
||||
}
|
||||
this.widthMap.push( currentWidth );
|
||||
this.indices.push( x-currentWidth );
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.Font.ALIGN = {
|
||||
LEFT: 0,
|
||||
RIGHT: 1,
|
||||
CENTER: 2
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,326 @@
|
||||
ig.module(
|
||||
'impact.game'
|
||||
)
|
||||
.requires(
|
||||
'impact.impact',
|
||||
'impact.entity',
|
||||
'impact.collision-map',
|
||||
'impact.background-map'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.Game = ig.Class.extend({
|
||||
|
||||
clearColor: '#000000',
|
||||
gravity: 0,
|
||||
screen: {x: 0, y: 0},
|
||||
_rscreen: {x: 0, y: 0},
|
||||
|
||||
entities: [],
|
||||
|
||||
namedEntities: {},
|
||||
collisionMap: ig.CollisionMap.staticNoCollision,
|
||||
backgroundMaps: [],
|
||||
backgroundAnims: {},
|
||||
|
||||
autoSort: false,
|
||||
sortBy: null,
|
||||
|
||||
cellSize: 64,
|
||||
|
||||
_deferredKill: [],
|
||||
_levelToLoad: null,
|
||||
_doSortEntities: false,
|
||||
|
||||
|
||||
staticInstantiate: function() {
|
||||
this.sortBy = this.sortBy || ig.Game.SORT.Z_INDEX;
|
||||
ig.game = this;
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
loadLevel: function( data ) {
|
||||
this.screen = {x: 0, y: 0};
|
||||
|
||||
// Entities
|
||||
this.entities = [];
|
||||
this.namedEntities = {};
|
||||
for( var i = 0; i < data.entities.length; i++ ) {
|
||||
var ent = data.entities[i];
|
||||
this.spawnEntity( ent.type, ent.x, ent.y, ent.settings );
|
||||
}
|
||||
this.sortEntities();
|
||||
|
||||
// Map Layer
|
||||
this.collisionMap = ig.CollisionMap.staticNoCollision;
|
||||
this.backgroundMaps = [];
|
||||
for( var i = 0; i < data.layer.length; i++ ) {
|
||||
var ld = data.layer[i];
|
||||
if( ld.name == 'collision' ) {
|
||||
this.collisionMap = new ig.CollisionMap(ld.tilesize, ld.data );
|
||||
}
|
||||
else {
|
||||
var newMap = new ig.BackgroundMap(ld.tilesize, ld.data, ld.tilesetName);
|
||||
newMap.anims = this.backgroundAnims[ld.tilesetName] || {};
|
||||
newMap.repeat = ld.repeat;
|
||||
newMap.distance = ld.distance;
|
||||
newMap.foreground = !!ld.foreground;
|
||||
newMap.preRender = !!ld.preRender;
|
||||
newMap.name = ld.name;
|
||||
this.backgroundMaps.push( newMap );
|
||||
}
|
||||
}
|
||||
|
||||
// Call post-init ready function on all entities
|
||||
for( var i = 0; i < this.entities.length; i++ ) {
|
||||
this.entities[i].ready();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
loadLevelDeferred: function( data ) {
|
||||
this._levelToLoad = data;
|
||||
},
|
||||
|
||||
|
||||
getMapByName: function( name ) {
|
||||
if( name == 'collision' ) {
|
||||
return this.collisionMap;
|
||||
}
|
||||
|
||||
for( var i = 0; i < this.backgroundMaps.length; i++ ) {
|
||||
if( this.backgroundMaps[i].name == name ) {
|
||||
return this.backgroundMaps[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
getEntityByName: function( name ) {
|
||||
return this.namedEntities[name];
|
||||
},
|
||||
|
||||
|
||||
getEntitiesByType: function( type ) {
|
||||
var entityClass = typeof(type) === 'string'
|
||||
? ig.global[type]
|
||||
: type;
|
||||
|
||||
var a = [];
|
||||
for( var i = 0; i < this.entities.length; i++ ) {
|
||||
var ent = this.entities[i];
|
||||
if( ent instanceof entityClass && !ent._killed ) {
|
||||
a.push( ent );
|
||||
}
|
||||
}
|
||||
return a;
|
||||
},
|
||||
|
||||
|
||||
spawnEntity: function( type, x, y, settings ) {
|
||||
var entityClass = typeof(type) === 'string'
|
||||
? ig.global[type]
|
||||
: type;
|
||||
|
||||
if( !entityClass ) {
|
||||
throw("Can't spawn entity of type " + type);
|
||||
}
|
||||
var ent = new (entityClass)( x, y, settings || {} );
|
||||
this.entities.push( ent );
|
||||
if( ent.name ) {
|
||||
this.namedEntities[ent.name] = ent;
|
||||
}
|
||||
return ent;
|
||||
},
|
||||
|
||||
|
||||
sortEntities: function() {
|
||||
this.entities.sort( this.sortBy );
|
||||
},
|
||||
|
||||
|
||||
sortEntitiesDeferred: function() {
|
||||
this._doSortEntities = true;
|
||||
},
|
||||
|
||||
|
||||
removeEntity: function( ent ) {
|
||||
// Remove this entity from the named entities
|
||||
if( ent.name ) {
|
||||
delete this.namedEntities[ent.name];
|
||||
}
|
||||
|
||||
// We can not remove the entity from the entities[] array in the midst
|
||||
// of an update cycle, so remember all killed entities and remove
|
||||
// them later.
|
||||
// Also make sure this entity doesn't collide anymore and won't get
|
||||
// updated or checked
|
||||
ent._killed = true;
|
||||
ent.type = ig.Entity.TYPE.NONE;
|
||||
ent.checkAgainst = ig.Entity.TYPE.NONE;
|
||||
ent.collides = ig.Entity.COLLIDES.NEVER;
|
||||
this._deferredKill.push( ent );
|
||||
},
|
||||
|
||||
|
||||
run: function() {
|
||||
this.update();
|
||||
this.draw();
|
||||
},
|
||||
|
||||
|
||||
update: function(){
|
||||
// load new level?
|
||||
if( this._levelToLoad ) {
|
||||
this.loadLevel( this._levelToLoad );
|
||||
this._levelToLoad = null;
|
||||
}
|
||||
|
||||
// update entities
|
||||
this.updateEntities();
|
||||
this.checkEntities();
|
||||
|
||||
// remove all killed entities
|
||||
for( var i = 0; i < this._deferredKill.length; i++ ) {
|
||||
this._deferredKill[i].erase();
|
||||
this.entities.erase( this._deferredKill[i] );
|
||||
}
|
||||
this._deferredKill = [];
|
||||
|
||||
// sort entities?
|
||||
if( this._doSortEntities || this.autoSort ) {
|
||||
this.sortEntities();
|
||||
this._doSortEntities = false;
|
||||
}
|
||||
|
||||
// update background animations
|
||||
for( var tileset in this.backgroundAnims ) {
|
||||
var anims = this.backgroundAnims[tileset];
|
||||
for( var a in anims ) {
|
||||
anims[a].update();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
updateEntities: function() {
|
||||
for( var i = 0; i < this.entities.length; i++ ) {
|
||||
var ent = this.entities[i];
|
||||
if( !ent._killed ) {
|
||||
ent.update();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
draw: function(){
|
||||
if( this.clearColor ) {
|
||||
ig.system.clear( this.clearColor );
|
||||
}
|
||||
|
||||
// This is a bit of a circle jerk. Entities reference game._rscreen
|
||||
// instead of game.screen when drawing themselfs in order to be
|
||||
// "synchronized" to the rounded(?) screen position
|
||||
this._rscreen.x = ig.system.getDrawPos(this.screen.x)/ig.system.scale;
|
||||
this._rscreen.y = ig.system.getDrawPos(this.screen.y)/ig.system.scale;
|
||||
|
||||
|
||||
var mapIndex;
|
||||
for( mapIndex = 0; mapIndex < this.backgroundMaps.length; mapIndex++ ) {
|
||||
var map = this.backgroundMaps[mapIndex];
|
||||
if( map.foreground ) {
|
||||
// All foreground layers are drawn after the entities
|
||||
break;
|
||||
}
|
||||
map.setScreenPos( this.screen.x, this.screen.y );
|
||||
map.draw();
|
||||
}
|
||||
|
||||
|
||||
this.drawEntities();
|
||||
|
||||
|
||||
for( mapIndex; mapIndex < this.backgroundMaps.length; mapIndex++ ) {
|
||||
var map = this.backgroundMaps[mapIndex];
|
||||
map.setScreenPos( this.screen.x, this.screen.y );
|
||||
map.draw();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
drawEntities: function() {
|
||||
for( var i = 0; i < this.entities.length; i++ ) {
|
||||
this.entities[i].draw();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
checkEntities: function() {
|
||||
// Insert all entities into a spatial hash and check them against any
|
||||
// other entity that already resides in the same cell. Entities that are
|
||||
// bigger than a single cell, are inserted into each one they intersect
|
||||
// with.
|
||||
|
||||
// A list of entities, which the current one was already checked with,
|
||||
// is maintained for each entity.
|
||||
|
||||
var hash = {};
|
||||
for( var e = 0; e < this.entities.length; e++ ) {
|
||||
var entity = this.entities[e];
|
||||
|
||||
// Skip entities that don't check, don't get checked and don't collide
|
||||
if(
|
||||
entity.type == ig.Entity.TYPE.NONE &&
|
||||
entity.checkAgainst == ig.Entity.TYPE.NONE &&
|
||||
entity.collides == ig.Entity.COLLIDES.NEVER
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var checked = {},
|
||||
xmin = Math.floor( entity.pos.x/this.cellSize ),
|
||||
ymin = Math.floor( entity.pos.y/this.cellSize ),
|
||||
xmax = Math.floor( (entity.pos.x+entity.size.x)/this.cellSize ) + 1,
|
||||
ymax = Math.floor( (entity.pos.y+entity.size.y)/this.cellSize ) + 1;
|
||||
|
||||
for( var x = xmin; x < xmax; x++ ) {
|
||||
for( var y = ymin; y < ymax; y++ ) {
|
||||
|
||||
// Current cell is empty - create it and insert!
|
||||
if( !hash[x] ) {
|
||||
hash[x] = {};
|
||||
hash[x][y] = [entity];
|
||||
}
|
||||
else if( !hash[x][y] ) {
|
||||
hash[x][y] = [entity];
|
||||
}
|
||||
|
||||
// Check against each entity in this cell, then insert
|
||||
else {
|
||||
var cell = hash[x][y];
|
||||
for( var c = 0; c < cell.length; c++ ) {
|
||||
|
||||
// Intersects and wasn't already checkd?
|
||||
if( entity.touches(cell[c]) && !checked[cell[c].id] ) {
|
||||
checked[cell[c].id] = true;
|
||||
ig.Entity.checkPair( entity, cell[c] );
|
||||
}
|
||||
}
|
||||
cell.push(entity);
|
||||
}
|
||||
} // end for y size
|
||||
} // end for x size
|
||||
} // end for entities
|
||||
}
|
||||
});
|
||||
|
||||
ig.Game.SORT = {
|
||||
Z_INDEX: function( a, b ){ return a.zIndex - b.zIndex; },
|
||||
POS_X: function( a, b ){ return (a.pos.x+a.size.x) - (b.pos.x+b.size.x); },
|
||||
POS_Y: function( a, b ){ return (a.pos.y+a.size.y) - (b.pos.y+b.size.y); }
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
ig.module(
|
||||
'impact.image'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.Image = ig.Class.extend({
|
||||
data: null,
|
||||
width: 0,
|
||||
height: 0,
|
||||
loaded: false,
|
||||
failed: false,
|
||||
loadCallback: null,
|
||||
path: '',
|
||||
|
||||
|
||||
staticInstantiate: function( path ) {
|
||||
return ig.Image.cache[path] || null;
|
||||
},
|
||||
|
||||
|
||||
init: function( path ) {
|
||||
this.path = path;
|
||||
this.load();
|
||||
},
|
||||
|
||||
|
||||
load: function( loadCallback ) {
|
||||
if( this.loaded ) {
|
||||
if( loadCallback ) {
|
||||
loadCallback( this.path, true );
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if( !this.loaded && ig.ready ) {
|
||||
this.loadCallback = loadCallback || null;
|
||||
|
||||
this.data = new Image();
|
||||
this.data.onload = this.onload.bind(this);
|
||||
this.data.onerror = this.onerror.bind(this);
|
||||
this.data.src = ig.prefix + this.path + ig.nocache;
|
||||
}
|
||||
else {
|
||||
ig.addResource( this );
|
||||
}
|
||||
|
||||
ig.Image.cache[this.path] = this;
|
||||
},
|
||||
|
||||
|
||||
reload: function() {
|
||||
this.loaded = false;
|
||||
this.data = new Image();
|
||||
this.data.onload = this.onload.bind(this);
|
||||
this.data.src = this.path + '?' + Date.now();
|
||||
},
|
||||
|
||||
|
||||
onload: function( event ) {
|
||||
this.width = this.data.width;
|
||||
this.height = this.data.height;
|
||||
this.loaded = true;
|
||||
|
||||
if( ig.system.scale != 1 ) {
|
||||
this.resize( ig.system.scale );
|
||||
}
|
||||
|
||||
if( this.loadCallback ) {
|
||||
this.loadCallback( this.path, true );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onerror: function( event ) {
|
||||
this.failed = true;
|
||||
|
||||
if( this.loadCallback ) {
|
||||
this.loadCallback( this.path, false );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
resize: function( scale ) {
|
||||
// Nearest-Neighbor scaling
|
||||
|
||||
// The original image is drawn into an offscreen canvas of the same size
|
||||
// and copied into another offscreen canvas with the new size.
|
||||
// The scaled offscreen canvas becomes the image (data) of this object.
|
||||
|
||||
var origPixels = ig.getImagePixels( this.data, 0, 0, this.width, this.height );
|
||||
|
||||
var widthScaled = this.width * scale;
|
||||
var heightScaled = this.height * scale;
|
||||
|
||||
var scaled = ig.$new('canvas');
|
||||
scaled.width = widthScaled;
|
||||
scaled.height = heightScaled;
|
||||
var scaledCtx = scaled.getContext('2d');
|
||||
var scaledPixels = scaledCtx.getImageData( 0, 0, widthScaled, heightScaled );
|
||||
|
||||
for( var y = 0; y < heightScaled; y++ ) {
|
||||
for( var x = 0; x < widthScaled; x++ ) {
|
||||
var index = (Math.floor(y / scale) * this.width + Math.floor(x / scale)) * 4;
|
||||
var indexScaled = (y * widthScaled + x) * 4;
|
||||
scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
|
||||
scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ];
|
||||
scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ];
|
||||
scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ];
|
||||
}
|
||||
}
|
||||
scaledCtx.putImageData( scaledPixels, 0, 0 );
|
||||
this.data = scaled;
|
||||
},
|
||||
|
||||
|
||||
draw: function( targetX, targetY, sourceX, sourceY, width, height ) {
|
||||
if( !this.loaded ) { return; }
|
||||
|
||||
var scale = ig.system.scale;
|
||||
sourceX = sourceX ? sourceX * scale : 0;
|
||||
sourceY = sourceY ? sourceY * scale : 0;
|
||||
width = (width ? width : this.width) * scale;
|
||||
height = (height ? height : this.height) * scale;
|
||||
|
||||
ig.system.context.drawImage(
|
||||
this.data, sourceX, sourceY, width, height,
|
||||
ig.system.getDrawPos(targetX),
|
||||
ig.system.getDrawPos(targetY),
|
||||
width, height
|
||||
);
|
||||
|
||||
ig.Image.drawCount++;
|
||||
},
|
||||
|
||||
|
||||
drawTile: function( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY ) {
|
||||
tileHeight = tileHeight ? tileHeight : tileWidth;
|
||||
|
||||
if( !this.loaded || tileWidth > this.width || tileHeight > this.height ) { return; }
|
||||
|
||||
var scale = ig.system.scale;
|
||||
var tileWidthScaled = Math.floor(tileWidth * scale);
|
||||
var tileHeightScaled = Math.floor(tileHeight * scale);
|
||||
|
||||
var scaleX = flipX ? -1 : 1;
|
||||
var scaleY = flipY ? -1 : 1;
|
||||
|
||||
if( flipX || flipY ) {
|
||||
ig.system.context.save();
|
||||
ig.system.context.scale( scaleX, scaleY );
|
||||
}
|
||||
ig.system.context.drawImage(
|
||||
this.data,
|
||||
( Math.floor(tile * tileWidth) % this.width ) * scale,
|
||||
( Math.floor(tile * tileWidth / this.width) * tileHeight ) * scale,
|
||||
tileWidthScaled,
|
||||
tileHeightScaled,
|
||||
ig.system.getDrawPos(targetX) * scaleX - (flipX ? tileWidthScaled : 0),
|
||||
ig.system.getDrawPos(targetY) * scaleY - (flipY ? tileHeightScaled : 0),
|
||||
tileWidthScaled,
|
||||
tileHeightScaled
|
||||
);
|
||||
if( flipX || flipY ) {
|
||||
ig.system.context.restore();
|
||||
}
|
||||
|
||||
ig.Image.drawCount++;
|
||||
}
|
||||
});
|
||||
|
||||
ig.Image.drawCount = 0;
|
||||
ig.Image.cache = {};
|
||||
ig.Image.reloadCache = function() {
|
||||
for( var path in ig.Image.cache ) {
|
||||
ig.Image.cache[path].reload();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,590 @@
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Impact Game Engine 1.24
|
||||
// http://impactjs.com/
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
(function(window){ "use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Native Object extensions
|
||||
|
||||
Number.prototype.map = function(istart, istop, ostart, ostop) {
|
||||
return ostart + (ostop - ostart) * ((this - istart) / (istop - istart));
|
||||
};
|
||||
|
||||
Number.prototype.limit = function(min, max) {
|
||||
return Math.min(max, Math.max(min, this));
|
||||
};
|
||||
|
||||
Number.prototype.round = function(precision) {
|
||||
precision = Math.pow(10, precision || 0);
|
||||
return Math.round(this * precision) / precision;
|
||||
};
|
||||
|
||||
Number.prototype.floor = function() {
|
||||
return Math.floor(this);
|
||||
};
|
||||
|
||||
Number.prototype.ceil = function() {
|
||||
return Math.ceil(this);
|
||||
};
|
||||
|
||||
Number.prototype.toInt = function() {
|
||||
return (this | 0);
|
||||
};
|
||||
|
||||
Number.prototype.toRad = function() {
|
||||
return (this / 180) * Math.PI;
|
||||
};
|
||||
|
||||
Number.prototype.toDeg = function() {
|
||||
return (this * 180) / Math.PI;
|
||||
};
|
||||
|
||||
Object.defineProperty(Array.prototype, 'erase', {value: function(item) {
|
||||
for( var i = this.length; i--; ) {
|
||||
if( this[i] === item ) {
|
||||
this.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}});
|
||||
|
||||
Object.defineProperty(Array.prototype, 'random', {value: function(item) {
|
||||
return this[ Math.floor(Math.random() * this.length) ];
|
||||
}});
|
||||
|
||||
Function.prototype.bind = Function.prototype.bind || function (oThis) {
|
||||
if( typeof this !== "function" ) {
|
||||
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
|
||||
}
|
||||
|
||||
var aArgs = Array.prototype.slice.call(arguments, 1),
|
||||
fToBind = this,
|
||||
fNOP = function () {},
|
||||
fBound = function () {
|
||||
return fToBind.apply(
|
||||
(this instanceof fNOP && oThis ? this : oThis),
|
||||
aArgs.concat(Array.prototype.slice.call(arguments))
|
||||
);
|
||||
};
|
||||
|
||||
fNOP.prototype = this.prototype;
|
||||
fBound.prototype = new fNOP();
|
||||
|
||||
return fBound;
|
||||
};
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ig Namespace
|
||||
|
||||
window.ig = {
|
||||
game: null,
|
||||
debug: null,
|
||||
version: '1.24',
|
||||
global: window,
|
||||
modules: {},
|
||||
resources: [],
|
||||
ready: false,
|
||||
baked: false,
|
||||
nocache: '',
|
||||
ua: {},
|
||||
prefix: (window.ImpactPrefix || ''),
|
||||
lib: 'lib/',
|
||||
|
||||
_current: null,
|
||||
_loadQueue: [],
|
||||
_waitForOnload: 0,
|
||||
|
||||
|
||||
$: function( selector ) {
|
||||
return selector.charAt(0) == '#'
|
||||
? document.getElementById( selector.substr(1) )
|
||||
: document.getElementsByTagName( selector );
|
||||
},
|
||||
|
||||
|
||||
$new: function( name ) {
|
||||
return document.createElement( name );
|
||||
},
|
||||
|
||||
|
||||
copy: function( object ) {
|
||||
if(
|
||||
!object || typeof(object) != 'object' ||
|
||||
object instanceof HTMLElement ||
|
||||
object instanceof ig.Class
|
||||
) {
|
||||
return object;
|
||||
}
|
||||
else if( object instanceof Array ) {
|
||||
var c = [];
|
||||
for( var i = 0, l = object.length; i < l; i++) {
|
||||
c[i] = ig.copy(object[i]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
else {
|
||||
var c = {};
|
||||
for( var i in object ) {
|
||||
c[i] = ig.copy(object[i]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
merge: function( original, extended ) {
|
||||
for( var key in extended ) {
|
||||
var ext = extended[key];
|
||||
if(
|
||||
typeof(ext) != 'object' ||
|
||||
ext instanceof HTMLElement ||
|
||||
ext instanceof ig.Class ||
|
||||
ext === null
|
||||
) {
|
||||
original[key] = ext;
|
||||
}
|
||||
else {
|
||||
if( !original[key] || typeof(original[key]) != 'object' ) {
|
||||
original[key] = (ext instanceof Array) ? [] : {};
|
||||
}
|
||||
ig.merge( original[key], ext );
|
||||
}
|
||||
}
|
||||
return original;
|
||||
},
|
||||
|
||||
|
||||
ksort: function( obj ) {
|
||||
if( !obj || typeof(obj) != 'object' ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var keys = [], values = [];
|
||||
for( var i in obj ) {
|
||||
keys.push(i);
|
||||
}
|
||||
|
||||
keys.sort();
|
||||
for( var i = 0; i < keys.length; i++ ) {
|
||||
values.push( obj[keys[i]] );
|
||||
}
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
// Ah, yes. I love vendor prefixes. So much fun!
|
||||
setVendorAttribute: function( el, attr, val ) {
|
||||
var uc = attr.charAt(0).toUpperCase() + attr.substr(1);
|
||||
el[attr] = el['ms'+uc] = el['moz'+uc] = el['webkit'+uc] = el['o'+uc] = val;
|
||||
},
|
||||
|
||||
|
||||
getVendorAttribute: function( el, attr ) {
|
||||
var uc = attr.charAt(0).toUpperCase() + attr.substr(1);
|
||||
return el[attr] || el['ms'+uc] || el['moz'+uc] || el['webkit'+uc] || el['o'+uc];
|
||||
},
|
||||
|
||||
|
||||
normalizeVendorAttribute: function( el, attr ) {
|
||||
var prefixedVal = ig.getVendorAttribute( el, attr );
|
||||
if( !el[attr] && prefixedVal ) {
|
||||
el[attr] = prefixedVal;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// This function normalizes getImageData to extract the real, actual
|
||||
// pixels from an image. The naive method recently failed on retina
|
||||
// devices with a backgingStoreRatio != 1
|
||||
getImagePixels: function( image, x, y, width, height ) {
|
||||
var canvas = ig.$new('canvas');
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Try to draw pixels as accurately as possible
|
||||
ig.System.SCALE.CRISP(canvas, ctx);
|
||||
|
||||
var ratio = ig.getVendorAttribute( ctx, 'backingStorePixelRatio' ) || 1;
|
||||
ig.normalizeVendorAttribute( ctx, 'getImageDataHD' );
|
||||
|
||||
var realWidth = image.width / ratio,
|
||||
realHeight = image.height / ratio;
|
||||
|
||||
canvas.width = Math.ceil( realWidth );
|
||||
canvas.height = Math.ceil( realHeight );
|
||||
|
||||
ctx.drawImage( image, 0, 0, realWidth, realHeight );
|
||||
|
||||
return (ratio === 1)
|
||||
? ctx.getImageData( x, y, width, height )
|
||||
: ctx.getImageDataHD( x, y, width, height );
|
||||
},
|
||||
|
||||
|
||||
module: function( name ) {
|
||||
if( ig._current ) {
|
||||
throw( "Module '"+ig._current.name+"' defines nothing" );
|
||||
}
|
||||
if( ig.modules[name] && ig.modules[name].body ) {
|
||||
throw( "Module '"+name+"' is already defined" );
|
||||
}
|
||||
|
||||
ig._current = {name: name, requires: [], loaded: false, body: null};
|
||||
ig.modules[name] = ig._current;
|
||||
ig._loadQueue.push(ig._current);
|
||||
return ig;
|
||||
},
|
||||
|
||||
|
||||
requires: function() {
|
||||
ig._current.requires = Array.prototype.slice.call(arguments);
|
||||
return ig;
|
||||
},
|
||||
|
||||
|
||||
defines: function( body ) {
|
||||
ig._current.body = body;
|
||||
ig._current = null;
|
||||
ig._initDOMReady();
|
||||
},
|
||||
|
||||
|
||||
addResource: function( resource ) {
|
||||
ig.resources.push( resource );
|
||||
},
|
||||
|
||||
|
||||
setNocache: function( set ) {
|
||||
ig.nocache = set
|
||||
? '?' + Date.now()
|
||||
: '';
|
||||
},
|
||||
|
||||
|
||||
// Stubs for ig.Debug
|
||||
log: function() {},
|
||||
assert: function( condition, msg ) {},
|
||||
show: function( name, number ) {},
|
||||
mark: function( msg, color ) {},
|
||||
|
||||
|
||||
_loadScript: function( name, requiredFrom ) {
|
||||
ig.modules[name] = {name: name, requires:[], loaded: false, body: null};
|
||||
ig._waitForOnload++;
|
||||
|
||||
var path = ig.prefix + ig.lib + name.replace(/\./g, '/') + '.js' + ig.nocache;
|
||||
var script = ig.$new('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = path;
|
||||
script.onload = function() {
|
||||
ig._waitForOnload--;
|
||||
ig._execModules();
|
||||
};
|
||||
script.onerror = function() {
|
||||
throw(
|
||||
'Failed to load module '+name+' at ' + path + ' ' +
|
||||
'required from ' + requiredFrom
|
||||
);
|
||||
};
|
||||
ig.$('head')[0].appendChild(script);
|
||||
},
|
||||
|
||||
|
||||
_execModules: function() {
|
||||
var modulesLoaded = false;
|
||||
for( var i = 0; i < ig._loadQueue.length; i++ ) {
|
||||
var m = ig._loadQueue[i];
|
||||
var dependenciesLoaded = true;
|
||||
|
||||
for( var j = 0; j < m.requires.length; j++ ) {
|
||||
var name = m.requires[j];
|
||||
if( !ig.modules[name] ) {
|
||||
dependenciesLoaded = false;
|
||||
ig._loadScript( name, m.name );
|
||||
}
|
||||
else if( !ig.modules[name].loaded ) {
|
||||
dependenciesLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if( dependenciesLoaded && m.body ) {
|
||||
ig._loadQueue.splice(i, 1);
|
||||
m.loaded = true;
|
||||
m.body();
|
||||
modulesLoaded = true;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if( modulesLoaded ) {
|
||||
ig._execModules();
|
||||
}
|
||||
|
||||
// No modules executed, no more files to load but loadQueue not empty?
|
||||
// Must be some unresolved dependencies!
|
||||
else if( !ig.baked && ig._waitForOnload == 0 && ig._loadQueue.length != 0 ) {
|
||||
var unresolved = [];
|
||||
for( var i = 0; i < ig._loadQueue.length; i++ ) {
|
||||
|
||||
// Which dependencies aren't loaded?
|
||||
var unloaded = [];
|
||||
var requires = ig._loadQueue[i].requires;
|
||||
for( var j = 0; j < requires.length; j++ ) {
|
||||
var m = ig.modules[ requires[j] ];
|
||||
if( !m || !m.loaded ) {
|
||||
unloaded.push( requires[j] );
|
||||
}
|
||||
}
|
||||
unresolved.push( ig._loadQueue[i].name + ' (requires: ' + unloaded.join(', ') + ')');
|
||||
}
|
||||
|
||||
throw(
|
||||
"Unresolved (or circular?) dependencies. " +
|
||||
"Most likely there's a name/path mismatch for one of the listed modules " +
|
||||
"or a previous syntax error prevents a module from loading:\n" +
|
||||
unresolved.join('\n')
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_DOMReady: function() {
|
||||
if( !ig.modules['dom.ready'].loaded ) {
|
||||
if ( !document.body ) {
|
||||
return setTimeout( ig._DOMReady, 13 );
|
||||
}
|
||||
ig.modules['dom.ready'].loaded = true;
|
||||
ig._waitForOnload--;
|
||||
ig._execModules();
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
|
||||
_boot: function() {
|
||||
if( document.location.href.match(/\?nocache/) ) {
|
||||
ig.setNocache( true );
|
||||
}
|
||||
|
||||
// Probe user agent string
|
||||
ig.ua.pixelRatio = window.devicePixelRatio || 1;
|
||||
ig.ua.viewport = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
ig.ua.screen = {
|
||||
width: window.screen.availWidth * ig.ua.pixelRatio,
|
||||
height: window.screen.availHeight * ig.ua.pixelRatio
|
||||
};
|
||||
|
||||
ig.ua.iPhone = /iPhone/i.test(navigator.userAgent);
|
||||
ig.ua.iPhone4 = (ig.ua.iPhone && ig.ua.pixelRatio == 2);
|
||||
ig.ua.iPad = /iPad/i.test(navigator.userAgent);
|
||||
ig.ua.android = /android/i.test(navigator.userAgent);
|
||||
ig.ua.winPhone = /Windows Phone/i.test(navigator.userAgent);
|
||||
ig.ua.iOS = ig.ua.iPhone || ig.ua.iPad;
|
||||
ig.ua.mobile = ig.ua.iOS || ig.ua.android || ig.ua.winPhone || /mobile/i.test(navigator.userAgent);
|
||||
ig.ua.touchDevice = (('ontouchstart' in window) || (window.navigator.msMaxTouchPoints));
|
||||
},
|
||||
|
||||
|
||||
_initDOMReady: function() {
|
||||
if( ig.modules['dom.ready'] ) {
|
||||
ig._execModules();
|
||||
return;
|
||||
}
|
||||
|
||||
ig._boot();
|
||||
|
||||
|
||||
ig.modules['dom.ready'] = { requires: [], loaded: false, body: null };
|
||||
ig._waitForOnload++;
|
||||
if ( document.readyState === 'complete' ) {
|
||||
ig._DOMReady();
|
||||
}
|
||||
else {
|
||||
document.addEventListener( 'DOMContentLoaded', ig._DOMReady, false );
|
||||
window.addEventListener( 'load', ig._DOMReady, false );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Provide ig.setAnimation and ig.clearAnimation as a compatible way to use
|
||||
// requestAnimationFrame if available or setInterval otherwise
|
||||
|
||||
// Use requestAnimationFrame if available
|
||||
ig.normalizeVendorAttribute( window, 'requestAnimationFrame' );
|
||||
if( window.requestAnimationFrame ) {
|
||||
var next = 1,
|
||||
anims = {};
|
||||
|
||||
window.ig.setAnimation = function( callback, element ) {
|
||||
var current = next++;
|
||||
anims[current] = true;
|
||||
|
||||
var animate = function() {
|
||||
if( !anims[current] ) { return; } // deleted?
|
||||
window.requestAnimationFrame( animate, element );
|
||||
callback();
|
||||
};
|
||||
window.requestAnimationFrame( animate, element );
|
||||
return current;
|
||||
};
|
||||
|
||||
window.ig.clearAnimation = function( id ) {
|
||||
delete anims[id];
|
||||
};
|
||||
}
|
||||
|
||||
// [set/clear]Interval fallback
|
||||
else {
|
||||
window.ig.setAnimation = function( callback, element ) {
|
||||
return window.setInterval( callback, 1000/60 );
|
||||
};
|
||||
window.ig.clearAnimation = function( id ) {
|
||||
window.clearInterval( id );
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Class object based on John Resigs code; inspired by base2 and Prototype
|
||||
// http://ejohn.org/blog/simple-javascript-inheritance/
|
||||
|
||||
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\bparent\b/ : /.*/;
|
||||
var lastClassId = 0;
|
||||
|
||||
window.ig.Class = function(){};
|
||||
var inject = function(prop) {
|
||||
var proto = this.prototype;
|
||||
var parent = {};
|
||||
for( var name in prop ) {
|
||||
if(
|
||||
typeof(prop[name]) == "function" &&
|
||||
typeof(proto[name]) == "function" &&
|
||||
fnTest.test(prop[name])
|
||||
) {
|
||||
parent[name] = proto[name]; // save original function
|
||||
proto[name] = (function(name, fn){
|
||||
return function() {
|
||||
var tmp = this.parent;
|
||||
this.parent = parent[name];
|
||||
var ret = fn.apply(this, arguments);
|
||||
this.parent = tmp;
|
||||
return ret;
|
||||
};
|
||||
})( name, prop[name] );
|
||||
}
|
||||
else {
|
||||
proto[name] = prop[name];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.ig.Class.extend = function(prop) {
|
||||
var parent = this.prototype;
|
||||
|
||||
initializing = true;
|
||||
var prototype = new this();
|
||||
initializing = false;
|
||||
|
||||
for( var name in prop ) {
|
||||
if(
|
||||
typeof(prop[name]) == "function" &&
|
||||
typeof(parent[name]) == "function" &&
|
||||
fnTest.test(prop[name])
|
||||
) {
|
||||
prototype[name] = (function(name, fn){
|
||||
return function() {
|
||||
var tmp = this.parent;
|
||||
this.parent = parent[name];
|
||||
var ret = fn.apply(this, arguments);
|
||||
this.parent = tmp;
|
||||
return ret;
|
||||
};
|
||||
})( name, prop[name] );
|
||||
}
|
||||
else {
|
||||
prototype[name] = prop[name];
|
||||
}
|
||||
}
|
||||
|
||||
function Class() {
|
||||
if( !initializing ) {
|
||||
|
||||
// If this class has a staticInstantiate method, invoke it
|
||||
// and check if we got something back. If not, the normal
|
||||
// constructor (init) is called.
|
||||
if( this.staticInstantiate ) {
|
||||
var obj = this.staticInstantiate.apply(this, arguments);
|
||||
if( obj ) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
for( var p in this ) {
|
||||
if( typeof(this[p]) == 'object' ) {
|
||||
this[p] = ig.copy(this[p]); // deep copy!
|
||||
}
|
||||
}
|
||||
if( this.init ) {
|
||||
this.init.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
Class.prototype = prototype;
|
||||
Class.prototype.constructor = Class;
|
||||
Class.extend = window.ig.Class.extend;
|
||||
Class.inject = inject;
|
||||
Class.classId = prototype.classId = ++lastClassId;
|
||||
|
||||
return Class;
|
||||
};
|
||||
|
||||
// Merge the ImpactMixin - if present - into the 'ig' namespace. This gives other
|
||||
// code the chance to modify 'ig' before it's doing any work.
|
||||
if( window.ImpactMixin ) {
|
||||
ig.merge(ig, window.ImpactMixin);
|
||||
}
|
||||
|
||||
})(window);
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The main() function creates the system, input, sound and game objects,
|
||||
// creates a preloader and starts the run loop
|
||||
|
||||
ig.module(
|
||||
'impact.impact'
|
||||
)
|
||||
.requires(
|
||||
'dom.ready',
|
||||
'impact.loader',
|
||||
'impact.system',
|
||||
'impact.input',
|
||||
'impact.sound'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.main = function( canvasId, gameClass, fps, width, height, scale, loaderClass ) {
|
||||
ig.system = new ig.System( canvasId, fps, width, height, scale || 1 );
|
||||
ig.input = new ig.Input();
|
||||
ig.soundManager = new ig.SoundManager();
|
||||
ig.music = new ig.Music();
|
||||
ig.ready = true;
|
||||
|
||||
var loader = new (loaderClass || ig.Loader)( gameClass, ig.resources );
|
||||
loader.load();
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,321 @@
|
||||
ig.module(
|
||||
'impact.input'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.KEY = {
|
||||
'MOUSE1': -1,
|
||||
'MOUSE2': -3,
|
||||
'MWHEEL_UP': -4,
|
||||
'MWHEEL_DOWN': -5,
|
||||
|
||||
'BACKSPACE': 8,
|
||||
'TAB': 9,
|
||||
'ENTER': 13,
|
||||
'PAUSE': 19,
|
||||
'CAPS': 20,
|
||||
'ESC': 27,
|
||||
'SPACE': 32,
|
||||
'PAGE_UP': 33,
|
||||
'PAGE_DOWN': 34,
|
||||
'END': 35,
|
||||
'HOME': 36,
|
||||
'LEFT_ARROW': 37,
|
||||
'UP_ARROW': 38,
|
||||
'RIGHT_ARROW': 39,
|
||||
'DOWN_ARROW': 40,
|
||||
'INSERT': 45,
|
||||
'DELETE': 46,
|
||||
'_0': 48,
|
||||
'_1': 49,
|
||||
'_2': 50,
|
||||
'_3': 51,
|
||||
'_4': 52,
|
||||
'_5': 53,
|
||||
'_6': 54,
|
||||
'_7': 55,
|
||||
'_8': 56,
|
||||
'_9': 57,
|
||||
'A': 65,
|
||||
'B': 66,
|
||||
'C': 67,
|
||||
'D': 68,
|
||||
'E': 69,
|
||||
'F': 70,
|
||||
'G': 71,
|
||||
'H': 72,
|
||||
'I': 73,
|
||||
'J': 74,
|
||||
'K': 75,
|
||||
'L': 76,
|
||||
'M': 77,
|
||||
'N': 78,
|
||||
'O': 79,
|
||||
'P': 80,
|
||||
'Q': 81,
|
||||
'R': 82,
|
||||
'S': 83,
|
||||
'T': 84,
|
||||
'U': 85,
|
||||
'V': 86,
|
||||
'W': 87,
|
||||
'X': 88,
|
||||
'Y': 89,
|
||||
'Z': 90,
|
||||
'NUMPAD_0': 96,
|
||||
'NUMPAD_1': 97,
|
||||
'NUMPAD_2': 98,
|
||||
'NUMPAD_3': 99,
|
||||
'NUMPAD_4': 100,
|
||||
'NUMPAD_5': 101,
|
||||
'NUMPAD_6': 102,
|
||||
'NUMPAD_7': 103,
|
||||
'NUMPAD_8': 104,
|
||||
'NUMPAD_9': 105,
|
||||
'MULTIPLY': 106,
|
||||
'ADD': 107,
|
||||
'SUBSTRACT': 109,
|
||||
'DECIMAL': 110,
|
||||
'DIVIDE': 111,
|
||||
'F1': 112,
|
||||
'F2': 113,
|
||||
'F3': 114,
|
||||
'F4': 115,
|
||||
'F5': 116,
|
||||
'F6': 117,
|
||||
'F7': 118,
|
||||
'F8': 119,
|
||||
'F9': 120,
|
||||
'F10': 121,
|
||||
'F11': 122,
|
||||
'F12': 123,
|
||||
'SHIFT': 16,
|
||||
'CTRL': 17,
|
||||
'ALT': 18,
|
||||
'PLUS': 187,
|
||||
'COMMA': 188,
|
||||
'MINUS': 189,
|
||||
'PERIOD': 190
|
||||
};
|
||||
|
||||
|
||||
ig.Input = ig.Class.extend({
|
||||
bindings: {},
|
||||
actions: {},
|
||||
presses: {},
|
||||
locks: {},
|
||||
delayedKeyup: {},
|
||||
|
||||
isUsingMouse: false,
|
||||
isUsingKeyboard: false,
|
||||
isUsingAccelerometer: false,
|
||||
mouse: {x: 0, y: 0},
|
||||
accel: {x: 0, y: 0, z: 0},
|
||||
|
||||
|
||||
initMouse: function() {
|
||||
if( this.isUsingMouse ) { return; }
|
||||
this.isUsingMouse = true;
|
||||
var mouseWheelBound = this.mousewheel.bind(this);
|
||||
ig.system.canvas.addEventListener('mousewheel', mouseWheelBound, false );
|
||||
ig.system.canvas.addEventListener('DOMMouseScroll', mouseWheelBound, false );
|
||||
|
||||
ig.system.canvas.addEventListener('contextmenu', this.contextmenu.bind(this), false );
|
||||
ig.system.canvas.addEventListener('mousedown', this.keydown.bind(this), false );
|
||||
ig.system.canvas.addEventListener('mouseup', this.keyup.bind(this), false );
|
||||
ig.system.canvas.addEventListener('mousemove', this.mousemove.bind(this), false );
|
||||
|
||||
if( ig.ua.touchDevice ) {
|
||||
// Standard
|
||||
ig.system.canvas.addEventListener('touchstart', this.keydown.bind(this), false );
|
||||
ig.system.canvas.addEventListener('touchend', this.keyup.bind(this), false );
|
||||
ig.system.canvas.addEventListener('touchmove', this.mousemove.bind(this), false );
|
||||
|
||||
// MS
|
||||
ig.system.canvas.addEventListener('MSPointerDown', this.keydown.bind(this), false );
|
||||
ig.system.canvas.addEventListener('MSPointerUp', this.keyup.bind(this), false );
|
||||
ig.system.canvas.addEventListener('MSPointerMove', this.mousemove.bind(this), false );
|
||||
ig.system.canvas.style.msTouchAction = 'none';
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
initKeyboard: function() {
|
||||
if( this.isUsingKeyboard ) { return; }
|
||||
this.isUsingKeyboard = true;
|
||||
window.addEventListener('keydown', this.keydown.bind(this), false );
|
||||
window.addEventListener('keyup', this.keyup.bind(this), false );
|
||||
},
|
||||
|
||||
|
||||
initAccelerometer: function() {
|
||||
if( this.isUsingAccelerometer ) { return; }
|
||||
this.isUsingAccelerometer = true;
|
||||
window.addEventListener('devicemotion', this.devicemotion.bind(this), false );
|
||||
},
|
||||
|
||||
|
||||
mousewheel: function( event ) {
|
||||
var delta = event.wheelDelta ? event.wheelDelta : (event.detail * -1);
|
||||
var code = delta > 0 ? ig.KEY.MWHEEL_UP : ig.KEY.MWHEEL_DOWN;
|
||||
var action = this.bindings[code];
|
||||
if( action ) {
|
||||
this.actions[action] = true;
|
||||
this.presses[action] = true;
|
||||
this.delayedKeyup[action] = true;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mousemove: function( event ) {
|
||||
var internalWidth = parseInt(ig.system.canvas.offsetWidth) || ig.system.realWidth;
|
||||
var scale = ig.system.scale * (internalWidth / ig.system.realWidth);
|
||||
|
||||
var pos = {left: 0, top: 0};
|
||||
if( ig.system.canvas.getBoundingClientRect ) {
|
||||
pos = ig.system.canvas.getBoundingClientRect();
|
||||
}
|
||||
|
||||
var ev = event.touches ? event.touches[0] : event;
|
||||
this.mouse.x = (ev.clientX - pos.left) / scale;
|
||||
this.mouse.y = (ev.clientY - pos.top) / scale;
|
||||
},
|
||||
|
||||
|
||||
contextmenu: function( event ) {
|
||||
if( this.bindings[ig.KEY.MOUSE2] ) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
keydown: function( event ) {
|
||||
var tag = event.target.tagName;
|
||||
if( tag == 'INPUT' || tag == 'TEXTAREA' ) { return; }
|
||||
|
||||
var code = event.type == 'keydown'
|
||||
? event.keyCode
|
||||
: (event.button == 2 ? ig.KEY.MOUSE2 : ig.KEY.MOUSE1);
|
||||
|
||||
// Focus window element for mouse clicks. Prevents issues when
|
||||
// running the game in an iframe.
|
||||
if( code < 0 && !ig.ua.mobile ) {
|
||||
window.focus();
|
||||
}
|
||||
|
||||
if( event.type == 'touchstart' || event.type == 'mousedown' ) {
|
||||
this.mousemove( event );
|
||||
}
|
||||
|
||||
var action = this.bindings[code];
|
||||
if( action ) {
|
||||
this.actions[action] = true;
|
||||
if( !this.locks[action] ) {
|
||||
this.presses[action] = true;
|
||||
this.locks[action] = true;
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
keyup: function( event ) {
|
||||
var tag = event.target.tagName;
|
||||
if( tag == 'INPUT' || tag == 'TEXTAREA' ) { return; }
|
||||
|
||||
var code = event.type == 'keyup'
|
||||
? event.keyCode
|
||||
: (event.button == 2 ? ig.KEY.MOUSE2 : ig.KEY.MOUSE1);
|
||||
|
||||
var action = this.bindings[code];
|
||||
if( action ) {
|
||||
this.delayedKeyup[action] = true;
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
devicemotion: function( event ) {
|
||||
this.accel = event.accelerationIncludingGravity;
|
||||
},
|
||||
|
||||
|
||||
bind: function( key, action ) {
|
||||
if( key < 0 ) { this.initMouse(); }
|
||||
else if( key > 0 ) { this.initKeyboard(); }
|
||||
this.bindings[key] = action;
|
||||
},
|
||||
|
||||
|
||||
bindTouch: function( selector, action ) {
|
||||
var element = ig.$( selector );
|
||||
|
||||
var that = this;
|
||||
element.addEventListener('touchstart', function(ev) {that.touchStart( ev, action );}, false);
|
||||
element.addEventListener('touchend', function(ev) {that.touchEnd( ev, action );}, false);
|
||||
element.addEventListener('MSPointerDown', function(ev) {that.touchStart( ev, action );}, false);
|
||||
element.addEventListener('MSPointerUp', function(ev) {that.touchEnd( ev, action );}, false);
|
||||
},
|
||||
|
||||
|
||||
unbind: function( key ) {
|
||||
var action = this.bindings[key];
|
||||
this.delayedKeyup[action] = true;
|
||||
|
||||
this.bindings[key] = null;
|
||||
},
|
||||
|
||||
|
||||
unbindAll: function() {
|
||||
this.bindings = {};
|
||||
this.actions = {};
|
||||
this.presses = {};
|
||||
this.locks = {};
|
||||
this.delayedKeyup = {};
|
||||
},
|
||||
|
||||
|
||||
state: function( action ) {
|
||||
return this.actions[action];
|
||||
},
|
||||
|
||||
|
||||
pressed: function( action ) {
|
||||
return this.presses[action];
|
||||
},
|
||||
|
||||
released: function( action ) {
|
||||
return !!this.delayedKeyup[action];
|
||||
},
|
||||
|
||||
clearPressed: function() {
|
||||
for( var action in this.delayedKeyup ) {
|
||||
this.actions[action] = false;
|
||||
this.locks[action] = false;
|
||||
}
|
||||
this.delayedKeyup = {};
|
||||
this.presses = {};
|
||||
},
|
||||
|
||||
touchStart: function( event, action ) {
|
||||
this.actions[action] = true;
|
||||
this.presses[action] = true;
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
touchEnd: function( event, action ) {
|
||||
this.delayedKeyup[action] = true;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
ig.module(
|
||||
'impact.loader'
|
||||
)
|
||||
.requires(
|
||||
'impact.image',
|
||||
'impact.font',
|
||||
'impact.sound'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.Loader = ig.Class.extend({
|
||||
resources: [],
|
||||
|
||||
gameClass: null,
|
||||
status: 0,
|
||||
done: false,
|
||||
|
||||
_unloaded: [],
|
||||
_drawStatus: 0,
|
||||
_intervalId: 0,
|
||||
_loadCallbackBound: null,
|
||||
|
||||
|
||||
init: function( gameClass, resources ) {
|
||||
this.gameClass = gameClass;
|
||||
this.resources = resources;
|
||||
this._loadCallbackBound = this._loadCallback.bind(this);
|
||||
|
||||
for( var i = 0; i < this.resources.length; i++ ) {
|
||||
this._unloaded.push( this.resources[i].path );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
load: function() {
|
||||
ig.system.clear( '#000' );
|
||||
|
||||
if( !this.resources.length ) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
for( var i = 0; i < this.resources.length; i++ ) {
|
||||
this.loadResource( this.resources[i] );
|
||||
}
|
||||
this._intervalId = setInterval( this.draw.bind(this), 16 );
|
||||
},
|
||||
|
||||
|
||||
loadResource: function( res ) {
|
||||
res.load( this._loadCallbackBound );
|
||||
},
|
||||
|
||||
|
||||
end: function() {
|
||||
if( this.done ) { return; }
|
||||
|
||||
this.done = true;
|
||||
clearInterval( this._intervalId );
|
||||
ig.system.setGame( this.gameClass );
|
||||
},
|
||||
|
||||
|
||||
draw: function() {
|
||||
this._drawStatus += (this.status - this._drawStatus)/5;
|
||||
var s = ig.system.scale;
|
||||
var w = ig.system.width * 0.6;
|
||||
var h = ig.system.height * 0.1;
|
||||
var x = ig.system.width * 0.5-w/2;
|
||||
var y = ig.system.height * 0.5-h/2;
|
||||
|
||||
ig.system.context.fillStyle = '#000';
|
||||
ig.system.context.fillRect( 0, 0, 480, 320 );
|
||||
|
||||
ig.system.context.fillStyle = '#fff';
|
||||
ig.system.context.fillRect( x*s, y*s, w*s, h*s );
|
||||
|
||||
ig.system.context.fillStyle = '#000';
|
||||
ig.system.context.fillRect( x*s+s, y*s+s, w*s-s-s, h*s-s-s );
|
||||
|
||||
ig.system.context.fillStyle = '#fff';
|
||||
ig.system.context.fillRect( x*s, y*s, w*s*this._drawStatus, h*s );
|
||||
},
|
||||
|
||||
|
||||
_loadCallback: function( path, status ) {
|
||||
if( status ) {
|
||||
this._unloaded.erase( path );
|
||||
}
|
||||
else {
|
||||
throw( 'Failed to load resource: ' + path );
|
||||
}
|
||||
|
||||
this.status = 1 - (this._unloaded.length / this.resources.length);
|
||||
if( this._unloaded.length == 0 ) { // all done?
|
||||
setTimeout( this.end.bind(this), 250 );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
ig.module(
|
||||
'impact.map'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.Map = ig.Class.extend({
|
||||
tilesize: 8,
|
||||
width: 1,
|
||||
height: 1,
|
||||
data: [[]],
|
||||
name: null,
|
||||
|
||||
|
||||
init: function( tilesize, data ) {
|
||||
this.tilesize = tilesize;
|
||||
this.data = data;
|
||||
this.height = data.length;
|
||||
this.width = data[0].length;
|
||||
|
||||
this.pxWidth = this.width * this.tilesize;
|
||||
this.pxHeight = this.height * this.tilesize;
|
||||
},
|
||||
|
||||
|
||||
getTile: function( x, y ) {
|
||||
var tx = Math.floor( x / this.tilesize );
|
||||
var ty = Math.floor( y / this.tilesize );
|
||||
if(
|
||||
(tx >= 0 && tx < this.width) &&
|
||||
(ty >= 0 && ty < this.height)
|
||||
) {
|
||||
return this.data[ty][tx];
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setTile: function( x, y, tile ) {
|
||||
var tx = Math.floor( x / this.tilesize );
|
||||
var ty = Math.floor( y / this.tilesize );
|
||||
if(
|
||||
(tx >= 0 && tx < this.width) &&
|
||||
(ty >= 0 && ty < this.height)
|
||||
) {
|
||||
this.data[ty][tx] = tile;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,516 @@
|
||||
ig.module(
|
||||
'impact.sound'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.SoundManager = ig.Class.extend({
|
||||
clips: {},
|
||||
volume: 1,
|
||||
format: null,
|
||||
|
||||
init: function() {
|
||||
// Quick sanity check if the Browser supports the Audio tag
|
||||
if( !ig.Sound.enabled || !window.Audio ) {
|
||||
ig.Sound.enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Probe sound formats and determine the file extension to load
|
||||
var probe = new Audio();
|
||||
for( var i = 0; i < ig.Sound.use.length; i++ ) {
|
||||
var format = ig.Sound.use[i];
|
||||
if( probe.canPlayType(format.mime) ) {
|
||||
this.format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No compatible format found? -> Disable sound
|
||||
if( !this.format ) {
|
||||
ig.Sound.enabled = false;
|
||||
}
|
||||
|
||||
// Create WebAudio Context
|
||||
if( ig.Sound.enabled && ig.Sound.useWebAudio ) {
|
||||
this.audioContext = new AudioContext();
|
||||
this.boundWebAudioUnlock = this.unlockWebAudio.bind(this);
|
||||
document.addEventListener('touchstart', this.boundWebAudioUnlock, false);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
unlockWebAudio: function() {
|
||||
document.removeEventListener('touchstart', this.boundWebAudioUnlock, false);
|
||||
|
||||
// create empty buffer
|
||||
var buffer = this.audioContext.createBuffer(1, 1, 22050);
|
||||
var source = this.audioContext.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
|
||||
source.connect(this.audioContext.destination);
|
||||
source.start(0);
|
||||
},
|
||||
|
||||
load: function( path, multiChannel, loadCallback ) {
|
||||
if( multiChannel && ig.Sound.useWebAudio ) {
|
||||
// Requested as Multichannel and we're using WebAudio?
|
||||
return this.loadWebAudio( path, multiChannel, loadCallback );
|
||||
}
|
||||
else {
|
||||
// Oldschool HTML5 Audio - always used for Music
|
||||
return this.loadHTML5Audio( path, multiChannel, loadCallback );
|
||||
}
|
||||
},
|
||||
|
||||
loadWebAudio: function( path, multiChannel, loadCallback ) {
|
||||
// Path to the soundfile with the right extension (.ogg or .mp3)
|
||||
var realPath = ig.prefix + path.replace(/[^\.]+$/, this.format.ext) + ig.nocache;
|
||||
|
||||
if( this.clips[path] ) {
|
||||
return this.clips[path];
|
||||
}
|
||||
|
||||
var audioSource = new ig.Sound.WebAudioSource()
|
||||
this.clips[path] = audioSource;
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', realPath, true);
|
||||
request.responseType = 'arraybuffer';
|
||||
|
||||
|
||||
var that = this;
|
||||
request.onload = function(ev) {
|
||||
that.audioContext.decodeAudioData(request.response,
|
||||
function(buffer) {
|
||||
audioSource.buffer = buffer;
|
||||
loadCallback( path, true, ev );
|
||||
},
|
||||
function(ev) {
|
||||
loadCallback( path, false, ev );
|
||||
}
|
||||
);
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
loadCallback( path, false, ev );
|
||||
};
|
||||
request.send();
|
||||
|
||||
return audioSource;
|
||||
},
|
||||
|
||||
loadHTML5Audio: function( path, multiChannel, loadCallback ) {
|
||||
|
||||
// Path to the soundfile with the right extension (.ogg or .mp3)
|
||||
var realPath = ig.prefix + path.replace(/[^\.]+$/, this.format.ext) + ig.nocache;
|
||||
|
||||
// Sound file already loaded?
|
||||
if( this.clips[path] ) {
|
||||
// Loaded as WebAudio, but now requested as HTML5 Audio? Probably Music?
|
||||
if( this.clips[path] instanceof ig.Sound.WebAudioSource ) {
|
||||
return this.clips[path];
|
||||
}
|
||||
|
||||
// Only loaded as single channel and now requested as multichannel?
|
||||
if( multiChannel && this.clips[path].length < ig.Sound.channels ) {
|
||||
for( var i = this.clips[path].length; i < ig.Sound.channels; i++ ) {
|
||||
var a = new Audio( realPath );
|
||||
a.load();
|
||||
this.clips[path].push( a );
|
||||
}
|
||||
}
|
||||
return this.clips[path][0];
|
||||
}
|
||||
|
||||
var clip = new Audio( realPath );
|
||||
if( loadCallback ) {
|
||||
|
||||
// The canplaythrough event is dispatched when the browser determines
|
||||
// that the sound can be played without interuption, provided the
|
||||
// download rate doesn't change.
|
||||
// Mobile browsers stubbornly refuse to preload HTML5, so we simply
|
||||
// ignore the canplaythrough event and immediately "fake" a successful
|
||||
// load callback
|
||||
if( ig.ua.mobile ) {
|
||||
setTimeout(function(){
|
||||
loadCallback( path, true, null );
|
||||
}, 0);
|
||||
}
|
||||
else {
|
||||
clip.addEventListener( 'canplaythrough', function cb(ev){
|
||||
clip.removeEventListener('canplaythrough', cb, false);
|
||||
loadCallback( path, true, ev );
|
||||
}, false );
|
||||
|
||||
clip.addEventListener( 'error', function(ev){
|
||||
loadCallback( path, false, ev );
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
clip.preload = 'auto';
|
||||
clip.load();
|
||||
|
||||
|
||||
this.clips[path] = [clip];
|
||||
if( multiChannel ) {
|
||||
for( var i = 1; i < ig.Sound.channels; i++ ) {
|
||||
var a = new Audio(realPath);
|
||||
a.load();
|
||||
this.clips[path].push( a );
|
||||
}
|
||||
}
|
||||
|
||||
return clip;
|
||||
},
|
||||
|
||||
|
||||
get: function( path ) {
|
||||
// Find and return a channel that is not currently playing
|
||||
var channels = this.clips[path];
|
||||
|
||||
// Is this a WebAudio source? We only ever have one for each Sound
|
||||
if( channels && channels instanceof ig.Sound.WebAudioSource ) {
|
||||
return channels;
|
||||
}
|
||||
|
||||
// Oldschool HTML5 Audio - find a channel that's not currently
|
||||
// playing or, if all are playing, rewind one
|
||||
for( var i = 0, clip; clip = channels[i++]; ) {
|
||||
if( clip.paused || clip.ended ) {
|
||||
if( clip.ended ) {
|
||||
clip.currentTime = 0;
|
||||
}
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
|
||||
// Still here? Pause and rewind the first channel
|
||||
channels[0].pause();
|
||||
channels[0].currentTime = 0;
|
||||
return channels[0];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.Music = ig.Class.extend({
|
||||
tracks: [],
|
||||
namedTracks: {},
|
||||
currentTrack: null,
|
||||
currentIndex: 0,
|
||||
random: false,
|
||||
|
||||
_volume: 1,
|
||||
_loop: false,
|
||||
_fadeInterval: 0,
|
||||
_fadeTimer: null,
|
||||
_endedCallbackBound: null,
|
||||
|
||||
|
||||
init: function() {
|
||||
this._endedCallbackBound = this._endedCallback.bind(this);
|
||||
|
||||
Object.defineProperty(this,"volume", {
|
||||
get: this.getVolume.bind(this),
|
||||
set: this.setVolume.bind(this)
|
||||
});
|
||||
|
||||
Object.defineProperty(this,"loop", {
|
||||
get: this.getLooping.bind(this),
|
||||
set: this.setLooping.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
add: function( music, name ) {
|
||||
if( !ig.Sound.enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var path = music instanceof ig.Sound ? music.path : music;
|
||||
|
||||
var track = ig.soundManager.load(path, false);
|
||||
|
||||
// Did we get a WebAudio Source? This is suboptimal; Music should be loaded
|
||||
// as HTML5 Audio so it can be streamed
|
||||
if( track instanceof ig.Sound.WebAudioSource ) {
|
||||
// Since this error will likely occour at game start, we stop the game
|
||||
// to not produce any more errors.
|
||||
ig.system.stopRunLoop();
|
||||
throw(
|
||||
"Sound '"+path+"' loaded as Multichannel but used for Music. " +
|
||||
"Set the multiChannel param to false when loading, e.g.: new ig.Sound(path, false)"
|
||||
);
|
||||
}
|
||||
|
||||
track.loop = this._loop;
|
||||
track.volume = this._volume;
|
||||
track.addEventListener( 'ended', this._endedCallbackBound, false );
|
||||
this.tracks.push( track );
|
||||
|
||||
if( name ) {
|
||||
this.namedTracks[name] = track;
|
||||
}
|
||||
|
||||
if( !this.currentTrack ) {
|
||||
this.currentTrack = track;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
next: function() {
|
||||
if( !this.tracks.length ) { return; }
|
||||
|
||||
this.stop();
|
||||
this.currentIndex = this.random
|
||||
? Math.floor(Math.random() * this.tracks.length)
|
||||
: (this.currentIndex + 1) % this.tracks.length;
|
||||
this.currentTrack = this.tracks[this.currentIndex];
|
||||
this.play();
|
||||
},
|
||||
|
||||
|
||||
pause: function() {
|
||||
if( !this.currentTrack ) { return; }
|
||||
this.currentTrack.pause();
|
||||
},
|
||||
|
||||
|
||||
stop: function() {
|
||||
if( !this.currentTrack ) { return; }
|
||||
this.currentTrack.pause();
|
||||
this.currentTrack.currentTime = 0;
|
||||
},
|
||||
|
||||
|
||||
play: function( name ) {
|
||||
// If a name was provided, stop playing the current track (if any)
|
||||
// and play the named track
|
||||
if( name && this.namedTracks[name] ) {
|
||||
var newTrack = this.namedTracks[name];
|
||||
if( newTrack != this.currentTrack ) {
|
||||
this.stop();
|
||||
this.currentTrack = newTrack;
|
||||
}
|
||||
}
|
||||
else if( !this.currentTrack ) {
|
||||
return;
|
||||
}
|
||||
this.currentTrack.play();
|
||||
},
|
||||
|
||||
|
||||
getLooping: function() {
|
||||
return this._loop;
|
||||
},
|
||||
|
||||
|
||||
setLooping: function( l ) {
|
||||
this._loop = l;
|
||||
for( var i in this.tracks ) {
|
||||
this.tracks[i].loop = l;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getVolume: function() {
|
||||
return this._volume;
|
||||
},
|
||||
|
||||
|
||||
setVolume: function( v ) {
|
||||
this._volume = v.limit(0,1);
|
||||
for( var i in this.tracks ) {
|
||||
this.tracks[i].volume = this._volume;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
fadeOut: function( time ) {
|
||||
if( !this.currentTrack ) { return; }
|
||||
|
||||
clearInterval( this._fadeInterval );
|
||||
this.fadeTimer = new ig.Timer( time );
|
||||
this._fadeInterval = setInterval( this._fadeStep.bind(this), 50 );
|
||||
},
|
||||
|
||||
|
||||
_fadeStep: function() {
|
||||
var v = this.fadeTimer.delta()
|
||||
.map(-this.fadeTimer.target, 0, 1, 0)
|
||||
.limit( 0, 1 )
|
||||
* this._volume;
|
||||
|
||||
if( v <= 0.01 ) {
|
||||
this.stop();
|
||||
this.currentTrack.volume = this._volume;
|
||||
clearInterval( this._fadeInterval );
|
||||
}
|
||||
else {
|
||||
this.currentTrack.volume = v;
|
||||
}
|
||||
},
|
||||
|
||||
_endedCallback: function() {
|
||||
if( this._loop ) {
|
||||
this.play();
|
||||
}
|
||||
else {
|
||||
this.next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.Sound = ig.Class.extend({
|
||||
path: '',
|
||||
volume: 1,
|
||||
currentClip: null,
|
||||
multiChannel: true,
|
||||
_loop: false,
|
||||
|
||||
|
||||
init: function( path, multiChannel ) {
|
||||
this.path = path;
|
||||
this.multiChannel = (multiChannel !== false);
|
||||
|
||||
Object.defineProperty(this,"loop", {
|
||||
get: this.getLooping.bind(this),
|
||||
set: this.setLooping.bind(this)
|
||||
});
|
||||
|
||||
this.load();
|
||||
},
|
||||
|
||||
getLooping: function() {
|
||||
return this._loop;
|
||||
},
|
||||
|
||||
setLooping: function( loop ) {
|
||||
this._loop = loop;
|
||||
|
||||
if( this.currentClip ) {
|
||||
this.currentClip.loop = loop;
|
||||
}
|
||||
},
|
||||
|
||||
load: function( loadCallback ) {
|
||||
if( !ig.Sound.enabled ) {
|
||||
if( loadCallback ) {
|
||||
loadCallback( this.path, true );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( ig.ready ) {
|
||||
ig.soundManager.load( this.path, this.multiChannel, loadCallback );
|
||||
}
|
||||
else {
|
||||
ig.addResource( this );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
play: function() {
|
||||
if( !ig.Sound.enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentClip = ig.soundManager.get( this.path );
|
||||
this.currentClip.loop = this._loop;
|
||||
this.currentClip.volume = ig.soundManager.volume * this.volume;
|
||||
this.currentClip.play();
|
||||
},
|
||||
|
||||
|
||||
stop: function() {
|
||||
if( this.currentClip ) {
|
||||
this.currentClip.pause();
|
||||
this.currentClip.currentTime = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.Sound.WebAudioSource = ig.Class.extend({
|
||||
sources: [],
|
||||
gain: null,
|
||||
buffer: null,
|
||||
_loop: false,
|
||||
|
||||
init: function() {
|
||||
this.gain = ig.soundManager.audioContext.createGain();
|
||||
this.gain.connect(ig.soundManager.audioContext.destination);
|
||||
|
||||
Object.defineProperty(this,"loop", {
|
||||
get: this.getLooping.bind(this),
|
||||
set: this.setLooping.bind(this)
|
||||
});
|
||||
|
||||
Object.defineProperty(this,"volume", {
|
||||
get: this.getVolume.bind(this),
|
||||
set: this.setVolume.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
play: function() {
|
||||
if( !this.buffer ) { return; }
|
||||
var source = ig.soundManager.audioContext.createBufferSource();
|
||||
source.buffer = this.buffer;
|
||||
source.connect(this.gain);
|
||||
source.loop = this._loop;
|
||||
|
||||
// Add this new source to our sources array and remove it again
|
||||
// later when it has finished playing.
|
||||
var that = this;
|
||||
this.sources.push(source);
|
||||
source.onended = function(){ that.sources.erase(source); }
|
||||
|
||||
source.start(0);
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
for( var i = 0; i < this.sources.length; i++ ) {
|
||||
try{
|
||||
this.sources[i].stop();
|
||||
} catch(err){}
|
||||
}
|
||||
},
|
||||
|
||||
getLooping: function() {
|
||||
return this._loop;
|
||||
},
|
||||
|
||||
setLooping: function( loop ) {
|
||||
this._loop = loop;
|
||||
|
||||
for( var i = 0; i < this.sources.length; i++ ) {
|
||||
this.sources[i].loop = loop;
|
||||
}
|
||||
},
|
||||
|
||||
getVolume: function() {
|
||||
return this.gain.gain.value;
|
||||
},
|
||||
|
||||
setVolume: function( volume ) {
|
||||
this.gain.gain.value = volume;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.Sound.FORMAT = {
|
||||
MP3: {ext: 'mp3', mime: 'audio/mpeg'},
|
||||
M4A: {ext: 'm4a', mime: 'audio/mp4; codecs=mp4a'},
|
||||
OGG: {ext: 'ogg', mime: 'audio/ogg; codecs=vorbis'},
|
||||
WEBM: {ext: 'webm', mime: 'audio/webm; codecs=vorbis'},
|
||||
CAF: {ext: 'caf', mime: 'audio/x-caf'}
|
||||
};
|
||||
ig.Sound.use = [ig.Sound.FORMAT.OGG, ig.Sound.FORMAT.MP3];
|
||||
ig.Sound.channels = 4;
|
||||
ig.Sound.enabled = true;
|
||||
|
||||
ig.normalizeVendorAttribute(window, 'AudioContext');
|
||||
ig.Sound.useWebAudio = !!window.AudioContext && !window.nwf;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
ig.module(
|
||||
'impact.system'
|
||||
)
|
||||
.requires(
|
||||
'impact.timer',
|
||||
'impact.image'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.System = ig.Class.extend({
|
||||
fps: 30,
|
||||
width: 320,
|
||||
height: 240,
|
||||
realWidth: 320,
|
||||
realHeight: 240,
|
||||
scale: 1,
|
||||
|
||||
tick: 0,
|
||||
animationId: 0,
|
||||
newGameClass: null,
|
||||
running: false,
|
||||
|
||||
delegate: null,
|
||||
clock: null,
|
||||
canvas: null,
|
||||
context: null,
|
||||
|
||||
init: function( canvasId, fps, width, height, scale ) {
|
||||
this.fps = fps;
|
||||
|
||||
this.clock = new ig.Timer();
|
||||
this.canvas = ig.$(canvasId);
|
||||
this.resize( width, height, scale );
|
||||
this.context = this.canvas.getContext('2d');
|
||||
|
||||
this.getDrawPos = ig.System.drawMode;
|
||||
|
||||
// Automatically switch to crisp scaling when using a scale
|
||||
// other than 1
|
||||
if( this.scale != 1 ) {
|
||||
ig.System.scaleMode = ig.System.SCALE.CRISP;
|
||||
}
|
||||
ig.System.scaleMode( this.canvas, this.context );
|
||||
},
|
||||
|
||||
|
||||
resize: function( width, height, scale ) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.scale = scale || this.scale;
|
||||
|
||||
this.realWidth = this.width * this.scale;
|
||||
this.realHeight = this.height * this.scale;
|
||||
this.canvas.width = this.realWidth;
|
||||
this.canvas.height = this.realHeight;
|
||||
},
|
||||
|
||||
|
||||
setGame: function( gameClass ) {
|
||||
if( this.running ) {
|
||||
this.newGameClass = gameClass;
|
||||
}
|
||||
else {
|
||||
this.setGameNow( gameClass );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setGameNow: function( gameClass ) {
|
||||
ig.game = new (gameClass)();
|
||||
ig.system.setDelegate( ig.game );
|
||||
},
|
||||
|
||||
|
||||
setDelegate: function( object ) {
|
||||
if( typeof(object.run) == 'function' ) {
|
||||
this.delegate = object;
|
||||
this.startRunLoop();
|
||||
} else {
|
||||
throw( 'System.setDelegate: No run() function in object' );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
stopRunLoop: function() {
|
||||
ig.clearAnimation( this.animationId );
|
||||
this.running = false;
|
||||
},
|
||||
|
||||
|
||||
startRunLoop: function() {
|
||||
this.stopRunLoop();
|
||||
this.animationId = ig.setAnimation( this.run.bind(this), this.canvas );
|
||||
this.running = true;
|
||||
},
|
||||
|
||||
|
||||
clear: function( color ) {
|
||||
this.context.fillStyle = color;
|
||||
this.context.fillRect( 0, 0, this.realWidth, this.realHeight );
|
||||
},
|
||||
|
||||
|
||||
run: function() {
|
||||
ig.Timer.step();
|
||||
this.tick = this.clock.tick();
|
||||
|
||||
this.delegate.run();
|
||||
ig.input.clearPressed();
|
||||
|
||||
if( this.newGameClass ) {
|
||||
this.setGameNow( this.newGameClass );
|
||||
this.newGameClass = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getDrawPos: null // Set through constructor
|
||||
});
|
||||
|
||||
ig.System.DRAW = {
|
||||
AUTHENTIC: function( p ) { return Math.round(p) * this.scale; },
|
||||
SMOOTH: function( p ) { return Math.round(p * this.scale); },
|
||||
SUBPIXEL: function( p ) { return p * this.scale; }
|
||||
};
|
||||
ig.System.drawMode = ig.System.DRAW.SMOOTH;
|
||||
|
||||
ig.System.SCALE = {
|
||||
CRISP: function( canvas, context ) {
|
||||
ig.setVendorAttribute( context, 'imageSmoothingEnabled', false );
|
||||
canvas.style.imageRendering = '-moz-crisp-edges';
|
||||
canvas.style.imageRendering = '-o-crisp-edges';
|
||||
canvas.style.imageRendering = '-webkit-optimize-contrast';
|
||||
canvas.style.imageRendering = 'crisp-edges';
|
||||
canvas.style.msInterpolationMode = 'nearest-neighbor'; // No effect on Canvas :/
|
||||
},
|
||||
SMOOTH: function( canvas, context ) {
|
||||
ig.setVendorAttribute( context, 'imageSmoothingEnabled', true );
|
||||
canvas.style.imageRendering = '';
|
||||
canvas.style.msInterpolationMode = '';
|
||||
}
|
||||
};
|
||||
ig.System.scaleMode = ig.System.SCALE.SMOOTH;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
ig.module(
|
||||
'impact.timer'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.Timer = ig.Class.extend({
|
||||
target: 0,
|
||||
base: 0,
|
||||
last: 0,
|
||||
pausedAt: 0,
|
||||
|
||||
init: function( seconds ) {
|
||||
this.base = ig.Timer.time;
|
||||
this.last = ig.Timer.time;
|
||||
|
||||
this.target = seconds || 0;
|
||||
},
|
||||
|
||||
|
||||
set: function( seconds ) {
|
||||
this.target = seconds || 0;
|
||||
this.base = ig.Timer.time;
|
||||
this.pausedAt = 0;
|
||||
},
|
||||
|
||||
|
||||
reset: function() {
|
||||
this.base = ig.Timer.time;
|
||||
this.pausedAt = 0;
|
||||
},
|
||||
|
||||
|
||||
tick: function() {
|
||||
var delta = ig.Timer.time - this.last;
|
||||
this.last = ig.Timer.time;
|
||||
return (this.pausedAt ? 0 : delta);
|
||||
},
|
||||
|
||||
|
||||
delta: function() {
|
||||
return (this.pausedAt || ig.Timer.time) - this.base - this.target;
|
||||
},
|
||||
|
||||
|
||||
pause: function() {
|
||||
if( !this.pausedAt ) {
|
||||
this.pausedAt = ig.Timer.time;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
unpause: function() {
|
||||
if( this.pausedAt ) {
|
||||
this.base += ig.Timer.time - this.pausedAt;
|
||||
this.pausedAt = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ig.Timer._last = 0;
|
||||
ig.Timer.time = Number.MIN_VALUE;
|
||||
ig.Timer.timeScale = 1;
|
||||
ig.Timer.maxStep = 0.05;
|
||||
|
||||
ig.Timer.step = function() {
|
||||
var current = Date.now();
|
||||
var delta = (current - ig.Timer._last) / 1000;
|
||||
ig.Timer.time += Math.min(delta, ig.Timer.maxStep) * ig.Timer.timeScale;
|
||||
ig.Timer._last = current;
|
||||
};
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user