EditorAnimationCurveExtension

From Unify Community Wiki
Jump to: navigation, search

Author: Nikolay Kandalintsev (nicloay)

Contents

Description

Unity has AnimationCurve class to work with animation.
Unfortunately you can set only smooth keyframes to it with left and right tangents through your code. But in the inspector you have 2 more options - Constant and Linear
Screenshot bellow show difference between them.

I used several methods writen by Unity guys. This methods highlighted by UnityEditor.CurveUtility.cs (c) Unity Technologies comment.

Curves.png

This utility solve this problem, you can find example how to create Linear and Constant animation

Usage

Place following scripts somewhere in your Assets folder under your Poroject

C# - CurveExtension.cs

This class used to add 2 new methods to AnimationCurve This methods used when you create Linear curve to update left and right tangents of keys.

using UnityEngine;
using System.Collections;
 
namespace CurveExtended{
 
	public static class CurveExtension {
 
		public static void UpdateAllLinearTangents(this AnimationCurve curve){
			for (int i = 0; i < curve.keys.Length; i++) {
				UpdateTangentsFromMode(curve, i);
			}
		}
 
		// UnityEditor.CurveUtility.cs (c) Unity Technologies
		public static void UpdateTangentsFromMode(AnimationCurve curve, int index)
		{
			if (index < 0 || index >= curve.length)
				return;
			Keyframe key = curve[index];
			if (KeyframeUtil.GetKeyTangentMode(key, 0) == TangentMode.Linear && index >= 1)
			{
				key.inTangent = CalculateLinearTangent(curve, index, index - 1);
				curve.MoveKey(index, key);
			}
			if (KeyframeUtil.GetKeyTangentMode(key, 1) == TangentMode.Linear && index + 1 < curve.length)
			{
				key.outTangent = CalculateLinearTangent(curve, index, index + 1);
				curve.MoveKey(index, key);
			}
			if (KeyframeUtil.GetKeyTangentMode(key, 0) != TangentMode.Smooth && KeyframeUtil.GetKeyTangentMode(key, 1) != TangentMode.Smooth)
				return;
			curve.SmoothTangents(index, 0.0f);
		}
 
		// UnityEditor.CurveUtility.cs (c) Unity Technologies
		private static float CalculateLinearTangent(AnimationCurve curve, int index, int toIndex)
		{
			return (float) (((double) curve[index].value - (double) curve[toIndex].value) / ((double) curve[index].time - (double) curve[toIndex].time));
		}
 
	}
}

C# - KeyframeUtil.cs

Use KeyframeUtil.GetNew(....) to create new keyframes with left and right TangentMode

using UnityEngine;
using System.Collections;
using System.Reflection;
using System;
 
namespace CurveExtended{
	public enum TangentMode
	{
		Editable = 0,
		Smooth = 1,
		Linear = 2,
		Stepped = Linear | Smooth,
	}
 
	public enum TangentDirection
	{
		Left,
		Right
	}
 
 
	public class KeyframeUtil {
 
		public static Keyframe GetNew( float time, float value, TangentMode leftAndRight){
			return GetNew(time,value, leftAndRight,leftAndRight);
		}
 
		public static Keyframe GetNew(float time, float value, TangentMode left, TangentMode right){
			object boxed = new Keyframe(time,value); // cant use struct in reflection			
 
			SetKeyBroken(boxed, true);
			SetKeyTangentMode(boxed, 0, left);
			SetKeyTangentMode(boxed, 1, right);
 
			Keyframe keyframe = (Keyframe)boxed;
			if (left == TangentMode.Stepped )
				keyframe.inTangent = float.PositiveInfinity;
			if (right == TangentMode.Stepped )
				keyframe.outTangent = float.PositiveInfinity;
 
			return keyframe;
		}
 
 
		// UnityEditor.CurveUtility.cs (c) Unity Technologies
		public static void SetKeyTangentMode(object keyframe, int leftRight, TangentMode mode)
		{
 
			Type t = typeof( UnityEngine.Keyframe );
			FieldInfo field = t.GetField( "m_TangentMode", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );
			int tangentMode =  (int)field.GetValue(keyframe);
 
			if (leftRight == 0)
			{
				tangentMode &= -7;
				tangentMode |= (int) mode << 1;
			}
			else
			{
				tangentMode &= -25;
				tangentMode |= (int) mode << 3;
			}
 
			field.SetValue(keyframe, tangentMode);
			if (GetKeyTangentMode(tangentMode, leftRight) == mode)
				return;
			Debug.Log("bug"); 
		}
 
