0
\$\begingroup\$

What I need assistance with:

  • I need to create a rope connecting two GameObjects together at runtime through code.

  • One end will be connected to Transform _pointA and the other will be connected to Transform _pointB.

  • I've divided the rope up into segments of GameObjects that have a Hinge joint 2D & a Rigidbody 2D.

  • Each segment uses the prior segment'sRigidbody 2D as the Connected Rigid Body.

  • Then I set positions on the LineRenderer to match the segment positions so that I can later use rope material to make it looks nicer.


Issue:

  • When the rope is initially created, it immediately falls straight down creating a vertical rope rather than a horizontal rope, even though the final rope segment is positioned at _pointB with the correct parent's Rigidbody connected.
  • After a few moments, the rope falls apart without moving any positions manually.
  • Additionally, if the rope is manually moved, it immediately falls apart.
  • To demonstrate the issue, I added a circle sprite to each segment GameObject in the rope.

enter image description hereenter image description here

  • Which causes the line to look like this:

enter image description here


Here's an example of how I would like it to look when moving the two objects further/closer:

enter image description here


The Code I'm Using:

public class Rope: MonoBehaviour
{

    [SerializeField] private LineRenderer _line;
    [SerializeField, Range(2, 50)] int _segmentCount = 2;

    [SerializeField] private Transform _pointA;
    [SerializeField] private Transform _pointB;

    [SerializeField] HingeJoint2D _hingeJoint;

    public Transform[] segments;
    private void Start()
    {
        GenerateRope();
    }
    // Update is called once per frame
    void Update()
    {
        SetEndOfRopePositions();
        SetLinePoints();
    }

    private void SetEndOfRopePositions()
    {
        segments[0].position = _pointA.position;
        segments[segments.Length - 1].position = _pointB.position;
    }

    private void SetLinePoints()
    {
        _line.positionCount = segments.Length;

        for (int i = 0; i < segments.Length; i++)
        {
            _line.SetPosition(i, segments[i].position);
        }
    }
    private Vector2 GetSegmentPosition(int segmentIndex)
    {
        Vector2 posA = _pointA.position;
        Vector2 posB = _pointB.position;

        float fraction = 1f / (float)_segmentCount;
        return Vector2.Lerp(posA, posB, fraction * segmentIndex);
    }
    void GenerateRope()
    {
        segments = new Transform[_segmentCount];

        for (int i = 0; i < _segmentCount; i++)
        {

            var currJoint = Instantiate(_hingeJoint, GetSegmentPosition(i), Quaternion.identity, this.transform);
            segments[i] = currJoint.transform;

            if (i > 0) // Not first hinge
            {
                int prevIndex = i - 1;
                currJoint.connectedBody = segments[prevIndex].GetComponent<Rigidbody2D>();
            }
        }
    }

}

Inspector for Rope + Segment GameObjects before running the game: enter image description here


Additional info: If it helps, I just need the rope to droop and straighten like an actual rope when the ends move closer and farther from each other. I don't need any other physics from the objects roped together as I would like to do all of the movement manually through code.

\$\endgroup\$
8
  • \$\begingroup\$ Which part do you need the help with? Your first image looks like a fixed animation for full rope might be the easiest. Your second can be done with a raycast and to stop the animation short. Your last one is letting taking the physic engine taking over with any of the rope/ chain solutions. Your bonus note is just interaction of rigidbodies, if your game allows moveable objects by simply pushing into them, the rope should be able to move them automatically. \$\endgroup\$
    – Zibelas
    Commented Jun 21 at 6:53
  • \$\begingroup\$ Thanks for the reply, @Zibelas! Well, if you could break down the last point it would be greatly appreciated. When the projectile hits a target, I'd want to replace the traveling animated projectile with a rope sprite connecting the hero to the object. How can I do that through code? \$\endgroup\$ Commented Jun 21 at 17:57
  • \$\begingroup\$ You seem to have everything you need to make this happen. It sounds to me like you asked this question just to get someone to hand you the complete solution on a silver platter. Get in there, try a couple of things. \$\endgroup\$ Commented Jun 22 at 12:56
  • \$\begingroup\$ Oh, goodness! That wasn't my intention at all. I apologize if it came across that way. I always try and struggle on my own for a good amount of time before consulting the Stack Exchange gods. With that being said- I really don't have what I need though. I don't need someone to gift wrap me the complete implementation, but I really would love to see an example of someone creating a rope that connects two objects through code. It's really how I learn. \$\endgroup\$ Commented Jun 22 at 19:54
  • \$\begingroup\$ Well, I was able to connect the two objects with a line renderer using: "void Update() { _line.SetPosition(0, _firstObject.transform.position); _line.SetPosition(1, _target.transform.position); }" But I don't really know if I should be doing it this way because I'm still pretty lost on turning the stiff line into a rope 😓 \$\endgroup\$ Commented Jun 22 at 20:55

1 Answer 1

3
\$\begingroup\$

The reason for this is that you force the positions of the two ends of the rope to be set in each update frame. The rigid bodies at both ends of the rope are of type dynamic. Simply put, the physics engine does not know that you will fix the positions of these two rigid bodies after the simulation ends.

As shown in the gif below, the physics engine expects the last node on the right to fall down in accordance with the laws of physics. But its position is forcibly set in each frame, causing this expectation to fail:

enter image description here

Why doesn't the left endpoint have this problem? See Joint2D-connectedBody:

If this property is set to null then the joint attaches to a fixed point in space rather than another Rigidbody2D.

