Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Area2D signals area_entered and body_entered not firing on same frame #41648

Open
Tracked by #45334
thomashodgkinson1987 opened this issue Aug 31, 2020 · 2 comments
Open
Tracked by #45334

Comments

@thomashodgkinson1987
Copy link

thomashodgkinson1987 commented Aug 31, 2020

Godot version:
3.2.2 C#/Mono 64 bit

OS/device including version:
Windows 10 Pro 64 bit (latest updates including May 2020 patch)
GTX 980 (Nvidia driver 452.06)
OpenGL ES 2.0

Issue description:
What happened: Area2D being detected on same frame as movement but KinematicBody2D not detected until the following frame.
Expected: Area2D signals area_entered and body_entered to fire on the same frame

Steps to reproduce:

  1. Create a new 2D scene
  2. Add a KinematicBody2D to the scene (named "MovingBody"), global position at x=screen width / 3, y=screen height / 2, with a rectangle collision shape sized 64x64
  3. Add an Area2D to MovingBody (named "InteractArea") local position at x=0, y=0, with a rectangle collision shape sized 64x64
  4. Create and add a script to MovingBody named MovingBody.cs, extending KinematicBody2D
public class MovingBody : KinematicBody2D
{
	[Export] public Vector2 Velocity { get; set; } = Vector2.Zero;

	public override void _PhysicsProcess (float delta)
	{
		base._PhysicsProcess(delta);
		Velocity = MoveAndSlide(Velocity, Vector2.Up);
	}

	private void OnAreaEnteredInteractArea (Area2D area) =>
		GD.Print($"MovingBody -> Area entered interact area: {area.Name}");

	private void OnAreaExitedInteractArea (Area2D area) =>
		GD.Print($"MovingBody -> Area exited interact area: {area.Name}");

	private void OnBodyEnteredInteractArea (PhysicsBody2D body) =>
		GD.Print($"MovingBody -> Body entered interact area: {body.Name}");

	private void OnBodyExitedInteractArea (PhysicsBody2D body) =>
		GD.Print($"MovingBody -> Body exited interact area: {body.Name}");
}
  1. Connect the following signals (on InteractArea) to the methods:
area_entered
area_exited
body_entered
body_exited
  1. Add an Area2D to the scene (named "InteractableArea"), global position at x=(screen width / 3) * 2, y=screen height / 2, with a rectangle collision shape sized 32x32
  2. Create and add a script to InteractableArea named InteractableArea.cs, extending Area2D
public class InteractableArea : Area2D
{
	[Export] public Vector2 Velocity { get; set; } = Vector2.Zero;

	public override void _PhysicsProcess (float delta)
	{
		base._PhysicsProcess(delta);
		Translate(Velocity * delta);
	}

	private void OnAreaEntered (Area2D area) =>
		GD.Print($"InteractableArea -> Area entered: {area.Name}");

	private void OnAreaExited (Area2D area) =>
		GD.Print($"InteractableArea -> Area exited: {area.Name}");

	private void OnBodyEntered (PhysicsBody2D body) =>
		GD.Print($"InteractableArea -> Body entered: {body.Name}");

	private void OnBodyExited (PhysicsBody2D body) =>
		GD.Print($"InteractableArea -> Body exited: {body.Name}");
}
  1. Connect the following signals (on InteractableArea) to the methods:
area_entered
area_exited
body_entered
body_exited
  1. Set the Velocity of MovingBody to x=1024, y=0 and set the Velocity of InteractableArea to x=0, y=0
    9a) Set the following collision layer and mask flags:
  • MovingBody: Layer 1, Mask none
  • InteractArea: Layer 2, Mask 3
  • InteractionArea: Layer 3, Mask 1 and 2
  1. Create a new script called Ticker.cs and add it to the scene node
  2. Set the scene node to "Process" on paused, and set MovingBody and InteractableArea to "Stop"
  3. Add the following code to Ticker.cs:
using Godot;

