How To Make A VR Grappling Gun For SteamVR

Those who have read some of my tutorials might know that I am a fan of the Titanfall universe. And those that have played apex or titanfall2 know how good their grappling hooks feel and it’s probably my favorite mechanic, besides wall-running of course. And legal wallhacks. But I digress, let’s get to swinging through the air like a graceful bird of the sky.

The First thing we want to do is get a gun model and a cable that stretches between the gun and the hook. (The models and code will be available on the downloads page) My grappling hook is made out of a gun, a hook, and rope that stretches between the hook and the gun:

The rope is just a default cylinder that is stretched between two points, I also Stretched the texture so it would look like the cable was coming out of the gun when I extend it. Just put this script on a cylinder and add a rope/cable like texture.

using UnityEngine;
[ExecuteInEditMode]
public class Rope : MonoBehaviour
{
    public Transform StartPoint;
    public Transform EndPoint;
    public float TilesPerMeter;//how much you want to steach the texture 
    public Material CableMaterial;//texture you want to streach 
   
    void Update()
    {
        transform.position = (StartPoint.position + EndPoint.position) / 2;
        transform.localScale = new Vector3(transform.localScale.x, Vector3.Distance(EndPoint.position, StartPoint.position) / 2, transform.localScale.z);
        transform.rotation =  Quaternion.LookRotation(EndPoint.position-StartPoint.position)*Quaternion.Euler(90, 0, 0);
        CableMaterial.mainTextureScale = new Vector2(CableMaterial.mainTextureScale.x, Vector3.Distance(EndPoint.position, StartPoint.position)/TilesPerMeter);
    }
}

Next, import your gun model and a hook model and set them up so you can grab the gun. (Tutorial here) (you will also need to add a public variable to the interactible script named GrippedBy that’s set to the [CameraRig] prefab whenever it’s picked up)We should have two hooks, one that does not have a rigidbody and is placed at the tip of the gun as if it is ready to fire, and another that has a rigidbody. Depending on the state of the gun we will hide one of them giving the illusion that they are the same one. And finally, we need and empty placed at the barrel of the gun so that we have an endpoint for the rope and the point to apply forces from. Now that our gun model is ready we can get to coding.

We need to create two more scripts: one for the gun itself, and one for the hook:

Grappling hook gun script:

using UnityEngine;
using Valve.VR;

public class GrapplingHookGun : MonoBehaviour
{
    public float BulletSpeed;//how fast to fire the hook
    public GameObject Rope;//refrence to the rope 
    public GameObject ActiveHook;//the hook with a rigidbody
    public GameObject StaticHook;//the hook without a rigidbody
    public Transform ConnectionPoint;//point that we should calculate forces with (barrel of the gun)
    public SteamVR_Action_Boolean FireAction;//SteamVR action for fireing

    private bool Grappling;//remeber if we are grappling 
    // Start is called before the first frame update
    void Start()
    {
        //make sure all thee right things are visible
        StaticHook.SetActive(true);
        Rope.SetActive(false);
        ActiveHook.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        if (GetComponent<Interactable>().gripped) {//check if the gun is held, if you are using a diffent interaction system you will want to change this accordingly
            if (Grappling)//if we are grappling
            {
                Rope.SetActive(true);//switch on or off the right objects
                ActiveHook.SetActive(true);
                StaticHook.SetActive(false);
                if (ActiveHook.GetComponent<GrapplingHook>().attached)//if the hook is attached to an object
                {
                    GetComponent<Interactable>().GrippedBy.GetComponent<Rigidbody>().useGravity = false;//Disable gravity for more awsome grapples 
                    //GetComponent<Interactable>().GrippedBy.GetComponent<Player>().DisableMovment = true;//this is to activate an alternate movment system for while I'm in the air. It's basicly the walking system but without the speed regulating part.
                }
                if (FireAction.GetStateUp(GetComponent<Interactable>().Hand))//if we let go of the trigger
                {
                    GetComponent<Interactable>().GrippedBy.GetComponent<Rigidbody>().useGravity = true;//reactivate gravity

                    //GetComponent<Interactable>().GrippedBy.GetComponent<Player>().DisableMovment = false;//turn off alternate movement system
                    ActiveHook.GetComponent<GrapplingHook>().Retract();//tell the hook to detach and retract
                }
            }
            else//if we arn't grappling
            {
                StaticHook.SetActive(true);//hide and show the relevent objects
                Rope.SetActive(false);
                ActiveHook.SetActive(false);
                if (FireAction.GetStateDown(GetComponent<Interactable>().Hand))//check if we want to fire
                {

                    Grappling = true;//set grappling to true
                    ActiveHook.transform.position = StaticHook.transform.position+StaticHook.transform.forward*.1f;//set the active grappling hooks position to the tip of the gun
                    ActiveHook.transform.rotation = StaticHook.transform.rotation;//set rotation
                    ActiveHook.GetComponent<Rigidbody>().isKinematic = false;//make sure it's active
                    ActiveHook.GetComponent<Rigidbody>().velocity = BulletSpeed * ActiveHook.transform.forward;//set it's velocity 
                    ActiveHook.SetActive(true);//set the hook as active
                    ActiveHook.GetComponent<GrapplingHook>().Fire();//fire the hook
                }
            }
        }
        else//if we arn't gripping the gun
        {
            Grappling = false;//put the gun in a passive state
            //RetractionSpring.connectedBody = null;
            StaticHook.SetActive(true);
            Rope.SetActive(false);
            ActiveHook.SetActive(false);
        }
    } 
    private void OnCollisionEnter(Collider collision)
    {
        if (collision.GetComponent<GrapplingHook>() && collision.GetComponent<GrapplingHook>().retracting)//if we touch the grappling hook and it's not in use set not grappling to true.
        {
            Grappling = false;
            GetComponent<Interactable>().touchCount--;//the object never leaves the gun so we need to update this manualy.
        }
    }
}

