Rope - a - Dope

On occasion I give back to the community some. In looking for something… I can’t remember what right now, I thought… oh, a rope would do the exact same thing. So I went looking up ropes in the wiki and found the rope that was done a while back… It wasn’t very good. :frowning: I even saw where someone created a swinging tether game and the physics on the rope were so bad that the player jerked around in mid air for no reason. I went off looking for a better solution.

The old rope used CharacterJoint’s. Where I thought we could use some improvement was to use springs instead. I searched some on Google and ran across many different ways to look at rope. Some were processor heavy, some were easy and very doable in game physics. I settled on a simple implementation of a spring rope and hacked up the code to make it work. I found out that the concept was really just a bunch of balls sprung together. So instead of using the Unity spring, I used the coded spring.

So here is my demonstration. (not complete, really just working with the balls and seeing how the physics work on it.
http://lod3dx.net/Unity/Rope/index.html

left mouse button moves the start point
right mouse button moves the end point
Shift left, unhooks the start point (or hooks it back)
Shift right, unhooks the end point (or hooks it back)
R reloads… just in case you need it.

And here is the free code that makes the actual rope work:

//RopeSimulator.cs
using UnityEngine;
using System.Collections;


public class RopeSimulator : MonoBehaviour {
	public Transform  endObject;
	public float  radius=0.1f;
	public float  mass=10.0f;
	public bool lockStart=true;
	public bool lockEnd=false;
	public float drag=1.0f;
	
	private float resoloution=1.8f;
	private float springConstant=100.0f;
	private float springFrictionConstant=0.5f;
	private RopeSpring[] springs;
	private GameObject[] knots;
	private int numKnots=0;
	
	void Start () {
		if(! endObject) return;
		Destroy(collider);
		if(gameObject.GetComponent<Renderer>())gameObject.GetComponent<Renderer>().enabled=false;
		Destroy(endObject.collider);
		if(endObject.gameObject.GetComponent<Renderer>())endObject.gameObject.GetComponent<Renderer>().enabled=false;
		numKnots=(int)(Vector3.Distance(transform.position, endObject.position) * resoloution);
		Debug.Log(endObject.position);
		float springLength=Vector3.Distance(transform.position, endObject.position)  / (float)(numKnots-1);
		float knotMass=mass/(float)numKnots;
		
		knots=new GameObject[numKnots];
		springs=new RopeSpring[numKnots-1];
		
		for (int i = 0; i < numKnots; i++){
			GameObject knot = GameObject.CreatePrimitive(PrimitiveType.Sphere);
			knot.name="Knot-" + i + "-" + gameObject.name;
			//knot.transform.parent=transform;
			knot.transform.localScale=Vector3.one * radius * 2;
			knot.AddComponent<Rigidbody>();
			knot.rigidbody.mass=knotMass;
			if(i==0  lockStart)knot.rigidbody.isKinematic=true;
			if(i==numKnots-1  lockEnd) knot.rigidbody.isKinematic=true;
			knot.transform.position=Vector3.Lerp(transform.position, endObject.position, (float)i/(float)(numKnots-1));
			knots[i]=knot;
		}
		//knots[0].transform.parent=transform;
		
		for (int i = 0; i < numKnots - 1; i++)
		{
			springs[i] = new RopeSpring(knots[i], knots[i + 1], 
				springConstant, springLength, springFrictionConstant);
		}
	}
	
	// Update is called once per frame
	void FixedUpdate () {
		
		if(Input.GetButton("Fire1")  ! Input.GetKey(KeyCode.LeftShift))
			MoveKnot(knots[0]);
		
		if(Input.GetButton("Fire2")  ! Input.GetKey(KeyCode.LeftShift))
			MoveKnot(knots[numKnots-1]);
		
		if(Input.GetButtonDown("Fire1")  Input.GetKey(KeyCode.LeftShift))
			knots[0].rigidbody.isKinematic=!knots[0].rigidbody.isKinematic;
		
		if(Input.GetButtonDown("Fire2")  Input.GetKey(KeyCode.LeftShift))
			knots[numKnots-1].rigidbody.isKinematic=!knots[numKnots-1].rigidbody.isKinematic;
		
		
		if(! endObject) return;
		for(int i=0; i<numKnots-1; i++){
			springs[i].solve(drag, Time.deltaTime);
		}
	}
	
	void MoveKnot(GameObject knot){
		Ray ray;
		float dist;
		Plane plane;
		ray=Camera.main.ScreenPointToRay(Input.mousePosition);
			plane=new Plane(-Camera.main.transform.forward, knot.transform.position);
			if(plane.Raycast(ray, out dist)){
				knot.transform.position=ray.GetPoint(dist);
			}
	}
}
//RopeSpring.cs
using UnityEngine;
using System.Collections;

public class RopeSpring{
	public Rigidbody mass1;
	public Rigidbody mass2;

	private float springConstant;
	private float springLength;
	private float frictionConstant;

	public RopeSpring(GameObject m1, GameObject m2, float sc, float sl, float fc){
		springConstant = sc;
		springLength = sl;
		frictionConstant = fc;

		mass1 = m1.rigidbody;
		mass2 = m2.rigidbody;
	}

	public void solve(float drag, float timestep)
	{
		
		Vector3 springVector =		// Vector Between The Two Masses
			mass1.transform.TransformPoint(mass1.centerOfMass) -
			mass2.transform.TransformPoint(mass2.centerOfMass);	
		
		float r = springVector.magnitude;				// Distance Between The Two Masses
		Vector3 d = Vector3.one * drag;

		Vector3 force=Vector3.zero;							// Force Initially Has A Zero Value
		
		if (r != 0)							// To Avoid A Division By Zero... Check If r Is Zero The Spring Force Is Added To The Force		
			force += -(springVector / r) * (r - springLength) * springConstant;
		
		// The Friction Force Is Added To The force With This Addition We Obtain The Net Force Of The Spring
		force += -(mass1.velocity - mass2.velocity) * frictionConstant;
		
		mass1.AddForce(force * drag);					// Force Is Applied To mass1
		mass2.AddForce(-force * drag);					// The Opposite Of Force Is Applied To mass2
	}
}

Reference: http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=40

looks interesting, but how do you get it to work with a rope rather than a row of spheres?

The rows of spheres represent line points and really only show the collision on the mesh. You could easily generate a mesh from point to point in a multi-point tube or even go as far as to create a camera.up billboarding to reduce the number of polygons. The cool thing is that you only have to create the mesh once, you just keep moving the vertices for whatever setup that you use.

What’s interesting is that although you’ve coded your own spring physics, it still “vibrates” when under excessive tension, just like the Unity / PhysX spring. I wonder if that’s just a by product of all physics springs / joints? What I haven’t seen is a rope that -doesn’t- stretch when under tension. For example using a rope / chain to pull a very heavy object. Jake’s more recent script attempted something like this and I hacked together some code that comes close, but I haven’t seen anyone get it exactly right.

I was looking into that, and probably still will add that to it at some point, I just thought it was an interesting find to share. :wink:

I redid the test using Unity springs… OMG it was terrible. Unity springs are so hard to control they are unusable.

I have the code here if anyone else can show me where I am going wrong, I have had so much trouble with almost all the Unity Joints that I can barely use them.

//RopeSimulator.cs
using UnityEngine;
using System.Collections;


public class RopeSimulator : MonoBehaviour {
	public Transform  endObject;
	public float  radius=0.1f;
	public float  mass=10.0f;
	public bool lockStart=true;
	public bool lockEnd=false;
	public float drag=1.0f;
	public bool useUnitySprings=false;
	
	private float resoloution=1.8f;
	private float springConstant=100.0f;
	private float springFrictionConstant=0.5f;
	private RopeSpring[] springs;
	private GameObject[] knots;
	private int numKnots=0;
	
	void Start () {
		if(! endObject) return;
		Destroy(collider);
		if(gameObject.GetComponent<Renderer>())gameObject.GetComponent<Renderer>().enabled=false;
		Destroy(endObject.collider);
		if(endObject.gameObject.GetComponent<Renderer>())endObject.gameObject.GetComponent<Renderer>().enabled=false;
		numKnots=(int)(Vector3.Distance(transform.position, endObject.position) * resoloution);
		Debug.Log(endObject.position);
		float springLength=Vector3.Distance(transform.position, endObject.position)  / (float)(numKnots-1);
		float knotMass=mass/(float)numKnots;
		
		knots=new GameObject[numKnots];
		springs=new RopeSpring[numKnots-1];
		
		for (int i = 0; i < numKnots; i++){
			GameObject knot = GameObject.CreatePrimitive(PrimitiveType.Sphere);
			knot.name="Knot-" + i + "-" + gameObject.name;
			//knot.transform.parent=transform;
			knot.transform.localScale=Vector3.one * radius * 2;
			knot.AddComponent<Rigidbody>();
			knot.rigidbody.mass=knotMass;
			if(i==0  lockStart)knot.rigidbody.isKinematic=true;
			if(i==numKnots-1  lockEnd) knot.rigidbody.isKinematic=true;
			knot.transform.position=Vector3.Lerp(transform.position, endObject.position, (float)i/(float)(numKnots-1));
			knots[i]=knot;
		}
		//knots[0].transform.parent=transform;
		
		for (int i = 0; i < numKnots - 1; i++)
		{
			if(useUnitySprings){
				SpringJoint spring=knots[i].AddComponent<SpringJoint>();
				spring.spring=springConstant;
				spring.damper=10.0f;
				spring.connectedBody=knots[i+1].rigidbody;
				spring.minDistance=springLength * 0.75f;
				spring.maxDistance=springLength * 1.25f;
				spring.axis = endObject.position-transform.position;
			} else {
				springs[i] = new RopeSpring(knots[i], knots[i + 1], 
					springConstant, springLength, springFrictionConstant);
			}
		}
	}
	
	// Update is called once per frame
	void FixedUpdate () {
		if(! endObject) return;
		
		if(Input.GetButton("Fire1")  ! Input.GetKey(KeyCode.LeftShift))
			MoveKnot(knots[0]);
		
		if(Input.GetButton("Fire2")  ! Input.GetKey(KeyCode.LeftShift))
			MoveKnot(knots[numKnots-1]);
		
		if(Input.GetButtonDown("Fire1")  Input.GetKey(KeyCode.LeftShift))
			knots[0].rigidbody.isKinematic=!knots[0].rigidbody.isKinematic;
		
		if(Input.GetButtonDown("Fire2")  Input.GetKey(KeyCode.LeftShift))
			knots[numKnots-1].rigidbody.isKinematic=!knots[numKnots-1].rigidbody.isKinematic;
		
		if(Input.GetKeyDown("r")) Application.LoadLevel(0);
		
		if(!useUnitySprings){
			for(int i=0; i<numKnots-1; i++){
				springs[i].solve(drag, Time.deltaTime);
			}
		}
	}
	
	void MoveKnot(GameObject knot){
		Ray ray;
		float dist;
		Plane plane;
		ray=Camera.main.ScreenPointToRay(Input.mousePosition);
			plane=new Plane(-Camera.main.transform.forward, knot.transform.position);
			if(plane.Raycast(ray, out dist)){
				knot.transform.position=ray.GetPoint(dist);
			}
	}
}

Hey, I was trying to use your rope for moving balloon attached to the ground, but the rope doesn’t react to the movement of its parent. What am I doing wrong?