Skip to content

Commit f99f182

Browse files
committed
Arcade Physics is now using a spacial pre-sort for all Sprite vs. Group and Group vs. Group collisions. You can define the direction the sort will prioritise via the new sortDirection property. By default it is set to Phaser.Physics.Arcade.LEFT_RIGHT. For example if you are making a horizontally scrolling game, where the player starts on the left and moves to the right, then this sort order will allow the physics system to quickly eliminate any object to the right of the players bounds. This cuts down on the sheer volume of actual collision checks needing to be made. In a densely populated level it can improve fps rate dramatically.
There are 3 other directions available (`RIGHT_LEFT`, `TOP_BOTTOM` and `BOTTOM_TOP`) and which one you need will depend on your game type. If you were making a vertically scrolling shoot-em-up then you'd pick `BOTTOM_TOP` so it sorts all objects above and can bail out quickly. More importantly you can switch the `sortDirection` at run-time with no loss of performance. Just make sure you do it *before* running any collision checks. So if you had a large 8-way scrolling world you could set the `sortDirection` to match the direction the player was moving in and adjust it in real-time, getting the benefits as you go. My thanks to Aaron Lahman for inspiring this update.
1 parent 74eeddf commit f99f182

2 files changed

Lines changed: 99 additions & 47 deletions

File tree

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,21 @@ Happy coding everyone! See you on the forums.
6060

6161
Version 2.3.0 - "Tarabon" - in dev
6262

63-
### Significant Update
63+
### Significant Updates
64+
65+
#### Game Objects, Components and Custom Builds
66+
67+
All of the core Game Objects have received a small but important restructuring.
68+
69+
### Arcade Physics
70+
71+
We've updated the core of Arcade Physics in a couple of significant ways. First we've dropped lots of internal private vars and moved to local cars. Equally array lengths are no longer cached and we've been a nice improvement all around as a result.
72+
73+
More importantly we're now using a spacial pre-sort for all Sprite vs. Group and Group vs. Group collisions. You can define the direction the sort will prioritise via the new `sortDirection` property. By default it is set to `Phaser.Physics.Arcade.LEFT_RIGHT`. For example if you are making a horizontally scrolling game, where the player starts on the left and moves to the right, then this sort order will allow the physics system to quickly eliminate any object to the right of the players bounds. This cuts down on the sheer volume of actual collision checks needing to be made. In a densely populated level it can improve fps rate dramatically.
74+
75+
There are 3 other directions available (`RIGHT_LEFT`, `TOP_BOTTOM` and `BOTTOM_TOP`) and which one you need will depend on your game type. If you were making a vertically scrolling shoot-em-up then you'd pick `BOTTOM_TOP` so it sorts all objects above and can bail out quickly.
76+
77+
More importantly you can switch the `sortDirection` at run-time with no loss of performance. Just make sure you do it *before* running any collision checks. So if you had a large 8-way scrolling world you could set the `sortDirection` to match the direction the player was moving in and adjust it in real-time, getting the benefits as you go. My thanks to Aaron Lahman for inspiring this update.
6478

6579
#### Phaser.Loader
6680

@@ -86,10 +100,6 @@ Loader.resetLocked is a boolean that allows you to control what happens when the
86100

87101
Thanks to @pnstickne for vast majority of this update.
88102

89-
#### Game Objects
90-
91-
All of the core Game Objects have received a small but important restructuring.
92-
93103
#### Pixi v2
94104

95105
We are now using our own custom build of Pixi v2. The Pixi project has moved all development resources over to Pixi v3, but it wasn't ready in time for the release of Phaser 2.3 so we've started applying our own fixes to the version of Pixi that Phaser uses.

