Make a 2D Grappling Hook Game in Unity – Part 2
How to make a 2D grappling hook in Unity – part 2. In this part you will learn how to unwrap your rope as you swing back past the pivot points. By Sean Duffy.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Make a 2D Grappling Hook Game in Unity – Part 2
20 mins
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.
-
anchorIndex
is the index in theropePositions
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 theropePositions
collection fills with more wrap points, this point will always be the wrap point two positions away from the slug. -
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 useropePositions.Count - 1
. -
anchorPosition
is calculated by referencing theanchorIndex
location in theropePositions
collection, and is simply a Vector2 value of that position. -
hingePosition
is calculated by referencing thehingeIndex
location in theropePositions
collection, and is simply a Vector2 value of that position. -
hingeDir
a vector that points from theanchorPosition
to thehingePosition
. It is used in the next variable to work out an angle. -
hingeAngle
is where the ever usefulVector2.Angle()
helper function is used to calculate the angle betweenanchorPosition
and the hinge point. -
playerDir
is the vector that points fromanchorPosition
to the current position of the slug (playerPosition) -
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!