PinJoint2D not working

Godot Version

4.2.1

Question

Hey everyone! This is my first post because I have an issue that’s driving me crazy. I spent hours looking into this, tried a lot of stuff but didn’t work.

I want to create a grappling hook for my player, that gets launch when the rmb is pressed and then retracted when it’s released.

However, in the code it’s not working at all, even though Node A and B is properly assigned (by print debugging), and the position it also correct (in debug mode the position of the pinjoint is where it should be, it is raycasted). The player’s root node is a CharacterBody2D, which is a PhysicsBody2D, and a StaticBody2D gets created in the code where the raycast hits, which is also a PhysicsBody2D.

So I tried it in a scene by adding it manually:

With this I can move freely, as if the PinJoint2D does not exist at all. The PinJoint2D’s position can be seen as a brown cross:

I don’t understand why it isn’t even working when I do it manually in the scene, but here’s the code for the player movement and grappling:

extends CharacterBody2D

# Variables for movement
@export var speed: float = 200.0
@export var jump_force: float = 250.0
@export var gravity: float = 800.0

@export var grapple_speed: float = 500.0  # Speed of the grappling hook
@export var rope_length: float = 100.0  # Max length of the rope
@export var hook_range: float = 300.0  # Max range of the grapple

@onready var line: Line2D = $DirectionVector
@onready var rope: Line2D = $Rope

var direction
var grapple_position: Vector2 = Vector2.ZERO
var is_grappling: bool = false
var grapple_joint: PinJoint2D = null
var rope_attachment: StaticBody2D = null

func _ready():
	rope.visible = false
	
	rope_attachment = StaticBody2D.new()

func _physics_process(delta: float) -> void:
	# Apply gravity
	if not is_on_floor():
		velocity.y += gravity * delta
	else:
		# Stop vertical movement when on the floor
		velocity.y = 0

	# Handle horizontal movement
	var input_direction = Input.get_axis("Left", "Right")
	velocity.x = input_direction * speed

	# Handle jumping
	if is_on_floor() and Input.is_action_just_pressed("Up"):
		velocity.y = -jump_force
		
	# Handle dash
	if Input.is_action_just_pressed("LeftClick"):
		dash(direction, 200)
		
	if Input.is_action_just_pressed("RightClick"):
		if !is_grappling:
			shoot_grapple()
	
	if Input.is_action_just_released("RightClick"):
		if is_grappling:
			release_grapple()

	# Move the character
	move_and_slide()
	
func _process(delta):
	var mouse_position = get_global_mouse_position()
	var player_position = global_position
	
	direction = (mouse_position - player_position).normalized()
	var direction_vector = mouse_position - player_position
	
	line.global_position = player_position
	line.points = [Vector2.ZERO, direction_vector]
	
	if is_grappling:
		rope.set_point_position(1, to_local(grapple_position))
		#print("Rope visible: ", rope.visible)
		#print("Rope points: ", rope.points)
	
#func _input(event: InputEvent) -> void:
	#if event.is_action_pressed("RightClick"):
		#if !is_grappling:
			#shoot_grapple()
			#
	#if event.is_action_released("RightClick"):
		#if is_grappling:
			#release_grapple()

func dash(direction: Vector2, distance: float) -> void:
	var target_position = global_position + direction * distance
	
	# Check if landing zone is clear
	var space_state = get_world_2d().direct_space_state
	var params = PhysicsPointQueryParameters2D.new()
	params.set_position(target_position)
	
	var collision = space_state.intersect_point(params, 1)
	print(collision)
	
	if !collision:
		global_position = target_position
		print("Dashed to: ", target_position)
	else:
		print("Collision detected! Cannot dash")
	
# grapple_position: Position in the world where the hook attaches after a successful hit.
# is_grappling: Boolean to track whether the player is currently grappling.
# grapple_joint: A PinJoint2D used to connect the player to the grapple point physically.
# rope_attachment: A Node2D used as the fixed point for the rope.

func shoot_grapple():
	if is_grappling:
		return
		
	var target_pos = global_position + direction * hook_range
	
	var space_state = get_world_2d().direct_space_state
	var params = PhysicsRayQueryParameters2D.create(global_position, target_pos)

	var hit = space_state.intersect_ray(params)
	print("Hit position: ", hit)
	
	if hit:
		grapple_position = hit.position
		is_grappling = true
		rope.visible = true
		
		#if not rope_attachment.is_inside_tree():
			#add_child(rope_attachment)
			#print("Added rope_attachment to the tree")
		
		rope_attachment.position = grapple_position
		print("Rope_attachment position: ", rope_attachment.position)
		
		if not rope_attachment.is_inside_tree():
			get_tree().current_scene.add_child(rope_attachment)
		
		grapple_joint = PinJoint2D.new()
		grapple_joint.node_a = get_path()
		grapple_joint.node_b = rope_attachment.get_path()
		grapple_joint.position = grapple_position
		
		print("Node A: ", grapple_joint.node_a)
		print("Node B: ", grapple_joint.node_b)
		print("Grapple joint position: ", grapple_joint.position)
		
		get_tree().current_scene.add_child(grapple_joint)
	else:
		print("Grapple missed")
		
func release_grapple():
	if is_grappling:
		is_grappling = false
		rope.visible = false
	
		if grapple_joint != null:
			grapple_joint.queue_free()
			grapple_joint = null
		

Thanks for the help in advance!