Creating a retractable grappling hook

I’m having trouble making a retracting grapple hook similar to the Hookshot featured in most Legend of Zelda games, where the hook retracts back to the Hookshot either if the player hits a button before the hook gets to its intended destination or if the hook reaches a specified distance without hitting a grapple point.

Right now, the hook I’ve created shoots out to a specified distance away from the player, then immediately ‘snaps’ into place when it’s greater than that distance. Below is the FixedUpdate function where most of the action takes place:

void FixedUpdate()
    {
        hookDist = Vector3.Distance(subItem.transform.position, hook.transform.position);
        
        Ray ray = Camera.mainCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
        RaycastHit hit;

        rotSpeed = Mathf.Clamp(rotSpeed, 0f, 1f);
        step = speed * Time.deltaTime;
        
        hook.transform.Rotate(Vector3.forward * (angle * rotSpeed), Space.Self);

        Debug.Log(rotSpeed);
        Debug.Log(hookDist);

        if (Input.GetMouseButton(0))
        {            
            if (Physics.Raycast(ray, out hit))
            {
                GrappleToGrapplePoint();
            }
        }                    

        if (isSpinning) { 
            rotSpeed += .2f * Time.deltaTime;
        }
        else
            rotSpeed -= .5f * Time.deltaTime;

        if (Input.GetMouseButtonDown(0)) {
            isSpinning = true;
        }

        if (Input.GetMouseButtonUp(0)) {
            isSpinning = false;
            if (rotSpeed >= 1f) {
                hook.rigidbody.velocity = subItem.transform.TransformDirection(new Vector3(0, 0, hookSpeed));
                hook.transform.parent = null;
            }            
        }

        if (hookDist >= 18)
        {
            hook.rigidbody.velocity = Vector3.zero;
            hook.transform.position = Vector3.Lerp(hook.transform.position, subItem.transform.position, retractSpeed * Time.time);
            hook.transform.parent = subItem.transform;
        }
    }

If you read the script, you can see that I use Vector3.Lerp to retract the hook to its initial position without snapping into place. However, as stated before, the hook ‘snaps’ back to the empty GameObject’s position.

As per usual, I may be missing something very small; I just can’t figure out what it is at the time. Any relevant input would be much appreciated.

Thank you for taking the time to read this.

Look at my answer in this question. The problem (at least what I can see right now) lies with the Vector3.Lerp in these:

if (hookDist >= 18)
        {
            hook.rigidbody.velocity = Vector3.zero;
            hook.transform.position = Vector3.Lerp(hook.transform.position, subItem.transform.position, retractSpeed * Time.time);
            hook.transform.parent = subItem.transform;
        }

You should not use Time.time for any Lerp function. For Lerp( a, b, factor) (Vector3.Lerp reference), if factor is smaller than 0, you will get a, if it is larger than 1, then you will always get b.

When you use retractSpeed * Time.time, chances are the result will be way larger than 1, so the hook snaps right back to subItem.transform.position.

To fix this, you can use Vector3.MoveTowards, something like this:

hook.transform.position = Vector3.MoveTowards(hook.transform.position, subItem.transform.position, retractSpeed * Time.deltaTime);

On a non-related but important issue, you should use a variable/state to store the current state of the hook, from what I can see:

void FixedUpdate()
    {
        hookDist = Vector3.Distance(subItem.transform.position, hook.transform.position);
 
        ...
 
        if (Input.GetMouseButtonDown(0)) {
           //...
        }
 
        if (Input.GetMouseButtonUp(0)) {
            //Shooting the hook          
        }
 
        if (hookDist >= 18)
        {
           //Retract hook 
        }
    }

When your hook starts retracting ( no more snapping after you fixed it ), when the hook distance becomes shorter than 18, it will not fully retract. You need a state to record the current state of the hook: standby, shooting, stick(if applicable), retracting.