Make a 2D Grappling Hook Game in Unity – Part 2

Sean Duffy
Note: This tutorial is intended for an intermediate to advanced audience, and won’t cover things such as adding components, creating new GameObjects scripts or the syntax of C#. If you need to level up your Unity skills, work through our tutorials on Getting Started with Unity and Introduction to Unity Scripting first.

How to Make a 2D Grappling Hook Game in Unity - Part 2

Welcome to the second and final part of this two-part series on how to make a 2D grappling hook game in Unity!

In Part 1 of this series, you learned how to hook up a fairly nifty grappling hook with a rope wrapping mechanic. However, you were left wanting for more. The rope could wrap around objects in the level, but didn’t unravel when you swung back past them again.

By the end of this tutorial, you’ll be unwrapping that rope like a professional!

Getting Started

In Unity, open your completed project from Part 1 in this tutorial series, or download the starter project for this part of the series and open 2DGrapplingHook-Part2-Starter. As with Part 1, you should be using Unity 2017.1 or newer.

Open the Game scene under the Scenes project folder in the editor.

Run the Game scene and try your grappling hook on the rocks above you, then swing around so that the rope wraps over a couple of edges on the rocks.

When swinging back again, you’ll notice that the points on the rocks where the rope had previously wrapped around don’t unwrap again.

Think about the point at which the rope should unwrap. To make this easier, it might be best to think about the case where you have the rope wrapping over edges.

If the slug swings to the right while grappled to a rock above, the rope will wrap at the threshold where it passes the 180 degree point with the edge the slug is currently grappled to, as indicated by the circled green point in the image below.

When the slug swings back around again in the other direction, the rope should unwrap at that same point again (the point highlighted in red below):

Unwrapping Logic

To calculate when to unwrap the rope from points it has wrapped around, you’ll need to employ the use of some geometry mathematics. Specifically, you’ll need to use angle comparison to work out when the rope should unwrap.

Thinking about this problem can be a little daunting. Math can invoke feelings of terror and despair even in those with the strongest of fortitude.

Luckily, Unity has some excellent math helper functions that should make your life a little bit easier.

Open RopeSystem in your IDE, and create a new method named HandleRopeUnwrap().

private void HandleRopeUnwrap()
{

}

Locate Update() and add a call to your shiny new method at the very end.

HandleRopeUnwrap();

Right now, HandleRopeUnwrap() doesn’t do anything, but you now have a handle on the logic that deals with this whole unwrapping business.

You may recall from part 1 of this series that you stored rope wrap positions in a collection named ropePositions, which is a List collection. Every time the rope wraps around an edge, you store the position of that wrap point in this collection.

In order to keep things more efficient, you won’t worry about running any of the logic in HandleRopeUnwrap() if this collection’s count of stored positions is 1 or less.

In other words, when the slug is grappled to a starting point, and its rope has not wrapped around any edges yet, the ropePositions count will be 1, and you won’t worry about handling unwrapping logic.

Add this simple return statement at the top of HandleRopeUnwrap() to save precious CPU cycles for these cases, as this method is being called from Update() many times a second.

if (ropePositions.Count <= 1)
{
    return;
}

Adding Extra Variables

Below this newly added check, you'll want some measurements and references to the various angles required to do the bulk of the unwrap logic. Add the following code to HandleRopeUnwrap():

// Hinge = next point up from the player position
// Anchor = next point up from the Hinge
// Hinge Angle = Angle between anchor and hinge
// Player Angle = Angle between anchor and player

// 1
var anchorIndex = ropePositions.Count - 2;
// 2
var hingeIndex = ropePositions.Count - 1;
// 3
var anchorPosition = ropePositions[anchorIndex];
// 4
var hingePosition = ropePositions[hingeIndex];
// 5
var hingeDir = hingePosition - anchorPosition;
// 6
var hingeAngle = Vector2.Angle(anchorPosition, hingeDir);
// 7
var playerDir = playerPosition - anchorPosition;
// 8
var playerAngle = Vector2.Angle(anchorPosition, playerDir);

That's a lot of variables, so here is some explanation around each one, along with a handy illustration that will help you match up each one to its purpose.

  1. anchorIndex is the index in the ropePositions collection two positions from the end of the collection. You can look at this as two positions in the rope back from the slug's position. In the image below, this happens to be the grappling hook's first hook point into the terrain. As the ropePositions collection fills with more wrap points, this point will always be the wrap point two positions away from the slug.
  2. hingeIndex is the index in the collection where the current hinge point is stored; in other words, the position where the rope is currently wrapping around a point closest to the 'slug' end of the rope. It’s always one position away from the slug, which is why you use ropePositions.Count - 1.
  3. anchorPosition is calculated by referencing the anchorIndex location in the ropePositions collection, and is simply a Vector2 value of that position.
  4. hingePosition is calculated by referencing the hingeIndex location in the ropePositions collection, and is simply a Vector2 value of that position.
  5. hingeDir a vector that points from the anchorPosition to the hingePosition. It is used in the next variable to work out an angle.
  6. hingeAngle is where the ever useful Vector2.Angle() helper function is used to calculate the angle between anchorPosition and the hinge point.
  7. playerDir is the vector that points from anchorPosition to the current position of the slug (playerPosition)
  8. playerAngle is then calculated by getting the angle between the anchor point and the player (slug).

These variables are all being calculated by looking at positions stored as Vector2 values in the ropePositions collection, and comparing these positions to other positions, or the current position of the player (slug).

The two important variables you now have stored for comparison are hingeAngle and playerAngle.

The value stored in hingeAngle should stay static, as it is always a fixed angle between the point two 'rope bends' away from the slug, and the current 'rope bend' closest to the slug which doesn't move until it unwraps or a new wrap point is added after this.

The playerAngle value is what changes while the slug is swinging. By comparing this angle to the hingeAngle, as well as whether the slug was last left or right of this angle, you can determine if the current wrap point closest to the slug should unwrap or not.

In part 1 of this tutorial, you stored wrap positions in a Dictionary collection named wrapPointsLookup. Each time you stored a wrap point, you added it to the dictionary with the position as the key, and 0 as the value. That 0 value was pretty mysterious though right?

This value is what you'll use to store the slug's position, relative to its angle to the hinge point (the current closest wrap point to the slug).

You'll set this to a value of -1 when the slug's angle (playerAngle) is less than the hinge's angle (hingeAngle), and a value of 1, when playerAngle is greater than hingeAngle.

By storing this in the dictionary, every time you check playerAngle against hingeAngle, you'll be able to tell if the slug has just passed the threshold at which the rope should unwrap.

Another way to put this is if the slug's angle has just been checked, and is less than the hinge's angle, but the last time it was stored in the wrap point dictionary it was marked with a value indicating it was on the other side of this angle, then the point should be immediately unwrapped!

Unwrapping

Take a look at this annotated screen capture where our friendly slug has anchored to a rock, then swung upward, wrapping the grappling hook rope around a rock edge on its way up.

You'll notice that at the apex of its swing, where the slug is a solid color, its current closest wrap point (where the white dot is) would be saved in the wrapPointsLookup dictionary with a value of 1.

On its way down, as playerAngle becomes less than hingeAngle (those two dotted green lines) as illustrated by the blue arrow, a check will be made, and if the wrap point's last (current) value was 1, then the point should be unwrapped.

You'll now code that logic in. But before you do that, create a placeholder for the method that will do the unwrapping first. Then the logic you're about to add won’t cause an error after you create it.

Add a new method UnwrapRopePosition(anchorIndex, hingeIndex) by adding the following lines:

private void UnwrapRopePosition(int anchorIndex, int hingeIndex)
{

}

After you've done that, return to HandleRopeUnwrap(). Just below the newly added variables, add the following logic which will handle the two cases, where playerAngle is less than hingeAngle, or playerAngle is greater than hingeAngle:

if (playerAngle < hingeAngle)
{
    // 1
    if (wrapPointsLookup[hingePosition] == 1)
    {
        UnwrapRopePosition(anchorIndex, hingeIndex);
        return;
    }

    // 2
    wrapPointsLookup[hingePosition] = -1;
}
else
{
    // 3
    if (wrapPointsLookup[hingePosition] == -1)
    {
        UnwrapRopePosition(anchorIndex, hingeIndex);
        return;
    }

    // 4
    wrapPointsLookup[hingePosition] = 1;
}

This code should align with the explanation of the logic above for the first case (where playerAngle < hingeAngle), but also handles the other case (where playerAngle > hingeAngle).

  1. If the current closest wrap point to the slug has a value of 1 at the point where playerAngle < hingeAngle then unwrap that point, and return so that the rest of the method is not handled.
  2. Otherwise, if the wrap point was not last marked with a value of 1, but playerAngle is less than the hingeAngle, the value is set to -1 instead.
  3. If the current closest wrap point to the slug has a value of -1 at the point where playerAngle > hingeAngle, unwrap the point and return.
  4. Otherwise, set the wrap point dictionary entry value at the hinge position to 1.

