flixel icon indicating copy to clipboard operation
flixel copied to clipboard

Multiple overlap between two objects

Open DrDerico opened this issue 11 years ago • 15 comments
trafficstars

flixel ver 3.3.5.

I met with strange (for me) behavior of FlxG.overlap function. If this is normal, please don't beat me :)

I call FlxG.overlap function with such type of objects: FlxObject (hero sprite), FlxGroup (contains triggers) - FlxG.overlap(hero, _Triggers, onTriggerOverlap);

So, when hero overlaps trigger (no other triggers overlapping with hero at the same time), the callback function for that trigger executes a few times - one or two or, sometimes, three-four times at one FlxG.overlap call.

After debugging I find out that trigger is adding to few ListTrees (north, south, ...) and thus when next tree call execute(), it again finds overlapping between hero and same trigger again. And it again calls callback function for trigger.

Is it normal behavior?

Demo here: https://dl.dropboxusercontent.com/u/18869478/flxOverlap/Tower.swf Source here: https://dl.dropboxusercontent.com/u/18869478/flxOverlap/flxOverlap.zip

DrDerico avatar Jul 27 '14 06:07 DrDerico

I've reduced this to the bare minimum, not really sure what's going on yet...

package;

import flixel.FlxState;
import flixel.group.FlxGroup;
import flixel.FlxObject;
import flixel.FlxG;

class PlayState extends FlxState
{
    var player = new Player();
    var group = new FlxGroup();

    var overlaps = 0;

    override public function create()
    {
        var x = [300, 320];
        var y = [100, 180];
        for (i in 0...2)
        {
            group.add(new FlxObject(0, 0, x[i], y[i]));
        }

        add(group);
        add(player);

        FlxG.signals.preUpdate.add(function()
        {
            overlaps = 0;
        });

        FlxG.watch.add(this, "overlaps");
        FlxG.debugger.visible = true;
        FlxG.debugger.drawDebug = true;
    }

    override public function update()
    {
        super.update();

        FlxG.overlap(player, group, function(_, _)
        {
            overlaps++;
        });
    }
}

class Player extends FlxObject
{
    public function new()
    {
        super(50, 50, 50, 50);
    }

    override public function update()
    {
        super.update();

        velocity.set();
        if (FlxG.keys.pressed.A) velocity.x -= 300;
        if (FlxG.keys.pressed.D) velocity.x += 300;
        if (FlxG.keys.pressed.W) velocity.y -= 300;
        if (FlxG.keys.pressed.S) velocity.y += 300;
    }
}

Gama11 avatar Jul 27 '14 08:07 Gama11

So, do you confirm that it's not a normal behavior?

DrDerico avatar Jul 27 '14 08:07 DrDerico

There definitely shouldn't be 8 overlap callback calls when 3 objects are overlapping... And I can't really see any obvious issues with this code. So, yeah, something strange is going on (or I'm missing something).

Gama11 avatar Jul 27 '14 09:07 Gama11

I did some digging about this, and I think I found something that could help with solving this. I'm not sure I understand Quad-Trees exactly, so this can be all wrong, but I think the problem lies in the fact that if two of the same objects are in more than one quadrant, then when you call the overlapNode() on the parent-node, both branch-node will execute the callback function. To demonstrate this, I've made a little change in the example above:

  • I've made the FlxG.worldDivisions variable changeable with the keyboard.
  • I've also added lines to show where each quadrant should lie on-screen.
package;

import flixel.FlxSprite;
import flixel.FlxState;
import flixel.group.FlxGroup;
import flixel.FlxObject;
import flixel.FlxG;

class PlayState extends FlxState
{
    var player = new Player();
    var group = new FlxGroup();
    var lines = new FlxGroup();

    var overlaps = 0;

    override public function create()
    {
        FlxG.debugger.visible = true;

        var x = [300, 320];
        var y = [100, 180];
        for (i in 0...2)
        {
            group.add(new FlxObject(0, 0, x[i], y[i]));
        }

        add(group);
        add(player);
        add(lines);

        FlxG.signals.preUpdate.add(function()
        {
            overlaps = 0;
        });

        FlxG.watch.add(this, "overlaps");
        FlxG.watch.add(FlxG, "worldDivisions");
        FlxG.debugger.drawDebug = true;
    }

    override public function update()
    {
        super.update();

        var changed = true;
        if (FlxG.keys.justPressed.O)
            FlxG.worldDivisions++;
        else if (FlxG.keys.justPressed.L)
            FlxG.worldDivisions--;
        else
            changed = false;

        if (changed)
        {
            lines.callAll("destroy");
                        lines.clear();
            for (i in 0...Std.int(Math.pow(FlxG.worldDivisions,2)))
            {
                var sprite = new FlxSprite(0, (i + 1) * FlxG.height / (Math.pow(FlxG.worldDivisions,2)));
                sprite.makeGraphic(FlxG.width, 2, 0x0);
                lines.add(sprite);
                sprite = new FlxSprite((i + 1) * FlxG.width / (Math.pow(FlxG.worldDivisions,2)), 0);
                sprite.makeGraphic(2, FlxG.height,0x0);
                lines.add(sprite);
            }
        }

        FlxG.overlap(player, group, function(_, _)
        {
            overlaps++;
        });
    }
}

class Player extends FlxObject
{
    public function new()
    {
        super(50, 50, 50, 50);
    }

    override public function update()
    {
        super.update();

        velocity.set();
        if (FlxG.keys.pressed.A) velocity.x -= 300;
        if (FlxG.keys.pressed.D) velocity.x += 300;
        if (FlxG.keys.pressed.W) velocity.y -= 300;
        if (FlxG.keys.pressed.S) velocity.y += 300;
    }
}

If you start testing, you can see, that at worldDivisions = 1, the overlaps works as expected. The problem can be seen the best when you change the worldDivisions to 2, and start moving the player. The number of overlaps changes almost exactly where the lines are, suggesting that both quadrants call the callback function. At higher levels, this doesn't always work (although when the numbers change, it's always at a line), so maybe I'm completely in the wrong here.

As for fixes, I couldn't find one that wouldn't break everything else. In this simple case changing overlapNode() to only call recursively the next quadrant if it didn't find the overlap on the previous was enough, but that, as expected, broke everything else, like the collision and grouping demo, where the boxes would sometimes collide, but go through the floor most of the time:

        if ((_northWestTree != null) && _northWestTree.execute())
        {
            overlapProcessed = true;
        }
        else if ((_northEastTree != null) && _northEastTree.execute())
        {
            overlapProcessed = true;
        }
        else if ((_southEastTree != null) && _southEastTree.execute())
        {
            overlapProcessed = true;
        }
        else if ((_southWestTree != null) && _southWestTree.execute())
        {
            overlapProcessed = true;
        }

So what do you guys think? Hope this helped somewhat.

danim1130 avatar Aug 10 '14 00:08 danim1130

I think one needs to check "overlap" function in original Flixel (AS3). May be it works the same "wrong" way. Or may be not

DrDerico avatar Aug 10 '14 09:08 DrDerico

I think one needs to check "overlap" function in original Flixel (AS3). May be it works the same "wrong" way. Or may be not

It seems like the FlxQuadTree source is literally copy pasted from the as3 with haxe syntax changes.

I think the only way to get around this is to keep a record of which objects have already been overlapped.

MSGhero avatar Aug 11 '14 06:08 MSGhero

There were some minor changes though. Might be worth checking if this example produces the same result in AS3.

Gama11 avatar Aug 11 '14 10:08 Gama11

I can confirm it's the same. I won't include the code, since it's basically the same, but if you need it, I can post that as well.

http://www.fastswf.com/wT_rW4I

After that, I did a search on the flixel forums, and found that many people have had this problem, but no good solution was found.

http://forums.flixel.org/index.php?topic=7090.0

danim1130 avatar Aug 11 '14 13:08 danim1130

Okay, after fiddling around a little, I've modified FlxQuadTree so that it only calls the functions once for every overlapping object pair. I've added two arrays to it to make everything work, so that could increase the memory usage a little. While testing, I haven't seen any difference, except the issue gone. Should I upload it, is there any chance that this could be a useful edit? (Since no one have really noticed this issue until now.) If it is, I will make it a little more memory efficient, then post it here.

danim1130 avatar Aug 12 '14 01:08 danim1130

Just post it or make a pull request and everyone can check it out and make suggestions. It'll either be a clever solution or a brute-force Map or Array check.

MSGhero avatar Aug 12 '14 02:08 MSGhero

Okay, here's the first version of it: http://pastebin.com/mKagqzB6

The main differences: -Overlaps are now checked during the load function instead of the execute function, if the loaded object goes to the A list. -For this reason, I first add the objects of B group to the list -If an overlap has been checked in one quadrant, it won't check ever again For this, I'm using the _checked array, which gets reset every time a new object gets added to the A list. This contains every object that has been checked against the specific _object. -If an overlap has been found, the two object gets added to the _overlappedArray. -In execute, I iterate through this array, and call the functions.

Although this is more of a poc than a real solution, since it creates a big array at every collide(), and there's no comment in my part of the code. I will post a pull request later with a better version.

(Edited for pastebin)

danim1130 avatar Aug 12 '14 12:08 danim1130

Please make a pull request directly through Github.

Also, if you're going to paste a lot of code like this, it is much better to use a service like http://pastebin.com/ or http://hastebin.com/about.md

Regards,

Tiago Ling Alexandre Tel: +55 41 8819-3191

Tiago-Ling avatar Aug 12 '14 12:08 Tiago-Ling

Yeah, please use a pull request to address this issue: https://help.github.com/articles/using-pull-requests

gamedevsam avatar Aug 12 '14 18:08 gamedevsam

I've made the pull request : https://github.com/HaxeFlixel/flixel/pull/1267

danim1130 avatar Aug 13 '14 02:08 danim1130

I think I ran into this, here is a very simplified use case with two sprites:

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;

class PlayState extends FlxState
{
	
	var obj1:FlxSprite;
	var obj2:FlxSprite;
	var frameCount:Int = 0;
	var overlapCount:Int = 0;
	
	
	override public function create():Void
	{
		super.create();
		obj1 = new FlxSprite(1, 302);
		obj1.makeGraphic(1, 1);
		obj2 = new FlxSprite(1, 302);
		obj2.makeGraphic(1, 1);
		
		add(obj2);
		add(obj1);
	}

	override public function update(elapsed:Float):Void
	{
		frameCount++;		
		FlxG.overlap(obj2, obj1, overlapCheck);
		super.update(elapsed);
	}
	
	public function overlapCheck(pobj2:FlxSprite, pobj1:FlxSprite)
	{
		overlapCount++;
		if (frameCount < 10)
		{
			trace("frameCount: " + frameCount + " overlapCount: " + overlapCount);
		}
	}
}

Output:

source/PlayState.hx:40: frameCount: 1 overlapCount: 1
source/PlayState.hx:40: frameCount: 1 overlapCount: 2
source/PlayState.hx:40: frameCount: 2 overlapCount: 3
source/PlayState.hx:40: frameCount: 2 overlapCount: 4
source/PlayState.hx:40: frameCount: 3 overlapCount: 5
source/PlayState.hx:40: frameCount: 3 overlapCount: 6
source/PlayState.hx:40: frameCount: 4 overlapCount: 7
source/PlayState.hx:40: frameCount: 4 overlapCount: 8
source/PlayState.hx:40: frameCount: 5 overlapCount: 9
source/PlayState.hx:40: frameCount: 5 overlapCount: 10
source/PlayState.hx:40: frameCount: 6 overlapCount: 11
source/PlayState.hx:40: frameCount: 6 overlapCount: 12
source/PlayState.hx:40: frameCount: 7 overlapCount: 13
source/PlayState.hx:40: frameCount: 7 overlapCount: 14
source/PlayState.hx:40: frameCount: 8 overlapCount: 15
source/PlayState.hx:40: frameCount: 8 overlapCount: 16
source/PlayState.hx:40: frameCount: 9 overlapCount: 17
source/PlayState.hx:40: frameCount: 9 overlapCount: 18

Expected Output: (you get this if y != 302)

source/PlayState.hx:40: frameCount: 1 overlapCount: 1
source/PlayState.hx:40: frameCount: 2 overlapCount: 2
source/PlayState.hx:40: frameCount: 3 overlapCount: 3
source/PlayState.hx:40: frameCount: 4 overlapCount: 4
source/PlayState.hx:40: frameCount: 5 overlapCount: 5
source/PlayState.hx:40: frameCount: 6 overlapCount: 6
source/PlayState.hx:40: frameCount: 7 overlapCount: 7
source/PlayState.hx:40: frameCount: 8 overlapCount: 8
source/PlayState.hx:40: frameCount: 9 overlapCount: 9

In the case I ran into this, I had a trigger checking if a key had been just been pressed, and having a callback call twice introduced some bugs. I was able to work around it by keep tracking of the frame number though and checking against it. Maybe that could be a quick fix to prevent a callback from being called more than once in a frame (though this assumes a function is being uniquely called back and not reused in other overlap callback functions)?

aeveis avatar Jul 16 '21 07:07 aeveis