2D GRAPPLE GUN QUEST: ‘Spiderman’ - Solutions

Quest: 2D Grapple Gun Quest
Challenge: Spiderman

Feel free to share your solutions, ideas and creations below. If you get stuck, you can find some ideas here for completing this challenge.

After many hours of head scratching and getting more comforatable with lerps, I’m super happy that I figured out a solution to the spiderman challenge. It’s not perfect, but it works lol woop woop!




using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class GrapplingHook : MonoBehaviour
{
    [SerializeField] private float distance = 10f;
    [SerializeField] private LayerMask mask;
    [SerializeField] private LineRenderer line;
    [SerializeField] private GameObject playerHand;
    [SerializeField] private GameObject lerpDot;
    [SerializeField] private float lerpSpeed = 1f;
    [SerializeField] private float pullDelay = 1f;

    DistanceJoint2D joint;
    Vector3 targetPos;
    RaycastHit2D hit;

    float t = 0f;
    bool isDrawing = false;

    private void Start()
    {
        joint = GetComponent<DistanceJoint2D>();
        joint.enabled = false;
        line.enabled = false;

        lerpDot.transform.position = playerHand.transform.position;
    }

    private void Update()
    {
        PullPlayer();
        DrawLine();

        if (Input.GetMouseButtonDown(0))
        {
            targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            targetPos.z = 0;

            hit = Physics2D.Raycast(playerHand.transform.position, targetPos - playerHand.transform.position, distance, mask);

            if (hit.collider != null && hit.collider.gameObject.GetComponent<Rigidbody2D>() != null)
            {
                joint.enabled = true;
                joint.connectedBody = hit.rigidbody; 
                joint.connectedAnchor = hit.transform.InverseTransformPoint(hit.point);
                joint.distance = Vector2.Distance(playerHand.transform.position, hit.point);

                line.enabled = true;
                isDrawing = true;
            }
        }

        if (Input.GetMouseButton(0))
        {
            line.SetPosition(0, playerHand.transform.position);
        }

        if (Input.GetMouseButtonUp(0))
        {
            joint.enabled = false;
            line.enabled = false;
            isDrawing = false;
            t = 0f;
        }
    }

    private void DrawLine()
    {
        if(isDrawing)
        {
            t += Time.deltaTime * lerpSpeed;

            lerpDot.transform.position = Vector3.Lerp(playerHand.transform.position, hit.point, t);

            line.SetPosition(0, playerHand.transform.position);
            line.SetPosition(1, lerpDot.transform.position);
        }

    }

    private void PullPlayer()
    {
        //Challenge 2:
        if (t > pullDelay)
        {
            joint.distance -= 0.1f;
        }
        if (joint.distance < 0.1f && joint.enabled && line.enabled)
        {
            //joint.enabled = false;
            line.enabled = false;
        }
    }
}





2 Likes

This is a great solution and it helped me to better understand lerping Vectors! Thank you :slight_smile:

1 Like

After studying and utilizing @tamio42’s logic, I was able to complete this challenge. The only issue it brought was to my “extra” feature from the challenge before. On top of the normal behavior with LMB, the player is also able to press RMB to grapple onto “pullable” objects which pulls the objects towards the player. Turned out to be a fun challenge to get the line to update appropriately with this feature. Here’s my code.

public class GrapplingHook : MonoBehaviour
{
    DistanceJoint2D distanceJoint;
    SpringJoint2D springJoint;
    Vector3 targetPos;
    RaycastHit2D hit;

    [Header("Componenet Reference")]
    [SerializeField] LayerMask mask;
    [SerializeField] LineRenderer line;
    [SerializeField] GameObject playerHand;
    [SerializeField] GameObject lerpGameObject;

    [Header("Grapple Configuration")]
    [SerializeField] float distance = 5f;
    [SerializeField] float grappleEnvSpeed = 1f;
    [SerializeField] float grappleObjSpeed = 4f;
    [SerializeField] float envLineLerpSpeed = 10f;
    [SerializeField] float objLineLerpSpeed = 10f;

    float lerpPercent = 0f;
    float pullDelay = 1f;

    bool isBeingPulled = false;
    bool isPulling = false;
    bool isLerping = false;
    bool isShooting = false;
    bool isReturning = false;

    private void Start()
    {
        distanceJoint = GetComponent<DistanceJoint2D>();
        springJoint = GetComponent<SpringJoint2D>();
        distanceJoint.enabled = false;
        springJoint.enabled = false;
        line.enabled = false;

        lerpGameObject.transform.position = playerHand.transform.position;
    }

    private void Update()
    {
        GrappleEnvironment();
        GrappleObject();
        ReleasePlayer();
        ReleaseObject();
        LerpLine();
    }

    private void GrappleEnvironment()
    {
        if (!isPulling)
        {            
            PullPlayer();

            if (Input.GetMouseButtonDown(0))
            {
                targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                targetPos.z = 0;

                hit = Physics2D.Raycast(playerHand.transform.position, targetPos - playerHand.transform.position, distance, mask);

                if (hit.collider != null && hit.collider.gameObject.GetComponent<Rigidbody2D>() != null && hit.collider.tag != "Pullable")
                {
                    distanceJoint.enabled = true;

                    distanceJoint.connectedBody = hit.rigidbody;

                    distanceJoint.connectedAnchor = hit.rigidbody.transform.InverseTransformPoint(hit.point);

                    distanceJoint.distance = Vector2.Distance(playerHand.transform.position, hit.point);

                    line.enabled = true;
                    isLerping = true;
                }
            }

            if (Input.GetMouseButton(0) && hit.collider != null && hit.collider.tag != "Pullable")
            {
                isBeingPulled = true;
            }

            if (Input.GetMouseButtonUp(0))
            {
                distanceJoint.enabled = false;
                line.enabled = false;
                isBeingPulled = false;
                isLerping = false;
                lerpPercent = 0f;
            }
        }
    }

    private void GrappleObject()
    {
        if (!isBeingPulled)
        {
            PullObject();

            if (Input.GetMouseButtonDown(1))
            {
                targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                targetPos.z = 0;

                hit = Physics2D.Raycast(playerHand.transform.position, targetPos - playerHand.transform.position, distance, mask);

                if (hit.collider != null && hit.collider.gameObject.GetComponent<Rigidbody2D>() != null && hit.collider.tag == "Pullable")
                {
                    StartCoroutine("LineState");

                    springJoint.enabled = true;
                 
                    springJoint.connectedBody = hit.rigidbody;

                    springJoint.connectedAnchor = hit.rigidbody.transform.InverseTransformPoint(hit.point);

                    springJoint.distance = Vector2.Distance(playerHand.transform.position, hit.point);

                    line.enabled = true;
                    isLerping = true;
                }
            }

            if (Input.GetMouseButton(1) && hit.collider != null && hit.collider.tag == "Pullable")
            {
                isPulling = true;
            }

            if (Input.GetMouseButtonUp(1))
            {
                springJoint.enabled = false;
                line.enabled = false;
                isPulling = false;
                isLerping = false;
                isReturning = false;
                lerpPercent = 0f;
            }
        }
    }

    private void PullPlayer()
    {
        if (lerpPercent > pullDelay)
        {
            distanceJoint.distance -= Time.deltaTime * grappleEnvSpeed;
        }
    }

    private void ReleasePlayer()
    {
        if (isBeingPulled)
        {
            if (Vector2.Distance(transform.position, hit.point) < 0.25f)
            {
                distanceJoint.enabled = false;
                line.enabled = false;
                isBeingPulled = false;
            }
        }
    }

    private void PullObject()
    {
        springJoint.distance -= Time.deltaTime * grappleObjSpeed;
    }

    private void ReleaseObject()
    {
        if (isPulling)
        {
            if (hit.collider != null)
            {
                if (Vector2.Distance(transform.position, hit.transform.position) < 0.3f)
                {
                    springJoint.enabled = false;
                    line.enabled = false;
                    isPulling = false;
                }
            }
        }
    }

    private void LerpLine()
    {
        if (isLerping)
        {
            if (isBeingPulled && hit.collider != null)
            {
                lerpGameObject.transform.position = Vector3.Lerp(playerHand.transform.position, hit.point, lerpPercent);

                lerpPercent += Time.deltaTime * envLineLerpSpeed;

                line.SetPosition(0, playerHand.transform.position);
                line.SetPosition(1, lerpGameObject.transform.position);
            }

            if (isPulling && hit.collider != null)
            {
                lerpGameObject.transform.position = Vector3.Lerp(playerHand.transform.position, hit.point, lerpPercent);

                lerpPercent += Time.deltaTime * objLineLerpSpeed;

                if (isShooting)
                {
                    line.SetPosition(0, playerHand.transform.position);
                    line.SetPosition(1, lerpGameObject.transform.position);
                }

                if (isReturning)
                {
                    line.SetPosition(0, playerHand.transform.position);
                    line.SetPosition(1, hit.collider.transform.position);
                }
            }
        }
    }

    IEnumerator LineState()
    {
        isShooting = true;
        yield return new WaitForSeconds(Vector3.Distance(hit.collider.transform.position, lerpGameObject.transform.position) / objLineLerpSpeed);
        isShooting = false;
        yield return new WaitForEndOfFrame();
        isReturning = true;
        yield return new WaitForSeconds(Vector3.Distance(hit.collider.transform.position, playerHand.transform.position) * objLineLerpSpeed);
        isReturning = false;
    }
}

Grapple_Gun

I simple destroy the “pullable” objects once it collides with the player. Super happy with how this looks! The most difficult part was understanding Lerp and how to utilize the returned variable, but with @tamio42’s help, I was able to reverse engineer it to work by using a Coroutine to control setting the whether the line was shooting towards the object versus coming back with the object. Then I calculate the distance the line needs to travel in either direction based on where the player and the object are in relation to each other and multiply or divide that by the line lerp speed.

Thank you for reading!

3 Likes

Privacy & Terms