If your character object happens to have a rigid body, things are pretty simple. You just add the character to your rope rigid body chain and let the physics simulation take over. For convenience, we directly treat the left and right endpoints of the rope as character objects. We no longer set their positions, but change their physical variables so that they are taken over by the physical simulation:

private void SetEndOfRopePositions()
{
    // segments[0].position = _pointA.position;
    // segments[segments.Length - 1].position = _pointB.position;
    segments[0].GetComponent<Rigidbody2D>().velocity = new Vector2(-1,2);
    segments[segments.Length - 1].GetComponent<Rigidbody2D>().velocity = new Vector2(1,2);
}

void GenerateRope()
{
    segments = new Transform[_segmentCount];

    for (int i = 0; i < _segmentCount; i++)
    {
        ...
        if (i > 0) // Not first hinge
        {
            ...
        }
        else
        {
            Destroy(currJoint);//remove joint component of first hinge
        }
    }
}

enter image description here

If setting the position directly is a mandatory requirement, for example you don't want the rope to be directly associated with the character object because it is just a VFX that is not relevant to the gameplay, or your character control system does not use rigid bodies, then you need to use other methods to solve this problem.

First, set the physical type of the rope endpoint rigid body to RigidbodyType2D.Kinematic, so that the physics system will know that these two rigid bodies are immovable in the physical simulation.

void GenerateRope()
{
    ...
    for (int i = 0; i < _segmentCount; i++)
    {
        ...
        currJoint.GetComponent<Rigidbody2D>().bodyType =
            i == 0 || i == _segmentCount - 1 ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
    }
}

enter image description here

This works, but there is another problem to solve. Since the rope nodes are made of HingeJoint2D, their function is to rotate around the target rigidbody. If the position of the rigidbody is changed outside of the physics system, they will only respect the angle between the rigidbody and themselves, not the distance between the rigidbodies. This will cause the length of the rope to be uncontrollable during the character movement:

enter image description here

So I recommend that rope nodes use joint components that respect distances, such as DistanceJoint2D and SpringJoint2D.

public class Rope : MonoBehaviour
{
    ...
    [SerializeField] DistanceJoint2D _hingeJoint;
    ...

    void GenerateRope()
    {
        ...
        var ropeLength = Vector2.Distance(_pointA.position, _pointB.position) * 1.2f;
        var ropeDistance = ropeLength / _segmentCount;

        for (int i = 0; i < _segmentCount; i++)
        {
            ...
            currJoint.autoConfigureDistance = false;
            currJoint.maxDistanceOnly = true;
            currJoint.distance = ropeDistance;
        }
    }
}

Here we replace the HingeJoint2D in the prefab with a DistanceJoint2D and set a fixed distance so that even if the positions of the two ends of the rope are modified outside the physics system, the rope will still respect their positions:

enter image description here

Edit 1: How to use ropes to limit the distance of a character object?

The premise of using this method is that the rope is treated as a vfx that is not relevant to the gameplay, so I recommend handling distance restrictions in the gameplay, rather than in the rope logic. For example, when the rope relationship is determined (character A is connected to character B with a rope), then you can limit their distance in the character movement code to not exceed the rope length:

void UpdateCharacterMove(){
    ...
    transform.position += velocity;

    if (ropeTarget != null){
        var sub = ropeTarget.transform.position - transform.position;
        if (sub.magnitude > ropeLength ){
            ropeTarget.transform.position = transform.position + sub.normalized * ropeLength;
        }
    }
}

Of course you can also do this in the rope code, the main way is to limit the length of the vector from A to B by the rope length:

if ( Vector2.Distance(_pointA.position, _pointB.position) > ropeLength ){
    _pointB.position = _pointA.position + (_pointB.position-_pointA.position).normalized * ropeLength;
}

Edit 2: How to tighten the rope quickly when the character moves at high speed?

After the distance between the joints exceeds the maximum value, DistanceJoint2D shrinks very slowly because it does not adjust the shrinking speed according to the distance:

enter image description here

In this case, I prefer SpringJoint2D:

enter image description here

\$\endgroup\$
8
  • \$\begingroup\$ Oh, wow! @Mangata, I can't thank you enough for the assistance!😊 I am currently using the last distance joint method, and it's working great. But I'd like the rope to stay the same length so that it doesn't get longer. If the left endpoint moved farther than the rope length, I want to manually move the right end point as well. This way the rope stays the same length. Something like this: if ( Vector2.Distance(_pointA.position, _pointB.position) > ropeLength ) // Move point B as well i.ibb.co/7C2NfkQ/image.png \$\endgroup\$ Commented Jun 27 at 16:05
  • 1
    \$\begingroup\$ @PayasoPrince Added an edit to show how to do this. \$\endgroup\$
    – Mangata
    Commented Jun 27 at 18:50
  • \$\begingroup\$ Thank you so much! When I move my character in small amounts, this works as expected. However, when I move at a normal speed, the nodes fall behind: youtube.com/watch?v=3UfblKC491o \$\endgroup\$ Commented Jun 27 at 20:37
  • 1
    \$\begingroup\$ @PayasoPrince You can get the reactionForce of each joint and apply an extra force to make them contract faster. Or you can use SpringJoint2D and set thier frequency to achieve a similar effect. \$\endgroup\$
    – Mangata
    Commented Jun 27 at 21:08
  • 1
    \$\begingroup\$ @PayasoPrince Yes, you need to read it and apply an extra force in its direction, rather than modifying it directly. For example, currJoint.GetComponent<Rigidbody2D>().AddForce(currJoint.reactionForce * 5f); \$\endgroup\$
    – Mangata
    Commented Jun 28 at 1:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .