PinJoint2D behaves very weirdly when connected at runtime...Bug?

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

When connecting PinJoint2Ds before runtime everything works fine. But when I do the EXACT same thing at runtime, everything just explodes. I even looked a thousand times at the remote tree and it’s EXACTLY the same structure as when connected before runtime with editor.

Node RopeLink:

RigidBody2D
Sprite
CollisionShape2D
PinJoint2D

Node Rope:

func _ready():
	_spawn(3)
	pass

func _spawn(link_num):
	var link_pos = []
	for i in range(link_num):
		var link = RopeLink.instance()
		if i == 0:link.set_mode(RigidBody2D.MODE_STATIC)
		link_pos.append(link.get_position())
		add_child(link)
		link.set_position(Vector2(link_pos[i].x, link_pos[i].y + i * 200))
	for i in range(link_num - 1):
		var link = get_child(i)
		var next_link = get_child(i + 1)
		var joint = link.get_child(2)
		joint.position.y += 0
		joint.node_a = "../../" + link.name
		joint.node_b = "../../" + next_link.name
	pass
:bust_in_silhouette: Reply From: Footurist

Solved this myself. Turns out using _init() gets rid of the problem, since spawning links in _ready() causes the joints to mess up due to gravity being applied while spawning them.

:bust_in_silhouette: Reply From: rolfpancake

This is the problem:

add_child(link)
link.set_position(Vector2(link_pos[i].x, link_pos[i].y + i * 200))

You add the link to the scenetree and this leads to the effect that the physics engine is taking control of its position. You have to set the position first. So just swap the lines.

link.set_position(Vector2(link_pos[i].x, link_pos[i].y + i * 200))
add_child(link)

By the way, your code can be refactored:

  • Remove meaningless pass
  • Remove meaningless joint.position.y += 0
  • Add some comments so it’s not hard to understand what is going on
  • Use godots function API to make your code more readable:

Change this:

joint.node_a = "../../" + link.name
joint.node_b = "../../" + next_link.name

To this:

joint.node_a = joint.get_path_to(link)
joint.node_b = joint.get_path_to(next_link)

I tried to rewrite your code the way I would like to read it. You don’t have to agree because everyone has their own style. It’s just a suggestion:

var RopeLink = preload("res://RopeLink.tscn")

func _ready():
	_spawn(3)
	
func _spawn(link_num):
	# Keep a list of creating links
	var links = []
	
	# Create those links
	for i in range(link_num):
		# Instance and set a relative position
		var link = RopeLink.instance()
		link.set_position(link.position + i * Vector2(0, 200))
		
		# First link should be fixed
		if i == 0:
			link.set_mode(RigidBody2D.MODE_STATIC)

		add_child(link)
		links.append(link)
	
	# Set their PinJoint2Ds
	for i in range(links.size() - 1):
		var joint = links[i].get_node("PinJoint2D")  # The node can be found by its name
		joint.node_a = joint.get_path_to(links[i])  # Relative paths are way easier to maintain
		joint.node_b = joint.get_path_to(links[i+1])

Thanks alot! What you said makes sense. And now I also understand better why my approach with doing the spawning in the _init(): function works.

Footurist | 2018-02-13 21:12

You are welcome. Using the _init function is also a valid way of implementing this. It depends on your needs and you should always consider every way possible.

rolfpancake | 2018-02-14 07:17