The Life of a Programmer

Search

Modify GameObject Values with Unity AnimationMode

I need to animate mechanical devices in Rabanaz, was having trouble with the standard Unity rig-based animation. The cyclic nature and axle driven mechanical devices diverged too much from its skeletal use-case. Yet I still wanted something I could manage inside Unity.

In this article I want to show how I interfaced with AnimationMode in order to build my animation editor. I wanted it to work like the built-in animation preview but with my own objects for encoding the animation, as opposed to AnimationClip. I could start preview, modify my joints, and watch the animation in the scene view.

I’ll cover the mechanical rigging system in another article.

Basic Problem

While writing editors for Unity, the basic problem is restoring state. If I have a slider that changes the position of a game object, how do I return that to its original position when I’m done editing?

At first I would record the original value and revert to that value when I exit my preview mode. This had a couple of problems though. First, I couldn’t tell visually what properties were being modified; yet the built-in animator has a feature that marks them in blue. Second, when recompiling, switching scenes, and saving, there was instability. I’d often find my restoration skipped, or somehow mangled.

I turned towards the AnimationMode system, which is poorly documented and lacks any kind of usable examples. But, with some help from the forums, I pushed through to get it working.

AnimationMode.StartAnimationMode and StopAnimationMode

I have a class called MachinePreview that derives from EditorWindow to implement a panel inside Unity. There’ are a lot of examples of how to do that, so I won’t go into that part here. The following code, to start and stop animation, is part of this class.

My MachineWindow editor window has a “Preview” button. It also automatically jumps into preview if I adjust the sliders or click a pose. A simple piece of code backs this in the editor:

void StartPreview() {
	if( machine == null || previewMode ) {
		return;
	}
	AnimationMode.StartAnimationMode();
	previewMode = true;
	machine.AnimationModeStart();
}

The key bit for Unity is AnimationMode.StartAnimationMode() which tells the editor that we’re starting animation mode. There does not appear to be any feedback in the editor that animation mode is active. I guess they assume you’re editing one of their animations, in which case the Animation window provides feedback. In any case, my editor highlights the “Preview” button.

I then start my own machine system via the machine.AnimationModeStart() entry point.

If I click “Preview” again I can stop the animation. The code is basically the inverse of the start.

void StopPreview() {
	previewMode = false;
	if( machine == null ) {
		return;
	}
	machine.AnimationModeStop();
	AnimationMode.StopAnimationMode();
}

Starting and stopping on its own doesn’t do much, other than toggle a few icons in Unity. So let’s look at how to mark a property as animated.

Animating a property

%

During animation we want to change property values, but also show they are modified, and have them revert to their original value when animation ends. This is precisely what AnimationMode is advertised to do, but it takes a bit to understand its nuances. We need to use the AnimationMode.AddPropertyModification function.

I have some generic functions now to handle this, but I’ll inline some values so it’s clear what is happening. Here’s some code to animate the position of a GameObject — well, just the x position — repeat for y and z. This is on a component on the GameObject I am animating, to explain where transform is coming from.

var binding = new EditorCurveBinding{
	type = typeof(float),
	path = AnimationUtility.CalculateTransformPath(transform, null),
	propertyName = "m_LocalPosition.x",
};

var modification = new PropertyModification{
	target = transform,
	propertyPath = "m_LocalPosition.x",
	value = transform.localPosition.x.ToString(),
};

AnimationMode.AddPropertyModification(binding, modification, false);

The path is the path in the serialized object I believe. There is a way to dynamically resolve them, but they aren’t ever going to change so I just hard-coded the ones I used.

EditorCurveBinding does not appear relevant — probably used specifically for the built-in animation editor. I changed random values on it and everything still appears to work. If somebody knows what this object does, please let me know. Or maybe we can convince Unity to release a version of the function that doesn’t need this argument.

The important part here is the PropertyModification object. This tells the animation system what property we are animating and its original value.

