Why does collision detection fail after wrapping around the screen?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By HalfHeart

I’m taking my first steps with Godot and trying a simple scene with a player ship and a meteor to knock around. I just set up a couple of RigidBody2D objects and gave them each a sprite and one or more CollisionDetection2D objects. All I was expecting was to be able to move the player ship, and for it to ‘kick’ the other object when they collide.

Everything works as I expected it to until either object gets wrapped to the other side of the screen, after that they don’t collide. However, if both objects get wrapped on the same side of the screen, the collision detection works as expected. If I take the ship through multiple screen wraps, it doesn’t collide with the meteor until I’ve reversed the same number of screen wraps.

I assume something funky is happening with the CollisionDetection2D objects, but I don’t know what steps to take to debug (I tried Debug > Visible Collision Shapes and everything looked OK).

Any hints on debugging, or even outright solutions, would be most appreciated.

The project is available at https://drive.google.com/file/d/1Es3N-ScafuEtVPTM5E7BwAztXaA5addJ/view?usp=sharing (the sprites are from the Kenny pack)

But for convenience here is the script which is attached to the player, followed by the one attached to the meteor.

extends RigidBody2D

var screensize
var thrust_power = 5
var brake_power = 40
var torque_power = 200

func _ready():
	screensize = get_viewport().size
	
func _integrate_forces(state):
	var thrust = Vector2( (Input.get_action_strength("thrust") * thrust_power), 0)
	var torque = (Input.get_action_strength("clockwise") * torque_power) - (Input.get_action_strength('anticlockwise') * torque_power)
	thrust = thrust.rotated(rotation)

	if Input.is_action_pressed("brake"):
		thrust.x = 0 - (state.linear_velocity.x / brake_power)
		thrust.y = 0 - (state.linear_velocity.y / brake_power)

	state.apply_impulse(Vector2(), thrust)
	state.apply_torque_impulse(torque)

	position.x = wrapf(position.x, 0, screensize.x)
	position.y = wrapf(position.y, 0, screensize.y)

The meteor script:

extends RigidBody2D

var screensize

func _ready():
	screensize = get_viewport().size

# Prefixed 'state' with underscore based on message from debugger, to indicate
# that it's knowingly not used
func _integrate_forces(_state):
	position.x = wrapf(position.x, 0, screensize.x)
	position.y = wrapf(position.y, 0, screensize.y)

Have you tried changing the meteor position in the _state.transform passed to_integrate_forces() instead of modifying position directly?

fractile | 2020-06-19 06:34

Yes. I replaced the code affecting position, in both the Meteor and Player scripts, with this:

var xform = state.transform
xform.origin.x = wrapf(xform.origin.x, 0, screensize.x)
xform.origin.y = wrapf(xform.origin.y, 0, screensize.y)
state.transform = xform

HalfHeart | 2020-06-19 10:09

:bust_in_silhouette: Reply From: jgodfrey

You should never set the position of a RigidBody2D directly (as you’re doing in _integrate_forces) - I’d assume those assignments for screen wrapping are breaking the physics system…

Rather than try to explain the issue here, KidsCanCode has a perfect article for this case. See specifically The Position Problem here…

https://kidscancode.org/godot_recipes/physics/godot3_kyn_rigidbody1/

Thankyou, that’s enabled me to code the screen wrap correctly. KidsCanCode looks like a really useful resource as well.

HalfHeart | 2020-06-19 06:42