Physics spring implementation

Hi All,

I’m migrating over to Unity from Virtools and am trying to reproduce an old Virtools test in Unity but have a hit a snag. It may be my lack of understanding of the spring implementation in Unity or it may be that I need to write my own little spring class to do what I want. Either way, a nudge in the right direction would be much appreciated :).

So here’s the Virtools test (very simple, about one hour of work):

http://www.steveswink.com/Bionic3d/Info_Bionic3d_01.htm
(click and hold to attach spring, make sure you go swinging on the high-up stuff and pulling yourself around on the ground - it’s actually pretty fun!)

…and here’s what I have thusfar in Unity:

http://www.steveswink.com/Unity/twist_Test_01.html
(With WASD controls inserted because I can’t get Spring Joint to pull the way I want.)

The problem I’m having (apart from a lot of trouble getting a good-feeling ball rolling physics in Unity - is there a way to tweak physics globals without menu digging?) is with the way springs are implemented in Unity. I want to have a ‘Spring Length’ that I can modify at run time to get that nice pulling effect I have in the Virtools test. It just seems like apples and oranges implementation wise.

Any thoughts would be greatly appreciated - I noticed the snippet about the grappling hook implementation, and that looked promising, but I think what I really want is just a different/modified spring class that functions the way the Virtools springs do. So, yeah, I’m also interesting in links to spring math that does what I want.

…which is:

  1. Instantiate a spring by feeding it two objects (in this case, a point on an object (if any) intersected by mouse-based raycast) and the center of the controllable sphere.

  2. A ‘Length’ parameter (float) that represents the spring’s desired length which can be modified at runtime to ‘pull’ the controllable sphere towards the object.

  3. A ‘Constant’ parameter representing the strength with which the spring tries to maintain its desired length.

  4. A ‘Dampening’ parameter.

That make sense?

Thanks in advance!

  • Steve

Ah, and here’s what I’m doing:

var springPower = 1.0; 
var object1 : Rigidbody; 
var particle : GameObject;

private var spring1; 	// SpringJoint; 
private var drawBetween; // Line Render for spring attached

function Update () {
	if (Input.GetMouseButtonDown(0)) {
		
		// Construct a ray from the current mouse coordinates
		var ray = Camera.main.ScreenPointToRay (Input.mousePosition);
		var hit : RaycastHit; 
		var connectTo : Rigidbody; 
		
		if (Physics.Raycast (ray,hit,1000)) {
		// Create a particle if hit
		Instantiate (particle, hit.point, transform.rotation); 
		
		connectTo = hit.rigidbody; 
		// Attach Spring to point of contact
		var dist = Vector3.Distance(transform.position, hit.point);

		spring1 = gameObject.AddComponent("SpringJoint"); 
		spring1.connectedBody = connectTo;
		spring1.anchor = transform.InverseTransformPoint(hit.point);  
		spring1.spring = 2; 
		spring1.maxDistance = 0; 
		spring1.minDistance = dist;
		Debug.Log(dist); 
		
		//rigidbody
  
		
		// Line Render for spring vis
		drawBetween = gameObject.AddComponent("DrawBetween"); 
		drawBetween.Object1 = transform;
		drawBetween.Object2 = hit.transform; 
		drawBetween.position2 = hit.transform.InverseTransformPoint(hit.point); 		
		//Debug.DrawLine(transform.position, hit.point, Color.red); 
		
		
		}
		//Debug.Log("Spring ON!"); 
	}

	if (Input.GetMouseButtonUp(0)) {
		Destroy(spring1); 
		Destroy(drawBetween); 
		Debug.Log("Spring OFF!"); 
		}

}

Really? Nothing at all?

I’m trying out your script now and should have some thoughts in a few minutes or so :slight_smile:

Thanks!

Note that drawBetween is a separate component that’s being added (just comment it out.)

Actually, what’s the best way to just share the whole project? I have the spheres, all the geometry, all the scripts in there - is there a quick and dirty way to share that without all the assets/project files in one huge package?

No problem, Steve! I’ll address your questions one at a time.

1) Sharing the whole project

If you want to put the entire project up on the forum, the best thing to do is zip up the whole project folder and simply attach that to your forum post. This is preferred to a package because a package can’t include information like your Manager settings. So if you have a custom input axis set up, any scripts using that axis won’t work since it won’t exist. The forum upload limit is 50MB so if your project is bigger than that and you need OTEE to take a look, please use the Bug Reporting app and submit the project folder that way.