Hopefully, you’re still with me after that, now on to the script for the hook:

using UnityEngine;

public class GrapplingHook : MonoBehaviour
{
    public bool attached;//are we attached to somthing?
    public bool retracting;//are we retracting?
    public Transform retractionPoint;//where should we aim the forces 
    public float RetractionSpeed;//how fast should we retract 
    public GameObject Gun;//the gun object

    // Update is called once per frame
    void Update()
    {
        if (retracting)
        {
            //when retracting move the hook towards the gun
            transform.position = Vector3.MoveTowards(transform.position, retractionPoint.position, .5f)+retractionPoint.forward*.05f;
            transform.rotation = retractionPoint.rotation;
        }
        if (attached)
        {
            //if attached to something apply force on the player.
            CameraRig.GetComponent<Rigidbody>().AddForce(Vector3.Normalize(transform.position-retractionPoint.position)*RetractionSpeed,ForceMode.VelocityChange);
            
            
        }
        else
        {
            //if we are not attached check the space in front of the hook to see if we are going to hit something 
            RaycastHit Hit;
            Debug.DrawRay(transform.position, transform.forward * GetComponent<Rigidbody>().velocity.magnitude * Time.deltaTime, Color.blue, 3);
            if (Physics.Raycast(transform.position, transform.forward, out Hit, GetComponent<Rigidbody>().velocity.magnitude * Time.deltaTime))
            {
                if (!attached && !retracting && !Hit.collider.GetComponent<GrapplingHookGun>())
                {
                    transform.position = Hit.point;//if we hit something stick to it
                    GetComponent<Rigidbody>().isKinematic = true;
                    attached = true;
                }
            }
        }
    }
    private void OnCollisionEnter(Collision collision)
    {
        if (!attached && !retracting && !collision.collider.GetComponent<GrapplingHookGun>())
        {//if we haven't attached to anything, attach to the collision
            
            GetComponent<Rigidbody>().isKinematic = true;
            attached = true;
            
        }
        if (collision.collider.GetComponent<GrapplingHookGun>())//fix bugs with the interaction system 
        {
            
            collision.collider.GetComponent<Interactable>().touchCount--;
        }
        
    }
    private void OnCollisionExit(Collision collision)//fix interaction issues
    {
        if (collision.collider.GetComponent<GrapplingHookGun>())
        {
            collision.collider.GetComponent<Interactable>().touchCount++;
        }

    }
    public void Retract()
    {
        GetComponent<Rigidbody>().isKinematic = true;//set our state to retracting
        attached = false;
        retracting = true;
        //retractionSpring.SetActive(true);
    }
    public void Fire()//get ready to fire
    {
        CameraRig = Gun.GetComponent<Interactable>().GrippedBy;
        GetComponent<Rigidbody>().isKinematic = false;
        retracting = false;
        //retractionSpring.SetActive(false);
    }
}

Attach these scripts to the right objects and try It out and hopefully, it works. The download page will have all the modified scripts you need. If you have any problems post them in the comments below.

Liked it? Take a second to support WireWhiz on Patreon!
Become a patron at Patreon!