src/physics/arcade/World.js

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Phaser.Physics.Arcade = function (game) {
5757

5858
/**
5959
* @property {number} sortDirection - Used when colliding a Sprite vs. a Group, or a Group vs. a Group, this defines the direction the sort is based on. Default is Phaser.Physics.Arcade.LEFT_RIGHT.
60+
* @default
6061
*/
6162
this.sortDirection = Phaser.Physics.Arcade.LEFT_RIGHT;
6263

@@ -75,11 +76,6 @@ Phaser.Physics.Arcade = function (game) {
7576
*/
7677
this.quadTree = new Phaser.QuadTree(this.game.world.bounds.x, this.game.world.bounds.y, this.game.world.bounds.width, this.game.world.bounds.height, this.maxObjects, this.maxLevels);
7778

78-
/**
79-
* @property {object} stats - Stats collected for each collision iteration.
80-
*/
81-
this.stats = { 'skipped': 0, 'ignored': 0, 'checked': 0 };
82-
8379
/**
8480
* @property {number} _total - Internal cache var.
8581
* @private
@@ -95,31 +91,31 @@ Phaser.Physics.Arcade.prototype.constructor = Phaser.Physics.Arcade;
9591

9692
/**
9793
* A constant used for the sortDirection value.
98-
* Use this if your game world is wide but short and scrolls from the left to the right (ala Mario)
94+
* Use this if your game world is wide but short and scrolls from the left to the right (i.e. Mario)
9995
* @constant
10096
* @type {number}
10197
*/
10298
Phaser.Physics.Arcade.LEFT_RIGHT = 0;
10399

104100
/**
105101
* A constant used for the sortDirection value.
106-
* Use this if your game world is wide but short and scrolls from the right to the left (ala Mario backwards)
102+
* Use this if your game world is wide but short and scrolls from the right to the left (i.e. Mario backwards)
107103
* @constant
108104
* @type {number}
109105
*/
110106
Phaser.Physics.Arcade.RIGHT_LEFT = 1;
111107

112108
/**
113109
* A constant used for the sortDirection value.
114-
* Use this if your game world is narrow but tall and scrolls from the top to the bottom (ala Dig Dug)
110+
* Use this if your game world is narrow but tall and scrolls from the top to the bottom (i.e. Dig Dug)
115111
* @constant
116112
* @type {number}
117113
*/
118114
Phaser.Physics.Arcade.TOP_BOTTOM = 2;
119115

120116
/**
121117
* A constant used for the sortDirection value.
122-
* Use this if your game world is narrow but tall and scrolls from the bottom to the top (ala Doodle Jump)
118+
* Use this if your game world is narrow but tall and scrolls from the bottom to the top (i.e. Commando or a vertically scrolling shoot-em-up)
123119
* @constant
124120
* @type {number}
125121
*/
@@ -643,10 +639,6 @@ Phaser.Physics.Arcade.prototype = {
643639

644640
if (this.skipQuadTree || sprite.body.skipQuadTree)
645641
{
646-
this.stats.skipped = 0;
647-
this.stats.ignored = 0;
648-
this.stats.checked = 0;
649-
650642
for (var i = 0; i < group._hash.length; i++)
651643
{
652644
// Skip duff entries - we can't check a non-existent sprite or one with no body
@@ -658,58 +650,49 @@ Phaser.Physics.Arcade.prototype = {
658650
// Skip items either side of the sprite
659651
if (this.sortDirection === Phaser.Physics.Arcade.LEFT_RIGHT)
660652
{
661-
if (group._hash[i].body.right < sprite.body.x)
653+
if (sprite.body.right < group._hash[i].body.x)
662654
{
663-
this.stats.ignored++;
664-
continue;
655+
break;
665656
}
666-
else if (sprite.body.right < group._hash[i].body.x)
657+
else if (group._hash[i].body.right < sprite.body.x)
667658
{
668-
this.stats.skipped = group._hash.length - i;
669-
break;
659+
continue;
670660
}
671661
}
672662
else if (this.sortDirection === Phaser.Physics.Arcade.RIGHT_LEFT)
673663
{
674-
if (group._hash[i].body.x > sprite.body.right)
664+
if (sprite.body.x > group._hash[i].body.right)
675665
{
676-
this.stats.ignored++;
677-
continue;
666+
break;
678667
}
679-
else if (sprite.body.x > group._hash[i].body.right)
668+
else if (group._hash[i].body.x > sprite.body.right)
680669
{
681-
this.stats.skipped = group._hash.length - i;
682-
break;
670+
continue;
683671
}
684672
}
685673
else if (this.sortDirection === Phaser.Physics.Arcade.TOP_BOTTOM)
686674
{
687-
if (group._hash[i].body.bottom < sprite.body.y)
675+
if (sprite.body.bottom < group._hash[i].body.y)
688676
{
689-
this.stats.ignored++;
690-
continue;
677+
break;
691678
}
692-
else if (sprite.body.bottom < group._hash[i].body.y)
679+
else if (group._hash[i].body.bottom < sprite.body.y)
693680
{
694-
this.stats.skipped = group._hash.length - i;
695-
break;
681+
continue;
696682
}
697683
}
698684
else if (this.sortDirection === Phaser.Physics.Arcade.BOTTOM_TOP)
699685
{
700-
if (group._hash[i].body.y > sprite.body.bottom)
686+
if (sprite.body.y > group._hash[i].body.bottom)
701687
{
702-
this.stats.ignored++;
703-
continue;
688+
break;
704689
}
705-
else if (sprite.body.y > group._hash[i].body.bottom)
690+
else if (group._hash[i].body.y > sprite.body.bottom)
706691
{
707-
this.stats.skipped = group._hash.length - i;
708-
break;
692+
continue;
709693
}
710694
}
711695

712-
this.stats.checked++;
713696
this.collideSpriteVsSprite(sprite, group._hash[i], collideCallback, processCallback, callbackContext, overlapOnly);
714697
}
715698
}
@@ -760,14 +743,73 @@ Phaser.Physics.Arcade.prototype = {
760743
return;
761744
}
762745

763-
for (var i = 0; i < group.children.length - 1; i++)
746+
for (var i = 0; i < group._hash.length; i++)
764747
{
765-
for (var j = i + 1; j < group.children.length; j++)
748+
// Skip duff entries - we can't check a non-existent sprite or one with no body
749+
if (!group._hash[i] || !group._hash[i].exists || !group._hash[i].body)
750+
{
751+
continue;
752+
}
753+
754+
var object1 = group._hash[i];
755+
756+
for (var j = i + 1; j < group._hash.length; j++)
766757
{
767-
if (group.children[i] && group.children[j] && group.children[i].exists && group.children[j].exists)
758+
// Skip duff entries - we can't check a non-existent sprite or one with no body
759+
if (!group._hash[j] || !group._hash[j].exists || !group._hash[j].body)
760+
{
761+
continue;
762+
}
763+
764+
var object2 = group._hash[j];
765+
766+
// Skip items either side of the sprite
767+
if (this.sortDirection === Phaser.Physics.Arcade.LEFT_RIGHT)
768+
{
769+
if (object1.body.right < object2.body.x)
770+
{
771+
break;
772+
}
773+
else if (object2.body.right < object1.body.x)
774+
{
775+
continue;
776+
}
777+
}
778+
else if (this.sortDirection === Phaser.Physics.Arcade.RIGHT_LEFT)
768779
{
769-
this.collideSpriteVsSprite(group.children[i], group.children[j], collideCallback, processCallback, callbackContext, overlapOnly);
780+
if (object1.body.x > object2.body.right)
781+
{
782+
continue;
783+
}
784+
else if (object2.body.x > object1.body.right)
785+
{
786+
break;
787+
}
770788
}
789+
else if (this.sortDirection === Phaser.Physics.Arcade.TOP_BOTTOM)
790+
{
791+
if (object1.body.bottom < object2.body.y)
792+
{
793+
continue;
794+
}
795+
else if (object2.body.bottom < object1.body.y)
796+
{
797+
break;
798+
}
799+
}
800+
else if (this.sortDirection === Phaser.Physics.Arcade.BOTTOM_TOP)
801+
{
802+
if (object1.body.y > object2.body.bottom)
803+
{
804+
continue;
805+
}
806+
else if (object2.body.y > object1.body.bottom)
807+
{
808+
break;
809+
}
810+
}
811+
812+
this.collideSpriteVsSprite(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
771813
}
772814
}
773815

0 commit comments

Comments
 (0)