Strange collision behavior when colliding against multiple objects

This took some time to narrow down...
Use this source:
package;
import flixel.FlxG;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.group.FlxGroup.FlxTypedGroup;
class PlayState extends FlxState
{
var boxes:FlxTypedGroup<FlxSprite>;
var player:FlxSprite;
override public function create():Void
{
add(boxes = new FlxTypedGroup<FlxSprite>());
for (i in 0...15)
{
var b = new FlxSprite(0, i * 50);
b.makeGraphic(50, 50, 0xFF000000 + Std.random(0xFFFFFF));
b.immovable = true;
b.solid = true;
boxes.add(b);
}
for (i in 0...15)
{
var b = new FlxSprite(i*50+50, FlxG.height- 50);
b.makeGraphic(50, 50, 0xFF000000 + Std.random(0xFFFFFF));
b.immovable = true;
b.solid = true;
boxes.add(b);
}
player = new FlxSprite(50, 0);
player.makeGraphic(20, 20);
player.acceleration.y = 500;
add(player);
}
override public function update(elapsed:Float):Void
{
FlxG.collide(player, boxes);
player.velocity.x = 0;
if (FlxG.keys.pressed.LEFT)
player.velocity.x = -100;
if (FlxG.keys.pressed.RIGHT)
player.velocity.x = 100;
if (FlxG.keys.justPressed.UP && player.isTouching(FlxObject.DOWN))
player.velocity.y = -650;
super.update(elapsed);
}
}
Hug the left-side wall and jump while holding left. You will collide with the bottom of one of the blocks.
What's happening is a 'perfect storm' of tiny issues.
First, FlxQuadTree detects possible collision between the player and 2 of the left-wall blocks. One block at Y (TopBlock) and another at Y+block.height (BottomBlock).

FlxObject.separate gets called for each of these possible collisions: first, the Player vs TopBlock.
separateX calls computeOverlapX which tries to verify that an overlap actually did happen on the X-axis, so, it 'ignores' the player's most-recent Y movement, so it sees the Player as being JUST SLIGHTLY below the TopBlock:

So, separateX determines that the player DOES NOT overlap the TopBlock, and therefore, does not need to move to the right at all.
Then separateY does it's thing, and it does it's verification a little differently, so it DOES see that the player overlaps the TopBlock, and triggers collision,so you 'hit' the bottom of the TopBlock.
When separate is called on the Player vs BottomBlock, next, it will see that the player overlaps on separateX and move it to the right, accordingly.
Notably, the order of checks is determined by the order of the objects in the FlxGroup - by adding boxes.sort(FlxSort.byY, FlxSort.DESCENDING); to PlayState.create, you no longer collide with the bottoms of boxes going UP, but now you collide with them coming DOWN.
I'm not 100% sure of all the details on this, but it seems like it could be fixed if the following we changed:
-
computerOverlapXandcomputeOverlapYshould build the same rectangles for confirming overlap, instead of having slightly different answers based on X or Y. - change the order of
seperate*being called based on the inverse of the biggest change in motion between the 2 objects - so, if ObjectA is moving up/down much more than it's moving left/right, callseparateXbeforeseparateY, and reverse if ObjectA is moving left/right more.
Hi, tricky stuff indeed. One thing though: you wrote "computerOverlapX and computeOverlapY should build the same rectangles for confirming overlap", but how are they difference apart from the fact that they operate somehow orthogonally? Maybe I am not seeing something while reading the code.
I think computeOverlapX includes the x-delta, and not the y-delta, while computeOverlapY is the reverse.
Btw, I don't know if this issue ever got solved, but just out of curiosity, changing the initial code into
FlxG.collide(player, boxes);
changes the outcome in any way?