2) Problem with Spring Joint

The problem you were running into is basically Our Fault™. The “correct” position of an object with a spring joint is determined by the relative position of the object from its connected body WHEN the spring joint is initialized. This makes sense if you’re setting up a nunchaku or a snake made from spring joints, but not for situations like yours.

So when the Joint is initialized and the connected body is set, the current position of the sphere relative to the body is assumed to be the “correct” position. Your scenario wants the “correct” position to be the connected body’s position. The worst part is, we don’t tell this to anyone in the documentation. (I guess that would make it My Fault™)

I’ve created a bit of a workaround here, by modifying your code. Essentially, it stores the sphere’s current position, moves it to the connected body’s position, initializes the joint, and moves the sphere back where it was. This does actually work, but it’s a bit of a hack! I’ve also changed the lines that set the Anchor Point. You don’t actually want to change this for your scenario, just leave it in the dead center of your sphere. Here’s the code:

var springPower = 1.0; 
var particle : GameObject; 

private var spring1;    // SpringJoint; 
private var drawBetween; // Line Render for spring attached 

function Update () {
	if (Input.GetMouseButtonDown(0)) {     
		rigidbody.AddForce(Vector3.up * 100.0);
		// Construct a ray from the current mouse coordinates 
		var ray = Camera.main.ScreenPointToRay (Input.mousePosition); 
		var hit : RaycastHit; 
		var connectTo : Rigidbody; 
       
		if (Physics.Raycast (ray,hit,1000)) { 
			// Create a particle if hit 
			Instantiate (particle, hit.point, transform.rotation); 
			
			connectTo = hit.rigidbody; 
			// Attach Spring to point of contact 
			var dist = Vector3.Distance(transform.position, hit.point);
			
			// !!New lines!!
			// Store the current position of the sphere
			var storedPos = transform.position;
			// Temporarily move the sphere to the position of the joint's connected body.
			// This is because the "correct" relative position of the two objects is finalized
			// when the joint is initialized.
			transform.position = hit.transform.position;
			
			// Initialize the joint
			spring1 = gameObject.AddComponent("SpringJoint"); 
			spring1.connectedBody = connectTo; 
			spring1.anchor = Vector3.zero;
			spring1.spring = springPower; 
			spring1.maxDistance = 0; 
			spring1.minDistance = 0;
			
			// !!New line!!
			// Put the sphere back where it was
			transform.position = storedPos;
			
			//Debug.Log(dist); 
			
			// Line Render for spring vis 
			drawBetween = gameObject.AddComponent("DrawBetween"); 
			drawBetween.Object1 = transform; 
			drawBetween.Object2 = hit.transform; 
			drawBetween.position2 = hit.transform.InverseTransformPoint(hit.point);        
			//Debug.DrawLine(transform.position, hit.point, Color.red);
		} 
		//Debug.Log("Spring ON!"); 
	} 
	
	if (Input.GetMouseButtonUp(0)) {
		Destroy(spring1); 
		Destroy(drawBetween); 
		Debug.Log("Spring OFF!"); 
	}
}

A more graceful solution would be to use an empty GameObject with a rigidbody that is ALWAYS the sphere’s connected body. Then as you click on different anchor points, the connected body would move to that point. This would also allow you to move the anchor point without having every anchor require a rigidbody. The problem with the Spring Joint is that it doesn’t allow you to set the “correct” distance like you were trying to do, which leads me to…

3) Creating SpringJoints from code

It sounds like you have a very good idea of how you’d like to be able to change spring length values at runtime. Please submit your idea using the Bug Reporting app. We’ll take a look and add it to the feature requests list.

Hope this helps!

4) Setting global Physics parameters

You can create a single Physic Material which you’d like to use as the global physics setting. You then apply it at Edit->Project Settings->Physics->Default Material.

Fantastic, thanks so much!

I changed the line

         transform.position = hit.transform.position;

to

         transform.position = hit.point;

and that was more or less exactly what I wanted.

In retrospect, my questions were many, vague, and poorly articulated…thanks so much for the comprehensive answers. I’ll be more concise in the future :).