How can I pull the player towards a specific point?

Godot Version

4.2.2

Question

I am new to Godot so please bear with me

What I am trying to do:
I am modifying this grapple hook addon: Grappling Hook 3D - Godot Asset Library
I am trying to make a grapple hook that works with moving objects

Currently the grapple hook attaches itself to an object and pulls the player forward by shooting a ray cast and getting the position of the object.

var pull_vector = (hook_target_position - player_body.global_position).normalized()

This works great for objects that never move. However when I try to hook onto an object that is moving, the position never gets updated. This causes the player to hook into the last spot the object was.

What I have tried:
Instead of giving the position to the player I just gave the position of the collider. The problem with this method is that the grapple hook just hooks into the center of the object.

I want it to hit at a specific point where the player looks and pulls them towards that point. I don’t want the player to be pulled constantly towards the center of the object.

func _attach_hook() -> void:
	is_hook_launched = true

	var hit_node = hook_raycast.get_collider()
	if hit_node:
		if hit_node.is_in_group("MoveBox"):  # Check if the hit node is in the "Boxes" group
			print("Hit a box:", hit_node.name)
			hit_node.announce_hit()
			#Makes it so that the player is pulled in the center of a moving object
			hook_target = hook_raycast.get_collider() as Node3D 
		
		
	hook_target_position = hook_raycast.get_collision_point()
	_hook_target_normal = hook_raycast.get_collision_normal()
	
	
	_hook_model = hook_scene.instantiate()
	add_child(_hook_model)
	hook_attached.emit()

Another approach I tried was instead of getting the collider I would get the position of the moving object itself and give that as the position. However upon doing this I am still being pulled into the center.

#Pulls only to the center
	var pull_vector = (moving_point- player_body.global_position).normalized()

I’m not sure what else to do. If anyone could give me some ideas or point me in the right direction I would really appreciate it.

For reference here is the entire script

    extends Node
    class_name HookController
    
    @export_category("Hook Controller")
    @export_group("Required Settings")
    @export var hook_raycast: RayCast3D
    @export var player_body: CharacterBody3D
    @export var launch_action_name: String
    @export var retract_action_name: String
    @export_group("Optional Settings")
    @export var pull_speed: float = 1
    @export var hook_source: Node3D
    @export_group("Advanced Settings")
    @export var hook_scene: PackedScene = preload("res://addons/grappling_hook_3d/src/hook.tscn")
    
    var _hook_model: Node3D
    var _hook_target_normal: Vector3
    
    var is_hook_launched: bool = false
    var hook_target: Node3D
    var hook_target_position: Vector3
    
    var anchor_offset: Vector3
    
    var moving_point:Vector3
    
    signal hook_launched()
    signal hook_attached()
    signal hook_detached()
    
    
    func _ready():
    	# Connect the 'boxposSignal' signal from all MoveBox nodes
    	for move_box in get_tree().get_nodes_in_group("MoveBox"):
    		# print("Connecting boxposSignal signal for", move_box)  # Debug print
    		move_box.connect("boxposSignal", Callable(self, "_on_boxpos_received"))
    	
    
    func _physics_process(delta: float) -> void:
    	if Input.is_action_just_pressed(launch_action_name):
    		hook_launched.emit()
    		
    		if not is_hook_launched and hook_raycast.is_colliding():
    			_attach_hook()
    		elif is_hook_launched:
    			_retract_hook()
    
    	if is_hook_launched:
    		_handle_hook(delta)
    
    func _attach_hook() -> void:
    	is_hook_launched = true
    	
    	var hit_node = hook_raycast.get_collider()
    	if hit_node:
    		if hit_node.is_in_group("MoveBox"):  # Check if the hit node is in the "Boxes" group
    			print("Hit a box:", hit_node.name)
    			hit_node.announce_hit()
    			#Makes it so that the player is pulled in the center of a moving object
    			#hook_target = hook_raycast.get_collider() as Node3D 
    		
    	
    	hook_target_position = hook_raycast.get_collision_point()
    	_hook_target_normal = hook_raycast.get_collision_normal()
 	
    	_hook_model = hook_scene.instantiate()
    	add_child(_hook_model)
    	
    	hook_attached.emit()
    
    func _on_boxpos_received(thepoint: Vector3) -> void:
    	#print("Received 'boxpos' signal with the point:", thepoint)
    	moving_point=thepoint
    	
  
    func _retract_hook() -> void:
    	is_hook_launched = false
    	_hook_model.queue_free()
    	hook_target = null
    	hook_detached.emit()
    
    func _handle_hook(delta: float) -> void:
    	if hook_target:
    		hook_target_position = hook_target.global_position
    
    	#print("hook target pos:", hook_target_position)
    	# Hook pull math
    	var pull_vector = (hook_target_position - player_body.global_position).normalized()
    	
    	#print("Pull vector is: ", pull_vector)
    	
    	player_body.velocity += pull_vector * pull_speed * delta * 60
    
    	# Hook model
    	var source_position: Vector3
    	match true if hook_source else false:
    		true: source_position = hook_source.global_position
    		false: source_position = player_body.global_position
    
    	_hook_model.extend_from_to(source_position, hook_target_position, _hook_target_normal)

Here is the moving box script

extends Node3D

signal box_hit  
signal boxposSignal

var thepoint = Vector3()
var ishit=false


func _ready():
	pass

func _process(delta):
	#pass
	#print("Global position is: ", self.global_position)
	if ishit:
		thepoint=self.global_position
		#print("Emitting pleasework signal with point:", thepoint)
		emit_signal("boxposSignal",thepoint)
		

func announce_hit():
	print("I'M HIT! The postion is", self.global_position)
	ishit=true
	emit_signal("box_hit", self)

Just wanted to give an update to anyone who comes across this.

This was solved by the creator of the addon. He added the functionality for the grapple hook to work with moving object.

His fix was essentially creating a target point that is a child of the collided object.

func _launch_hook() -> void:
	if not hook_raycast.is_colliding():
		return
	
	is_hook_launched = true
	hook_attached.emit()
	
	var body: Node3D = hook_raycast.get_collider()
	
	hook_target_node = Marker3D.new()
	body.add_child(hook_target_node)
	
	hook_target_node.position = hook_raycast.get_collision_point() - body.global_position
	hook_target_normal = hook_raycast.get_collision_normal()
	
	_hook_model = hook_scene.instantiate()
	add_child(_hook_model)
1 Like