This code will now ensure that the wrapPointsLookup dictionary is always updated to ensure the current wrap point (closest to the slug) is always up to date with the slug's current angle relative to the wrap point.

Remember that -1 is when the slug's angle is less than the hinge angle (relative to the anchor position), and that 1 is when the slug's angle is greater than the hinge angle.

Now complete UnwrapRopePosition() in the RopeSystem script with the code that will actually do the unwrap by moving the anchored position and resetting the rope's DistanceJoint2D distance value to the new distance. Add the following lines to the placeholder you created earlier:

    // 1
    var newAnchorPosition = ropePositions[anchorIndex];
    wrapPointsLookup.Remove(ropePositions[hingeIndex]);
    ropePositions.RemoveAt(hingeIndex);

    // 2
    ropeHingeAnchorRb.transform.position = newAnchorPosition;
    distanceSet = false;

    // Set new rope distance joint distance for anchor position if not yet set.
    if (distanceSet)
    {
        return;
    }
    ropeJoint.distance = Vector2.Distance(transform.position, newAnchorPosition);
    distanceSet = true;
  1. The current anchor index (the second rope position away from the slug) becomes the new hinge position and the old hinge position is removed (the one that was previously closest to the slug that we are now 'unwrapping'). The newAnchorPosition variable is set to the anchorIndex value in the rope positions list. This will be used to position the updated anchor position next.
  2. The rope hinge RigidBody2D (which is what the rope's DistanceJoint2D is attached to) has its position changed here to the new anchor position. This allows the seamless continued movement of the slug on his rope as he is connected to the DistanceJoint2D, and this joint should allow him to continue swinging based off the new position he is anchored to — in other words, the next point down the rope from his position.
  3. Next, the distance joint's distance value needs to be updated to account for the sudden change in distance of the slug to the new anchor point. A quick check against the distanceSet flag ensures that this is done, if not already done, and the distance is set based on calculated the distance between the slug and the new anchor position.

Save your script and return to the editor. Run the game again, and marvel at the rope unwrapping from edges as the slug passes each wrap point threshold!

Although the logic is complete, add one small bit of housekeeping code to HandleRopeUnwrap() just before the check of playerAngle against hingeAngle (if (playerAngle < hingeAngle)).

if (!wrapPointsLookup.ContainsKey(hingePosition))
{
    Debug.LogError("We were not tracking hingePosition (" + hingePosition + ") in the look up dictionary.");
    return;
}

This shouldn't really ever happen, as you're already resetting and detaching the grappling hook if it wraps around an edge twice, but it doesn't hurt to bail out of this method if this does happen with a simple return statement and an error message to the console.

Plus it makes you feel rather dapper when you handle edge cases like this; and furthermore, you get a custom error message indicating you've done something you shouldn't have.

Where to Go From Here?

Here's a link to the completed project for this second and final part of the tutorial.

Congratulations on completing this tutorial series! Things got pretty complex with all the angle and position comparisons, but you persevered and now have great grappling hook and rope system that can wrap and unwrap objects in your game like nobody's business.

Did you know the Unity team has created a book? If not, check out Unity Games By Tutorials. The book will teach you to create four complete games from scratch:

  • A twin-stick shooter
  • A first-person shooter
  • A tower defense game (with VR support!)
  • A 2D platformer

By the end of this book, you’ll be ready to make your own games for Windows, macOS, iOS, and more!

This book is for complete beginners to Unity, as well as for those who’d like to bring their Unity skills to a professional level. The book assumes you have some prior programming experience (in any language).

If you have any questions or comments on this tutorial or tutorial series as a whole, please join the discussion below!

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Sean Duffy

Sean is a software engineer by day, and hobbyist game and tools developer by night. He loves working with Unity, and is also a Unity Asset Store developer with a special focus on 2D tools to help other game developers. Some of Sean's more popular Unity Assets include his 2D Shooter Bullet and Weapon System and 2D Homing Missiles assets.

You can find Sean at his personal blog or on Twitter

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

iOS Team

... 78 total!

Android Team

... 26 total!

Unity Team

... 11 total!

Articles Team

... 15 total!

Resident Authors Team

... 18 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!