flixel icon indicating copy to clipboard operation
flixel copied to clipboard

Strange collision behavior when colliding against multiple objects

Open SeiferTim opened this issue 10 years ago • 3 comments

Demonstration

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).

Example

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:

Perceived Position for X-Axis Check

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:

  • computerOverlapX and computeOverlapY should 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, call separateX before separateY, and reverse if ObjectA is moving left/right more.

SeiferTim avatar Jul 15 '15 17:07 SeiferTim

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.

winterismute avatar Jul 16 '15 18:07 winterismute

I think computeOverlapX includes the x-delta, and not the y-delta, while computeOverlapY is the reverse.

SeiferTim avatar Jul 16 '15 20:07 SeiferTim

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?

winterismute avatar Feb 20 '16 00:02 winterismute