Handle modified variables being temporarily reset at runtime

(I asked this same question in the forum before finding this section. I apologise if that breaks any rules and will remove the offending duplicate if necessary)

edit: here’s the link to the forum thread
http://forum.unity3d.com/threads/174300-Variables-modified-by-handles-are-being-temporarily-reset-at-runtime

Hello, long-time lurker, first time poster. I’ve tried my best to get as far as I could with existing documentation and support that I’ve found online, but now I am officially stuck! I’m sure I’m missing something fundamental here, so lets see how badly I’m failing.

What I’m trying to do:

I’m creating an editor extension that will include the ability to quickly add and modify box and sphere colliders to aid in camera tracking.

I’ve made prefabs of these objects with custom inspectors to modify their size and shape, among other settings. I’ve also written some custom camera facing gizmos to display their size and shape to the user while they are testing their game. The objects also have handles that will allow the user to modify the collider’s size and shape from within the scene view, with the intention being that they can quickly make modifications without having to look over to the inspector.

The actual problem:

My problem is that, while these handles will successfully modify the variables I want, updating the gizmos to show the updated information, the moment I press play, the gizmos reset to the last known setting made by the inspector, completely ignoring the handle modifications.

The variables aren’t forgotten though, and when I select the object again in editor mode, the gizmos instantly revert to their correct size and shape.

This is only happening when the variables are modified by the handles. when they are modified by the custom inspector, the whole thing works as intended.

http://i.imgur.com/OpNXTyz.jpg
http://i.imgur.com/2udYF93.jpg
http://i.imgur.com/Et1duGu.jpg
http://i.imgur.com/qkug9lx.jpg
http://i.imgur.com/tUPtTE6.jpg

Here is the code I’m using. It’s based on this star tutorial.

The data storage, retrieval and gizmo drawing script

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class TestData : MonoBehaviour {
	
	public Vector2 innerRect = new Vector2(7, 4);	//Area data
	public Vector2 outerRect = new Vector2(10, 5);
	
	public float Radius = 0.5f;		//radius data

	
	//Set Zone Data
	public void UpdateTestData(){
		
		ClampValues();
		
		var boxInner = GetComponent<BoxCollider>();
		boxInner.size = new Vector3(innerRect.x, innerRect.y, innerRect.x);
		
		var boxOuter = transform.Find("Outer Box").GetComponent<BoxCollider>();
		boxOuter.size = new Vector3(outerRect.x, outerRect.y, outerRect.x);
		
		var sphere = transform.Find("Outer Sphere").GetComponent<SphereCollider>();
		sphere.radius = Radius;
	}
	
	//Get Test Data
	public void GetTestData(){
		
		var boxInner = GetComponent<BoxCollider>();
		innerRect = new Vector2((boxInner.size.x > boxInner.size.z) ? boxInner.size.x : boxInner.size.z, boxInner.size.y);
		
		var boxOuter = transform.Find("Outer Box").GetComponent<BoxCollider>();
		outerRect = new Vector2((boxOuter.size.x > boxOuter.size.z) ? boxOuter.size.x : boxOuter.size.z, boxOuter.size.y);
		
		var sphere = transform.Find("Outer Sphere").GetComponent<SphereCollider>();
		Radius = sphere.radius;
		
		ClampValues();
	}
	
	//Clamp Values to reasonable limits
	void ClampValues() {
		
		//Inner Bounds
		innerRect.x = (innerRect.x < 0.1f) ? 0.1f : innerRect.x;
		innerRect.y = (innerRect.y < 0.1f) ? 0.1f : innerRect.y;
		
		//Outer Bounds
		outerRect.x = (innerRect.x >= outerRect.x) ? innerRect.x : outerRect.x;
		outerRect.y = (innerRect.y >= outerRect.y) ? innerRect.y : outerRect.y;
		
		Radius = (Radius < 0) ? 0: Radius;
		
	}
	
	void onEnable(){
		UpdateTestData();
	}
	
	void Reset () {
		UpdateTestData();
	}
	
	void OnDrawGizmos(){
		//Drawing a custom rectangle to represent the zone bounds
		
		Vector3 currentCamRot = Camera.main.transform.forward;
		Quaternion lookRot = Quaternion.LookRotation(currentCamRot);		//Get the camera-facing angle
		
		//Draw inner bounds
		Gizmos.color = Color.red;
		//Get the 4 corners of the area
		Vector3 PointA = new Vector3((-innerRect.x/2), innerRect.y/2, 0f),
				PointB = new Vector3(innerRect.x/2, innerRect.y/2, 0f),
				PointC = new Vector3(innerRect.x/2, (-innerRect.y/2), 0f),
				PointD = new Vector3((-innerRect.x/2), (-innerRect.y/2), 0f);
		
		//Draw the 4 edges of the area
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointA),  transform.TransformPoint(lookRot * PointB));
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointB), transform.TransformPoint(lookRot * PointC));
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointC),  transform.TransformPoint(lookRot * PointD));
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointD), transform.TransformPoint(lookRot * PointA));
		
		
		//Draw outer bounds
		Gizmos.color = Color.yellow;
		//Get the 4 corners of the area
		PointA = new Vector3((-outerRect.x/2), outerRect.y/2, 0f);
		PointB = new Vector3(outerRect.x/2, outerRect.y/2, 0f);
		PointC = new Vector3(outerRect.x/2, (-outerRect.y/2), 0f);
		PointD = new Vector3((-outerRect.x/2), (-outerRect.y/2), 0f);
		
		//Draw the 4 edges of the area
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointA),  transform.TransformPoint(lookRot * PointB));
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointB), transform.TransformPoint(lookRot * PointC));
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointC),  transform.TransformPoint(lookRot * PointD));
		Gizmos.DrawLine(transform.TransformPoint(lookRot * PointD), transform.TransformPoint(lookRot * PointA));
		
		//Draw Circle
		Gizmos.color = Color.green;
		int sides = 32;
		Vector3 RadiusVector = new Vector3(0f, Radius, 0f);
		for(int i=0;i<sides;i++){
			
			Quaternion 	rotationA = Quaternion.Euler(0f, 0f, (360f/sides)*i),
						rotationB = Quaternion.Euler(0f, 0f, (360f/sides)*(i+1));//get points for the side we're on
			
			PointA = rotationA * RadiusVector;
			PointB = rotationB * RadiusVector;//scale points by the size of the ring
			
			PointA = transform.TransformPoint(lookRot * PointA);
			PointB = transform.TransformPoint(lookRot * PointB);//rotate the ring towards the camera
			
			Gizmos.DrawLine(PointA, PointB);
		}
	}
}

The editor script

using UnityEngine;
using UnityEditor;
using System.Collections;

[CanEditMultipleObjects, CustomEditor( typeof(TestData))]
public class ACTestEditor : Editor {
	
	private static Vector3 pointSnap = Vector3.one * 0.1f;
	
	private SerializedObject test;
	private SerializedProperty
		innerRect,
		outerRect,
		Radius;
	
	void OnEnable(){
		
		test   		= new SerializedObject(targets);
		innerRect  	= test.FindProperty("innerRect");
		outerRect  	= test.FindProperty("outerRect");
		Radius 	 	= test.FindProperty("Radius");
	}
	
	public override void OnInspectorGUI ()
	{
		foreach(TestData t in targets){
				t.GetTestData();
		}
		test.Update();
		
		
		EditorGUILayout.BeginHorizontal();
		{
			EditorGUILayout.PropertyField(innerRect, true);
			EditorGUILayout.PropertyField(outerRect, true);
		}
		EditorGUILayout.EndHorizontal();
		EditorGUILayout.BeginHorizontal();
		{
			EditorGUILayout.PropertyField(Radius);
		}
		EditorGUILayout.EndHorizontal();
		
		if(test.ApplyModifiedProperties() ||	(Event.current.type == EventType.ValidateCommand &&	Event.current.commandName == "UndoRedoPerformed")){
			foreach(TestData t in targets){
				t.UpdateTestData();
			}
		}
	}
	
	void OnSceneGUI(){
		//Custom Handles
		TestData test = (TestData)target;
		test.GetTestData();
		Transform tTransform = test.transform;
		Undo.SetSnapshotTarget(test, "Adjust Test Data");
		
		//Camera angle
		Vector3 CamRot = Camera.main.transform.forward;
		Quaternion lookRot = Quaternion.LookRotation(CamRot);
		
		
		//inner box
		Handles.color = Color.red;
		Vector3 innerRectWidth  = new Vector3(test.innerRect.x/2, 0, 0),
				innerRectHeight = new Vector3(0, test.innerRect.y/2, 0);
		
		for(int i=0; i<4; i++){

			Vector3 handlePoint;
			
			switch(i){
				case 0:
					handlePoint = innerRectHeight - innerRectWidth/2;
					break;
				case 1:
					handlePoint = innerRectWidth + innerRectHeight/2;
					break;
				case 2:
					handlePoint = -innerRectHeight + innerRectWidth/2;
					break;
				case 3:
					handlePoint = -innerRectWidth - innerRectHeight/2;
					break;
				default:
					handlePoint = Vector3.zero;
					break;
			}
			
			handlePoint = tTransform.TransformPoint(lookRot * handlePoint);
			
			Vector3	newPoint = Handles.FreeMoveHandle(handlePoint, Quaternion.identity, HandleUtility.GetHandleSize(handlePoint)/5, pointSnap, Handles.SphereCap);
			
			if(handlePoint != newPoint){
				newPoint = newPoint - tTransform.position;
				newPoint = Quaternion.Inverse(lookRot) * newPoint;
				
				switch(i){
					case 0:
						test.innerRect.y = newPoint.y*2;
						break;
					case 1:
						test.innerRect.x = newPoint.x*2;
						break;
					case 2:
						test.innerRect.y = -newPoint.y*2;
						break;
					case 3:
						test.innerRect.x = -newPoint.x*2;
						break;
					default:
						break;
				}
				test.UpdateTestData();
			}
		}
		
		//outer box
		Handles.color = Color.yellow;
		Vector3 outerRectWidth  = new Vector3(test.outerRect.x/2, 0, 0),
				outerRectHeight = new Vector3(0, test.outerRect.y/2, 0);
		
		for(int i=0; i<4; i++){

			Vector3 handlePoint;
			
			switch(i){
				case 0:			
					handlePoint = outerRectHeight + outerRectWidth/2;
					break;
				case 1:
					handlePoint = outerRectWidth - outerRectHeight/2;
					break;
				case 2:
					handlePoint = -outerRectHeight - outerRectWidth/2;
					break;
				case 3:
					handlePoint = -outerRectWidth + outerRectHeight/2;
					break;
				default:
					handlePoint = Vector3.zero;
					break;
			}
			
			handlePoint = tTransform.TransformPoint(lookRot * handlePoint);
				
			Vector3	newPoint = Handles.FreeMoveHandle(handlePoint, Quaternion.identity, HandleUtility.GetHandleSize(handlePoint)/5, pointSnap, Handles.SphereCap);
				
			if(handlePoint != newPoint){
				newPoint = newPoint - tTransform.position;
				newPoint = Quaternion.Inverse(lookRot) * newPoint;
					
				switch(i){
					case 0:
						test.outerRect = new Vector2(test.outerRect.x, newPoint.y*2);
						break;
					case 1:
						test.outerRect = new Vector2(newPoint.x*2, test.outerRect.y);
						break;
					case 2:
						test.outerRect = new Vector2(test.outerRect.x, -newPoint.y*2);
						break;
					case 3:
						test.outerRect = new Vector2(-newPoint.x*2, test.outerRect.y);
						break;
					default:
						break;
				}
				test.UpdateTestData();
			}
		}
		
		
		//radius
		Handles.color = Color.green;
		Vector3 RadiusVector = new Vector3(0f, test.Radius, 0f);
		for(int i = 0; i < 4; i++){
			Quaternion rotation = Quaternion.Euler(0f, 0f, 90f*i);
			Vector3	oldPoint = rotation * RadiusVector;
				
			oldPoint = tTransform.TransformPoint(lookRot * oldPoint);
				
			Vector3 newPoint = Handles.FreeMoveHandle(oldPoint, Quaternion.identity, HandleUtility.GetHandleSize(oldPoint)/5, pointSnap, Handles.SphereCap);
			
			if(oldPoint != newPoint){
				test.Radius = Vector3.Magnitude(newPoint - tTransform.position);
				test.UpdateTestData();
			}
		}
	}
}

An example:

I’ve created an isolated package of the problem in hopes that it might make my issue clearer to others.
https://dl.dropbox.com/u/1672880/test%20bug%20package.unitypackage

  • Simply add the package to an empty
    project, drag the test object into
    the scene and modify its shape using
    the handles.
  • (If you see wireframes of the collider components over the gizmos,
    just collapse the components in the
    inspector)
  • Make sure the object is
    deselected and then press play.
    You’ll see the gizmos reset to their
    original shape and size.
  • If you go
    back to edit mode and reselect the
    object, you should see the gizmos
    update to their proper settings.
  • Additionally, if you now edit the
    shapes from the custom inspector,
    those settings will be remembered at
    runtime.

My current suspicions are that it is something to do with serialization, which I admit I know very little about, so any help or push in the right direction would be greatly appreciated, thankyou.

A long question, but a short answer!

You need to put an EditorUtility.SetDirty(test); after each test.UpdateTestData() to tell the system that you’ve modified something and it needs saving.

If you cross post on the forum please include a link to the other post in each direction.