PinJoint2D physics messed up by adding GPUParticles2D to Node B's RigidBody2D

Godot Version

v4.6.stable.official [89cea1439]

Question

I set up my 2D character like this: a CharacterBody2D (Node A) which can be moved by the player and a Rigidbody2D (Node B) for a head which follows the body through a PinJoint2D. After some tweaking everything worked fine.

Then I added a GPUParticles2D to the Rigidbody2D (Node B) and it messed up the PinJoint2D’s physics. If I reparent the particle effect to the CharacterBody2D the issue disappears but then the effect won’t follow the head. Is this a bug? Any help would be appreciated.

1 Like

What do you mean by messed up physics? GPUParticles2D has nothing to do with physics so it can’t affect them.

That’s what I’d think. And maybe still think but the GPUParticles2D’s parenting is the only difference. I made two videos which I’m only able to link as a new user:

How the pinjoint should work: Dropbox

How it is when Node B gets a GPUParticles2D: Dropbox

Hope this clears up at least what the issue looks like.

Uhm, you are right… Just accessing the RigidBody2D.global_transform inside the _physics_process() method in the CharacterBody2D messes up the RigidBody2D (GPUParticles2D does this internally) but only before calling move_and_slide() :confused:. Like:

extends CharacterBody2D

@onready var rigid_body_2d: RigidBody2D = %RigidBody2D

func _physics_process(delta: float) -> void:
	rigid_body_2d.global_transform
	# ...
	move_and_slide()

but doing this does not:

extends CharacterBody2D

@onready var rigid_body_2d: RigidBody2D = %RigidBody2D

func _physics_process(delta: float) -> void:
	# ...
	move_and_slide()
	rigid_body_2d.global_transform

There’s a note in the RigidBody2D documentation about accessing the global transform if the node is a child of another moving node:

Note: Changing the 2D transform or linear_velocity of a RigidBody2D very often may lead to some unpredictable behaviors. This also happens when a RigidBody2D is the descendant of a constantly moving node, like another RigidBody2D, as that will cause its global transform to be set whenever its ancestor moves.

I’m not sure if this is technically a bug or just a limitation but try checking the issue tracker if there’s any issue related to this or open a new one.

As a workaround you can re-parent the GPUParticles2D node to the CharacterBody2D node and do:

extends CharacterBody2D

@onready var gpu_particles_2d: GPUParticles2D = %GPUParticles2D
@onready var rigid_body_2d: RigidBody2D = %RigidBody2D

func _physics_process(delta: float) -> void:
	# ...
	move_and_slide()
	
	gpu_particles_2d.global_transform = rigid_body_2d.global_transform

Thanks for the quick and thorough reply!

Not sure why this happens but if there’s a note about it in the documentation maybe someday someone will fix the cause of these “unpredictable behaviors”.

Your suggestion worked like a charm, thanks again!