Search Unity

Create a 2D rope without hinge joints breaking!

Discussion in 'Physics' started by DroidifyDevs, Jun 22, 2017.

  1. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Hello!

    So I'm trying to make a simply 2D rope grappling game to show to someone, and I'm creating a rope with a script. I'm spawning objects between 2 points and hooking their anchors together; all that is done by 1 script.

    However, if the rope is too long, the hinges break and cause the physics engine to glitch out.
    Here's a video:


    And here's the script:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Threading;
    5. using UnityEngine;
    6. using UnityEngine.SceneManagement;
    7. using UnityEditor;
    8.  
    9. public class RopeMaker : MonoBehaviour {
    10.     public Transform Player;
    11.     public GameObject RopeLinkPrefab;
    12.     public Transform RopeParent;
    13.     public List<GameObject> RopeLinks;
    14.     public float RopeLinkLength;
    15.     public Vector3 StartPoint;
    16.     public Vector3 EndPoint;
    17.     public float DistanceBetweenPoints;
    18.     public float AngleBetweenPoints;
    19.     public Vector3 Offset;
    20.     public int AmountOfLinksNeeded;
    21.     Ray ray;
    22.     RaycastHit hit;
    23.  
    24.     void GenerateRope()
    25.     {
    26.         DistanceBetweenPoints = Vector3.Distance(StartPoint, EndPoint);
    27.         GetAngle();
    28.         //AngleBetweenPoints = Vector3.Angle(StartPoint, EndPoint);
    29.         AmountOfLinksNeeded = Mathf.RoundToInt(DistanceBetweenPoints / RopeLinkLength);
    30.         //FIX! Offset MUST take into account angle
    31.         //Offset = new Vector3((DistanceBetweenPoints / AmountOfLinksNeeded) * Mathf.Cos(AngleBetweenPoints), ((DistanceBetweenPoints / AmountOfLinksNeeded) * Mathf.Sin(AngleBetweenPoints)), 0);
    32.         if(RopeLinks.Count <=0)
    33.         {
    34.             var ropelink = Instantiate(RopeLinkPrefab, StartPoint, Quaternion.Euler(0, 0, AngleBetweenPoints - 90), RopeParent);
    35.             ropelink.GetComponent<SpriteRenderer>().color = Color.green;
    36.             ropelink.transform.name = "Link0";
    37.             Player.GetComponent<HingeJoint2D>().enabled = true;
    38.             Player.GetComponent<HingeJoint2D>().connectedBody = ropelink.GetComponent<Rigidbody2D>();
    39.             RopeLinks.Add(ropelink);
    40.         }
    41.         for(int i = 1; i < AmountOfLinksNeeded; i++)
    42.         {
    43.             //Offset = new Vector3(RopeLinks.Last().transform.position.x + (RopeLinkLength * 2) * Mathf.Cos(RopeLinks.Last().transform.rotation.eulerAngles.z),
    44.             //RopeLinks.Last().transform.position.y + (RopeLinkLength* 2) * Mathf.Sin(RopeLinks.Last().transform.rotation.eulerAngles.z), 0);
    45.             var ropelink = Instantiate(RopeLinkPrefab, RopeLinks.Last().transform.position + (RopeLinks.Last().transform.up * (RopeLinkLength)), Quaternion.Euler(0, 0, AngleBetweenPoints - 90), RopeParent);
    46.             Debug.Log("Amount of rope links: " + RopeLinks.Count);
    47.             if (i != (AmountOfLinksNeeded - 1))
    48.                 RopeLinks[RopeLinks.Count - 1].GetComponent<HingeJoint2D>().connectedBody = ropelink.GetComponent<Rigidbody2D>();
    49.             else
    50.             {
    51.                 ropelink.GetComponent<Rigidbody2D>().isKinematic = true;
    52.                 RopeLinks[RopeLinks.Count - 1].GetComponent<HingeJoint2D>().connectedBody = ropelink.GetComponent<Rigidbody2D>();
    53.                 ropelink.GetComponent<SpriteRenderer>().color = Color.red;
    54.             }
    55.             ropelink.transform.name = "Link" + i;
    56.             RopeLinks.Add(ropelink);          
    57.         }
    58.     }
    59.  
    60.     //gets angle between start and end clicks so we can spawn rope links in correct direction
    61.     void GetAngle()
    62.     {
    63.         //1st and 4th quadrants
    64.         if (StartPoint.x < EndPoint.x)
    65.         {
    66.             if (StartPoint.y < EndPoint.y)
    67.             {
    68.                 //ASin(Y)/Distance - to radians
    69.                 AngleBetweenPoints = (Mathf.Asin((EndPoint.y - StartPoint.y) / DistanceBetweenPoints));
    70.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    71.                 //to degrees
    72.                 AngleBetweenPoints = (AngleBetweenPoints * 180) / Mathf.PI;
    73.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    74.                 Debug.Log("1st quadrant");
    75.             }
    76.             if (StartPoint.y > EndPoint.y)
    77.             {
    78.                 //ASin(Y)/Distance - to radians, this gives small angle down from right X axis
    79.                 AngleBetweenPoints = (Mathf.Asin((StartPoint.y - EndPoint.y) / DistanceBetweenPoints));
    80.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    81.                 //to degrees
    82.                 AngleBetweenPoints = 360 - (AngleBetweenPoints * 180) / Mathf.PI;
    83.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    84.                 Debug.Log("4th quadrant");
    85.             }
    86.         }
    87.         //2nd and 3rd quadrants
    88.         if (StartPoint.x > EndPoint.x)
    89.         {
    90.             if (StartPoint.y > EndPoint.y)
    91.             {
    92.                 //ASin(Y)/Distance - to radians
    93.                 AngleBetweenPoints = (Mathf.Asin((StartPoint.y - EndPoint.y) / DistanceBetweenPoints));
    94.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    95.                 //to degrees
    96.                 AngleBetweenPoints = 180 + ((AngleBetweenPoints * 180) / Mathf.PI);
    97.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    98.                 Debug.Log("3rd quadrant");
    99.             }
    100.             if (StartPoint.y < EndPoint.y)
    101.             {
    102.                 //ASin(Y)/Distance - to radians, this gives small angle down from right X axis
    103.                 AngleBetweenPoints = (Mathf.Asin((EndPoint.y - StartPoint.y) / DistanceBetweenPoints));
    104.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    105.                 //to degrees
    106.                 AngleBetweenPoints = 180 - (AngleBetweenPoints * 180) / Mathf.PI;
    107.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    108.                 Debug.Log("2nd quadrant");
    109.             }
    110.         }
    111.     }
    112.    
    113.     // Update is called once per frame
    114.     void Update ()
    115.     {
    116.         if (Input.GetMouseButtonDown(0))
    117.         {
    118.             ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    119.             if (Physics.Raycast(ray, out hit))
    120.             {
    121.                 RopeLinks.Clear();
    122.                 StartPoint = new Vector2(Player.transform.position.x + Player.GetComponent<HingeJoint2D>().anchor.x,
    123.                     Player.transform.position.y + Player.GetComponent<HingeJoint2D>().anchor.y);
    124.                 EndPoint = new Vector3(hit.point.x, hit.point.y, 0);
    125.             }
    126.             GenerateRope();
    127.             Debug.Log("<color=green>Generating rope between</color> " + StartPoint + " & " + EndPoint);
    128.             Debug.Log("<color=green>Rope start point: </color>" + StartPoint);
    129.             Debug.Log("<color=red>Rope end point: </color>" + EndPoint);
    130.         }
    131.     }
    132. }
    133.  
    Is using 2D Hinge Joints the best way to make a 2D rope? If not, what can I do?

    Thank you!
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,455
    You can use HingeJoint2D to connect each rope segment but to keep things stable you should connect the start and end parts of the rope/chain with a DistanceJoint2D with its MaxDistanceOnly property set to true.

    You can see an example of this here, specifically this scene which simulates a chain and shows the difference in stablility.
     
  3. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Thanks for the link! That's a very nice collection of 2D examples that should be more easily visible on Unity's website!!

    I did as you said and now I'm adding a distance joint between the 1st and last links in the chain (that's also how you did it in your scene).

    This is my result:


    As you see, when spawning the rope, the 2D box colliders on each of the rope's "links" create an initial bounce which the distance joint contains by curving the rope. Without the distance joint, the rope stretches.

    Here's the slightly modified script:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Threading;
    5. using UnityEngine;
    6. using UnityEngine.SceneManagement;
    7. using UnityEditor;
    8. public class RopeMaker : MonoBehaviour {
    9.     public Transform Player;
    10.     public GameObject RopeLinkPrefab;
    11.     public Transform RopeParent;
    12.     public List<GameObject> RopeLinks;
    13.     public float RopeLinkLength;
    14.     public Vector3 StartPoint;
    15.     public Vector3 EndPoint;
    16.     public float DistanceBetweenPoints;
    17.     public float AngleBetweenPoints;
    18.     public Vector3 Offset;
    19.     public int AmountOfLinksNeeded;
    20.     Ray ray;
    21.     RaycastHit hit;
    22.     //generates the rope
    23.     void GenerateRope()
    24.     {
    25.         DistanceBetweenPoints = Vector3.Distance(StartPoint, EndPoint);
    26.         GetAngle();
    27.         AmountOfLinksNeeded = Mathf.RoundToInt(DistanceBetweenPoints / RopeLinkLength);
    28.         //this happens for 1st rope link (green one)
    29.         if(RopeLinks.Count <=0)
    30.         {
    31.             var ropelink = Instantiate(RopeLinkPrefab, StartPoint, Quaternion.Euler(0, 0, AngleBetweenPoints - 90), RopeParent);
    32.             ropelink.GetComponent<SpriteRenderer>().color = Color.green;
    33.             ropelink.transform.name = "Link0";
    34.             ropelink.GetComponent<DistanceJoint2D>().enabled = true;
    35.             Player.GetComponent<HingeJoint2D>().enabled = true;
    36.             Player.GetComponent<HingeJoint2D>().connectedBody = ropelink.GetComponent<Rigidbody2D>();
    37.             RopeLinks.Add(ropelink);
    38.         }
    39.         for(int i = 1; i < AmountOfLinksNeeded; i++)
    40.         {
    41.             var ropelink = Instantiate(RopeLinkPrefab, RopeLinks.Last().transform.position + (RopeLinks.Last().transform.up * (RopeLinkLength)), Quaternion.Euler(0, 0, AngleBetweenPoints - 90), RopeParent);
    42.             Debug.Log("Amount of rope links: " + RopeLinks.Count);
    43.             if (i != (AmountOfLinksNeeded - 1))
    44.                 RopeLinks[RopeLinks.Count - 1].GetComponent<HingeJoint2D>().connectedBody = ropelink.GetComponent<Rigidbody2D>();
    45.             else
    46.             {
    47.                 //this happens for last rope link (red one)
    48.                 ropelink.GetComponent<Rigidbody2D>().isKinematic = true;
    49.                 RopeLinks[RopeLinks.Count - 1].GetComponent<HingeJoint2D>().connectedBody = ropelink.GetComponent<Rigidbody2D>();
    50.                 ropelink.GetComponent<SpriteRenderer>().color = Color.red;
    51.                 RopeLinks[0].GetComponent<DistanceJoint2D>().connectedBody = ropelink.GetComponent<Rigidbody2D>();
    52.                 Debug.Break();
    53.             }
    54.             ropelink.transform.name = "Link" + i;
    55.             RopeLinks.Add(ropelink);      
    56.         }
    57.     }
    58.     //gets angle between start and end clicks so we can spawn rope links in correct direction
    59.     void GetAngle()
    60.     {
    61.         //1st and 4th quadrants
    62.         if (StartPoint.x < EndPoint.x)
    63.         {
    64.             if (StartPoint.y < EndPoint.y)
    65.             {
    66.                 //ASin(Y)/Distance - to radians
    67.                 AngleBetweenPoints = (Mathf.Asin((EndPoint.y - StartPoint.y) / DistanceBetweenPoints));
    68.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    69.                 //to degrees
    70.                 AngleBetweenPoints = (AngleBetweenPoints * 180) / Mathf.PI;
    71.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    72.                 Debug.Log("1st quadrant");
    73.             }
    74.             if (StartPoint.y > EndPoint.y)
    75.             {
    76.                 //ASin(Y)/Distance - to radians, this gives small angle down from right X axis
    77.                 AngleBetweenPoints = (Mathf.Asin((StartPoint.y - EndPoint.y) / DistanceBetweenPoints));
    78.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    79.                 //to degrees
    80.                 AngleBetweenPoints = 360 - (AngleBetweenPoints * 180) / Mathf.PI;
    81.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    82.                 Debug.Log("4th quadrant");
    83.             }
    84.         }
    85.         //2nd and 3rd quadrants
    86.         if (StartPoint.x > EndPoint.x)
    87.         {
    88.             if (StartPoint.y > EndPoint.y)
    89.             {
    90.                 //ASin(Y)/Distance - to radians
    91.                 AngleBetweenPoints = (Mathf.Asin((StartPoint.y - EndPoint.y) / DistanceBetweenPoints));
    92.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    93.                 //to degrees
    94.                 AngleBetweenPoints = 180 + ((AngleBetweenPoints * 180) / Mathf.PI);
    95.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    96.                 Debug.Log("3rd quadrant");
    97.             }
    98.             if (StartPoint.y < EndPoint.y)
    99.             {
    100.                 //ASin(Y)/Distance - to radians, this gives small angle down from right X axis
    101.                 AngleBetweenPoints = (Mathf.Asin((EndPoint.y - StartPoint.y) / DistanceBetweenPoints));
    102.                 Debug.Log("Angle in radians: " + AngleBetweenPoints);
    103.                 //to degrees
    104.                 AngleBetweenPoints = 180 - (AngleBetweenPoints * 180) / Mathf.PI;
    105.                 Debug.Log("Angle in degrees: " + AngleBetweenPoints);
    106.                 Debug.Log("2nd quadrant");
    107.             }
    108.         }
    109.     }
    110.  
    111.  // Update is called once per frame
    112.  void Update ()
    113.     {
    114.         if (Input.GetMouseButtonDown(0))
    115.         {
    116.             ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    117.             if (Physics.Raycast(ray, out hit))
    118.             {
    119.                 RopeLinks.Clear();
    120.                 StartPoint = new Vector2(Player.transform.position.x + Player.GetComponent<HingeJoint2D>().anchor.x,
    121.                     Player.transform.position.y + Player.GetComponent<HingeJoint2D>().anchor.y);
    122.                 EndPoint = new Vector3(hit.point.x, hit.point.y, 0);
    123.             }
    124.             GenerateRope();
    125.             Debug.Log("<color=green>Generating rope between</color> " + StartPoint + " & " + EndPoint);
    126.             Debug.Log("<color=green>Rope start point: </color>" + StartPoint);
    127.             Debug.Log("<color=red>Rope end point: </color>" + EndPoint);
    128.         }
    129.  }
    130. }
    131.  
    Now here's a 2nd video, this time without the 2D box colliders on each rope link:


    The 1st play is with distance joint. It prevents stretch but curves the rope. I'm not moving the ball with my controls.

    The 2nd play is without the distance joint. Because there are no colliders, the rope doesn't break, but it stretches. Since there's no distance joint, it doesn't curve either.

    I need the colliders though to make the rope bend properly, as in your example :(

    Do you want me to share a link to the project?

    I've spent almost all my time in 3D, so I wanted to get my 2D skills up to shape with this project but I'm stuck on generating a rope :(

    Thank you again for your help!
     
    Last edited: Jun 23, 2017
  4. layinka

    layinka

    Joined:
    Jun 26, 2013
    Posts:
    6
    Hi DroidifyDevs,
    Please Can you help with how the prefabs and the joints are setup?

    I am trying to replicate a rope exactly like this in a game i am working on, but the joints are sacttering a lot, once they are moved.

    Or if you can help with a sample scene like the ones Melvmay sent
     
  5. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    I didn't find a solution for my use case (maybe if I spent more time on it I would have). So I dumped that project indefinitely.