- Home /
Grappling Hook
First of all, I'd like to thank you for reading and attempting to help me.
So I'm in a bit of a pickle trying to create a grapple system for my game. It has quite the amount of specific needs, and it's broken down into multiple parts. Luckily, that's a good thing, as I can tackle each part one by one.
What I am looking for:
- An idea of how I should go about tackling this system
- The components I should try to use to achieve the right outcome for each part
- (If possible) The right settings for these components
The system
The player is made using rigidbody, so I'm applying force to get it to move where I want it to.
The first part of the grappling system is for the player to choose a location of where they want to grapple and shoot the hook out towards the location.
This part I have finished. What I did was, simply shoot a raycast towards the intended position, and used Vector3.MoveTowards() to move the hook towards the hit point.
The second part is what has me hanging. When a button is pressed, the player needs to move at a constant speed towards the target without overshooting it. In other words, it moves directly towards the target, and as it reaches the point, it just hangs there.
I've tried a number of things, one that I can remember is getting the difference between the hook position and player position, normalizing it, and adding force to the rigidbody to move towards that normalized value as velocity changed.
This almost worked, but it didn't seem to move the player at a constant speed, and it kept overshooting him.
Another one I tried was movetowards, then after it came close to the intended position, apply the velocity after, so when I let go of the button, it continues in the direction. Although, this felt clunky, but I should have probably tested it more.
The third part is the part I couldn't even begin on. I have literally no idea how to go about this. If the player is grappling, and let go of the button that moves it towards the position, it should keep the velocity, but move around the object. It's hard to explain, but say you have a rope that can extend and contract on will, and the player is attached to that rope, and the other end is attached to a static object. The player can reel in the rope to move towards, but as he stops reeling in, he keeps his velocity, but at some point, he turns and starts to swing around that static object.
This is what I'm looking to achieve. Sorry if it seems like I'm asking you to make a full system for me, it's not my intention. I'm just looking for an idea as to how I should go about doing this, or what I should use to go about doing this.
Thank you very much for reading my wall of text and attempting to assist me!
EDIT:
So thanks to @CheetahSpeedLion and @NorthStar79 I've come up with this script for the first part and the second part of my problem
if (input.ReelIn)
{
if (Vector3.Distance(transform.position, grapple.position) > 2.0f)
{
Vector3 normalized = (grapple.position - transform.position).normalized;
rigidbody.velocity = Vector3.MoveTowards(rigidbody.velocity, normalized * grappleSpeed, 1.0f);
}
else
{
rigidbody.velocity = Vector3.zero;
}
}
But as for the third part of my problem, I believe I haven't explained it well enough.
Answer by Onk3y · Aug 04, 2017 at 04:50 PM
@ForceGaming2013 I came up with a solution that might work for you.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class grapplingHook : MonoBehaviour {
Camera cam;
Rigidbody rb;
RaycastHit grapplePoint;
bool isGrappling = false;
//keeps track of the lenght of your "rope"
float distance;
//lets you control how fast you want your grappling hook to be
public float grappleSpeed = 5f;
void Start () {
// Get Camera and Rigidbody
cam = Camera.main;
rb = GetComponent<Rigidbody>();
}
void Update () {
// ray from camera into the scene
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
// Check if a button is pressed and if the Raycast hits something
if (Input.GetButtonDown("Fire1") && Physics.Raycast(ray, out grapplePoint))
{
isGrappling = true;
Vector3 grappleDirection = (grapplePoint.point - transform.position);
rb.velocity = grappleDirection.normalized * grappleSpeed;//This will instantly accelerate the player towards the grappling point
}
//turn grappling mode off when the button is released
if (Input.GetButtonUp("Fire1"))
isGrappling = false;
//when in grappling mode (Thats when the magic happens :D)
if (isGrappling)
{
//Look at the object you are currently grappling. Not really necessary but it looks cool
transform.LookAt(grapplePoint.point);
//Get Vector between player and grappling point
Vector3 grappleDirection = (grapplePoint.point - transform.position);
if (distance < grappleDirection.magnitude)// With this you can determine if you are overshooting your target. You are basically checking, if you are further away from your target then during the last frame
{
float velocity = rb.velocity.magnitude;//How fast you are currently
Vector3 newDirection = Vector3.ProjectOnPlane(rb.velocity, grappleDirection);//So this is a bit more complicated
//basically I am using the grappleDirection Vector as a normal vector of a plane.
//I am really bad at explaining it. Partly due to my bad english but it is best if you just look up what Vector3.ProjectOnPlane does.
rb.velocity = newDirection.normalized * velocity;//Now I just have to redirect the velocity
}
else//So this part is executed when you are grappling towards the grappling point
rb.AddForce(grappleDirection.normalized * grappleSpeed);//I use addforce just to keep the velocity rather constant. You can fiddle around with the forcemodes a bit to get better results
//Calculate distance between player and grappling point
distance = grappleDirection.magnitude;
}
else
transform.rotation = Quaternion.LookRotation(Vector3.forward, Vector3.up); //resets the rotation. Only necessary if you used transform.LookAt() before
}
}
The script needs to be on a rigidbody to work. Just hit me up if you encounter any problems.
How would someone use this in 2D instead of 3D? I'm basically trying to do the same but the different is, I don't use the mouse for the Raycast. The Raycast Is alway shot out of the grapplepoint.position. I tried this: if (Input.GetButtonDown("Fire1") && !wallGrab && !wallSlide) { isGrappling = true; GrapplingArm(); } }
private void GrapplingArm()
{
RaycastHit2D hitInfo = Physics2D.Raycast(grapplePoint.position, grapplePoint.right);
if (hitInfo)
{
Debug.Log(hitInfo.distance);
}
Vector2 grappleDirection = (grapplePoint.position - transform.position);
rb.velocity = grappleDirection.normalized * grappleSpeed; //$$anonymous$$ove player to grapplepoint
}
I just want the player to be moved to the grapplepoint.
Projection in 2d is done using the dot product on a vector that is perpendicular to the surface normal of a plane. In practice, do something like this:
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class Grapple2d : $$anonymous$$onoBehaviour
{
public float rope_length;
private Rigidbody2D rb;
public GameObject grapple_object; // Object to grapple
public float pull_force; // When rope is loose, how fast should the player pull towards target
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
if (!grapple_object) return;
if (Input.GetButton("Fire1"))
{
Grapple(grapple_object.transform.position);
}
else
{
rope_length = $$anonymous$$athf.Infinity; // Reset rope length
}
}
void Grapple(Vector2 grapple_point)
{
Vector2 position_2d = new Vector2(transform.position.x, transform.position.y);
Vector2 grapple_direction = grapple_point - position_2d; // Get difference vector between player and grapple point
if(grapple_direction.magnitude > rope_length) // If rope is under stress
{
Vector2 p = Vector2.Perpendicular(grapple_direction).normalized; // a vector that is perpendicular to grapple direction. The player should move along this vector when the rope is under stress
Vector2 new_direction = Vector2.Dot(rb.velocity, p) * p; // project velocity onto perpenducular vector
rb.velocity = new_direction.normalized * rb.velocity.magnitude; // set velocity
}
else // if rope is loose
{
rb.AddForce(grapple_direction.normalized * pull_force); // Add force towards grapple point. Does "pulling effect"
rope_length = grapple_direction.magnitude; // Adjust rope length
}
}
}
I'm bad at coding, but luckily I know how vectors work. But just to be clear, I must add a Game Object in the scene that the player can grapple then, the script will find the vector of this object then make it to the grapple_point. Then we find the Vector between the player and grapplepoint then Addforce to the player.
So i tried it out, but for some reason nothing happens. I have noticed that Rope does change to a number then back to infinity(Sometimes), so i believe the script do calculate the vectors. But the player doesn't get pulled to the grappe_point. [HideInInspector] public Rigidbody2D rb;
[Header("Grabbling $$anonymous$$echanics")]
public float rope_length;
public GameObject grapple_object; // Object to grapple
public float pull_force; // When rope is loose, how fast should the player pull towards target
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
if (!grapple_object) return;
if (Input.GetButtonDown("Fire1") && !wallGrab && wallSlide)
{
Grapple(grapple_object.transform.position);
}
else
{
rope_length = $$anonymous$$athf.Infinity;
}
}
private void Grapple(Vector2 grapple_point)
{
Vector2 position2D = new Vector2(transform.position.x, transform.position.y);
Vector2 grappleDirection = grapple_point - position2D; //The vector between player and the grapple point (understand)
if(grappleDirection.magnitude > rope_length)
{
Vector2 P = Vector2.Perpendicular(grappleDirection).normalized; // a vector that is perpendicular to grapple direction. The player should move along this vector when the rope is under stress (Understand)
Vector2 newDirection = Vector2.Dot(rb.velocity, P) * P; // project velocity onto perpenducular vector (kinda understand)
rb.velocity = newDirection.normalized * rb.velocity.magnitude; // set velocity
}
else // rope is loose
{
rb.AddForce(grappleDirection.normalized * pull_force); // Add force towards grapple point. Does "pulling effect"
rope_length = grappleDirection.magnitude; // Adjust rope lenght
}
}
If you want a better overview of the code. https://www.codepile.net/pile/wpWNR$$anonymous$$em By the way thank you for helping me.
Answer by NorthStar79 · Jul 28, 2017 at 05:37 AM
well, thanks to well-explained question. you already did very well, i just want to remind one thing that could help you for achieving your desired result.
when adding force to a rigidbody , there is an option: "ForceMode.VelocityChange" . https://docs.unity3d.com/ScriptReference/ForceMode.VelocityChange.html
you can make a variable for velocity, and lerp it from "0" towards your desired velocity, and when the player reached stopping distance you can instantly make it "0" again, this prevent overshooting.
Oh right! I am using VelocityChange, but I forgot I could just make the velocity 0 when it's at the destination. Thanks for the help!!
Answer by CheetahSpeedLion · Jul 28, 2017 at 02:43 AM
I think you can achieve #2 and #3 using addforce. The trick is that if you keep adding force in one direction, the player will keep accelerating. So if you want to have a constant velocity, you need to stop applying the force once the player reaches your desired velocity. Then if you let go of the hook, the player has momentum and it will keep moving in the direction it was.
You can use something like:
if (rigidbody.velocity.magnitude < maxSpeed) rigidbody.AddForce(transform.forward * force);
Hope that helps, Areleli Games
Thanks, I'll give that a try and come back with the results.
Answer by exp626stitch · Dec 22, 2020 at 03:35 PM
@ZhavShaw Have you thought about a spring joint?, with setting the connected anchor to hit.point?
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
How to create a dynamic hole in game 0 Answers
Make a player jump when he jumps on a spring!! 1 Answer
Grab objects and enemies 1 Answer