		// UnityEditor.CurveUtility.cs (c) Unity Technologies
		public static TangentMode GetKeyTangentMode(int tangentMode, int leftRight)
		{
			if (leftRight == 0)
				return (TangentMode) ((tangentMode & 6) >> 1);
			else
				return (TangentMode) ((tangentMode & 24) >> 3);
		}
 
		// UnityEditor.CurveUtility.cs (c) Unity Technologies
		public static TangentMode GetKeyTangentMode(Keyframe keyframe, int leftRight)
		{
			Type t = typeof( UnityEngine.Keyframe );
			FieldInfo field = t.GetField( "m_TangentMode", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );
			int tangentMode =  (int)field.GetValue(keyframe);
			if (leftRight == 0)
				return (TangentMode) ((tangentMode & 6) >> 1);
			else
				return (TangentMode) ((tangentMode & 24) >> 3);
		}
 
 
		// UnityEditor.CurveUtility.cs (c) Unity Technologies
		public static void SetKeyBroken(object keyframe, bool broken)
		{
			Type t = typeof( UnityEngine.Keyframe );
			FieldInfo field = t.GetField( "m_TangentMode", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );
			int tangentMode =  (int)field.GetValue(keyframe);
 
			if (broken)
				tangentMode |= 1;
			else
				tangentMode &= -2;
			field.SetValue(keyframe, tangentMode);
		}
 
	}
}

Example

I will not show here how to save object to AssetDatabase, Assume that you already have Animator component and AnimationClip. See the full source code at the end of this page.

Linear curve

AnimationCurve linearCurve = new AnimationCurve();
// Add 2 keyframe on 0.5 and 1.0 seconds with 1 and 2 values
linearCurve.AddKey(KeyframeUtil.GetNew(0.5f, 1.0f, TangentMode.Linear));
linearCurve.AddKey(KeyframeUtil.GetNew(1.0f, 2.0f, TangentMode.Linear));
// If you have at leas one keyframe with TangentMode.Linear you should recalculate tangents after you assign all values
linearCurve.UpdateAllLinearTangents();
// assign this curve to clip
animationClip.SetCurve(gameObject, typeof(Transform),"localPosition.x", linearCurve);

Constant curve

This type of curve is very useful for m_IsActive properties (to enable and disable gameobjects)

AnimationCurve constantCurve = new AnimationCurve();
 
constantCurve.AddKey(KeyframeUtil.GetNew(0.5f, 0.0f, TangentMode.Linear)); //false on 0.5 second
constantCurve.AddKey(KeyframeUtil.GetNew(1.0f, 1.0f, TangentMode.Linear)); // true on 1.0 second
 
animationClip.SetCurve(gameObject, typeof(GameObject),"m_IsActive", constantCurve);



Example Full Source Code C# - CreateAnimation.cs

This is editor class so place this code to any Editor folder inside your Assets folder
After that you will have new menu item Assets/animation
When you click on it, following script will create gameobjects on opened scene, AnimatorController and Animation assets in the Assets folder


using UnityEngine;
using System.Collections;
using UnityEditor;
using UnityEditorInternal;
using System.Reflection;
using CurveExtended;
 
public class CreateAnimation : Editor {
	public static string GAME_OBJECT_NAME="gameObject";
	public static string ANIMATION_CLIP_PATH = "Assets/animation.anim";
	public static string CUBE1_NAME = "blnkCube";
	public static string CUBE2_NAME = "lineCube";
	public static string CUBE3_NAME = "smoothCube";
	public static Vector3 CUBE1_POSITION = Vector3.left*2;
	public static Vector3 CUBE2_POSITION = new Vector3(-1.0f, +2.0f, 0);
 
	public static float stepTime = 0.5f;
	public static float[] stepValues = new float[]{
		0,1,0,1,0
	};
 
	[MenuItem("Assets/animation")]
	public static void createAnimation(){
		GameObject go = GameObject.Find(GAME_OBJECT_NAME);
		if (go != null)
			removeGameObjectAndComponents(go);
 
		Animator animator;
		go = createGameObjects(out animator);
		AnimationClip animationClip = new AnimationClip();
		AnimationUtility.SetAnimationType(animationClip, ModelImporterAnimationType.Generic);
 
 
		AnimationCurve activeCurve = new AnimationCurve();
		AnimationCurve positionLineCurve = new AnimationCurve();
		AnimationCurve positionSmoothCurve = new AnimationCurve();
 
 
		for (int i = 0; i < stepValues.Length; i++) {
			float time = stepTime * i;
			activeCurve        .AddKey(KeyframeUtil.GetNew(time, stepValues[i]    , TangentMode.Stepped));
			positionLineCurve  .AddKey(KeyframeUtil.GetNew(time, stepValues[i] + 2, TangentMode.Linear));
			positionSmoothCurve.AddKey(KeyframeUtil.GetNew(time, stepValues[i] - 2, TangentMode.Smooth));			
		}
 
		//this will be linear curve, so need to update tangents (should be after keyframes assignments)
		positionLineCurve.UpdateAllLinearTangents();
 
 
 
		animationClip.SetCurve(CUBE1_NAME, typeof(GameObject),"m_IsActive", activeCurve);
		animationClip.SetCurve(CUBE2_NAME, typeof(Transform),"localPosition.x", positionLineCurve);
		animationClip.SetCurve(CUBE2_NAME, typeof(Transform),"localPosition.y", positionSmoothCurve);
 
		AssetDatabase.CreateAsset(animationClip, ANIMATION_CLIP_PATH);
		AssetDatabase.SaveAssets();
		AddClipToAnimatorComponent(go, animator, animationClip);
	}
 
	static GameObject createGameObjects(out Animator animator){
		GameObject go = new GameObject(GAME_OBJECT_NAME);
 
		createCubeAndAttachTo(CUBE1_NAME, CUBE1_POSITION, go);
		createCubeAndAttachTo(CUBE2_NAME, CUBE2_POSITION, go);
 
		animator =  go.GetComponent<Animator>();
		if (animator == null)
			animator = go.AddComponent<Animator>();
		return go;
	}
 
	static void createCubeAndAttachTo(string cubeName, Vector3 position, GameObject parentGO){
		GameObject go= GameObject.CreatePrimitive(PrimitiveType.Cube);
		go.name= cubeName;
		go.transform.parent = parentGO.transform;
		go.transform.localPosition = position;
	}
 
 
	static void removeGameObjectAndComponents(GameObject go){
		AnimatorController animCtrl = AnimatorController.GetEffectiveAnimatorController(go.GetComponent<Animator>());
		AssetDatabase.DeleteAsset(ANIMATION_CLIP_PATH);
		AssetDatabase.DeleteAsset(
			AssetDatabase.GetAssetPath( animCtrl)
			);
		GameObject.DestroyImmediate(go);
	}
 
 
	public static AnimationClip AddClipToAnimatorComponent(GameObject go, Animator animator, AnimationClip newClip)
	{
 
		AnimatorController animatorController = AnimatorController.GetEffectiveAnimatorController(animator);
		if (animatorController == null)
		{
			AnimatorController controllerForClip = AnimatorController.CreateAnimatorControllerForClip(newClip, go);
			AnimatorController.SetAnimatorController(animator, controllerForClip);
			if (controllerForClip != null)
				return newClip;
			else
				return null;
		}
		else
		{
			AnimatorController.AddAnimationClipToController(animatorController, newClip);
			return newClip;
		}
	}
 
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox