Creating a cat hook in Unity. Part 2

Original author: Sean Duffy
  • Transfer
image

Note : this tutorial is intended for advanced and experienced users, and it does not cover such topics as adding components, creating new GameObject scripts and C # syntax. If you need to improve Unity skills, then learn our tutorials Getting Started with Unity and Introduction to Unity Scripting .

In the first part of the tutorial we learned how to create a hook-cat with the mechanics of wrapping the rope around obstacles. However, we want more: the rope can turn around objects at the level, but does not come off when you come back.

Getting Started


Open the finished project from the first part in Unity or download the project blank for this part of the tutorial, then open 2DGrapplingHook-Part2-Starter . As in the first part, we will use Unity version 2017.1 or higher.

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


Launch the Game scene and try to hook a cat hook to the stones above the character, and then swing, so that the rope turns around a pair of stone edges.

When returning back, you will notice that the points of the stone through which the rope had previously turned around are not unhooked again.


Think about the point at which the rope should unfold. To simplify the task, it is better to use the case when the rope is wrapped around the edges.

If a slug, clinging to a stone above its head, swings to the right, then the rope will be bent after the threshold, in which it crosses a corner point of 180 degrees with an edge, to which the slug is currently attached. In the figure below it is shown highlighted green point.


When the slug swings back in a different direction, the rope must again unhook at the same point (highlighted in red above):


Unwinding logic


To calculate the moment when you need to unwind the rope at the points about which it turned around earlier, we will need knowledge of geometry. In particular, we can use a comparison of angles to determine when the rope should be detached from the rib.

This task may seem a bit intimidating. Mathematics can strike terror and despair even in the most courageous.

Fortunately, in Unity there are excellent auxiliary math functions that can simplify our lives a bit.

Open the RopeSystem script in the IDE and create a new method called HandleRopeUnwrap().

privatevoidHandleRopeUnwrap()
{
}

Go to Update()and add to the very end a call to our new method.

HandleRopeUnwrap();

So far, he HandleRopeUnwrap()is not doing anything, but now we will be able to process the logic associated with the entire process of detaching from the ribs.

As you remember from the first part of the tutorial, we kept the position of wrapping the rope in a collection with a name ropePositionsthat is a collection List<Vector2>. Each time the rope wraps around an edge, we save the position of this wrapping point to this collection.

To make the process more efficient, we will not perform any logic in HandleRopeUnwrap()if the number of positions stored in the collection is equal to or less than 1.

In other words, when the slug hooked to the starting point and its rope did not turn around the ribs, the number ropePositionswould be 1, and we We will not perform the logic of spin processing.

Add this simple statement returnto the top HandleRopeUnwrap()to save precious CPU cycles, because this method is called from Update()many times per second.

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

Adding new variables


Under this new test, we will add several dimensions and references to the various angles needed to implement the basic unwinding logic. Add to the HandleRopeUnwrap()following code:

// Hinge = следующая точка вверх от позиции игрока// Anchor = следующая точка вверх от Hinge// Hinge Angle = угол между anchor и hinge// Player Angle = угол между anchor и player// 1var anchorIndex = ropePositions.Count - 2;
// 2var hingeIndex = ropePositions.Count - 1;
// 3var anchorPosition = ropePositions[anchorIndex];
// 4var hingePosition = ropePositions[hingeIndex];
// 5var hingeDir = hingePosition - anchorPosition;
// 6var hingeAngle = Vector2.Angle(anchorPosition, hingeDir);
// 7var playerDir = playerPosition - anchorPosition;
// 8var playerAngle = Vector2.Angle(anchorPosition, playerDir);

Here are a bunch of variables, so I will explain each of them, and also add a handy illustration that will allow you to understand their purpose.

  1. anchorIndex- this is the index in the collection ropePositionsin two positions from the end of the collection. We can consider it as a point in two positions on the rope from the position of the slug. In the figure below, this is the first point of hook attachment to the surface. In the process of filling the collection ropePositionswith new wrapping points, this point will always remain a wrapping point at a distance of two positions from the slug.
  2. hingeIndex- this is the index of the collection in which the point of the current hinge is stored; in other words, the position in which the rope is currently wrapped around the point closest to the end of the rope from the side of the slug. It is always at the same position until the slug, so we use it ropePositions.Count - 1.
  3. anchorPositioncomputed by executing a reference to a place anchorIndexin the collection ropePositionsand is a simple Vector2 value of this position.
  4. hingePositioncomputed by executing a reference to a place hingeIndexin the collection ropePositionsand is a simple Vector2 value of this position.
  5. hingeDirIs a vector directed from anchorPositionto hingePosition. It is used in the following variable to get the angle.
  6. hingeAngle- here a useful helper function is used Vector2.Angle()to calculate the angle between anchorPositionand the point of the hinge.
  7. playerDir- is a vector directed from anchorPositionto the current position of the slug (playerPosition)
  8. Then, using the angle between the reference point and the player (slug), it is calculated playerAngle.


All these variables are calculated using the positions saved as Vector2 values ​​in the collection ropePositionsand comparing these positions with other positions or the current position of the player (slug).

The two important variables used for comparison are hingeAngleand playerAngle.

The value stored in hingeAnglemust remain static, because it is always a constant angle between the point at a distance of two “rope folds” from the slug and the current “rope fold” closest to the slug that does not move until the rope unwinds or after the fold Added new bend point.

When rocking the slug changes playerAngle. Comparing this angle withhingeAngleand also by checking whether the slug is to the left or right of this angle, we can determine whether the current bend point closest to the slug should be uncoupled.

In the first part of this tutorial, we kept the positions of the folds in a dictionary called wrapPointsLookup. Each time we save a fold point, we add it to the dictionary with the position as the key and with 0 as the value. However, this value of 0 was pretty mysterious, right?

We will use this value to store the position of the slug relative to its angle with the point of the hinge (the current point of the fold nearest to the slug).

If you assign a value of -1 , then the angle of the slug ( playerAngle) is less than the angle of the hinge ( hingeAngle), and with a value of 1, the angle is playerAnglegreater than hingeAngle.

Due to the fact that we store the values ​​in the dictionary, each time we compare playerAnglewith hingeAngle, we can understand whether the slug just passed through the limit, after which the rope should come off.

This can be explained differently: if the slug angle was just checked and it is less than the hinge angle, but the last time it was saved in the dictionary of fold points it was marked with a value indicating that it was on the other side of that angle, then the point should be deleted immediately !

Rope release


Look at the screenshot below with notes. Our slug clung to the rock, swung up, wrapped the rope around the edge of the rock on the way up.


You can see that in the highest swinging position, where the slug is opaque, its current closest bend point (marked with a white dot) will be stored in a dictionary wrapPointsLookupwith a value of 1 .

On the way down, when playerAngleit becomes smaller hingeAngle(two dotted green lines), as shown by the blue arrow, a check is performed, and if the last (current) value of the bend point was 1 , then the bend point needs to be removed.

Now let's implement this logic in the code. But before we begin, let's create a blank of the method that we will use for unwinding. Thanks to this, after creating the logic, it will not lead to an error.

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

privatevoidUnwrapRopePosition(int anchorIndex, int hingeIndex)
{
}

Having done that, let's go back to HandleRopeUnwrap(). Under the newly added variables, add the following logic, which will handle two cases: playerAngleless hingeAngleand playerAnglemore hingeAngle:

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

This code should correspond to the explanation of the logic described above for the first case (when playerAngle< hingeAngle), but also handles the second case (when playerAngle> hingeAngle).

  1. If the current bend point closest to the slug is 1 at the point where playerAngle< hingeAngle, then we remove this point and perform a return so that the rest of the method is not executed.
  2. Otherwise, if the bend point was not last marked with the value 1 , but playerAngleless hingeAngle, then the value -1 is assigned .
  3. If the current bend point closest to the slug is -1 at the point where playerAngle> hingeAngle, then remove the point and return.
  4. Otherwise, we assign the dictionary entries of the points of the folds in the hinge position the value 1 .

This code ensures that the dictionary is wrapPointsLookupalways updated, ensuring that the value of the current fold point (closest to the slug) matches the current slug angle relative to the fold point.

Remember that the value is -1 when the angle of the slug is smaller than the hinge angle (relative to the reference point), and 1 when the angle of the slug is greater than the angle of the hinge.

Now, UnwrapRopePosition()in the RopeSystem script, we will add code that will directly deal with uncoupling, moving the reference position and assigning a new distance to the distance value of the DistanceJoint2D rope. Add the following lines to the previously created disc of the method:

// 1var 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 index of the current pivot point (the second position of the rope from the slug) becomes the new position of the hinge, and the old position of the hinge is removed (the one that was previously closest to the slug and which we now "unwind"). The variable is newAnchorPositionassigned a value anchorIndexin the list of positions of the rope. Further it will be used for the location of the updated position of the reference point.
  2. The RigidBody2D of the rope joint (to which the DistanceJoint2D of the rope is attached) changes its position to the new position of the reference point. This ensures a smooth, continuous movement of the slug on the rope when it is connected to DistanceJoint2D, and this connection should allow it to continue to swing relative to the new position that has become the reference position — in other words, relative to the next point down the rope from its position.
  3. Then you need to update the DistanceJoint2D distance value to take into account the sharp change in the distance from the slug to the new reference point. If this has not yet been done, a quick check of the flag is performed distanceSet, and the distance is assigned the value of the calculated distance between the slug and the new position of the reference point.

Save the script and return to the editor. Start the game again and watch the rope unhook from the ribs when the slug passes the threshold values ​​of each bend point!


Although the logic is ready, we will add some helper code in HandleRopeUnwrap()right before the comparison playerAnglewith hingeAngle( if (playerAngle < hingeAngle)).

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

In fact, this should not happen, because we redefine and detach the cat hook when it wraps around one edge twice, but if it does happen, it’s not difficult for us to get out of this method with a simple operator returnand an error message in console.

In addition, thanks to this we will be more convenient to handle such limiting cases; moreover, we get our own error message when something unnecessary happens.

Where to go next?


Here is a link to the finished project of this second and last part of the tutorial.

Congratulations on this tutorial series! When it came to comparing angles and positions, everything became quite difficult, but we survived it and now we have a wonderful hook-cat system and ropes, which is capable of winding on objects in the game.


Did you know that our Unity development team wrote a book? If not, look at Unity Games By Tutorials . This game will teach you to create four ready-made games from scratch:

  • Shooter with two stick
  • First Person Shooter
  • Tower defense game (with VR support!)
  • 2D Platformer

After reading this book, you will learn how to create your own games for Windows, macOS, iOS and other platforms!

This book is intended for both beginners and those who want to improve their skills in Unity to the professional level. To master the book you need to have programming experience (in any language).

Also popular now: