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.