public class Ticker : Node2D
{
	private bool m_wasDoFrame = false;
	private bool m_isDoFrame = false;
	private float m_frameCounter = 0;

	public override void _Ready ()
	{
		base._Ready();
		GetTree().Paused = true;
	}

	public override void _PhysicsProcess (float delta)
	{
		base._PhysicsProcess(delta);
		if (m_isDoFrame)
		{
			m_isDoFrame = false;
			m_wasDoFrame = true;
			m_frameCounter++;
			GD.Print($"Frame counter: {m_frameCounter}");
			GetTree().Paused = false;
		}
		else if (m_wasDoFrame)
		{
			m_wasDoFrame = false;
			GetTree().Paused = true;
		}
	}

	public override void _Input (InputEvent @event)
	{
		base._Input(@event);
		if (@event.IsActionReleased("ui_accept")) m_isDoFrame = true;
	}
}
  1. Start the scene. MovingBody should be on the left and InteractableArea should be on the right, and when "ui_enter" is pressed the game should do one physics tick then pause, while outputting the frame count and any interactions or any fired signals:
Frame counter: 1
Frame counter: 2
Frame counter: 3
Frame counter: 4
Frame counter: 5
Frame counter: 6
Frame counter: 7
Frame counter: 8
Frame counter: 9
Frame counter: 10
Frame counter: 11
Frame counter: 12
Frame counter: 13
Frame counter: 14
Frame counter: 15
InteractableArea -> Area entered: InteractArea
MovingBody -> Area entered interact area: InteractableArea
Frame counter: 16
InteractableArea -> Body entered: MovingBody
Frame counter: 17
Frame counter: 18
Frame counter: 19
Frame counter: 20
Frame counter: 21
Frame counter: 22
Frame counter: 23
Frame counter: 24
Frame counter: 25
Frame counter: 26
InteractableArea -> Area exited: InteractArea
MovingBody -> Area exited interact area: InteractableArea
Frame counter: 27
InteractableArea -> Body exited: MovingBody
Frame counter: 28
Frame counter: 29
Frame counter: 30
  1. Notice that the Area2D's signals are fired on frame 6 but the body is not detected until frame 7.

Minimal reproduction project:
GodotTest.zip

Minor addition:

If the MovingBody's Velocity is Zero but the InteractableArea's Velocity is x=-1024, y = 0 then when they overlap the signals fire as expected. I have tried putting "ForceUpdateTransform()" near enough everywhere and re-ran at different points with no use. The X speed does not need to be 1024 but in this example it works well as on one frame it is not overlapping at all then the following frame the InteractableArea is overlapped nicely to show th effect (the area being detected as overlapping on the same frame as the movement but the body not being detected until the following frame). I have read that the bodys are all moved at once but I thought that ForceUpdateTransform() would help this?

@KoBeWi
Copy link
Member

KoBeWi commented Dec 15, 2020

I've just ran into this. Here's a simpler repro:
ReproductionProject.zip
Expected result is

before move
signal emitted
after move

If this is intended, get_overlapping_bodies() shouldn't point to the signal, because they both work the same. But IMO signal should be emitted immediately or at least there should be a way to do so.

@OmarShehata
Copy link
Contributor

Does anyone know why the area_enetered signal doesn't fire immediately? My best guess is that, the rigid body does check for overlap every frame, whereas the area does not (if this is true, how often is it running this / what controls that?)

  • For the area_entered signal, it seems to be checked on Area2D::_area_inout
    • Which itself runs on the physics server's area_set_area_monitor_callback

PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), callable_mp(this, &Area2D::_area_inout));

  • For body_entered signal, it is checked in RigidBody2D::_body_inout
    • Which runs on RigidBody2D::_body_state_changed
    • Which runs on physics server's body_set_state_sync_callback

PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &RigidBody2D::_body_state_changed));

I wonder if it makes sense to allow running the area's monitoring on body_set_state_sync_callback as well? Is there a reason why it's not already doing that (and what explains the few frames of delay? it seems to be between 2 and 4 frames in my test).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants