How to create rope physics by logic?

Developers, especially novices, are often tempted to use the physic engine to implement the mechanics of the game logic. This is because the mathematical equations needed to carry it out are not trivial and developers probably do not know to implement it without the help of a book or a tutorial. Having those algorithms already program in the physic engine, the most natural thing is to feel tempted to use them, but it is usually a misconception.

Normally, use the physic engine to implement logic mechanics is a bad idea, unless the mechanics of the game are based on physics. For example, the Trine series. But if this is not the case, it is better to have more control over the game logic and not depend on the physic engine. The physic engine can produce many problems in logic due to errors in precision and generate undesirable collateral effects. Quite the opposite of what one expects from the game logic, to be predictable.

Following this premise, the implementation of the rope physic within the game can take two paths: The initially easy but difficult to adjust or the more complicated initially but that produces greater reliability in the long term.

The easy path is to use the Hinge Joint component of the physic engine (Physx in the case of Unity). This is the tool that is normally used to model ropes, chains, cloths or any other mechanism in which there are fixed pieces that depend on to move their interactions between them. So it is ideal for modeling pendulums, ropes, chains, suspension bridges, etc.

Hinge Joint in Unity

But when such objects are modeled in the game, they are usually part of the elements with which the character interacts. So normally their interactions depend only on a simple interaction of pushing an object, dragging, swinging, etc. For such a simple interaction, joints are the most appropriate programming model.

But, what happens when the interaction with the rope is part of the game logic?

Again, the first idea that comes up in the developer is to use a hinge joint. But what is the problem is we use them…? If we try to configure a hinge joint to model the chains that join two kinematic objects that can move freely around the world, jumping, dying, respawning, etc soon we will see the answer. The hinge joints are starting to have erratic behaviors, entering a state of permanent movement or undesirable collateral effects. These happen mainly because of problems with restrictions that are not well-defined, well-weighted or because of the rounding error in the calculations that generate unexpected behaviors. Giving the key to the hinge joint configuration to behaves correctly is no easy task due to the number of parameters involved in its configuration and due unpredictable behavior of the players when playing.

Hinge Joint en Unity

What is the best alternative in these cases?

Yeah, you guessed it! program the physics of the rope itself manually. The Hinge Joint is a very versatile physical component that simulates a rope as well as a suspension bridge or a cloth. Use all the physic interaction of the engine and can model a very realistic behavior that surely you will not need for your game logic.

Therefore, the idea is not to re-implement the joint (that is what we should use in those cases). The idea is to program a simplification of the rope physic that is realistic enough for what your game looking for. To achieve this, it is necessary to reduce the complexity of the calculations to have more control over them. The goal is to handle fewer parameters so you can adjust them better and reduce the chances of a rope malfunction. That is to say, to make it much more predictable and easier to configure.

And this is where the integration of Verlet comes into play.

The Verlet Integration

Verlet integration is a numerical integration method whose main feature is that it conserves the energy and therefore produces less error than classical Euler integration.

Verlet integration

We are not going to explain its mathematical goodness, but we are going to define in concrete what is the behavior of this method when we want to calculate the movement in the space. We can define the equation of motion with the Verlet’s integral as follows: X(t) is the position of the object at the time t. Δt is the time increment produced by the next game update and v(t) is the velocity of the object at the time t. Therefore, the position in the next moment (t+Δt) will be the previous position multiplied by the increment of time and by the velocity that the object carried at the previous moment.

x(t+Δt) = x(t) + Δt·v(t)

Let’s see in code how this is programmed:

We create a set of rope sections called RopeSection. We will make a list of them. To integrate the movement, we iterate the list and apply the calculation described before. In RopeSection we have the current position of the section and the previous position.

With both positions, we calculate the velocity that is the difference between both positions. This velocity and gravity are added to the position and finally, we update the old position with the new one before changing the position.


void VerletIntegration()
{
    for (int i = 1; i < _ropeSections.Count; i++)
    {
        RopeSection currentRopeSection = _ropeSections[i];
        Vector3 velocity = currentRopeSection.pos - currentRopeSection.oldPos;
        //updating the old position
        currentRopeSection.oldPos = currentRopeSection.pos;
        //we add the velocity at the current position
        currentRopeSection.pos += velocity;
        //adding the gravity
        currentRopeSection.pos += gravity * Time.deltaTime;
    }
}

As we can see in the previous example, in each iteration we are calculating the subtraction between the previous position and the new position. When there is a movement in one of the ends of the rope, the movement is propagated by all the segments (thanks to the length restriction of the rope segments). When applied iteratively, the intensity of the movement will be reduced until stops. but will take several cycles to reach its stationary state. This is because we do not copy the position when applying velocity o gravity, but before applying it. Thanks to this, the position in the next iteration will be different from the stored oldPos and will continue to be updated, until the velocity increases are minimal.

But only this method no guarantee the propagation of the movement between the different segments. Nor does it guarantee that the sections of the rope will maintain their integrity. The segments must always be of the size defined in the rope configuration. To do this, we check that the rope lengths are preserved and if this is not, we modify the positions of the segments to adapt them to the size of the segments.

A section of the rope may be compressed o stretched. Depending on whether it is compressed (the ends of the segments are closer than it should be) or stretched (the ends of the segments are farther than it should be) we take a strategy. The idea is that if they are stretched, we compress the rope to fit the size and if they are compressed, we stretch the rope. Let’s see an example:


void RopeStretchMax()
{
    for (int i = 1; i < _ropeSections.Count-1; i++)
    {
            RopeSection top = _ropeSections[i];
            RopeSection bottom = _ropeSections[i + 1];
            //calculating the distance
            float distance = Vector3.Distance(top.pos - bottom.pos);
            //calculating the distance error, comparing to the length of the segment
            float distError = Mathf.Abs(distance - ropeSectionLength);
            Vector3 changeDir = Vector3.zero;
            if (distance > ropeSectionLength) //rope fragment stretched => we compressed it
                changeDir = top.pos - bottom.pos; //positive direction is up
            else  if (distance > ropeSectionLength) //rope fragment compressed => we stretch it
                changeDir = bottom.pos - top.pos; //positive direction is down
            if(changeDir != Vector3.zero)
            {
                //The distance correction is apportioned between the wo segments at 50%
                bottom.pos += changeDir.normalized * distError * 0.5f; 
                top.pos -= changeDir.normalized * distError * 0.5f;
            }
    }
}

This process is carried out a series of times so that the behavior of the rope does not have problems of precision in the calculations and produces instability. We can calculate the RopeStretchMax between 10 and 20 times per update to get good results. We can also add an elasticity factor to the distance to give some more elasticity to the rope and simulate a rubber.

Now we need to explain how we would initialize the values of the rope so that it can follow a moving object. The first element of the rope takes the position of the object to which it is attached. And the object dragging from the rope takes the position of the last segment of the rope, which will have a value determined by Verlet’s integral and by the implementation of RopeStretchMax. In our case **_transforToConectTheRope** is the object to which we anchor the rope and **_transforHangingFromTheRope** is the object that drags or hangs from the rope. VerletIntegrationInitStep would be executed before the integration and VerletIntegrationFinalStep after the integration and execution of RopeStretchMax.


void VerletIntegrationInitStep()
{
    RopeSection firstRopeSection = _ropeSections[0];

    firstRopeSection.pos = _transforToConectTheRope.position;
}

void VerletIntegrationFinalStep()
{
    //Colocamos la cuerda en la posición del último
    _transforHangingFromTheRope.position = _ropeSections[_ropeSections.Count - 1].pos;
}

//el update quedaría así:
void Update()
{
    VerletIntegrationInitStep();
    VerletIntegration();
    for(int i = 0; i < numStepsRunRopeStretchMax; i++)
    {
        RopeStretchMax();
    }
    VerletIntegrationFinalStep();
}

With this we get the following effect:

With this implementation, we can control the behavior of a rope, but it is not very different from the behavior that we can achieve with Unity Hinge Joint, but, much easier to configure. But where we have a problem with Unity physics is when, as with Fulvinter, both ends of the rope are mobile. If you remember, in the first approximation, we only update the position of the anchor, and we place the object that is dragged in the last position of the rope.

To get the two ends of the rope to influence its behavior, we will have to make some modifications to the initial algorithm:

  • The first is not to teleport the object that hangs at the end of the rope, as we must allow it to move freely.
  • Now, its position is determined by its logic and not by the rest of the rope.
  • So we eliminate VerletIntegrationFinalStep
  • Instead, we update the last position of the rope with the position of the object that until now was the hanging object and that will have changed position according to its logic.

Let’s see how it would look:


void VerletIntegrationInitStep()
{
    RopeSection firstRopeSection = _ropeSections[0];

    firstRopeSection.pos = _transforToConectTheRope.position;

    RopeSection lastRopeSection = _ropeSections[_ropeSections.Count-1];

    lastRopeSection.pos = _transformT_transforHangingFromTheRopeoConectTheRope.position;
}

This is what makes the Hinge Joint get out of control and it’s completely unpredictable what can happen when you have two extremes of physics controlled by two different players.

Once we have updated the first and final position, the rest of the positions are determined by Verlet’s integral and with the own maximum distances of the segments that compose the rope. With this small change, the behavior of the object is as follows:

To complete the behavior, we must apply collision physics to the objects. In the case of the rope that join the two protagonists, and that we will see in action in the next video, besides in each section there must be a physical verification of the collisions. It can be done with Unity’s physics overlapping methods. Our implementation is different and depends on the logic of our game to be more efficient. We can see the final result in this video.

Additional note: we have put collision to the object hanging in the initial version of the rope to be able to interact with it. The collision consists of the detection of the collision with the event OnControllerColliderHit in the player, and we simulate the physics applying an impulse to the last section of the rope ^_^. Remember that we don’t have physics or Rigidbody anywhere. Everything is being simulated by us, therefore, we must also simulate the physical impulse.

void OnControllerColliderHit(ControllerColliderHit hit)
{
    CollisionWithPlayer collisionWithPlayer = hit.gameObject.GetComponent<CollisionWithPlayer>();
    if (collisionWithPlayer != null)
        collisionWithPlayer.ApplyForce(hit.moveDirection);
}

public void ApplyForce(Vector3 force)
{
    RopeSection ropSelection = _ropeSections[_ropeSections.Count - 1];
    ropSelection.pos = ropSelection.pos + force;
}

And this is it. That’s how we programmed the physical behavior of the ropes in the game. Not only the one that unites the characters but also others that you will find in the game. I hope it serves as a guide if you want to do something similar in your game.