/*global Quintus:false */ Quintus["2D"] = function(Q) { Q.component('viewport',{ added: function() { this.entity.on('prerender',this,'prerender'); this.entity.on('render',this,'postrender'); this.x = 0; this.y = 0; this.offsetX = 0; this.offsetY = 0; this.centerX = Q.width/2; this.centerY = Q.height/2; this.scale = 1; }, extend: { follow: function(sprite,directions,boundingBox) { this.off('poststep',this.viewport,'follow'); this.viewport.directions = directions || { x: true, y: true }; this.viewport.following = sprite; this.viewport.boundingBox = boundingBox; this.on('poststep',this.viewport,'follow'); this.viewport.follow(true); }, unfollow: function() { this.off('poststep',this.viewport,'follow'); }, centerOn: function(x,y) { this.viewport.centerOn(x,y); }, moveTo: function(x,y) { return this.viewport.moveTo(x,y); } }, follow: function(first) { var followX = Q._isFunction(this.directions.x) ? this.directions.x(this.following) : this.directions.x; var followY = Q._isFunction(this.directions.y) ? this.directions.y(this.following) : this.directions.y; this[first === true ? 'centerOn' : 'softCenterOn']( followX ? this.following.p.x + this.following.p.w/2 - this.offsetX : undefined, followY ? this.following.p.y + this.following.p.h/2 - this.offsetY : undefined ); }, offset: function(x,y) { this.offsetX = x; this.offsetY = y; }, softCenterOn: function(x,y) { if(x !== void 0) { var dx = (x - Q.width / 2 / this.scale - this.x)/3; if(this.boundingBox) { if(this.x + dx < this.boundingBox.minX) { this.x = this.boundingBox.minX / this.scale; } else if(this.x + dx > (this.boundingBox.maxX - Q.width) / this.scale) { this.x = (this.boundingBox.maxX - Q.width) / this.scale; } else { this.x += dx; } } else { this.x += dx; } } if(y !== void 0) { var dy = (y - Q.height / 2 / this.scale - this.y)/3; if(this.boundingBox) { if(this.y + dy < this.boundingBox.minY) { this.y = this.boundingBox.minY / this.scale; } else if(this.y + dy > (this.boundingBox.maxY - Q.height) / this.scale) { this.y = (this.boundingBox.maxY - Q.height) / this.scale; } else { this.y += dy; } } else { this.y += dy; } } }, centerOn: function(x,y) { if(x !== void 0) { this.x = x - Q.width / 2 / this.scale; } if(y !== void 0) { this.y = y - Q.height / 2 / this.scale; } }, moveTo: function(x,y) { if(x !== void 0) { this.x = x; } if(y !== void 0) { this.y = y; } return this.entity; }, prerender: function() { this.centerX = this.x + Q.width / 2 /this.scale; this.centerY = this.y + Q.height / 2 /this.scale; Q.ctx.save(); Q.ctx.translate(Math.floor(Q.width/2),Math.floor(Q.height/2)); Q.ctx.scale(this.scale,this.scale); Q.ctx.translate(-Math.floor(this.centerX), -Math.floor(this.centerY)); }, postrender: function() { Q.ctx.restore(); } }); Q.TileLayer = Q.Sprite.extend({ init: function(props) { this._super(props,{ tileW: 32, tileH: 32, blockTileW: 10, blockTileH: 10, type: 1, layerIndex: 0 }); if(this.p.dataAsset) { this.load(this.p.dataAsset); } this.blocks = []; this.p.blockW = this.p.tileW * this.p.blockTileW; this.p.blockH = this.p.tileH * this.p.blockTileH; this.colBounds = {}; this.directions = [ 'top','left','right','bottom']; this.collisionObject = { p: { w: this.p.tileW, h: this.p.tileH, cx: this.p.tileW/2, cy: this.p.tileH/2 } }; this.collisionNormal = { separate: []}; }, load: function(dataAsset) { var fileParts = dataAsset.split("."), fileExt = fileParts[fileParts.length-1].toLowerCase(), data; if (fileExt === "json") { data = Q._isString(dataAsset) ? Q.asset(dataAsset) : dataAsset; } else if (fileExt === "tmx" || fileExt === "xml") { var parser = new DOMParser(), doc = parser.parseFromString(Q.asset(dataAsset), "application/xml"); var layer = doc.getElementsByTagName("layer")[this.p.layerIndex], width = parseInt(layer.getAttribute("width"),10), height = parseInt(layer.getAttribute("height"),10); var tiles = layer.getElementsByTagName("tile"), idx = 0; data = []; for(var y = 0;y < height;y++) { data[y] = []; for(var x = 0;x < width;x++) { var tile = tiles[idx]; data[y].push(parseInt(tile.getAttribute("gid")-1,10)); idx++; } } } else { throw "file type not supported"; } this.p.tiles = data; this.p.rows = data.length; this.p.cols = data[0].length; this.p.w = this.p.cols * this.p.tileW; this.p.h = this.p.rows * this.p.tileH; }, getTile: function(tileX,tileY) { return this.p.tiles[tileY] && this.p.tiles[tileY][tileX]; }, setTile: function(x,y,tile) { var p = this.p, blockX = Math.floor(x/p.blockTileW), blockY = Math.floor(y/p.blockTileH); if(blockX >= 0 && blockY >= 0 && blockX < this.p.cols && blockY < this.p.rows) { this.p.tiles[y][x] = tile; if(this.blocks[blockY]) { this.blocks[blockY][blockX] = null; } } }, tilePresent: function(tileX,tileY) { return this.p.tiles[tileY] && this.collidableTile(this.p.tiles[tileY][tileX]); }, // Overload this method to draw tiles at frame 0 or not draw // tiles at higher number frames drawableTile: function(tileNum) { return tileNum > 0; }, // Overload this method to control which tiles trigger a collision // (defaults to all tiles > number 0) collidableTile: function(tileNum) { return tileNum > 0; }, collide: function(obj) { var p = this.p, tileStartX = Math.floor((obj.p.x - obj.p.cx - p.x) / p.tileW), tileStartY = Math.floor((obj.p.y - obj.p.cy - p.y) / p.tileH), tileEndX = Math.ceil((obj.p.x - obj.p.cx + obj.p.w - p.x) / p.tileW), tileEndY = Math.ceil((obj.p.y - obj.p.cy + obj.p.h - p.y) / p.tileH), colObj = this.collisionObject, normal = this.collisionNormal, col; normal.collided = false; for(var tileY = tileStartY; tileY<=tileEndY; tileY++) { for(var tileX = tileStartX; tileX<=tileEndX; tileX++) { if(this.tilePresent(tileX,tileY)) { colObj.p.x = tileX * p.tileW + p.x + p.tileW/2; colObj.p.y = tileY * p.tileH + p.y + p.tileH/2; col = Q.collision(obj,colObj); if(col && col.magnitude > 0 && (!normal.collided || normal.magnitude < col.magnitude )) { normal.collided = true; normal.separate[0] = col.separate[0]; normal.separate[1] = col.separate[1]; normal.magnitude = col.magnitude; normal.distance = col.distance; normal.normalX = col.normalX; normal.normalY = col.normalY; normal.tileX = tileX; normal.tileY = tileY; normal.tile = this.getTile(tileX,tileY); } } } } return normal.collided ? normal : false; }, prerenderBlock: function(blockX,blockY) { var p = this.p, tiles = p.tiles, sheet = this.sheet(), blockOffsetX = blockX*p.blockTileW, blockOffsetY = blockY*p.blockTileH; if(blockOffsetX < 0 || blockOffsetX >= this.p.cols || blockOffsetY < 0 || blockOffsetY >= this.p.rows) { return; } var canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'); canvas.width = p.blockW; canvas.height= p.blockH; this.blocks[blockY] = this.blocks[blockY] || {}; this.blocks[blockY][blockX] = canvas; for(var y=0;y 0) { p.vy = 0; } col.impact = impactY; entity.trigger("bump.bottom",col); } if(col.normalY > 0.3) { if(p.vy < 0) { p.vy = 0; } col.impact = impactY; entity.trigger("bump.top",col); } if(col.normalX < -0.3) { if(p.vx > 0) { p.vx = 0; } col.impact = impactX; entity.trigger("bump.right",col); } if(col.normalX > 0.3) { if(p.vx < 0) { p.vx = 0; } col.impact = impactX; entity.trigger("bump.left",col); } }, step: function(dt) { var p = this.entity.p, dtStep = dt; // TODO: check the entity's magnitude of vx and vy, // reduce the max dtStep if necessary to prevent // skipping through objects. while(dtStep > 0) { dt = Math.min(1/30,dtStep); // Updated based on the velocity and acceleration p.vx += p.ax * dt + (p.gravityX === void 0 ? Q.gravityX : p.gravityX) * dt * p.gravity; p.vy += p.ay * dt + (p.gravityY === void 0 ? Q.gravityY : p.gravityY) * dt * p.gravity; p.x += p.vx * dt; p.y += p.vy * dt; this.entity.stage.collide(this.entity); dtStep -= dt; } } }); Q.component('aiBounce', { added: function() { this.entity.on("bump.right",this,"goLeft"); this.entity.on("bump.left",this,"goRight"); }, goLeft: function(col) { this.entity.p.vx = -col.impact; if(this.entity.p.defaultDirection === 'right') { this.entity.p.flip = 'x'; } else { this.entity.p.flip = false; } }, goRight: function(col) { this.entity.p.vx = col.impact; if(this.entity.p.defaultDirection === 'left') { this.entity.p.flip = 'x'; } else { this.entity.p.flip = false; } } }); };