var points={'x':[],'y':[]};//empty data set(used in interpolation)
var edgeDistance=0.3;//interpolation accuracy lower is less accurate
var game = new Phaser.Game(800, 480, Phaser.CANVAS, 'game');
var PhaserGame = function (game) {
this.tank = null;
this.turret = null;
this.flame = null;
this.bullet = null;
this.background = null;
this.targets = null;
this.land = null;
this.emitter = null;
this.power = 300;
this.powerText = null;
this.cursors = null;
this.fireButton = null;
this.edgeSize=4;//thicker edge more accurate collision
};
PhaserGame.prototype = {
init: function () {
this.game.renderer.renderSession.roundPixels = true;
this.game.world.setBounds(0, 0, 992, 480);
this.physics.startSystem(Phaser.Physics.ARCADE);
this.physics.arcade.gravity.y = 200;
},
preload: function () {
// We need this because the assets are on Amazon S3
// Remove the next 2 lines if running locally
this.load.baseURL = '../assets/';
this.load.image('tank', 'tank.png');
this.load.image('turret', 'turret.png');
this.load.image('bullet', 'bullet.png');
this.load.image('background', 'background.png');
this.load.image('flame', 'flame.png');
this.load.image('target', 'target.png');
this.load.image('land', 'land.png');
// Note: Graphics from Amiga Tanx Copyright 1991 Gary Roberts
},
create: function () {
this.game.physics.startSystem(Phaser.Physics.P2JS);
this.background = this.add.sprite(0, 0, 'background');
this.targets = this.add.group(this.game.world, 'targets', false, true, Phaser.Physics.ARCADE);
this.targets.create(284, 378, 'target');
this.targets.create(456, 153, 'target');
this.targets.create(545, 305, 'target');
this.targets.create(726, 391, 'target');
this.targets.create(972, 74, 'target');
// Stop gravity from pulling them away
this.targets.setAll('body.allowGravity', false);
// The land is a BitmapData the size of the game world
// We draw the 'lang.png' to it then add it to the world
this.land = this.add.bitmapData(800, 600);
this.mypoly=game.add.group();
this.land.draw('land');
this.land.addToWorld();
this.land.update();
this.buildLand();// call this to build the physics land
// A small burst of particles when a target is hit
this.emitter = this.add.emitter(0, 0, 30);
this.emitter.makeParticles('flame');
this.emitter.setXSpeed(-120, 120);
this.emitter.setYSpeed(-100, -200);
this.emitter.setRotation();
// A single bullet that the tank will fire
this.bullet = this.add.sprite(0, 0, 'bullet');
this.bullet.exists = false;
this.physics.arcade.enable(this.bullet);
// The body of the tank
this.tank = this.add.sprite(200, 150, 'tank');
game.physics.p2.enable(this.tank,true);
this.tank.body.clearShapes();
this.tank.body.addCapsule(30,20,0,0);
this.tank.body.mass=1000;
game.physics.p2.gravity.y = 500;
this.tank.body.damping=0.9;
// The turret which we rotate (offset 30x14 from the tank)
this.turret = this.add.sprite(0,-8, 'turret');
this.tank.addChild(this.turret);
// When we shoot this little flame sprite will appear briefly at the end of the turret
this.flame = this.add.sprite(0, 0, 'flame');
this.turret.addChild(this.flame);
this.flame.anchor.set(0.5);
this.flame.visible = false;
// Used to display the power of the shot
this.power = 300;
this.powerText = this.add.text(8, 8, 'Power: 300', { font: "18px Arial", fill: "#ffffff" });
this.powerText.setShadow(1, 1, 'rgba(0, 0, 0, 0.8)', 1);
this.powerText.fixedToCamera = true;
// Some basic controls
this.cursors = this.input.keyboard.createCursorKeys();
this.fireButton = this.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
this.fireButton.onDown.add(this.fire, this);
},
/**
Core update loop. Handles collision checks and player input.
@method update
/
update: function () {
// If the bullet is in flight we don't let them control anything
if (this.bullet.exists)
{
// Bullet vs. the Targets
this.physics.arcade.overlap(this.bullet, this.targets, this.hitTarget, null, this);
// Bullet vs. the land
this.bulletVsLand();
}
else
{
// Allow them to set the power between 100 and 600
if (this.cursors.left.isDown && this.power > 100)
{
this.power -= 2;
}
else if (this.cursors.right.isDown && this.power < 600)
{
this.power += 2;
}
// Allow them to set the angle, between -90 (straight up) and 0 (facing to the right)
if (this.cursors.up.isDown && this.turret.angle > -90)
{
this.turret.angle--;
}
else if (this.cursors.down.isDown && this.turret.angle < 0)
{
this.turret.angle++;
}
// Update the text
this.powerText.text = 'Power: ' + this.power;
}
},
/**
Called by update if the bullet is in flight.
@method bulletVsLand
/
bulletVsLand: function () {
// Simple bounds check
if (this.bullet.x < 0 || this.bullet.x > this.game.world.width || this.bullet.y > this.game.height)
{
this.removeBullet();
return;
}
var x = Math.floor(this.bullet.x);
var y = Math.floor(this.bullet.y);
var rgba = this.land.getPixel(x, y);
if (rgba.a > 0)
{
console.log("rebuilding land");
this.land.blendDestinationOut();
this.land.circle(x, y, 16, 'rgba(0, 0, 0, 255');
this.land.blendReset();
this.land.update();
// If you like you could combine the above 4 lines:
// this.land.blendDestinationOut().circle(x, y, 16, 'rgba(0, 0, 0, 255').blendReset().update();
this.removeBullet();
this.buildLand();
}
},
/**
Called by fireButton.onDown
@method fire
/
fire: function () {
if (this.bullet.exists)
{
return;
}
// Re-position the bullet where the turret is
this.bullet.reset(this.turret.x+this.tank.x, this.turret.y+this.tank.y);
// Now work out where the END of the turret is
var p = new Phaser.Point(this.turret.x, this.turret.y);
p.rotate(p.x, p.y, this.turret.rotation, false, 34);
// And position the flame sprite there
this.flame.x = p.x;
this.flame.y = p.y;
this.flame.alpha = 1;
this.flame.visible = true;
// Boom
this.add.tween(this.flame).to( { alpha: 0 }, 100, "Linear", true);
// So we can see what's going on when the bullet leaves the screen
this.camera.follow(this.bullet);
// Our launch trajectory is based on the angle of the turret and the power
this.physics.arcade.velocityFromRotation(this.turret.rotation, this.power, this.bullet.body.velocity);
},
/**
Called by physics.arcade.overlap if the bullet and a target overlap
@method hitTarget
@param {Phaser.Sprite} bullet - A reference to the bullet (same as this.bullet)
@param {Phaser.Sprite} target - The target the bullet hit
/
hitTarget: function (bullet, target) {
this.emitter.at(target);
this.emitter.explode(2000, 10);
target.kill();
this.removeBullet(true);
},
/**
Removes the bullet, stops the camera following and tweens the camera back to the tank.
Have put this into its own method as it's called from several places.
@method removeBullet
/
removeBullet: function (hasExploded) {
if (typeof hasExploded === 'undefined') { hasExploded = false; }
this.bullet.kill();
this.camera.follow();
var delay = 1000;
if (hasExploded)
{
delay = 2000;
}
this.add.tween(this.camera).to( { x: 0 }, 1000, "Quint", true, delay);
},
buildLand:function(){
this.xs=1;
this.ys=0;
this.mypoly.removeAll(true);
points.x=[];
points.y=[];
superData=this.land.context.getImageData(this.xs,this.ys,800,600).data;
console.log(superData);
this.polygon=geom.contour(defineNonTransparent); //contour it
//this.polygon=this.downSample(edgeDistance); //downsample it via interpolation
this.polygon = simplify(this.polygon,1/edgeDistance); //downsample using douglas peucker algorithm
this.polygon.forEach(this.addEdge, this);//this will build your polygon from the interpolation data, again you can build the primitive shape too if you want
},
downSample: function(edgeDistance){
var polygon=[];
var x = 1 / game.world.width;
for (var i = 0; i <= 1; i += x/edgeDistance)
{
var px = game.math.linearInterpolation(points.x,i);
var py = game.math.linearInterpolation(points.y,i);
//var px = game.math.catmullRomInterpolation(points.x, i); //same result in this case
//var py = game.math.catmullRomInterpolation(points.y, i); //same result in this case
polygon.push([px,py]);
}
return polygon;
},
addEdge:function(element,index,polygon) {
if(index<polygon.length-1){
point2=index<polygon.length-1?polygon[index+1]:this[0];
point1=element;
distanceToNext=this.magnitude(point2[0],point1[0],point2[1],point1[1]);
angleRadians = Math.atan2(point2[1] - point1[1], point2[0] - point1[0]);
line=game.add.sprite(point1[0]+this.xs,point1[1]+this.ys);
game.physics.p2.enable(line,true);
line.body.static=true;
line.body.setRectangle(distanceToNext,this.edgeSize,distanceToNext/2);
line.body.rotation=angleRadians;
this.mypoly.add(line);
}
},
magnitude:function(x1,x2,y1,y2){
return Math.sqrt((x2-x1)(x2-x1)+(y2-y1)*(y2-y1));
},
};
game.state.add('Game', PhaserGame, true);
GEOM
//////////////////////////////// marching squares algorithm, modified d3 plugin.
//////////////////////////////// source: https://github.com/d3/d3-plugins/blob/master/geom/contour/contour.js
// this is a "marching squares" algorithm used to calc the outline path
(function() {
// d3-plugin for calculating outline paths
// License: https://github.com/d3/d3-plugins/blob/master/LICENSE
//
// Copyright (c) 2012-2014, Michael Bostock
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//* Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//* Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//* The name Michael Bostock may not be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
geom = {};
geom.contour = function(grid, start) {
var s = start || phaser_geom_contourStart(grid), // starting point
c = [], // contour polygon
x = s[0], // current x position
y = s[1], // current y position
dx = 0, // next x direction
dy = 0, // next y direction
pdx = NaN, // previous x direction
pdy = NaN, // previous y direction
i = 0;
do {
// determine marching squares index
i = 0;
if (grid(x-1, y-1)) i += 1;
if (grid(x, y-1)) i += 2;
if (grid(x-1, y )) i += 4;
if (grid(x, y )) i += 8;
// determine next direction
if (i === 6) {
dx = pdy === -1 ? -1 : 1;
dy = 0;
} else if (i === 9) {
dx = 0;
dy = pdx === 1 ? -1 : 1;
} else {
dx = phaser_geom_contourDx[i];
dy = phaser_geom_contourDy[i];
}
// update contour polygon
if (dx != pdx && dy != pdy) {
c.push([x, y]);
pdx = dx;
pdy = dy;
points.x.push(x);points.y.push(y);
}
x += dx;
y += dy;
} while (s[0] != x || s[1] != y);
return c;
};
// lookup tables for marching directions
var phaser_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN],
phaser_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN];
function phaser_geom_contourStart(grid) {
var x = 0,
y = 0;
// search for a starting point; begin at origin
// and proceed along outward-expanding diagonals
while (true) {
if (grid(x,y)) {
console.log("found starting point at :" +x+" "+y);
return [x,y];
}
if (x === 0) {
x = y + 1;
y = 0;
} else {
x = x - 1;
y = y + 1;
}
if(x>game.width||y>game.height){break};
}
}
})();
// the 1D imegadata array that will be iterated
var superData=[];
//this is the alpha lookup
var defineNonTransparent=function(x,y){
var a=superData[(y*800+x)*4+3];
return(a>0);
}
////////////////////////////////douglas peucker algorithm
//////////////////////////////// source: http://mourner.github.io/simplify-js/
// square distance between 2 points
function getSqDist(p1, p2) {
var dx = p1[0] - p2[0],
dy = p1[1] - p2[1];
return dx * dx + dy * dy;
}
// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {
var x = p1[0],
y = p1[1],
dx = p2[0] - x,
dy = p2[1] - y;
if (dx !== 0 || dy !== 0) {
var t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = p2[0];
y = p2[1];
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p[0] - x;
dy = p[1] - y;
return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format
// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {
var prevPoint = points[0],
newPoints = [prevPoint],
point;
for (var i = 1, len = points.length; i < len; i++) {
point = points[i];
if (getSqDist(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) newPoints.push(point);
return newPoints;
}
function simplifyDPStep(points, first, last, sqTolerance, simplified) {
var maxSqDist = sqTolerance,
index;
for (var i = first + 1; i < last; i++) {
var sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
simplified.push(points[index]);
if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}
// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(points, sqTolerance) {
var last = points.length - 1;
var simplified = [points[0]];
simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.push(points[last]);
return simplified;
}
function simplify(points, tolerance, highestQuality) {
if (points.length <= 2) return points;
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
}
Comments