Whenever animation stops, it’ll revert this property to the value specified here. It seems more stable than manually reverting values: this appears to work regardless of how animation mode is stopped. Though I haven’t tested crashes or other unusual exits. I presume if it works well enough for the built-in animation editor then it should be safe enough.

Non-Transform

If you want to change a property on another component, you need to target that component. The EditorCurveBinding.path is always the path of the transform… presumably, since the binding entirely seems to be ignored. If somebody knows for sure then let me know so I can correct this.

The propertyName is the serialized name. For most of my objects this is a simple name like speed. The rest is the same.

I’ve generalized the pattern in these functions on my base PartBehaviour that implements all the pieces in my machine.

protected void AddProperty(System.Type valueType, string propertyPath, string initialValue) {
	AddObjectProperty( this, valueType, propertyPath, initialValue );
}

protected void AddTransformProperty(System.Type valueType, string propertyPath, string initialValue) {
	AddObjectProperty( transform, valueType, propertyPath, initialValue );
}

protected void AddObjectProperty(Object target, System.Type valueType, string propertyPath, string initialValue) {
	var binding = new EditorCurveBinding{
		type = valueType,
		path = AnimationUtility.CalculateTransformPath(transform, null),
		propertyName = propertyPath,
	};

	var modification = new PropertyModification{
		target = target,
		propertyPath = propertyPath,
		value = initialValue,
	};

	AnimationMode.AddPropertyModification(binding, modification, false);
}

Timer interface

With the above, I can drag the slider in my editor and have my objects respond in the editing scene. But I also wanted to test the final animation, switching between poses. In my MachinePrevie, the EditorWindow, I subscribe to EditorApplication.update.

void OnEnable() {
	EditorSceneManager.sceneSaving += OnSceneSaving;
	EditorApplication.update += OnUpdate;
	lastTime = (float)EditorApplication.timeSinceStartup;
}

float lastTime;
void OnUpdate() {
	var nextTime = (float)EditorApplication.timeSinceStartup;
	var delta = nextTime - lastTime;
	lastTime = nextTime;
	
	if( !AnimationMode.InAnimationMode() ) {
		StopPreview();
	} else if( machine != null && previewMode ) {
		machine.EditorTick( delta );
		EditorApplication.QueuePlayerLoopUpdate();
	}
}

This calls my OnUpdate at whatever frequency the editor deems appropriate. I could not find a built-in variable that tracks the time delta, so I calculate it myself. I then pump this value into my machine to run its animation. At runtime I hook into the normal game timer and the delta value to drive the animation.

previewMode is my variable to track if I’ve entered my own preview state. It is true when my editor is previewing the animations. I also check if we’re still in Unity’s animation mode in case something else turned it off — I did not see any event that is triggered when this happens.

You’ll also see here that I call EditorApplication.QueuePlayerLoopUpdate. This forces Unity to do an update now. Otherwise it waits to detect a change or some longer timeout period. Calling this function ensures the scene redraws on each “tick”.

Note that I also have a hook on EditorSceneManager.sceneSaving. I added this when I was manually reverting values before, to ensure that I wouldn’t save the modified values. OnSceneSaving calls StopPreview. I’m not sure if I strictly need this now that I’ve got animation mode fully working.

Article.Stop

I should note that all of this is part of the public API. Though the documentation isn’t good, it is all documented.

That’s it. This doesn’t cover the entirety of my preview or my machine rig, but it catalogs how to do this bit which isn’t well documented. I’ll get back to the machine animation in a future article.

Please join me on Discord to discuss, or ping me on Mastadon.

Modify GameObject Values with Unity AnimationMode

How I used AnimationMode in Unity to tell the editor that I'm editing values and have them revert when the preview mode stops.

A Harmony of People. Code That Runs the World. And the Individual Behind the Keyboard.

Mailing List

Signup to my mailing list to get notified of each article I publish.

Recent Posts

Search