/// <summary>
    /// Fires the rope projectile in the direction of transform.forward.
    /// </summary>
    private void ShootProjectile(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
    {
        // Only allow for projectiles to be fired if we're ready to fire
        if (ropeProjectileState != RopeProjectileState.UNFIRED)
        {
            return;
        }

        ropeProjectileState = RopeProjectileState.FIRED;
        elapsedFiredTime    = 0f;

        ropeProjectile.projectileCollider.enabled = true;

        // Un-parent the projectile's transform
        ropeProjectile.transform.parent     = null;
        ropeProjectile.transform.localScale = Vector3.one;

        // Add force to the projectile
        // Optional: Add the player's rb velocity too
        ropeProjectile.rb.isKinematic = false;
        ropeProjectile.rb.velocity    = Vector3.zero;
        ropeProjectile.rb.AddForce(transform.forward * maxProjectileDistance / projectileFireDuration, ForceMode.VelocityChange);

        // Play SFX
        audioSource.PlayOneShot(projectileFiredSFX);
    }
    public void OnProjectileConnected()
    {
        Rigidbody targetRB;

        // Try to find a rigidbody in the gameobject we just collided with
        targetRB = ropeProjectile.transform.parent.GetComponent <Rigidbody>();
        if (targetRB != null)
        {
            ropeProjectileState = RopeProjectileState.ATTACHED;

            ropeJointManager.AddJoint(targetRB, ropeProjectile.transform);
            ropeProjectile.audioSource.PlayOneShot(projectileAttachedSFX);
        }
        else
        {
            // If that fails, try to find it in the parent(s)
            targetRB = ropeProjectile.transform.parent.GetComponentInParent <Rigidbody>();
            if (targetRB != null)
            {
                ropeProjectileState = RopeProjectileState.ATTACHED;

                ropeJointManager.AddJoint(targetRB, ropeProjectile.transform);
                ropeProjectile.audioSource.PlayOneShot(projectileAttachedSFX);
            }
            else
            {
                // No joint created, play a different SFX
                audioSource.PlayOneShot(projectileCollisionSFX);
            }
        }
    }
    private void FixedUpdate()
    {
        if (ropeProjectileState == RopeProjectileState.FIRED)
        {
            // If the projectile has been fired for long enough, then start lerping it back to this transform
            elapsedFiredTime += Time.fixedDeltaTime;

            if (elapsedFiredTime > projectileFireDuration)
            {
                ReturnProjectile();
            }
        }

        if (ropeProjectileState == RopeProjectileState.RETURNING)
        {
            // Lerp the projectile back to the transform
            elapsedReturnTime += Time.fixedDeltaTime;
            ropeProjectile.rb.MovePosition(Vector3.Lerp(initialReturnPosition, transform.position, elapsedReturnTime / projectileReturnDuration));
            ropeProjectile.rb.MoveRotation(Quaternion.Lerp(initialReturnRotation, transform.rotation, elapsedReturnTime / projectileReturnDuration));

            // Return to the "Unfired" state
            if (elapsedReturnTime >= projectileReturnDuration)
            {
                ropeProjectileState = RopeProjectileState.UNFIRED;

                // Reset projectile transform
                ropeProjectile.transform.parent   = transform;
                ropeProjectile.transform.position = transform.position;
                ropeProjectile.transform.rotation = transform.rotation;
            }
        }
    }
    public void ReturnProjectileImmediate()
    {
        ropeProjectile.Detach();
        ropeJointManager.DestroyJoint();

        ropeProjectileState = RopeProjectileState.UNFIRED;

        // Reset projectile transform
        ropeProjectile.transform.parent   = transform;
        ropeProjectile.transform.position = transform.position;
        ropeProjectile.transform.rotation = transform.rotation;
    }
    /// <summary>
    /// Lerps the rope projectile from it's current position back to transform.position
    /// </summary>
    private void DoReturnProjectile(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
    {
        if (ropeProjectileState == RopeProjectileState.FIRED || ropeProjectileState == RopeProjectileState.ATTACHED)
        {
            ropeProjectileState = RopeProjectileState.RETURNING;

            // Set the initial return time proportional to how far away the projectile is, compared to the max distance
            elapsedReturnTime = (1 - (ropeProjectile.transform.position - transform.position).magnitude / maxProjectileDistance)
                                * projectileReturnDuration;

            initialReturnPosition = ropeProjectile.transform.position;
            initialReturnRotation = ropeProjectile.transform.rotation;

            ropeProjectile.Detach();
            ropeJointManager.DestroyJoint();
        }
    }