Integrated SAT. Fixed lots of examples. Fixed documentation. Added new examples and built new phaser.js file for testing.

This commit is contained in:
photonstorm 2014-01-27 06:29:26 +00:00
parent 45518b3ecd
commit 4505aa50f6
35 changed files with 4065 additions and 2261 deletions

View file

@ -112,6 +112,7 @@ module.exports = function (grunt) {
'src/sound/SoundManager.js',
'src/utils/Debug.js',
'src/utils/Color.js',
'src/physics/arcade/SAT.js',
'src/physics/arcade/ArcadePhysics.js',
'src/physics/arcade/Body.js',
'src/particles/Particles.js',

View file

@ -67,6 +67,7 @@ Significant API changes:
* Body.customSeparateCallback allows you to set your own callback when two Bodies need to separate rather than using the built-in method.
* Body.collideCallback allows you to set a callback that is fired whenever the Body is hit on any of its active faces.
* Body.allowCollision has been renamed to Body.checkCollision.
* Body.rebound is a boolean that controls if a body will exchange velocity on collision. Set to false to allow it to be 'pushed' (see new examples).
New features:
@ -167,6 +168,7 @@ Updates:
* Removed ArcadePhysics binding to the QuadTree, so it can now be used independantly of the physics system.
* Removed ArcadePhysics.preUpdate and postUpdate as neither are needed any more.
* Body.bottom and Body.right are no longer rounded, so will give accurate sub-pixel values.
* Fixed lots of documentation in the Emitter class.
Bug Fixes:

View file

@ -131,6 +131,7 @@
<script src="$path/src/utils/Debug.js"></script>
<script src="$path/src/utils/Color.js"></script>
<script src="$path/src/physics/arcade/SAT.js"></script>
<script src="$path/src/physics/arcade/ArcadePhysics.js"></script>
<script src="$path/src/physics/arcade/Body.js"></script>

File diff suppressed because it is too large Load diff

22
build/phaser.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -110,6 +110,10 @@
}
],
"collision": [
{
"file": "body+scale.js",
"title": "body scale"
},
{
"file": "bounding+box.js",
"title": "bounding box"
@ -118,6 +122,10 @@
"file": "group+vs+group.js",
"title": "group vs group"
},
{
"file": "group+vs+self.js",
"title": "group vs self"
},
{
"file": "larger+bounding+box.js",
"title": "larger bounding box"
@ -130,10 +138,6 @@
"file": "process+callback.js",
"title": "process callback"
},
{
"file": "sprite+tiles.js",
"title": "sprite tiles"
},
{
"file": "sprite+vs+group.js",
"title": "sprite vs group"
@ -142,10 +146,6 @@
"file": "sprite+vs+sprite.js",
"title": "sprite vs sprite"
},
{
"file": "transform.js",
"title": "transform"
},
{
"file": "vertical+collision.js",
"title": "vertical collision"

View file

@ -136,6 +136,7 @@
<script src="../src/utils/Debug.js"></script>
<script src="../src/utils/Color.js"></script>
<script src="../src/physics/arcade/SAT.js"></script>
<script src="../src/physics/arcade/ArcadePhysics.js"></script>
<script src="../src/physics/arcade/Body.js"></script>

View file

@ -136,6 +136,7 @@
<script src="../src/utils/Debug.js"></script>
<script src="../src/utils/Color.js"></script>
<script src="../src/physics/arcade/SAT.js"></script>
<script src="../src/physics/arcade/ArcadePhysics.js"></script>
<script src="../src/physics/arcade/Body.js"></script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

View file

@ -0,0 +1,46 @@
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
game.load.spritesheet('gameboy', 'assets/sprites/gameboy_seize_color_40x60.png', 40, 60);
}
var sprite;
var sprite2;
function create() {
game.stage.backgroundColor = '#124184';
// Here we're tweening the scale of the sprite, which translates to the scale of the Body as well
// The collision will carry on working even against the scaled body.
sprite = game.add.sprite(200, 300, 'gameboy', 2);
sprite.name = 'green';
sprite.anchor.setTo(0.5, 0.5);
sprite.body.immovable = true;
sprite2 = game.add.sprite(600, 270, 'gameboy', 3);
sprite2.name = 'yellow';
sprite2.body.rebound = false;
sprite2.body.velocity.x = -200;
game.add.tween(sprite.scale).to( { x: 3, y: 3 }, 2000, Phaser.Easing.Linear.None, true, 0, 1000, true);
}
function update() {
game.physics.collide(sprite, sprite2);
}
function render() {
game.debug.renderPolygon(sprite.body.polygons, 'rgb(255,0,0)');
game.debug.renderPolygon(sprite2.body.polygons, 'rgb(255,0,0)');
}

View file

@ -14,16 +14,15 @@ function create() {
game.stage.backgroundColor = '#2d2d2d';
sprite1 = game.add.sprite(50, 200, 'atari');
sprite1 = game.add.sprite(150, 300, 'atari');
sprite1.name = 'atari';
sprite1.body.velocity.x = 100;
// This adjusts the collision body size.
// 100x100 is the new width/height.
// This adjusts the collision body size to be 104 x 104.
// See the offset bounding box for another example.
sprite1.body.setSize(100, 100, 0, 0);
sprite1.body.setSize(104, 104, 0, 0);
sprite1.body.immovable = true;
sprite2 = game.add.sprite(700, 220, 'mushroom');
sprite2 = game.add.sprite(700, 320, 'mushroom');
sprite2.name = 'mushroom';
sprite2.body.velocity.x = -100;
@ -40,16 +39,13 @@ function collisionHandler (obj1, obj2) {
game.stage.backgroundColor = '#992d2d';
console.log(obj1.name + ' collided with ' + obj2.name);
}
function render() {
game.debug.renderSpriteInfo(sprite1, 32, 32);
game.debug.renderSpriteCollision(sprite1, 32, 400);
game.debug.renderBodyInfo(sprite1, 32, 32);
game.debug.renderSpriteBody(sprite1);
game.debug.renderSpriteBody(sprite2);
game.debug.renderPolygon(sprite1.body.polygons, 'rgb(255,0,0)');
game.debug.renderPolygon(sprite2.body.polygons, 'rgb(255,0,0)');
}

View file

@ -12,8 +12,9 @@ function preload() {
var sprite;
var bullets;
var veggies;
var bulletTime = 0;
var cursors;
var bulletTime = 0;
var bullet;
function create() {
@ -44,23 +45,26 @@ function create() {
sprite = game.add.sprite(400, 550, 'phaser');
// Stop the following keys from propagating up to the browser
game.input.keyboard.addKeyCapture([ Phaser.Keyboard.LEFT, Phaser.Keyboard.RIGHT, Phaser.Keyboard.SPACEBAR ]);
cursors = game.input.keyboard.createCursorKeys();
game.input.keyboard.addKeyCapture([ Phaser.Keyboard.SPACEBAR ]);
}
function update() {
// As we don't need to exchange any velocities or motion we can the 'overlap' check instead of 'collide'
game.physics.overlap(bullets, veggies, collisionHandler, null, this);
sprite.body.velocity.x = 0;
sprite.body.velocity.y = 0;
if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT))
if (cursors.left.isDown)
{
sprite.body.velocity.x = -200;
sprite.body.velocity.x = -300;
}
else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT))
else if (cursors.right.isDown)
{
sprite.body.velocity.x = 200;
sprite.body.velocity.x = 300;
}
if (game.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))
@ -68,9 +72,6 @@ function update() {
fireBullet();
}
// As we don't need to exchange any velocities or motion we can use the faster 'overlap' check instead of 'collide':
game.physics.overlap(bullets, veggies, collisionHandler, null, this);
}
function fireBullet () {
@ -83,7 +84,7 @@ function fireBullet () {
{
bullet.reset(sprite.x + 6, sprite.y - 8);
bullet.body.velocity.y = -300;
bulletTime = game.time.now + 250;
bulletTime = game.time.now + 150;
}
}
@ -91,9 +92,12 @@ function fireBullet () {
// Called if the bullet goes out of the screen
function resetBullet (bullet) {
bullet.kill();
}
// Called if the bullet hits one of the veg sprites
function collisionHandler (bullet, veg) {
bullet.kill();

View file

@ -0,0 +1,40 @@
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update });
function preload() {
game.load.spritesheet('spinner', 'assets/sprites/bluemetal_32x32x4.png', 32, 32);
}
var sprites;
function create() {
// Here we create a group, populate it with sprites, give them all a random velocity
// and then check the group against itself for collision
sprites = game.add.group();
for (var i = 0; i < 30; i++)
{
var s = sprites.create(game.rnd.integerInRange(100, 700), game.rnd.integerInRange(32, 200), 'spinner');
s.animations.add('spin', [0,1,2,3]);
s.play('spin', 20, true);
s.body.velocity.x = game.rnd.integerInRange(-200, 200);
s.body.velocity.y = game.rnd.integerInRange(-200, 200);
}
sprites.setAll('body.collideWorldBounds', true);
sprites.setAll('body.bounce.x', 1);
sprites.setAll('body.bounce.y', 1);
sprites.setAll('body.friction', 0);
sprites.setAll('body.minBounceVelocity', 0);
}
function update() {
game.physics.collide(sprites);
}

View file

@ -15,14 +15,14 @@ function create() {
game.stage.backgroundColor = '#2d2d2d';
sprite1 = game.add.sprite(50, 200, 'atari');
sprite1 = game.add.sprite(130, 200, 'atari');
sprite1.name = 'atari';
sprite1.body.velocity.x = 100;
// In this example the new collision box is much larger than the original sprite
sprite1.body.setSize(400, 50, -100, 20);
sprite1.body.immovable = true;
sprite2 = game.add.sprite(700, 220, 'mushroom');
sprite2 = game.add.sprite(700, 210, 'mushroom');
sprite2.name = 'mushroom';
sprite2.body.velocity.x = -100;
@ -39,13 +39,13 @@ function collisionHandler (obj1, obj2) {
game.stage.backgroundColor = '#992d2d';
console.log(obj1.name + ' collided with ' + obj2.name);
}
function render() {
game.debug.renderRectangle(sprite1.body);
game.debug.renderRectangle(sprite2.body);
game.debug.renderBodyInfo(sprite1, 32, 32);
game.debug.renderPolygon(sprite1.body.polygons, 'rgb(255,0,0)');
game.debug.renderPolygon(sprite2.body.polygons, 'rgb(255,0,0)');
}

View file

@ -15,15 +15,13 @@ function create() {
game.stage.backgroundColor = '#2d2d2d';
sprite1 = game.add.sprite(50, 200, 'atari');
sprite1 = game.add.sprite(150, 200, 'atari');
sprite1.name = 'atari';
sprite1.body.velocity.x = 100;
// This adjusts the collision body size.
// 100x50 is the new width/height.
// This adjusts the collision body size to be a 100x50 box.
// 50, 25 is the X and Y offset of the newly sized box.
// In this case the box is 50px in and 25px down.
sprite1.body.setSize(100, 50, 50, 25);
sprite1.body.immovable = true;
sprite2 = game.add.sprite(700, 220, 'mushroom');
sprite2.name = 'mushroom';
@ -42,13 +40,13 @@ function collisionHandler (obj1, obj2) {
game.stage.backgroundColor = '#992d2d';
console.log(obj1.name + ' collided with ' + obj2.name);
}
function render() {
game.debug.renderRectangle(sprite1.body);
game.debug.renderRectangle(sprite2.body);
game.debug.renderBodyInfo(sprite1, 32, 32);
game.debug.renderPolygon(sprite1.body.polygons, 'rgb(255,0,0)');
game.debug.renderPolygon(sprite2.body.polygons, 'rgb(255,0,0)');
}

View file

@ -1,69 +0,0 @@
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
game.load.spritesheet('tiles', 'assets/tiles/platformer_tiles.png', 16, 16);
game.load.image('carrot', 'assets/sprites/carrot.png');
}
var tiles;
var sprite;
function create() {
game.stage.backgroundColor = '#2d2d2d';
tiles = game.add.group();
for (var x = 0; x < 40; x++)
{
var tile = tiles.create(100 + (x * 16), 300, 'tiles', 4);
tile.body.immovable = true;
}
sprite = game.add.sprite(300, 150, 'carrot');
sprite.name = 'mushroom';
sprite.body.collideWorldBounds = true;
sprite.body.velocity.x = 40;
sprite.body.velocity.y = 120;
sprite.body.bounce.setTo(1, 1);
game.input.onDown.add(carryOn, this);
}
function carryOn() {
game.paused = false;
}
function update() {
// object1, object2, collideCallback, processCallback, callbackContext
game.physics.collide(sprite, tiles, collisionHandler, null, this);
}
function collisionHandler (s, t) {
t.alpha = 0.5;
console.log('---------------------------------------------');
console.log(t.body);
game.paused = true;
}
function render() {
// game.debug.renderSpriteInfo(sprite1, 32, 32);
// game.debug.renderSpriteCollision(sprite1, 32, 400);
game.debug.renderSpriteBody(sprite);
// game.debug.renderSpriteBody(sprite2);
}

View file

@ -1,5 +1,5 @@
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update });
function preload() {
@ -10,12 +10,13 @@ function preload() {
var sprite;
var group;
var cursors;
function create() {
game.stage.backgroundColor = '#2d2d2d';
// This will check Sprite vs. Group collision
// This example will check Sprite vs. Group collision
sprite = game.add.sprite(32, 200, 'phaser');
sprite.name = 'phaser-dude';
@ -24,7 +25,7 @@ function create() {
for (var i = 0; i < 50; i++)
{
var c = group.create(100 + Math.random() * 700, game.world.randomY, 'veggies', game.rnd.integerInRange(0, 36));
var c = group.create(game.rnd.integerInRange(100, 770), game.rnd.integerInRange(0, 570), 'veggies', game.rnd.integerInRange(0, 36));
c.name = 'veg' + i;
c.body.immovable = true;
}
@ -32,58 +33,51 @@ function create() {
for (var i = 0; i < 20; i++)
{
// Here we'll create some chillis which the player can pick-up. They are still part of the same Group.
var c = group.create(100 + Math.random() * 700, game.world.randomY, 'veggies', 17);
var c = group.create(game.rnd.integerInRange(100, 770), game.rnd.integerInRange(0, 570), 'veggies', 17);
c.name = 'chilli' + i;
c.body.immovable = true;
}
game.input.keyboard.addKeyCapture([ Phaser.Keyboard.LEFT, Phaser.Keyboard.RIGHT, Phaser.Keyboard.UP, Phaser.Keyboard.DOWN ]);
cursors = game.input.keyboard.createCursorKeys();
}
function update() {
game.physics.collide(sprite, group, collisionHandler, null, this);
game.physics.collide(group, group);
sprite.body.velocity.x = 0;
sprite.body.velocity.y = 0;
if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT))
if (cursors.left.isDown)
{
sprite.body.velocity.x = -200;
}
else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT))
else if (cursors.right.isDown)
{
sprite.body.velocity.x = 200;
}
if (game.input.keyboard.isDown(Phaser.Keyboard.UP))
if (cursors.up.isDown)
{
sprite.body.velocity.y = -200;
}
else if (game.input.keyboard.isDown(Phaser.Keyboard.DOWN))
else if (cursors.down.isDown)
{
sprite.body.velocity.y = 200;
}
game.physics.collide(sprite, group, collisionHandler, null, this);
}
function collisionHandler (obj1, obj2) {
function collisionHandler (player, veg) {
// If the player collides with the chillis then they get eaten :)
// The chilli frame ID is 17
console.log('Hit', obj2.name);
if (obj2.frame == 17)
if (veg.frame == 17)
{
obj2.kill();
veg.kill();
}
}
function render () {
// game.debug.renderQuadTree(game.physics.quadTree);
}

View file

@ -1,117 +0,0 @@
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
game.load.image('atari', 'assets/sprites/atari130xe.png');
game.load.image('mushroom', 'assets/sprites/mushroom2.png');
game.load.image('flectrum', 'assets/sprites/flectrum.png');
}
var testGroup;
var sprite1;
var sprite2;
var sprite3;
function create() {
game.stage.backgroundColor = '#2d2d2d';
game.world.setBounds(-1000, -1000, 2000, 2000);
testGroup = game.add.group();
test2();
}
function test1 () {
// Test 1 - 2 sprites in world space (seems to work fine with local transform?)
sprite1 = game.add.sprite(-600, 200, 'atari');
sprite1.name = 'atari';
// sprite1.body.setSize(100, 100, 0, 0);
sprite2 = game.add.sprite(-100, 220, 'mushroom');
sprite2.name = 'mushroom';
game.camera.focusOn(sprite1);
game.camera.x += 300;
game.input.onDown.add(go1, this);
}
function test2 () {
// 1 sprite in world space (seems to work fine with local transform?) and 1 in a group
sprite1 = testGroup.create(0, -150, 'atari');
sprite1.name = 'atari';
sprite1.body.immovable = true;
// sprite1.body.setSize(100, 100, 0, 0);
sprite2 = game.add.sprite(-100, 140, 'mushroom');
sprite2.name = 'mushroom';
sprite3 = game.add.sprite(-200, 150, 'flectrum');
sprite3.name = 'tall';
testGroup.x = -600;
testGroup.y = 200;
game.camera.focusOn(sprite2);
game.camera.x -= 300;
game.input.onDown.add(go2, this);
}
function go1 () {
sprite1.body.velocity.x = 100;
sprite2.body.velocity.x = -100;
}
function go2 () {
sprite2.body.velocity.x = -100;
}
function update () {
game.physics.collide(sprite1, sprite2, collisionHandler, null, this);
// sprite3.angle += 0.5;
}
function collisionHandler (obj1, obj2) {
game.stage.backgroundColor = '#992d2d';
console.log(obj1.name + ' collided with ' + obj2.name);
}
function render() {
// game.debug.renderSpriteInfo(sprite1, 32, 32);
// game.debug.renderSpriteCollision(sprite1, 32, 400);
game.debug.renderSpriteCoords(sprite1, 32, 32);
game.debug.renderSpriteCoords(sprite2, 300, 32);
game.debug.renderCameraInfo(game.camera, 32, 500);
game.debug.renderSpriteBody(sprite1);
game.debug.renderSpriteBody(sprite2);
game.debug.renderSpriteBody(sprite3);
game.debug.renderGroupInfo(testGroup, 500, 500);
game.debug.renderPixel(testGroup.x, testGroup.y, 'rgb(255,255,0)');
}

View file

@ -16,7 +16,7 @@ function create() {
emitter = game.add.emitter(0, 0, 200);
emitter.makeParticles('diamond');
emitter.gravity = 10;
emitter.gravity = 200;
game.input.onDown.add(particleBurst, this);

View file

@ -17,9 +17,8 @@ function create() {
emitter.minParticleSpeed.setTo(-200, -300);
emitter.maxParticleSpeed.setTo(200, -400);
emitter.gravity = 8;
emitter.gravity = 150;
emitter.bounce.setTo(0.5, 0.5);
emitter.particleDrag.x = 10;
emitter.angularDrag = 30;
emitter.start(false, 8000, 400);
@ -28,6 +27,6 @@ function create() {
function update() {
game.physics.collide(emitter, emitter);
game.physics.collide(emitter);
}

View file

@ -31,8 +31,8 @@ function create() {
flyer.body.bounce.setTo(0.8, 0.8);
// This sets the gravity the sprite responds to in the world, as a point
// Leave x=0 and set y=8 to simulate falling
flyer.body.gravity.setTo(0, 8);
// Leave x=0 and set y=80 to simulate falling
flyer.body.gravity.setTo(0, 80);
}

View file

@ -20,7 +20,9 @@ function create() {
// displays it on-screen
// and assign it to a variable
ball = game.add.sprite(400, 200, 'ball');
knocker = game.add.sprite(400, 200, 'dude');
knocker.body.immovable = true;
// This gets it moving
ball.body.velocity.setTo(200, 200);
@ -33,37 +35,37 @@ function create() {
ball.body.bounce.setTo(1, 1);
// This sets the gravity the sprite responds to in the world, as a point
// Here we leave x=0 and set y=8 to simulate falling
ball.body.gravity.setTo(0, 8);
// Here we leave x=0 and set y=80 to simulate falling
ball.body.gravity.setTo(0, 80);
}
// Move the knocker with the arrow keys
function update () {
// Enable physics between the knocker and the ball
game.physics.collide(knocker, ball);
if (cursors.up.isDown)
{
knocker.body.velocity.y = -400;
knocker.body.velocity.y = -300;
}
else if (cursors.down.isDown)
{
knocker.body.velocity.y = 400;
knocker.body.velocity.y = 300;
}
else if (cursors.left.isDown)
{
knocker.body.velocity.x = -400;
knocker.body.velocity.x = -300;
}
else if (cursors.right.isDown)
{
knocker.body.velocity.x = 400;
knocker.body.velocity.x = 300;
}
else
{
knocker.body.velocity.setTo(0, 0);
}
// Enable physics between the knocker and the ball
game.physics.collide(knocker, ball);
}

View file

@ -31,8 +31,8 @@ function create() {
image.body.bounce.setTo(0.8, 0.8);
// This sets the gravity the sprite responds to in the world, as a point
// Leave x=0 and set y=8 to simulate falling
image.body.gravity.setTo(0, 8);
// Leave x=0 and set y=180 to simulate falling
image.body.gravity.setTo(0, 180);
}

View file

@ -19,7 +19,7 @@ var launchVelocity = 0;
function create() {
// set global gravity
game.physics.gravity.y = 100;
game.physics.gravity.y = 200;
game.stage.backgroundColor = '#0072bc';
var graphics = game.add.graphics(0,0);

View file

@ -28,7 +28,61 @@ function create() {
var bg = game.add.sprite(0, 0, bmd);
bg.body.moves = false;
test10();
test12();
}
function test12() {
// game.physics.gravity.y = 150;
sprite = game.add.sprite(200, 300, 'gameboy', 0);
sprite.name = 'red';
sprite.body.collideWorldBounds = true;
sprite.body.checkCollision.right = false;
// sprite.body.checkCollision.up = false;
// sprite.body.immovable = true;
sprite2 = game.add.sprite(400, 358, 'gameboy', 2);
sprite2.name = 'green';
sprite2.body.collideWorldBounds = true;
sprite2.body.bounce.setTo(0.9, 0.9);
game.input.onDown.add(launch12, this);
}
function launch12() {
sprite2.body.velocity.x = -200;
// sprite2.body.velocity.y = -60;
}
function test11() {
// game.physics.gravity.y = 150;
sprite = game.add.sprite(300, 200, 'gameboy', 0);
sprite.name = 'red';
sprite.body.collideWorldBounds = true;
// sprite.body.checkCollision.down = false;
// sprite.body.checkCollision.up = false;
sprite.body.checkCollision.right = false;
// sprite.body.immovable = true;
sprite2 = game.add.sprite(290, 400, 'gameboy', 2);
sprite2.name = 'green';
sprite2.body.collideWorldBounds = true;
sprite2.body.bounce.setTo(0.9, 0.9);
game.input.onDown.add(launch11, this);
}
function launch11() {
sprite2.body.velocity.y = -200;
}

View file

@ -0,0 +1,50 @@
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
game.load.image('arrow', 'assets/sprites/longarrow.png');
}
var sprite;
var cursors;
function create() {
game.stage.backgroundColor = '#124184';
sprite = game.add.sprite(400, 300, 'arrow');
cursors = game.input.keyboard.createCursorKeys();
}
function update() {
sprite.body.velocity.x = 0;
sprite.body.velocity.y = 0;
sprite.body.angularVelocity = 0;
if (cursors.left.isDown)
{
sprite.body.angularVelocity = -100;
}
else if (cursors.right.isDown)
{
sprite.body.angularVelocity = 100;
}
if (cursors.up.isDown)
{
game.physics.velocityFromAngle(sprite.angle, 300, sprite.body.velocity);
}
}
function render() {
game.debug.renderBodyInfo(sprite, 16, 24);
}

View file

@ -0,0 +1,48 @@
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
game.load.spritesheet('gameboy', 'assets/sprites/gameboy_seize_color_40x60.png', 40, 60);
}
var sprite;
var sprite2;
function create() {
game.stage.backgroundColor = '#124184';
// Here we're tweening the scale of the sprite, which translates to the scale of the Body as well
// The collision will carry on working even against the scaled body.
sprite = game.add.sprite(200, 300, 'gameboy', 2);
sprite.name = 'green';
// sprite.anchor.setTo(0.5, 0.5);
sprite.body.immovable = true;
sprite2 = game.add.sprite(600, 300, 'gameboy', 3);
sprite2.name = 'yellow';
sprite2.body.rebound = false;
// sprite2.body.velocity.x = -200;
game.add.tween(sprite2).to( { x: 0 }, 3000, Phaser.Easing.Linear.None, true);
// game.add.tween(sprite.scale).to( { x: 3, y: 3 }, 2000, Phaser.Easing.Linear.None, true, 0, 1000, true);
}
function update() {
game.physics.collide(sprite, sprite2);
}
function render() {
game.debug.renderPolygon(sprite.body.polygons, 'rgb(255,0,0)');
game.debug.renderPolygon(sprite2.body.polygons, 'rgb(255,0,0)');
}

View file

@ -15,7 +15,7 @@ var platform;
function create() {
// Our ball sprite
ball = game.add.sprite(420, 100, 'wizball');
ball = game.add.sprite(440, 100, 'wizball');
ball.anchor.setTo(0.5, 0.5);
ball.body.customSeparateX = true;
@ -42,25 +42,20 @@ function update() {
circle.y = ball.y;
// This is a rect vs. rect collision. The callback will check the circle.
game.physics.overlap(ball, platform, null, processCallback, this);
game.physics.overlap(ball, platform, collisionCallback, processCallback, this);
}
function collisionCallback(a, b) {
ball.body.y -= 10;
ball.body.velocity.y *= -1 * ball.body.bounce.y;
}
function processCallback(a, b) {
// console.log('p', a.y, b.y);
if (Phaser.Circle.intersectsRectangle(circle, platform.body))
{
console.log('boom', ball.body.overlapX, ball.body.overlapY);
// ball.body.x = ball.body.x - ball.body.overlapX;
// ball.body.velocity.x = platform.body.velocity.x - ball.body.velocity.x * ball.body.bounce.x;
ball.body.y -= 10;
ball.body.velocity.y *= -1 * ball.body.bounce.y;
}
return true;
return (Phaser.Circle.intersectsRectangle(circle, platform.body));
}

View file

@ -77,7 +77,6 @@
<meta charset="UTF-8" />
<title>phaser</title>
<base href="../"></base>
<script src="wip/SAT.js" type="text/javascript"></script>
<?php
require('../../build/config.php');

View file

@ -1,76 +1,40 @@
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update });
function preload() {
game.load.image('arrow', 'assets/sprites/asteroids_ship.png');
game.load.image('ball', 'assets/sprites/shinyball.png');
game.load.spritesheet('gameboy', 'assets/sprites/gameboy_seize_color_40x60.png', 40, 60);
game.load.spritesheet('spinner', 'assets/sprites/bluemetal_32x32x4.png', 32, 32);
}
var gameboy;
var sprites;
var bmd;
function create() {
game.stage.backgroundColor = '#124184';
bmd = game.add.bitmapData(800, 600);
bmd.fillStyle('#ffffff');
var bg = game.add.sprite(0, 0, bmd);
bg.body.moves = false;
game.physics.gravity.y = 250;
// game.physics.gravity.x = 250;
// Here we create a group, populate it with sprites, give them all a random velocity
// and then check the group against itself for collision
sprites = game.add.group();
for (var i = 0; i < 20; i++)
for (var i = 0; i < 30; i++)
{
var s = sprites.create(game.rnd.integerInRange(100, 700), game.rnd.integerInRange(32, 200), 'ball');
s.body.velocity.x = game.rnd.integerInRange(-400, 400);
var s = sprites.create(game.rnd.integerInRange(100, 700), game.rnd.integerInRange(32, 200), 'spinner');
s.animations.add('spin', [0,1,2,3]);
s.play('spin', 20, true);
s.body.velocity.x = game.rnd.integerInRange(-200, 200);
s.body.velocity.y = game.rnd.integerInRange(-200, 200);
s.name = 'ball' + i;
}
sprites.setAll('body.collideWorldBounds', true);
sprites.setAll('body.bounce.x', 0.9);
sprites.setAll('body.bounce.y', 0.9);
sprites.setAll('body.minBounceVelocity', 0.8);
gameboy = game.add.sprite(300, 50, 'gameboy', 0);
gameboy.name = 'gameboy';
gameboy.body.collideWorldBounds = true;
gameboy.body.bounce.setTo(0.9, 0.9);
gameboy.body.velocity.x = game.rnd.integerInRange(-400, 400);
gameboy.body.velocity.y = game.rnd.integerInRange(-200, 200);
sprites.setAll('body.bounce.x', 1);
sprites.setAll('body.bounce.y', 1);
sprites.setAll('body.friction', 0);
sprites.setAll('body.minBounceVelocity', 0);
}
function update() {
game.physics.collide(gameboy, sprites);
game.physics.collide(sprites);
// sprite.rotation = sprite.body.angle;
// if (sprite)
// {
// bmd.fillStyle('#ffff00');
// bmd.fillRect(sprite.body.center.x, sprite.body.center.y, 2, 2);
// }
// if (sprite2)
// {
// bmd.fillStyle('#ff00ff');
// bmd.fillRect(sprite2.body.center.x, sprite2.body.center.y, 2, 2);
// }
}
function render() {
}

View file

@ -22,7 +22,6 @@
Phaser.Particles.Arcade.Emitter = function (game, x, y, maxParticles) {
/**
* The total number of particles in this emitter.
* @property {number} maxParticles - The total number of particles in this emitter..
* @default
*/
@ -36,7 +35,8 @@ Phaser.Particles.Arcade.Emitter = function (game, x, y, maxParticles) {
this.name = 'emitter' + this.game.particles.ID++;
/**
* @property {Description} type - Description.
* @property {number} type - Internal Phaser Type value.
* @protected
*/
this.type = Phaser.EMITTER;
@ -65,140 +65,114 @@ Phaser.Particles.Arcade.Emitter = function (game, x, y, maxParticles) {
this.height = 1;
/**
* The minimum possible velocity of a particle.
* The default value is (-100,-100).
* @property {Phaser.Point} minParticleSpeed
* @property {Phaser.Point} minParticleSpeed - The minimum possible velocity of a particle.
* @default
*/
this.minParticleSpeed = new Phaser.Point(-100, -100);
/**
* The maximum possible velocity of a particle.
* The default value is (100,100).
* @property {Phaser.Point} maxParticleSpeed
* @property {Phaser.Point} maxParticleSpeed - The maximum possible velocity of a particle.
* @default
*/
this.maxParticleSpeed = new Phaser.Point(100, 100);
/**
* The minimum possible scale of a particle.
* The default value is 1.
* @property {number} minParticleScale
* @property {number} minParticleScale - The minimum possible scale of a particle.
* @default
*/
this.minParticleScale = 1;
/**
* The maximum possible scale of a particle.
* The default value is 1.
* @property {number} maxParticleScale
* @property {number} maxParticleScale - The maximum possible scale of a particle.
* @default
*/
this.maxParticleScale = 1;
/**
* The minimum possible angular velocity of a particle. The default value is -360.
* @property {number} minRotation
* @property {number} minRotation - The minimum possible angular velocity of a particle.
* @default
*/
this.minRotation = -360;
/**
* The maximum possible angular velocity of a particle. The default value is 360.
* @property {number} maxRotation
* @property {number} maxRotation - The maximum possible angular velocity of a particle.
* @default
*/
this.maxRotation = 360;
/**
* Sets the <code>gravity.y</code> of each particle to this value on launch.
* @property {number} gravity
* @property {number} gravity - Sets the `body.gravity.y` of each particle sprite to this value on launch.
* @default
*/
this.gravity = 2;
this.gravity = 100;
/**
* Set your own particle class type here.
* @property {Description} particleClass
* @property {any} particleClass - For emitting your own particle class types.
* @default
*/
this.particleClass = null;
/**
* The friction component of particles launched from the emitter.
* @property {number} particleFriction
* @property {number} particleFriction - The friction component of particles launched from the emitter.
* @default
*/
this.particleFriction = 0;
/**
* The angular drag component of particles launched from the emitter if they are rotating.
* @property {number} angularDrag
* @property {number} angularDrag - The angular drag component of particles launched from the emitter if they are rotating.
* @default
*/
this.angularDrag = 0;
/**
* How often a particle is emitted in ms (if emitter is started with Explode === false).
* @property {boolean} frequency
* @property {boolean} frequency - How often a particle is emitted in ms (if emitter is started with Explode === false).
* @default
*/
this.frequency = 100;
/**
* How long each particle lives once it is emitted in ms. Default is 2 seconds.
* Set lifespan to 'zero' for particles to live forever.
* @property {number} lifespan
* @property {number} lifespan - How long each particle lives once it is emitted in ms. Default is 2 seconds. Set lifespan to 'zero' for particles to live forever.
* @default
*/
this.lifespan = 2000;
/**
* How much each particle should bounce on each axis. 1 = full bounce, 0 = no bounce.
* @property {Phaser.Point} bounce
* @property {Phaser.Point} bounce - How much each particle should bounce on each axis. 1 = full bounce, 0 = no bounce.
*/
this.bounce = new Phaser.Point();
/**
* Internal helper for deciding how many particles to launch.
* @property {number} _quantity
* @property {number} _quantity - Internal helper for deciding how many particles to launch.
* @private
* @default
*/
this._quantity = 0;
/**
* Internal helper for deciding when to launch particles or kill them.
* @property {number} _timer
* @property {number} _timer - Internal helper for deciding when to launch particles or kill them.
* @private
* @default
*/
this._timer = 0;
/**
* Internal counter for figuring out how many particles to launch.
* @property {number} _counter
* @property {number} _counter - Internal counter for figuring out how many particles to launch.
* @private
* @default
*/
this._counter = 0;
/**
* Internal helper for the style of particle emission (all at once, or one at a time).
* @property {boolean} _explode
* @property {boolean} _explode - Internal helper for the style of particle emission (all at once, or one at a time).
* @private
* @default
*/
this._explode = true;
/**
* Determines whether the emitter is currently emitting particles.
* It is totally safe to directly toggle this.
* @property {boolean} on
* @property {boolean} on - Determines whether the emitter is currently emitting particles. It is totally safe to directly toggle this.
* @default
*/
this.on = false;
/**
* Determines whether the emitter is being updated by the core game loop.
* @property {boolean} exists
* @property {boolean} exists - Determines whether the emitter is being updated by the core game loop.
* @default
*/
this.exists = true;
@ -272,27 +246,19 @@ Phaser.Particles.Arcade.Emitter.prototype.update = function () {
* This function generates a new array of particle sprites to attach to the emitter.
*
* @method Phaser.Particles.Arcade.Emitter#makeParticles
* @param {Description} keys - Description.
* @param {number} frames - Description.
* @param {number} quantity - The number of particles to generate when using the "create from image" option.
* @param {number} collide - Description.
* @param {boolean} collideWorldBounds - Description.
* @return This Emitter instance (nice for chaining stuff together, if you're into that).
* @param {array|string} keys - A string or an array of strings that the particle sprites will use as their texture. If an array one is picked at random.
* @param {array|number} frames - A frame number, or array of frames that the sprite will use. If an array one is picked at random.
* @param {number} quantity - The number of particles to generate.
* @param {boolean} [collide=false] - Sets the checkCollision.none flag on the particle sprites body.
* @param {boolean} [collideWorldBounds=false] - A particle can be set to collide against the World bounds automatically and rebound back into the World if this is set to true. Otherwise it will leave the World.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.makeParticles = function (keys, frames, quantity, collide, collideWorldBounds) {
if (typeof frames == 'undefined')
{
frames = 0;
}
quantity = quantity || this.maxParticles;
collide = collide || 0;
if (typeof collideWorldBounds == 'undefined')
{
collideWorldBounds = false;
}
if (typeof frames === 'undefined') { frames = 0; }
if (typeof quantity === 'undefined') { quantity = this.maxParticles; }
if (typeof collide === 'undefined') { collide = false; }
if (typeof collideWorldBounds === 'undefined') { collideWorldBounds = false; }
var particle;
var i = 0;
@ -301,14 +267,14 @@ Phaser.Particles.Arcade.Emitter.prototype.makeParticles = function (keys, frames
while (i < quantity)
{
if (this.particleClass == null)
if (this.particleClass === null)
{
if (typeof keys == 'object')
if (typeof keys === 'object')
{
rndKey = this.game.rnd.pick(keys);
}
if (typeof frames == 'object')
if (typeof frames === 'object')
{
rndFrame = this.game.rnd.pick(frames);
}
@ -320,7 +286,7 @@ Phaser.Particles.Arcade.Emitter.prototype.makeParticles = function (keys, frames
// particle = new this.particleClass(this.game);
// }
if (collide > 0)
if (collide)
{
particle.body.checkCollision.any = true;
particle.body.checkCollision.none = false;
@ -347,9 +313,9 @@ Phaser.Particles.Arcade.Emitter.prototype.makeParticles = function (keys, frames
}
/**
* Call this function to turn off all the particles and the emitter.
* @method Phaser.Particles.Arcade.Emitter#kill
*/
* Call this function to turn off all the particles and the emitter.
* @method Phaser.Particles.Arcade.Emitter#kill
*/
Phaser.Particles.Arcade.Emitter.prototype.kill = function () {
this.on = false;
@ -359,10 +325,9 @@ Phaser.Particles.Arcade.Emitter.prototype.kill = function () {
}
/**
* Handy for bringing game objects "back to life". Just sets alive and exists back to true.
* In practice, this is most often called by <code>Object.reset()</code>.
* @method Phaser.Particles.Arcade.Emitter#revive
*/
* Handy for bringing game objects "back to life". Just sets alive and exists back to true.
* @method Phaser.Particles.Arcade.Emitter#revive
*/
Phaser.Particles.Arcade.Emitter.prototype.revive = function () {
this.alive = true;
@ -371,27 +336,19 @@ Phaser.Particles.Arcade.Emitter.prototype.revive = function () {
}
/**
* Call this function to start emitting particles.
* @method Phaser.Particles.Arcade.Emitter#start
* @param {boolean} explode - Whether the particles should all burst out at once.
* @param {number} lifespan - How long each particle lives once emitted. 0 = forever.
* @param {number} frequency - Ignored if Explode is set to true. Frequency is how often to emit a particle in ms.
* @param {number} quantity - How many particles to launch. 0 = "all of the particles".
*/
* Call this function to start emitting particles.
* @method Phaser.Particles.Arcade.Emitter#start
* @param {boolean} [explode=true] - Whether the particles should all burst out at once.
* @param {number} [lifespan=0] - How long each particle lives once emitted. 0 = forever.
* @param {number} [frequency=250] - Ignored if Explode is set to true. Frequency is how often to emit a particle in ms.
* @param {number} [quantity=0] - How many particles to launch. 0 = "all of the particles".
*/
Phaser.Particles.Arcade.Emitter.prototype.start = function (explode, lifespan, frequency, quantity) {
if (typeof explode !== 'boolean')
{
explode = true;
}
lifespan = lifespan || 0;
// How many ms between emissions?
frequency = frequency || 250;
// Total number of particles to emit
quantity = quantity || 0;
if (typeof explode === 'undefined') { explode = true; }
if (typeof lifespan === 'undefined') { lifespan = 0; }
if (typeof frequency === 'undefined') { frequency = 250; }
if (typeof quantity === 'undefined') { quantity = 0; }
this.revive();
@ -417,9 +374,9 @@ Phaser.Particles.Arcade.Emitter.prototype.start = function (explode, lifespan, f
}
/**
* This function can be used both internally and externally to emit the next particle.
* @method Phaser.Particles.Arcade.Emitter#emitParticle
*/
* This function can be used both internally and externally to emit the next particle.
* @method Phaser.Particles.Arcade.Emitter#emitParticle
*/
Phaser.Particles.Arcade.Emitter.prototype.emitParticle = function () {
var particle = this.getFirstExists(false);
@ -485,8 +442,8 @@ Phaser.Particles.Arcade.Emitter.prototype.emitParticle = function () {
/**
* A more compact way of setting the width and height of the emitter.
* @method Phaser.Particles.Arcade.Emitter#setSize
* @param {number} width - The desired width of the emitter (particles are spawned randomly within these dimensions).
* @param {number} height - The desired height of the emitter.
* @param {number} width - The desired width of the emitter (particles are spawned randomly within these dimensions).
* @param {number} height - The desired height of the emitter.
*/
Phaser.Particles.Arcade.Emitter.prototype.setSize = function (width, height) {
@ -498,8 +455,8 @@ Phaser.Particles.Arcade.Emitter.prototype.setSize = function (width, height) {
/**
* A more compact way of setting the X velocity range of the emitter.
* @method Phaser.Particles.Arcade.Emitter#setXSpeed
* @param {number} min - The minimum value for this range.
* @param {number} max - The maximum value for this range.
* @param {number} [min=0] - The minimum value for this range.
* @param {number} [max=0] - The maximum value for this range.
*/
Phaser.Particles.Arcade.Emitter.prototype.setXSpeed = function (min, max) {
@ -514,8 +471,8 @@ Phaser.Particles.Arcade.Emitter.prototype.setXSpeed = function (min, max) {
/**
* A more compact way of setting the Y velocity range of the emitter.
* @method Phaser.Particles.Arcade.Emitter#setYSpeed
* @param {number} min - The minimum value for this range.
* @param {number} max - The maximum value for this range.
* @param {number} [min=0] - The minimum value for this range.
* @param {number} [max=0] - The maximum value for this range.
*/
Phaser.Particles.Arcade.Emitter.prototype.setYSpeed = function (min, max) {
@ -530,8 +487,8 @@ Phaser.Particles.Arcade.Emitter.prototype.setYSpeed = function (min, max) {
/**
* A more compact way of setting the angular velocity constraints of the emitter.
* @method Phaser.Particles.Arcade.Emitter#setRotation
* @param {number} min - The minimum value for this range.
* @param {number} max - The maximum value for this range.
* @param {number} [min=0] - The minimum value for this range.
* @param {number} [max=0] - The maximum value for this range.
*/
Phaser.Particles.Arcade.Emitter.prototype.setRotation = function (min, max) {
@ -544,14 +501,17 @@ Phaser.Particles.Arcade.Emitter.prototype.setRotation = function (min, max) {
}
/**
* Change the emitter's midpoint to match the midpoint of a <code>Object</code>.
* Change the emitters center to match the center of any object with a `center` property, such as a Sprite.
* @method Phaser.Particles.Arcade.Emitter#at
* @param {object} object - The <code>Object</code> that you want to sync up with.
* @param {object|Phaser.Sprite} object - The object that you wish to match the center with.
*/
Phaser.Particles.Arcade.Emitter.prototype.at = function (object) {
this.emitX = object.center.x;
this.emitY = object.center.y;
if (object.center)
{
this.emitX = object.center.x;
this.emitY = object.center.y;
}
}

View file

@ -111,6 +111,18 @@ Phaser.Physics.Arcade = function (game) {
};
/**
* @constant
* @type {number}
*/
Phaser.Physics.Arcade.RECT = 0;
/**
* @constant
* @type {number}
*/
Phaser.Physics.Arcade.CIRCLE = 1;
Phaser.Physics.Arcade.prototype = {
/**
@ -327,7 +339,7 @@ Phaser.Physics.Arcade.prototype = {
collideHandler: function (object1, object2, collideCallback, processCallback, callbackContext, overlapOnly) {
// Only collide valid objects
if (typeof object2 === 'undefined' && object1.type === Phaser.GROUP)
if (typeof object2 === 'undefined' && (object1.type === Phaser.GROUP || object1.type === Phaser.EMITTER))
{
this.collideGroupVsSelf(object1, collideCallback, processCallback, callbackContext, overlapOnly);
return;

View file

@ -211,6 +211,12 @@ Phaser.Physics.Arcade.Body = function (sprite) {
*/
this.facing = Phaser.NONE;
/**
* @property {boolean} rebound - A Body set to rebound will exchange velocity with another Body during collision. Set to false to allow this body to be 'pushed' rather than exchange velocity.
* @default
*/
this.rebound = true;
/**
* @property {boolean} immovable - An immovable Body will not receive any impacts or exchanges of velocity from other bodies.
* @default
@ -289,6 +295,22 @@ Phaser.Physics.Arcade.Body = function (sprite) {
*/
this.blockedPoint = new Phaser.Point(0, 0);
/**
* @property {Phaser.Physics.Arcade.RECT|Phaser.Physics.Arcade.CIRCLE} type - The type of SAT Shape.
*/
this.type = Phaser.Physics.Arcade.RECT;
/**
* @property {SAT.Box|SAT.Circle|SAT.Polygon} shape - The SAT Collision shape.
*/
this.shape = new SAT.Box(new SAT.Vector(this.x, this.y), this.width, this.height);
/**
* @property {SAT.Polygon} polygons - The SAT Polygons, as derived from the Shape.
* @private
*/
this.polygons = this.shape.toPolygon();
/**
* @property {number} _dx - Internal cache var.
* @private
@ -314,10 +336,10 @@ Phaser.Physics.Arcade.Body = function (sprite) {
this._sy = sprite.scale.y;
/**
* @property {number} _average - Internal cache var.
* @property {array} _distances - Internal cache var.
* @private
*/
this.polygons = new SAT.Box(new SAT.Vector(this.x, this.y), this.width, this.height).toPolygon();
this._distances = [0, 0, 0, 0];
this._debug = 0;
@ -325,6 +347,22 @@ Phaser.Physics.Arcade.Body = function (sprite) {
Phaser.Physics.Arcade.Body.prototype = {
setCircle: function (radius) {
this.shape = new SAT.Circle(new SAT.Vector(this.x, this.y), radius);
this.polygons = null;
this.type = Phaser.Physics.Arcade.CIRCLE;
},
setBox: function () {
this.shape = new SAT.Box(new SAT.Vector(this.x, this.y), this.width, this.height);
this.polygons = this.shape.toPolygon();
this.type = Phaser.Physics.Arcade.RECT;
},
/**
* Internal method.
*
@ -340,8 +378,10 @@ Phaser.Physics.Arcade.Body.prototype = {
this.halfWidth = Math.floor(this.width / 2);
this.halfHeight = Math.floor(this.height / 2);
// Scale by the difference between this and what it was previously
this.polygons.scale(scaleX / this._sx, scaleY / this._sy);
if (this.polygons)
{
this.polygons.scale(scaleX / this._sx, scaleY / this._sy);
}
this._sx = scaleX;
this._sy = scaleY;
@ -395,8 +435,7 @@ Phaser.Physics.Arcade.Body.prototype = {
this.applyMotion();
}
this.polygons.pos.x = this.x;
this.polygons.pos.y = this.y;
this.syncPosition();
},
@ -573,22 +612,7 @@ Phaser.Physics.Arcade.Body.prototype = {
this.velocity.y = -this.maxVelocity.y;
}
this.polygons.pos.x = this.x;
this.polygons.pos.y = this.y;
},
/**
* Checks for an overlap between this Body and the given Body.
*
* @method Phaser.Physics.Arcade#overlap
* @param {Phaser.Physics.Arcade.Body} body - The Body that collided.
* @param {SAT.Response} response - SAT Response handler.
* @return {boolean} True if the two bodies overlap, otherwise false.
*/
overlap: function (body, response) {
return SAT.testPolygonPolygon(this.polygons, body.polygons, response);
this.syncPosition();
},
@ -664,6 +688,34 @@ Phaser.Physics.Arcade.Body.prototype = {
},
/**
* Subtracts the given Vector from this Body.
*
* @method Phaser.Physics.Arcade#sub
* @protected
* @param {SAT.Vector} v - The vector to substract from this Body.
*/
sub: function (v) {
this.x -= v.x;
this.y -= v.y;
},
/**
* Adds the given Vector from this Body.
*
* @method Phaser.Physics.Arcade#add
* @protected
* @param {SAT.Vector} v - The vector to add to this Body.
*/
add: function (v) {
this.x += v.x;
this.y += v.y;
},
/**
* Separation response handler.
*
@ -675,7 +727,11 @@ Phaser.Physics.Arcade.Body.prototype = {
give: function (body, response) {
this.add(response.overlapV);
this.rebound(body);
if (this.rebound)
{
this.processRebound(body);
}
},
@ -690,7 +746,11 @@ Phaser.Physics.Arcade.Body.prototype = {
take: function (body, response) {
this.sub(response.overlapV);
this.rebound(body);
if (this.rebound)
{
this.processRebound(body);
}
},
@ -708,7 +768,10 @@ Phaser.Physics.Arcade.Body.prototype = {
this.sub(response.overlapV);
body.add(response.overlapV);
this.exchange(body);
if (this.rebound)
{
this.exchange(body);
}
},
@ -721,9 +784,9 @@ Phaser.Physics.Arcade.Body.prototype = {
*/
exchange: function (body) {
if (this.mass === body.mass)
if (this.mass === body.mass && this.speed > 0 && body.speed > 0)
{
// A direct velocity exchange
// A direct velocity exchange (as they are both moving and have the same mass)
this._dx = body.velocity.x;
this._dy = body.velocity.y;
@ -762,11 +825,11 @@ Phaser.Physics.Arcade.Body.prototype = {
/**
* Rebound the velocity of this Body.
*
* @method Phaser.Physics.Arcade#rebound
* @method Phaser.Physics.Arcade#processRebound
* @protected
* @param {Phaser.Physics.Arcade.Body} body - The Body that collided.
*/
rebound: function (body) {
processRebound: function (body) {
this.velocity.x = body.velocity.x - this.velocity.x * this.bounce.x;
this.velocity.y = body.velocity.y - this.velocity.y * this.bounce.y;
@ -774,8 +837,99 @@ Phaser.Physics.Arcade.Body.prototype = {
},
/**
* Checks for an overlap between this Body and the given Body.
*
* @method Phaser.Physics.Arcade#overlap
* @param {Phaser.Physics.Arcade.Body} body - The Body that is being checked against this Body.
* @param {SAT.Response} response - SAT Response handler.
* @return {boolean} True if the two bodies overlap, otherwise false.
*/
overlap: function (body, response) {
if (this.type === Phaser.Physics.Arcade.RECT && body.type === Phaser.Physics.Arcade.RECT)
{
return SAT.testPolygonPolygon(this.polygons, body.polygons, response);
}
else if (this.type === Phaser.Physics.Arcade.CIRCLE && body.type === Phaser.Physics.Arcade.CIRCLE)
{
return SAT.testCircleCircle(this.shape, body.shape, response);
}
else if (this.type === Phaser.Physics.Arcade.RECT && body.type === Phaser.Physics.Arcade.CIRCLE)
{
return SAT.testPolygonCircle(this.polygons, body.shape, response);
}
else if (this.type === Phaser.Physics.Arcade.CIRCLE && body.type === Phaser.Physics.Arcade.RECT)
{
return SAT.testCirclePolygon(this.shape, body.polygons, response);
}
},
/**
* This separates this Body from the given Body unless a customSeparateCallback is set.
* It assumes they have already been overlap checked and the resulting overlap is stored in overlapX and overlapY.
*
* @method Phaser.Physics.Arcade#separate
* @protected
* @param {Phaser.Physics.Arcade.Body} body - The Body to be separated from this one.
* @param {SAT.Response} response - SAT Response handler.
* @return {boolean}
*/
separate: function (body, response) {
if (this.customSeparateCallback)
{
return this.customSeparateCallback.call(this.customSeparateContext, this, response);
}
this._distances[0] = body.right - this.x; // Distance of B to face on left side of A
this._distances[1] = this.right - body.x; // Distance of B to face on right side of A
this._distances[2] = body.bottom - this.y; // Distance of B to face on bottom side of A
this._distances[3] = this.bottom - body.y; // Distance of B to face on top side of A
if (response.overlapN.x)
{
// Which is smaller? Left or Right?
if (this._distances[0] < this._distances[1])
{
// console.log(this.sprite.name, 'collided on the LEFT with', body.sprite.name, response);
this.hitLeft(body, response);
}
else if (this._distances[1] < this._distances[0])
{
// console.log(this.sprite.name, 'collided on the RIGHT with', body.sprite.name, response);
this.hitRight(body, response);
}
}
else if (response.overlapN.y)
{
// Which is smaller? Top or Bottom?
if (this._distances[2] < this._distances[3])
{
// console.log(this.sprite.name, 'collided on the TOP with', body.sprite.name, response);
this.hitTop(body, response);
}
else if (this._distances[3] < this._distances[2])
{
// console.log(this.sprite.name, 'collided on the BOTTOM with', body.sprite.name, response);
this.hitBottom(body, response);
}
}
this.syncPosition();
body.syncPosition();
return true;
},
/**
* Process a collision with the left face of this Body.
* Collision and separation can be further checked by setting a collideCallback.
* This callback will be sent 4 parameters: The face of collision, this Body, the colliding Body and the SAT Response.
* If the callback returns true then separation, rebounds and the touching flags will all be set.
* If it returns false this will be skipped and must be handled manually.
*
* @method Phaser.Physics.Arcade#hitLeft
* @protected
@ -784,13 +938,13 @@ Phaser.Physics.Arcade.Body.prototype = {
*/
hitLeft: function (body, response) {
// We know that Body is overlapping with This on the left-hand side
if ((body.deltaX() < 0 && !this.checkCollision.right) || (body.deltaX() > 0 && !this.checkCollision.left))
// We know that Body is overlapping with This on the left hand side (deltaX < 0 = moving left, > 0 = moving right)
if (body.speed > 0 && (body.deltaX() <= 0 || (body.deltaX() > 0 && !this.checkCollision.left)))
{
return;
}
if ((body.deltaY() < 0 && !this.checkCollision.down) || (body.deltaY() > 0 && !this.checkCollision.up) || (body.deltaY() > 0 && !this.checkCollision.up) || (body.deltaY() > 0 && !this.checkCollision.down))
if (this.collideCallback && !this.collideCallback.call(this.collideCallbackContext, Phaser.LEFT, this, body, response))
{
return;
}
@ -823,15 +977,14 @@ Phaser.Physics.Arcade.Body.prototype = {
this.x -= this.right - this.game.world.bounds.right;
}
if (this.collideCallback)
{
this.collideCallback.call(this.collideCallbackContext, Phaser.LEFT, this, body);
}
},
/**
* Process a collision with the right face of this Body.
* Collision and separation can be further checked by setting a collideCallback.
* This callback will be sent 4 parameters: The face of collision, this Body, the colliding Body and the SAT Response.
* If the callback returns true then separation, rebounds and the touching flags will all be set.
* If it returns false this will be skipped and must be handled manually.
*
* @method Phaser.Physics.Arcade#hitRight
* @protected
@ -840,13 +993,13 @@ Phaser.Physics.Arcade.Body.prototype = {
*/
hitRight: function (body, response) {
// We know that Body is overlapping with This on the right-hand side
if ((body.deltaX() < 0 && !this.checkCollision.right) || (body.deltaX() > 0 && !this.checkCollision.left))
// We know that Body is overlapping with This on the right hand side (deltaX < 0 = moving left, > 0 = moving right)
if (body.speed > 0 && (body.deltaX() >= 0 || (body.deltaX() < 0 && !this.checkCollision.right)))
{
return;
}
if ((body.deltaY() < 0 && !this.checkCollision.down) || (body.deltaY() > 0 && !this.checkCollision.up) || (body.deltaY() > 0 && !this.checkCollision.up) || (body.deltaY() > 0 && !this.checkCollision.down))
if (this.collideCallback && !this.collideCallback.call(this.collideCallbackContext, Phaser.RIGHT, this, body))
{
return;
}
@ -879,15 +1032,14 @@ Phaser.Physics.Arcade.Body.prototype = {
this.x += this.game.world.bounds.x - this.x;
}
if (this.collideCallback)
{
this.collideCallback.call(this.collideCallbackContext, Phaser.RIGHT, this, body);
}
},
/**
* Process a collision with the top face of this Body.
* Collision and separation can be further checked by setting a collideCallback.
* This callback will be sent 4 parameters: The face of collision, this Body, the colliding Body and the SAT Response.
* If the callback returns true then separation, rebounds and the touching flags will all be set.
* If it returns false this will be skipped and must be handled manually.
*
* @method Phaser.Physics.Arcade#hitTop
* @protected
@ -896,8 +1048,13 @@ Phaser.Physics.Arcade.Body.prototype = {
*/
hitTop: function (body, response) {
// We know that Body is overlapping with This on the top side (deltaY > 0 = moving down < 0 = moving up)
if ((body.deltaY() > 0 && (!this.checkCollision.up || !this.checkCollision.down)) || (body.deltaY() < 0 && (!this.checkCollision.down || !this.checkCollision.up)))
// We know that Body is overlapping with This on the bottom side (deltaY < 0 = moving up, > 0 = moving down)
if (body.speed > 0 && (body.deltaY() <= 0 || (body.deltaY() > 0 && !this.checkCollision.up)))
{
return;
}
if (this.collideCallback && !this.collideCallback.call(this.collideCallbackContext, Phaser.UP, this, body))
{
return;
}
@ -930,15 +1087,14 @@ Phaser.Physics.Arcade.Body.prototype = {
this.y -= this.bottom - this.game.world.bounds.bottom;
}
if (this.collideCallback)
{
this.collideCallback.call(this.collideCallbackContext, Phaser.UP, this, body);
}
},
/**
* Process a collision with the bottom face of this Body.
* Collision and separation can be further checked by setting a collideCallback.
* This callback will be sent 4 parameters: The face of collision, this Body, the colliding Body and the SAT Response.
* If the callback returns true then separation, rebounds and the touching flags will all be set.
* If it returns false this will be skipped and must be handled manually.
*
* @method Phaser.Physics.Arcade#hitBottom
* @protected
@ -947,8 +1103,13 @@ Phaser.Physics.Arcade.Body.prototype = {
*/
hitBottom: function (body, response) {
// We know that Body is overlapping with This on the bottom side (deltaY > 0 = moving down < 0 = moving up)
if ((body.deltaY() < 0 && (!this.checkCollision.down || !this.checkCollision.up)) || (body.deltaY() < 0 && (!this.checkCollision.down || !this.checkCollision.up)))
// We know that Body is overlapping with This on the bottom side (deltaY < 0 = moving up, > 0 = moving down)
if (body.speed > 0 && (body.deltaY() >= 0 || (body.deltaY() < 0 && !this.checkCollision.down)))
{
return;
}
if (this.collideCallback && !this.collideCallback.call(this.collideCallbackContext, Phaser.DOWN, this, body))
{
return;
}
@ -981,102 +1142,25 @@ Phaser.Physics.Arcade.Body.prototype = {
this.y += this.game.world.bounds.y - this.y;
}
if (this.collideCallback)
{
this.collideCallback.call(this.collideCallbackContext, Phaser.DOWN, this, body);
}
},
/**
* Subtracts the given Vector from this Body.
* Internal method that syncs the Body coordinates with the SAT shape and polygon positions.
*
* @method Phaser.Physics.Arcade#sub
* @method Phaser.Physics.Arcade#syncPosition
* @protected
* @param {SAT.Vector} v - The vector to substract from this Body.
*/
sub: function (v) {
syncPosition: function () {
this.x -= v.x;
this.y -= v.y;
this.shape.pos.x = this.x;
this.shape.pos.y = this.y;
},
/**
* Adds the given Vector from this Body.
*
* @method Phaser.Physics.Arcade#add
* @protected
* @param {SAT.Vector} v - The vector to add to this Body.
*/
add: function (v) {
this.x += v.x;
this.y += v.y;
},
/**
* This separates this Body from the given Body unless a customSeparateCallback is set.
* It assumes they have already been overlap checked and the resulting overlap is stored in overlapX and overlapY.
*
* @method Phaser.Physics.Arcade#separate
* @protected
* @param {Phaser.Physics.Arcade.Body} body - The Body to be separated from this one.
* @param {SAT.Response} response - SAT Response handler.
* @return {boolean}
*/
separate: function (body, response) {
if (this.customSeparateCallback)
if (this.polygons)
{
return this.customSeparateCallback.call(this.customSeparateContext, this, response);
this.polygons.pos.x = this.x;
this.polygons.pos.y = this.y;
}
var distances = [
(body.right - this.x), // distance of box 'b' to face on 'left' side of 'a'.
(this.right - body.x), // distance of box 'b' to face on 'right' side of 'a'.
(body.bottom - this.y), // distance of box 'b' to face on 'bottom' side of 'a'.
(this.bottom - body.y) // distance of box 'b' to face on 'top' side of 'a'.
];
if (response.overlapN.x)
{
// Which is smaller? Left or Right?
if (distances[0] < distances[1])
{
console.log(this.sprite.name, 'collided on the LEFT with', body.sprite.name, response);
this.hitLeft(body, response);
}
else if (distances[1] < distances[0])
{
console.log(this.sprite.name, 'collided on the RIGHT with', body.sprite.name, response);
this.hitRight(body, response);
}
}
else if (response.overlapN.y)
{
// Which is smaller? Top or Bottom?
if (distances[2] < distances[3])
{
console.log(this.sprite.name, 'collided on the TOP with', body.sprite.name, response);
this.hitTop(body, response);
}
else if (distances[3] < distances[2])
{
console.log(this.sprite.name, 'collided on the BOTTOM with', body.sprite.name, response);
this.hitBottom(body, response);
}
}
this.polygons.pos.x = this.x;
this.polygons.pos.y = this.y;
body.polygons.pos.x = body.x;
body.polygons.pos.y = body.y;
return true;
},
/**
@ -1152,9 +1236,17 @@ Phaser.Physics.Arcade.Body.prototype = {
this.halfWidth = Math.floor(this.width / 2);
this.halfHeight = Math.floor(this.height / 2);
this.offset.setTo(offsetX, offsetY);
this.center.setTo(this.x + this.halfWidth, this.y + this.halfHeight);
if (this.type === Phaser.Physics.Arcade.RECT)
{
this.setBox();
}
else
{
this.setCircle();
}
},
/**
@ -1177,8 +1269,14 @@ Phaser.Physics.Arcade.Body.prototype = {
this.y = this.preY;
this.rotation = this.preRotation;
this.polygons.pos.x = this.x;
this.polygons.pos.y = this.y;
this.shape.pos.x = this.x;
this.shape.pos.y = this.y;
if (this.polygons)
{
this.polygons.pos.x = this.x;
this.polygons.pos.y = this.y;
}
this.center.setTo(this.x + this.halfWidth, this.y + this.halfHeight);
@ -1303,20 +1401,3 @@ Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, "right", {
}
});
/**
* @name Phaser.Physics.Arcade.Body#movingLeft
* @property {boolean} movingLeft - True if this Body is moving left, based on its angle and speed.
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, "movingLeft", {
/**
* True if this Body is moving left, based on its angle and speed.
* @method movingLeft
* @return {boolean}
*/
get: function () {
return (this.speed > 0 && this.angle >= 0 && this.angle <= 0);
},
});

862
src/physics/arcade/SAT.js Normal file
View file

@ -0,0 +1,862 @@
// Version 0.2 - Copyright 2013 - Jim Riecken <jimr@jimr.ca>
//
// Released under the MIT License - https://github.com/jriecken/sat-js
//
// A simple library for determining intersections of circles and
// polygons using the Separating Axis Theorem.
/** @preserve SAT.js - Version 0.2 - Copyright 2013 - Jim Riecken <jimr@jimr.ca> - released under the MIT License. https://github.com/jriecken/sat-js */
/*global define: false, module: false*/
/*jshint shadow:true, sub:true, forin:true, noarg:true, noempty:true,
eqeqeq:true, bitwise:true, strict:true, undef:true,
curly:true, browser:true */
// Create a UMD wrapper for SAT. Works in:
//
// - Plain browser via global SAT variable
// - AMD loader (like require.js)
// - Node.js
//
// The quoted properties all over the place are used so that the Closure Compiler
// does not mangle the exposed API in advanced mode.
/**
* @param {*} root - The global scope
* @param {Function} factory - Factory that creates SAT module
*/
(function (root, factory) {
"use strict";
if (typeof define === 'function' && define['amd']) {
define(factory);
} else if (typeof exports === 'object') {
module['exports'] = factory();
} else {
root['SAT'] = factory();
}
}(this, function () {
"use strict";
var SAT = {};
//
// ## Vector
//
// Represents a vector in two dimensions with `x` and `y` properties.
// Create a new Vector, optionally passing in the `x` and `y` coordinates. If
// a coordinate is not specified, it will be set to `0`
/**
* @param {?number=} x The x position.
* @param {?number=} y The y position.
* @constructor
*/
function Vector(x, y) {
this['x'] = x || 0;
this['y'] = y || 0;
}
SAT['Vector'] = Vector;
// Alias `Vector` as `V`
SAT['V'] = Vector;
// Copy the values of another Vector into this one.
/**
* @param {Vector} other The other Vector.
* @return {Vector} This for chaining.
*/
Vector.prototype['copy'] = Vector.prototype.copy = function(other) {
this['x'] = other['x'];
this['y'] = other['y'];
return this;
};
// Change this vector to be perpendicular to what it was before. (Effectively
// roatates it 90 degrees in a clockwise direction)
/**
* @return {Vector} This for chaining.
*/
Vector.prototype['perp'] = Vector.prototype.perp = function() {
var x = this['x'];
this['x'] = this['y'];
this['y'] = -x;
return this;
};
// Rotate this vector (counter-clockwise) by the specified angle (in radians).
/**
* @param {number} angle The angle to rotate (in radians)
* @return {Vector} This for chaining.
*/
Vector.prototype['rotate'] = Vector.prototype.rotate = function (angle) {
var x = this['x'];
var y = this['y'];
this['x'] = x * Math.cos(angle) - y * Math.sin(angle);
this['y'] = x * Math.sin(angle) + y * Math.cos(angle);
return this;
};
// Rotate this vector (counter-clockwise) by the specified angle (in radians) which has already been calculated into sin and cos.
/**
* @param {number} sin - The Math.sin(angle)
* @param {number} cos - The Math.cos(angle)
* @return {Vector} This for chaining.
*/
Vector.prototype['rotatePrecalc'] = Vector.prototype.rotatePrecalc = function (sin, cos) {
var x = this['x'];
var y = this['y'];
this['x'] = x * cos - y * sin;
this['y'] = x * sin + y * cos;
return this;
};
// Reverse this vector.
/**
* @return {Vector} This for chaining.
*/
Vector.prototype['reverse'] = Vector.prototype.reverse = function() {
this['x'] = -this['x'];
this['y'] = -this['y'];
return this;
};
// Normalize this vector. (make it have length of `1`)
/**
* @return {Vector} This for chaining.
*/
Vector.prototype['normalize'] = Vector.prototype.normalize = function() {
var d = this.len();
if(d > 0) {
this['x'] = this['x'] / d;
this['y'] = this['y'] / d;
}
return this;
};
// Add another vector to this one.
/**
* @param {Vector} other The other Vector.
* @return {Vector} This for chaining.
*/
Vector.prototype['add'] = Vector.prototype.add = function(other) {
this['x'] += other['x'];
this['y'] += other['y'];
return this;
};
// Subtract another vector from this one.
/**
* @param {Vector} other The other Vector.
* @return {Vector} This for chaiing.
*/
Vector.prototype['sub'] = Vector.prototype.sub = function(other) {
this['x'] -= other['x'];
this['y'] -= other['y'];
return this;
};
// Scale this vector. An independant scaling factor can be provided
// for each axis, or a single scaling factor that will scale both `x` and `y`.
/**
* @param {number} x The scaling factor in the x direction.
* @param {?number=} y The scaling factor in the y direction. If this
* is not specified, the x scaling factor will be used.
* @return {Vector} This for chaining.
*/
Vector.prototype['scale'] = Vector.prototype.scale = function(x,y) {
this['x'] *= x;
this['y'] *= y || x;
return this;
};
// Project this vector on to another vector.
/**
* @param {Vector} other The vector to project onto.
* @return {Vector} This for chaining.
*/
Vector.prototype['project'] = Vector.prototype.project = function(other) {
var amt = this.dot(other) / other.len2();
this['x'] = amt * other['x'];
this['y'] = amt * other['y'];
return this;
};
// Project this vector onto a vector of unit length. This is slightly more efficient
// than `project` when dealing with unit vectors.
/**
* @param {Vector} other The unit vector to project onto.
* @return {Vector} This for chaining.
*/
Vector.prototype['projectN'] = Vector.prototype.projectN = function(other) {
var amt = this.dot(other);
this['x'] = amt * other['x'];
this['y'] = amt * other['y'];
return this;
};
// Reflect this vector on an arbitrary axis.
/**
* @param {Vector} axis The vector representing the axis.
* @return {Vector} This for chaining.
*/
Vector.prototype['reflect'] = Vector.prototype.reflect = function(axis) {
var x = this['x'];
var y = this['y'];
this.project(axis).scale(2);
this['x'] -= x;
this['y'] -= y;
return this;
};
// Reflect this vector on an arbitrary axis (represented by a unit vector). This is
// slightly more efficient than `reflect` when dealing with an axis that is a unit vector.
/**
* @param {Vector} axis The unit vector representing the axis.
* @return {Vector} This for chaining.
*/
Vector.prototype['reflectN'] = Vector.prototype.reflectN = function(axis) {
var x = this['x'];
var y = this['y'];
this.projectN(axis).scale(2);
this['x'] -= x;
this['y'] -= y;
return this;
};
// Get the dot product of this vector and another.
/**
* @param {Vector} other The vector to dot this one against.
* @return {number} The dot product.
*/
Vector.prototype['dot'] = Vector.prototype.dot = function(other) {
return this['x'] * other['x'] + this['y'] * other['y'];
};
// Get the squared length of this vector.
/**
* @return {number} The length^2 of this vector.
*/
Vector.prototype['len2'] = Vector.prototype.len2 = function() {
return this.dot(this);
};
// Get the length of this vector.
/**
* @return {number} The length of this vector.
*/
Vector.prototype['len'] = Vector.prototype.len = function() {
return Math.sqrt(this.len2());
};
// ## Circle
//
// Represents a circle with a position and a radius.
// Create a new circle, optionally passing in a position and/or radius. If no position
// is given, the circle will be at `(0,0)`. If no radius is provided, the circle will
// have a radius of `0`.
/**
* @param {Vector=} pos A vector representing the position of the center of the circle
* @param {?number=} r The radius of the circle
* @constructor
*/
function Circle(pos, r) {
this['pos'] = pos || new Vector();
this['r'] = r || 0;
}
SAT['Circle'] = Circle;
// ## Polygon
//
// Represents a *convex* polygon with any number of points (specified in counter-clockwise order)
//
// The edges/normals of the polygon will be calculated on creation and stored in the
// `edges` and `normals` properties. If you change the polygon's points, you will need
// to call `recalc` to recalculate the edges/normals.
// Create a new polygon, passing in a position vector, and an array of points (represented
// by vectors relative to the position vector). If no position is passed in, the position
// of the polygon will be `(0,0)`.
/**
* @param {Vector=} pos A vector representing the origin of the polygon. (all other
* points are relative to this one)
* @param {Array.<Vector>=} points An array of vectors representing the points in the polygon,
* in counter-clockwise order.
* @constructor
*/
function Polygon(pos, points) {
this['pos'] = pos || new Vector();
this['points'] = points || [];
this.recalc();
}
SAT['Polygon'] = Polygon;
// Recalculates the edges and normals of the polygon. This **must** be called
// if the `points` array is modified at all and the edges or normals are to be
// accessed.
/**
* @return {Polygon} This for chaining.
*/
Polygon.prototype['recalc'] = Polygon.prototype.recalc = function() {
// The edges here are the direction of the `n`th edge of the polygon, relative to
// the `n`th point. If you want to draw a given edge from the edge value, you must
// first translate to the position of the starting point.
this['edges'] = [];
// The normals here are the direction of the normal for the `n`th edge of the polygon, relative
// to the position of the `n`th point. If you want to draw an edge normal, you must first
// translate to the position of the starting point.
this['normals'] = [];
var points = this['points'];
var len = points.length;
for (var i = 0; i < len; i++) {
var p1 = points[i];
var p2 = i < len - 1 ? points[i + 1] : points[0];
var e = new Vector().copy(p2).sub(p1);
var n = new Vector().copy(e).perp().normalize();
this['edges'].push(e);
this['normals'].push(n);
}
return this;
};
// Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`).
//
// Note: You do **not** need to call `recalc` after rotation.
/**
* @param {number} angle The angle to rotate (in radians)
* @return {Polygon} This for chaining.
*/
Polygon.prototype['rotate'] = Polygon.prototype.rotate = function(angle) {
var i;
var points = this['points'];
var edges = this['edges'];
var normals = this['normals'];
var len = points.length;
// Calc it just the once, rather than 4 times per array element
var cos = Math.cos(angle);
var sin = Math.sin(angle);
for (i = 0; i < len; i++) {
points[i].rotatePrecalc(sin, cos);
edges[i].rotatePrecalc(sin, cos);
normals[i].rotatePrecalc(sin, cos);
}
return this;
};
// Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`).
//
// Note: You do **not** need to call `recalc` after rotation.
/**
* @param {number} angle The angle to rotate (in radians)
* @return {Polygon} This for chaining.
*/
Polygon.prototype['scale'] = Polygon.prototype.scale = function(x, y) {
var i;
var points = this['points'];
var edges = this['edges'];
var normals = this['normals'];
var len = points.length;
for (i = 0; i < len; i++) {
points[i].scale(x,y);
edges[i].scale(x,y);
normals[i].scale(x,y);
}
return this;
};
// Translates the points of this polygon by a specified amount relative to the origin of *its own coordinate
// system* (i.e. `pos`).
//
// This is most useful to change the "center point" of a polygon.
//
// Note: You do **not** need to call `recalc` after translation.
/**
* @param {number} x The horizontal amount to translate.
* @param {number} y The vertical amount to translate.
* @return {Polygon} This for chaining.
*/
Polygon.prototype['translate'] = Polygon.prototype.translate = function (x, y) {
var i;
var points = this['points'];
var len = points.length;
for (i = 0; i < len; i++) {
points[i].x += x;
points[i].y += y;
}
return this;
};
// ## Box
//
// Represents an axis-aligned box, with a width and height.
// Create a new box, with the specified position, width, and height. If no position
// is given, the position will be `(0,0)`. If no width or height are given, they will
// be set to `0`.
/**
* @param {Vector=} pos A vector representing the top-left of the box.
* @param {?number=} w The width of the box.
* @param {?number=} h The height of the box.
* @constructor
*/
function Box(pos, w, h) {
this['pos'] = pos || new Vector();
this['w'] = w || 0;
this['h'] = h || 0;
}
SAT['Box'] = Box;
// Returns a polygon whose edges are the same as this box.
/**
* @return {Polygon} A new Polygon that represents this box.
*/
Box.prototype['toPolygon'] = Box.prototype.toPolygon = function() {
var pos = this['pos'];
var w = this['w'];
var h = this['h'];
return new Polygon(new Vector(pos['x'], pos['y']), [
new Vector(), new Vector(w, 0),
new Vector(w,h), new Vector(0,h)
]);
};
// ## Response
//
// An object representing the result of an intersection. Contains:
// - The two objects participating in the intersection
// - The vector representing the minimum change necessary to extract the first object
// from the second one (as well as a unit vector in that direction and the magnitude
// of the overlap)
// - Whether the first object is entirely inside the second, and vice versa.
/**
* @constructor
*/
function Response() {
this['a'] = null;
this['b'] = null;
this['overlapN'] = new Vector();
this['overlapV'] = new Vector();
this.clear();
}
SAT['Response'] = Response;
// Set some values of the response back to their defaults. Call this between tests if
// you are going to reuse a single Response object for multiple intersection tests (recommented
// as it will avoid allcating extra memory)
/**
* @return {Response} This for chaining
*/
Response.prototype['clear'] = Response.prototype.clear = function() {
this['aInB'] = true;
this['bInA'] = true;
this['overlap'] = Number.MAX_VALUE;
return this;
};
// ## Object Pools
// A pool of `Vector` objects that are used in calculations to avoid
// allocating memory.
/**
* @type {Array.<Vector>}
*/
var T_VECTORS = [];
for (var i = 0; i < 10; i++) { T_VECTORS.push(new Vector()); }
// A pool of arrays of numbers used in calculations to avoid allocating
// memory.
/**
* @type {Array.<Array.<number>>}
*/
var T_ARRAYS = [];
for (var i = 0; i < 5; i++) { T_ARRAYS.push([]); }
// ## Helper Functions
// Flattens the specified array of points onto a unit vector axis,
// resulting in a one dimensional range of the minimum and
// maximum value on that axis.
/**
* @param {Array.<Vector>} points The points to flatten.
* @param {Vector} normal The unit vector axis to flatten on.
* @param {Array.<number>} result An array. After calling this function,
* result[0] will be the minimum value,
* result[1] will be the maximum value.
*/
function flattenPointsOn(points, normal, result) {
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
var len = points.length;
for (var i = 0; i < len; i++ ) {
// The magnitude of the projection of the point onto the normal
var dot = points[i].dot(normal);
if (dot < min) { min = dot; }
if (dot > max) { max = dot; }
}
result[0] = min; result[1] = max;
}
// Check whether two convex polygons are separated by the specified
// axis (must be a unit vector).
/**
* @param {Vector} aPos The position of the first polygon.
* @param {Vector} bPos The position of the second polygon.
* @param {Array.<Vector>} aPoints The points in the first polygon.
* @param {Array.<Vector>} bPoints The points in the second polygon.
* @param {Vector} axis The axis (unit sized) to test against. The points of both polygons
* will be projected onto this axis.
* @param {Response=} response A Response object (optional) which will be populated
* if the axis is not a separating axis.
* @return {boolean} true if it is a separating axis, false otherwise. If false,
* and a response is passed in, information about how much overlap and
* the direction of the overlap will be populated.
*/
function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) {
var rangeA = T_ARRAYS.pop();
var rangeB = T_ARRAYS.pop();
// The magnitude of the offset between the two polygons
var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos);
var projectedOffset = offsetV.dot(axis);
// Project the polygons onto the axis.
flattenPointsOn(aPoints, axis, rangeA);
flattenPointsOn(bPoints, axis, rangeB);
// Move B's range to its position relative to A.
rangeB[0] += projectedOffset;
rangeB[1] += projectedOffset;
// Check if there is a gap. If there is, this is a separating axis and we can stop
if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) {
T_VECTORS.push(offsetV);
T_ARRAYS.push(rangeA);
T_ARRAYS.push(rangeB);
return true;
}
// This is not a separating axis. If we're calculating a response, calculate the overlap.
if (response) {
var overlap = 0;
// A starts further left than B
if (rangeA[0] < rangeB[0]) {
response['aInB'] = false;
// A ends before B does. We have to pull A out of B
if (rangeA[1] < rangeB[1]) {
overlap = rangeA[1] - rangeB[0];
response['bInA'] = false;
// B is fully inside A. Pick the shortest way out.
} else {
var option1 = rangeA[1] - rangeB[0];
var option2 = rangeB[1] - rangeA[0];
overlap = option1 < option2 ? option1 : -option2;
}
// B starts further left than A
} else {
response['bInA'] = false;
// B ends before A ends. We have to push A out of B
if (rangeA[1] > rangeB[1]) {
overlap = rangeA[0] - rangeB[1];
response['aInB'] = false;
// A is fully inside B. Pick the shortest way out.
} else {
var option1 = rangeA[1] - rangeB[0];
var option2 = rangeB[1] - rangeA[0];
overlap = option1 < option2 ? option1 : -option2;
}
}
// If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap.
var absOverlap = Math.abs(overlap);
if (absOverlap < response['overlap']) {
response['overlap'] = absOverlap;
response['overlapN'].copy(axis);
if (overlap < 0) {
response['overlapN'].reverse();
}
}
}
T_VECTORS.push(offsetV);
T_ARRAYS.push(rangeA);
T_ARRAYS.push(rangeB);
return false;
}
// Calculates which Vornoi region a point is on a line segment.
// It is assumed that both the line and the point are relative to `(0,0)`
//
// | (0) |
// (-1) [S]--------------[E] (1)
// | (0) |
/**
* @param {Vector} line The line segment.
* @param {Vector} point The point.
* @return {number} LEFT_VORNOI_REGION (-1) if it is the left region,
* MIDDLE_VORNOI_REGION (0) if it is the middle region,
* RIGHT_VORNOI_REGION (1) if it is the right region.
*/
function vornoiRegion(line, point) {
var len2 = line.len2();
var dp = point.dot(line);
// If the point is beyond the start of the line, it is in the
// left vornoi region.
if (dp < 0) { return LEFT_VORNOI_REGION; }
// If the point is beyond the end of the line, it is in the
// right vornoi region.
else if (dp > len2) { return RIGHT_VORNOI_REGION; }
// Otherwise, it's in the middle one.
else { return MIDDLE_VORNOI_REGION; }
}
// Constants for Vornoi regions
/**
* @const
*/
var LEFT_VORNOI_REGION = -1;
/**
* @const
*/
var MIDDLE_VORNOI_REGION = 0;
/**
* @const
*/
var RIGHT_VORNOI_REGION = 1;
// ## Collision Tests
// Check if two circles collide.
/**
* @param {Circle} a The first circle.
* @param {Circle} b The second circle.
* @param {Response=} response Response object (optional) that will be populated if
* the circles intersect.
* @return {boolean} true if the circles intersect, false if they don't.
*/
function testCircleCircle(a, b, response) {
// Check if the distance between the centers of the two
// circles is greater than their combined radius.
var differenceV = T_VECTORS.pop().copy(b['pos']).sub(a['pos']);
var totalRadius = a['r'] + b['r'];
var totalRadiusSq = totalRadius * totalRadius;
var distanceSq = differenceV.len2();
// If the distance is bigger than the combined radius, they don't intersect.
if (distanceSq > totalRadiusSq) {
T_VECTORS.push(differenceV);
return false;
}
// They intersect. If we're calculating a response, calculate the overlap.
if (response) {
var dist = Math.sqrt(distanceSq);
response['a'] = a;
response['b'] = b;
response['overlap'] = totalRadius - dist;
response['overlapN'].copy(differenceV.normalize());
response['overlapV'].copy(differenceV).scale(response['overlap']);
response['aInB']= a['r'] <= b['r'] && dist <= b['r'] - a['r'];
response['bInA'] = b['r'] <= a['r'] && dist <= a['r'] - b['r'];
}
T_VECTORS.push(differenceV);
return true;
}
SAT['testCircleCircle'] = testCircleCircle;
// Check if a polygon and a circle collide.
/**
* @param {Polygon} polygon The polygon.
* @param {Circle} circle The circle.
* @param {Response=} response Response object (optional) that will be populated if
* they interset.
* @return {boolean} true if they intersect, false if they don't.
*/
function testPolygonCircle(polygon, circle, response) {
// Get the position of the circle relative to the polygon.
var circlePos = T_VECTORS.pop().copy(circle['pos']).sub(polygon['pos']);
var radius = circle['r'];
var radius2 = radius * radius;
var points = polygon['points'];
var len = points.length;
var edge = T_VECTORS.pop();
var point = T_VECTORS.pop();
// For each edge in the polygon:
for (var i = 0; i < len; i++) {
var next = i === len - 1 ? 0 : i + 1;
var prev = i === 0 ? len - 1 : i - 1;
var overlap = 0;
var overlapN = null;
// Get the edge.
edge.copy(polygon['edges'][i]);
// Calculate the center of the circle relative to the starting point of the edge.
point.copy(circlePos).sub(points[i]);
// If the distance between the center of the circle and the point
// is bigger than the radius, the polygon is definitely not fully in
// the circle.
if (response && point.len2() > radius2) {
response['aInB'] = false;
}
// Calculate which Vornoi region the center of the circle is in.
var region = vornoiRegion(edge, point);
// If it's the left region:
if (region === LEFT_VORNOI_REGION) {
// We need to make sure we're in the RIGHT_VORNOI_REGION of the previous edge.
edge.copy(polygon['edges'][prev]);
// Calculate the center of the circle relative the starting point of the previous edge
var point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]);
region = vornoiRegion(edge, point2);
if (region === RIGHT_VORNOI_REGION) {
// It's in the region we want. Check if the circle intersects the point.
var dist = point.len();
if (dist > radius) {
// No intersection
T_VECTORS.push(circlePos);
T_VECTORS.push(edge);
T_VECTORS.push(point);
T_VECTORS.push(point2);
return false;
} else if (response) {
// It intersects, calculate the overlap.
response['bInA'] = false;
overlapN = point.normalize();
overlap = radius - dist;
}
}
T_VECTORS.push(point2);
// If it's the right region:
} else if (region === RIGHT_VORNOI_REGION) {
// We need to make sure we're in the left region on the next edge
edge.copy(polygon['edges'][next]);
// Calculate the center of the circle relative to the starting point of the next edge.
point.copy(circlePos).sub(points[next]);
region = vornoiRegion(edge, point);
if (region === LEFT_VORNOI_REGION) {
// It's in the region we want. Check if the circle intersects the point.
var dist = point.len();
if (dist > radius) {
// No intersection
T_VECTORS.push(circlePos);
T_VECTORS.push(edge);
T_VECTORS.push(point);
return false;
} else if (response) {
// It intersects, calculate the overlap.
response['bInA'] = false;
overlapN = point.normalize();
overlap = radius - dist;
}
}
// Otherwise, it's the middle region:
} else {
// Need to check if the circle is intersecting the edge,
// Change the edge into its "edge normal".
var normal = edge.perp().normalize();
// Find the perpendicular distance between the center of the
// circle and the edge.
var dist = point.dot(normal);
var distAbs = Math.abs(dist);
// If the circle is on the outside of the edge, there is no intersection.
if (dist > 0 && distAbs > radius) {
// No intersection
T_VECTORS.push(circlePos);
T_VECTORS.push(normal);
T_VECTORS.push(point);
return false;
} else if (response) {
// It intersects, calculate the overlap.
overlapN = normal;
overlap = radius - dist;
// If the center of the circle is on the outside of the edge, or part of the
// circle is on the outside, the circle is not fully inside the polygon.
if (dist >= 0 || overlap < 2 * radius) {
response['bInA'] = false;
}
}
}
// If this is the smallest overlap we've seen, keep it.
// (overlapN may be null if the circle was in the wrong Vornoi region).
if (overlapN && response && Math.abs(overlap) < Math.abs(response['overlap'])) {
response['overlap'] = overlap;
response['overlapN'].copy(overlapN);
}
}
// Calculate the final overlap vector - based on the smallest overlap.
if (response) {
response['a'] = polygon;
response['b'] = circle;
response['overlapV'].copy(response['overlapN']).scale(response['overlap']);
}
T_VECTORS.push(circlePos);
T_VECTORS.push(edge);
T_VECTORS.push(point);
return true;
}
SAT['testPolygonCircle'] = testPolygonCircle;
// Check if a circle and a polygon collide.
//
// **NOTE:** This is slightly less efficient than polygonCircle as it just
// runs polygonCircle and reverses everything at the end.
/**
* @param {Circle} circle The circle.
* @param {Polygon} polygon The polygon.
* @param {Response=} response Response object (optional) that will be populated if
* they interset.
* @return {boolean} true if they intersect, false if they don't.
*/
function testCirclePolygon(circle, polygon, response) {
// Test the polygon against the circle.
var result = testPolygonCircle(polygon, circle, response);
if (result && response) {
// Swap A and B in the response.
var a = response['a'];
var aInB = response['aInB'];
response['overlapN'].reverse();
response['overlapV'].reverse();
response['a'] = response['b'];
response['b'] = a;
response['aInB'] = response['bInA'];
response['bInA'] = aInB;
}
return result;
}
SAT['testCirclePolygon'] = testCirclePolygon;
// Checks whether polygons collide.
/**
* @param {Polygon} a The first polygon.
* @param {Polygon} b The second polygon.
* @param {Response=} response Response object (optional) that will be populated if
* they interset.
* @return {boolean} true if they intersect, false if they don't.
*/
function testPolygonPolygon(a, b, response) {
var aPoints = a['points'];
var aLen = aPoints.length;
var bPoints = b['points'];
var bLen = bPoints.length;
// If any of the edge normals of A is a separating axis, no intersection.
for (var i = 0; i < aLen; i++) {
if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, a['normals'][i], response)) {
return false;
}
}
// If any of the edge normals of B is a separating axis, no intersection.
for (var i = 0;i < bLen; i++) {
if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, b['normals'][i], response)) {
return false;
}
}
// Since none of the edge normals of A or B are a separating axis, there is an intersection
// and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the
// final overlap vector.
if (response) {
response['a'] = a;
response['b'] = b;
response['overlapV'].copy(response['overlapN']).scale(response['overlap']);
}
return true;
}
SAT['testPolygonPolygon'] = testPolygonPolygon;
return SAT;
}));

View file

@ -1,754 +0,0 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Physics Body is linked to a single Sprite. All physics operations should be performed against the body rather than
* the Sprite itself. For example you can set the velocity, acceleration, bounce values etc all on the Body.
*
* @class Phaser.Physics.Arcade.Body
* @classdesc Arcade Physics Body Constructor
* @constructor
* @param {Phaser.Sprite} sprite - The Sprite object this physics body belongs to.
*/
Phaser.Physics.Arcade.Body = function (sprite) {
/**
* @property {Phaser.Sprite} sprite - Reference to the parent Sprite.
*/
this.sprite = sprite;
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = sprite.game;
/**
* @property {Phaser.Point} offset - The offset of the Physics Body from the Sprite x/y position.
*/
this.offset = new Phaser.Point();
/**
* @property {number} x - The x position of the physics body.
* @readonly
*/
this.x = sprite.x;
/**
* @property {number} y - The y position of the physics body.
* @readonly
*/
this.y = sprite.y;
/**
* @property {number} preX - The previous x position of the physics body.
* @readonly
*/
this.preX = sprite.x;
/**
* @property {number} preY - The previous y position of the physics body.
* @readonly
*/
this.preY = sprite.y;
/**
* @property {number} preRotation - The previous rotation of the physics body.
* @readonly
*/
this.preRotation = sprite.angle;
/**
* @property {number} width - The calculated width of the physics body.
*/
this.width = sprite.currentFrame.sourceSizeW;
/**
* @property {number} height - The calculated height of the physics body.
*/
this.height = sprite.currentFrame.sourceSizeH;
/**
* @property {Phaser.Point} center - The center coordinate of the Physics Body.
*/
this.center = new Phaser.Point(this.x + this.halfWidth, this.y + this.halfHeight);
/**
* @property {Phaser.Point} motionVelocity - The data from the updateMotion function.
*/
this.motionVelocity = new Phaser.Point();
/**
* @property {Phaser.Point} velocity - The velocity in pixels per second sq. of the Body.
*/
this.velocity = new Phaser.Point();
/**
* @property {Phaser.Point} acceleration - The velocity in pixels per second sq. of the Body.
*/
this.acceleration = new Phaser.Point();
/**
* @property {number} speed - The speed in pixels per second sq. of the Body.
*/
this.speed = 0;
/**
* @property {number} angle - The angle of the Body in radians.
*/
this.angle = 0;
/**
* @property {number} minBounceVelocity - The minimum bounce velocity (could just be the bounce value?).
*/
// this.minBounceVelocity = 0.5;
this._debug = 0;
/**
* @property {Phaser.Point} gravity - The gravity applied to the motion of the Body.
*/
this.gravity = new Phaser.Point();
/**
* @property {Phaser.Point} bounce - The elasticitiy of the Body when colliding. bounce.x/y = 1 means full rebound, bounce.x/y = 0.5 means 50% rebound velocity.
*/
this.bounce = new Phaser.Point();
/**
* @property {Phaser.Point} minVelocity - When a body rebounds off another the minVelocity is checked, if the new velocity is lower than the minVelocity the body is stopped.
* @default
*/
this.minVelocity = new Phaser.Point(20, 20);
/**
* @property {Phaser.Point} maxVelocity - The maximum velocity in pixels per second sq. that the Body can reach.
* @default
*/
this.maxVelocity = new Phaser.Point(10000, 10000);
/**
* @property {number} angularVelocity - The angular velocity in pixels per second sq. of the Body.
* @default
*/
this.angularVelocity = 0;
/**
* @property {number} angularAcceleration - The angular acceleration in pixels per second sq. of the Body.
* @default
*/
this.angularAcceleration = 0;
/**
* @property {number} angularDrag - The angular drag applied to the rotation of the Body.
* @default
*/
this.angularDrag = 0;
/**
* @property {number} maxAngular - The maximum angular velocity in pixels per second sq. that the Body can reach.
* @default
*/
this.maxAngular = 1000;
/**
* @property {number} mass - The mass of the Body.
* @default
*/
this.mass = 1;
/**
* Set the allowCollision properties to control which directions collision is processed for this Body.
* For example allowCollision.up = false means it won't collide when the collision happened while moving up.
* @property {object} allowCollision - An object containing allowed collision.
*/
this.allowCollision = { none: false, any: true, up: true, down: true, left: true, right: true };
/**
* This object is populated with boolean values when the Body collides with another.
* touching.up = true means the collision happened to the top of this Body for example.
* @property {object} touching - An object containing touching results.
*/
this.touching = { none: true, up: false, down: false, left: false, right: false };
/**
* @property {boolean} immovable - An immovable Body will not receive any impacts or exchanges of velocity from other bodies.
* @default
*/
this.immovable = false;
/**
* @property {boolean} moves - Set to true to allow the Physics system to move this Body, other false to move it manually.
* @default
*/
this.moves = true;
/**
* @property {number} rotation - The amount the parent Sprite is rotated. Note: You cannot rotate an AABB.
* @default
*/
this.rotation = 0;
/**
* @property {boolean} allowRotation - Allow angular rotation? This will cause the Sprite to be rotated via angularVelocity, etc. Note that the AABB remains un-rotated.
* @default
*/
this.allowRotation = true;
/**
* @property {boolean} allowGravity - Allow this Body to be influenced by the global Gravity value? Note: It will always be influenced by the local gravity value.
* @default
*/
this.allowGravity = true;
/**
* This flag allows you to disable the custom x separation that takes place by Physics.Arcade.separate.
* Used in combination with your own collision processHandler you can create whatever type of collision response you need.
* @property {boolean} customSeparateX - Use a custom separation system or the built-in one?
* @default
*/
this.customSeparateX = false;
/**
* This flag allows you to disable the custom y separation that takes place by Physics.Arcade.separate.
* Used in combination with your own collision processHandler you can create whatever type of collision response you need.
* @property {boolean} customSeparateY - Use a custom separation system or the built-in one?
* @default
*/
this.customSeparateY = false;
/**
* When this body collides with another, the amount of overlap is stored here.
* @property {number} overlapX - The amount of horizontal overlap during the collision.
*/
this.overlapX = 0;
/**
* When this body collides with another, the amount of overlap is stored here.
* @property {number} overlapY - The amount of vertical overlap during the collision.
*/
this.overlapY = 0;
/**
* @property {number} friction - The amount of friction this body experiences during motion.
* @default
*/
this.friction = 0;
/**
* A Body can be set to collide against the World bounds automatically and rebound back into the World if this is set to true. Otherwise it will leave the World.
* @property {boolean} collideWorldBounds - Should the Body collide with the World bounds?
*/
this.collideWorldBounds = false;
/**
* This object is populated with boolean values when the Body collides with the World bounds or a Tile.
* For example if blocked.up is true then the Body cannot move up.
* @property {object} blocked - An object containing on which faces this Body is blocked from moving, if any.
*/
this.blocked = { up: false, down: false, left: false, right: false };
/**
* @property {number} _dx - Internal cache var.
* @private
*/
this._dx = 0;
/**
* @property {number} _dy - Internal cache var.
* @private
*/
this._dy = 0;
/**
* @property {number} _sx - Internal cache var.
* @private
*/
this._sx = sprite.scale.x;
/**
* @property {number} _sy - Internal cache var.
* @private
*/
this._sy = sprite.scale.y;
this.shape = new SAT.Box(new SAT.Vector(this.x, this.y), this.width, this.height);
this.polygon = this.shape.toPolygon();
this.response = new SAT.Response();
};
Phaser.Physics.Arcade.Body.prototype = {
/**
* Internal method.
*
* @method Phaser.Physics.Arcade#updateBounds
* @protected
*/
updateBounds: function (centerX, centerY, scaleX, scaleY) {
// if (scaleX != this._sx || scaleY != this._sy)
// {
// this.width = this.sourceWidth * scaleX;
// this.height = this.sourceHeight * scaleY;
// this.halfWidth = Math.floor(this.width / 2);
// this.halfHeight = Math.floor(this.height / 2);
// this._sx = scaleX;
// this._sy = scaleY;
// this.center.setTo(this.x + this.halfWidth, this.y + this.halfHeight);
// }
},
/**
* Internal method.
*
* @method Phaser.Physics.Arcade#preUpdate
* @protected
*/
preUpdate: function () {
// this.screenX = (this.sprite.worldTransform[2] - (this.sprite.anchor.x * this.width)) + this.offset.x;
// this.screenY = (this.sprite.worldTransform[5] - (this.sprite.anchor.y * this.height)) + this.offset.y;
this.preX = (this.sprite.world.x - (this.sprite.anchor.x * this.width)) + this.offset.x;
this.preY = (this.sprite.world.y - (this.sprite.anchor.y * this.height)) + this.offset.y;
// this.preRotation = this.sprite.angle;
this.preRotation = this.sprite.rotation;
// This all needs to move - because a body may start the preUpdate already touching something
this.blocked.up = false;
this.blocked.down = false;
this.blocked.left = false;
this.blocked.right = false;
this.x = this.preX;
this.y = this.preY;
// this.rotation = this.preRotation;
this.speed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y);
this.angle = Math.atan2(this.velocity.y, this.velocity.x);
this._debug++;
if (this.moves)
{
if (this.collideWorldBounds)
{
this.checkWorldBounds();
}
this.game.physics.updateMotion(this);
this.applyMotion();
}
this.polygon.pos.x = this.x;
this.polygon.pos.y = this.y;
if (this.deltaZ() !== 0)
{
// this.polygon.rotate(this.sprite.rotation);
// console.log(this.sprite.rotation);
}
// this.polygon.rotate(this.game.math.degToRad(this.rotation));
},
/**
* Internal method used to check the Body against the World Bounds.
*
* @method Phaser.Physics.Arcade#checkWorldBounds
* @protected
*/
checkWorldBounds: function () {
if (this.x <= this.game.world.bounds.x)
{
this.overlapX = this.game.world.bounds.x - this.x;
this.blocked.left = true;
// console.log(this._debug, 'cwl', this.overlapX, this.x, this.game.world.bounds.x);
}
else if (this.right >= this.game.world.bounds.right)
{
this.overlapX = this.right - this.game.world.bounds.right;
this.blocked.right = true;
// console.log(this._debug, 'cwr', this.overlapX, this.x, this.game.world.bounds.x);
}
if (this.y <= this.game.world.bounds.y)
{
this.overlapY = this.game.world.bounds.y - this.y;
this.blocked.up = true;
// console.log(this._debug, 'cwu', this.overlapY, this.y, this.height, this.bottom, this.game.world.bounds.bottom);
}
else if (this.bottom >= this.game.world.bounds.bottom)
{
this.overlapY = this.bottom - this.game.world.bounds.bottom;
this.blocked.down = true;
// console.log(this._debug, 'cwd', this.overlapY, this.y, this.height, this.bottom, this.game.world.bounds.bottom);
}
this.blockedPoint.setTo(this.x, this.y);
},
/**
* Internal method.
*
* @method Phaser.Physics.Arcade#applyMotion
* @protected
*/
applyMotion: function () {
if (this.friction > 0 && this.acceleration.isZero())
{
if (this.speed > this.friction)
{
this.speed -= this.friction;
}
else
{
this.speed = 0;
}
this.velocity.x = Math.cos(this.angle) * this.speed;
this.velocity.y = Math.sin(this.angle) * this.speed;
}
// overlapX/Y values at this point will be penetration into the bounds and DELTA WILL BE ZERO
if (this.blocked.left)
{
// Separate
this.x += this.overlapX;
// console.log(this._debug, 'blocked left', this.x, this.overlapX);
this.velocity.x *= -this.bounce.x;
this._dx = this.game.time.physicsElapsed * (this.velocity.x + this.motionVelocity.x / 2);
// if (this._dx > this.minBounceVelocity)
if (Math.abs(this.velocity.x) > this.minVelocity.x)
{
this.x += this._dx;
this.velocity.x += this.motionVelocity.x;
// console.log(this._debug, 'blocked left', this._dx, 'overlap', this.overlapX, 'delta', this.deltaX(), 'newy', this.x);
}
else
{
// Kill it dead :)
this.preX = this.x; // because we don't want any delta from a separation
this.velocity.x = 0;
this.motionVelocity.x = 0;
// console.log(this._debug, 'blocked left KILL', this._dx, 'overlap', this.overlapX, 'delta', this.deltaX(), 'newy', this.x);
}
}
else if (this.blocked.right)
{
// Separate
this.x -= this.overlapX;
this.velocity.x *= -this.bounce.x;
this._dx = this.game.time.physicsElapsed * (this.velocity.x + this.motionVelocity.x / 2);
if (this._dx < -this.minBounceVelocity)
{
this.x += this._dx;
this.velocity.x += this.motionVelocity.x;
// console.log(this._debug, 'blocked right', this._dx, 'overlap', this.overlapX, 'delta', this.deltaX(), 'newy', this.x);
}
else
{
// Kill it dead :)
this.preX = this.x; // because we don't want any delta from a separation
this.velocity.x = 0;
this.motionVelocity.x = 0;
// console.log(this._debug, 'blocked right KILL', this._dx, 'overlap', this.overlapX, 'delta', this.deltaX(), 'newy', this.x);
}
}
else
{
this.x += this.game.time.physicsElapsed * (this.velocity.x + this.motionVelocity.x / 2);
this.velocity.x += this.motionVelocity.x;
}
// overlapX/Y values at this point will be penetration into the bounds and DELTA WILL BE ZERO
if (this.blocked.up)
{
// Separate
this.y += this.overlapY;
this.velocity.y *= -this.bounce.y;
this._dy = this.game.time.physicsElapsed * (this.velocity.y + this.motionVelocity.y / 2);
// if (this._dy > this.minBounceVelocity)
if (Math.abs(this.velocity.y) > this.minVelocity.y)
{
this.y += this._dy;
this.velocity.y += this.motionVelocity.y;
}
else
{
// Kill it dead :)
this.preY = this.y; // because we don't want any delta from a separation
this.velocity.y = 0;
this.motionVelocity.y = 0;
// console.log(this._debug, 'void1', this.velocity.y, 'delta', this.deltaY());
}
}
else if (this.blocked.down)
{
// Separate
this.y -= this.overlapY;
this.velocity.y *= -this.bounce.y;
this._dy = this.game.time.physicsElapsed * (this.velocity.y + this.motionVelocity.y / 2);
// if (this._dy < -this.minBounceVelocity)
if (Math.abs(this.velocity.y) > this.minVelocity.y)
{
this.y += this._dy;
this.velocity.y += this.motionVelocity.y;
// console.log(this._debug, 'rb', this._dy, 'delta', this.deltaY(), 'newy', this.y);
}
else
{
// Kill it dead :)
this.preY = this.y; // because we don't want any delta from a separation
this.velocity.y = 0;
this.motionVelocity.y = 0;
// console.log(this._debug, 'void1', this.velocity.y, 'delta', this.deltaY());
}
}
else
{
this.y += this.game.time.physicsElapsed * (this.velocity.y + this.motionVelocity.y / 2);
this.velocity.y += this.motionVelocity.y;
}
if (this.velocity.x > this.maxVelocity.x)
{
this.velocity.x = this.maxVelocity.x;
}
else if (this.velocity.x < -this.maxVelocity.x)
{
this.velocity.x = -this.maxVelocity.x;
}
if (this.velocity.y > this.maxVelocity.y)
{
this.velocity.y = this.maxVelocity.y;
}
else if (this.velocity.y < -this.maxVelocity.y)
{
this.velocity.y = -this.maxVelocity.y;
}
},
collide: function (body) {
this.response.clear();
// if (this._debug === 100)
// {
// console.log(this.sprite.name, this.polygon);
// }
if (SAT.testPolygonPolygon(this.polygon, body.polygon, this.response))
{
// b1.pos.sub(r.overlapV);
// sprite.body.velocity.x *= -sprite.body.bounce.x;
this.velocity.x = 0;
// separate
this.x += Math.ceil(Math.abs(this.response.overlapV.x));
this.y += Math.ceil(Math.abs(this.response.overlapV.y));
console.log(this.response, this.deltaX());
console.log('sprite moved to', this.x, this.deltaX());
}
},
/**
* Internal method. This is called directly before the sprites are sent to the renderer.
*
* @method Phaser.Physics.Arcade#postUpdate
* @protected
*/
postUpdate: function () {
if (this.moves)
{
this.sprite.x += this.deltaX();
this.sprite.y += this.deltaY();
this.sprite.worldTransform[2] += this.deltaX();
this.sprite.worldTransform[5] += this.deltaY();
this.center.setTo(this.x + this.halfWidth, this.y + this.halfHeight);
this.rotation = this.preRotation;
if (this.allowRotation)
{
// this.sprite.angle += this.deltaZ();
}
}
},
/**
* Resets all Body values (velocity, acceleration, rotation, etc)
*
* @method Phaser.Physics.Arcade#reset
*/
reset: function () {
this.velocity.setTo(0, 0);
this.acceleration.setTo(0, 0);
this.angularVelocity = 0;
this.angularAcceleration = 0;
this.preX = (this.sprite.world.x - (this.sprite.anchor.x * this.width)) + this.offset.x;
this.preY = (this.sprite.world.y - (this.sprite.anchor.y * this.height)) + this.offset.y;
this.preRotation = this.sprite.angle;
this.x = this.preX;
this.y = this.preY;
this.rotation = this.preRotation;
this.center.setTo(this.x + this.halfWidth, this.y + this.halfHeight);
},
/**
* Returns the absolute delta x value.
*
* @method Phaser.Physics.Arcade.Body#deltaAbsX
* @return {number} The absolute delta value.
*/
deltaAbsX: function () {
return (this.deltaX() > 0 ? this.deltaX() : -this.deltaX());
},
/**
* Returns the absolute delta y value.
*
* @method Phaser.Physics.Arcade.Body#deltaAbsY
* @return {number} The absolute delta value.
*/
deltaAbsY: function () {
return (this.deltaY() > 0 ? this.deltaY() : -this.deltaY());
},
/**
* Returns the delta x value. The difference between Body.x now and in the previous step.
*
* @method Phaser.Physics.Arcade.Body#deltaX
* @return {number} The delta value. Positive if the motion was to the right, negative if to the left.
*/
deltaX: function () {
return this.x - this.preX;
},
/**
* Returns the delta y value. The difference between Body.y now and in the previous step.
*
* @method Phaser.Physics.Arcade.Body#deltaY
* @return {number} The delta value. Positive if the motion was downwards, negative if upwards.
*/
deltaY: function () {
return this.y - this.preY;
},
deltaZ: function () {
return this.rotation - this.preRotation;
}
};
Phaser.Physics.Arcade.Body.prototype.constructor = Phaser.Physics.Arcade.Body;
/**
* @name Phaser.Physics.Arcade.Body#bottom
* @property {number} bottom - The bottom value of this Body (same as Body.y + Body.height)
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, "bottom", {
/**
* The sum of the y and height properties. Changing the bottom property of a Rectangle object has no effect on the x, y and width properties, but does change the height property.
* @method bottom
* @return {number}
*/
get: function () {
return this.y + this.height;
},
/**
* The sum of the y and height properties. Changing the bottom property of a Rectangle object has no effect on the x, y and width properties, but does change the height property.
* @method bottom
* @param {number} value
*/
set: function (value) {
if (value <= this.y)
{
this.height = 0;
}
else
{
this.height = (this.y - value);
}
}
});
/**
* @name Phaser.Physics.Arcade.Body#right
* @property {number} right - The right value of this Body (same as Body.x + Body.width)
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, "right", {
/**
* The sum of the x and width properties. Changing the right property of a Rectangle object has no effect on the x, y and height properties.
* However it does affect the width property.
* @method right
* @return {number}
*/
get: function () {
return this.x + this.width;
},
/**
* The sum of the x and width properties. Changing the right property of a Rectangle object has no effect on the x, y and height properties.
* However it does affect the width property.
* @method right
* @param {number} value
*/
set: function (value) {
if (value <= this.x)
{
this.width = 0;
}
else
{
this.width = this.x + value;
}
}
});