- Home /
Undocumented property: Keyframe.tangentMode
So yeah.. basically I'm trying to create an AnimationCurve dynamically by generating keyframes and then adding them to an AnimationCurve object. The problem is, Unity is adding them all with flat tangents- so a nice sine wave ends up looking like a jittery pile of garbage :P
In the AnimationCurve editor, you can right click on a node and set the tangent mode to Linear, Constant, Free, etc. Now- the unity docs make no mention of Keyframe.tangentMode, and through my own tests I'm unable to get it to do anything. Is this just a stubbed out property that isn't hooked up or something? Is somebody aware of another way to set a tangent to linear without having to open the animation curve editor?
For reference, here's Unity's Keyframe struct. Assignment of tangentMode always ends up with the same result- it never seems to change from zero
namespace UnityEngine
{
public struct Keyframe
{
public Keyframe( float time, float value );
public Keyframe( float time, float value, float inTangent, float outTangent );
public float inTangent { get; set; }
public float outTangent { get; set; }
public int tangentMode { get; set; }
public float time { get; set; }
public float value { get; set; }
}
}
I threw together this test script if anybody wants to see the behavior I'm talking about. Just throw this on any gameobject and look at the animationcurve- it's a basic quadratic slope, but you can see how the node tangents are causing it to have bumps at every node. If you select all of those nodes and right click->set both nodes to linear, you get the appropriate quadratic slope. I'm looking for a way in code to convert these nodes to linear. Thanks!
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class curvetest : MonoBehaviour
{
public AnimationCurve testCurve;
void OnEnable()
{
if ( testCurve.keys.Length > 0 )
return;
testCurve = new AnimationCurve();
for ( float i = 0f; i < 1f; i += .1f )
{
float v = i;
v /= .5f;
if ( i < 1 )
{
testCurve.AddKey( new Keyframe( i, 1f / 2 * v * v ) );
}
else
{
v--;
testCurve.AddKey( new Keyframe( i, -1f / 2 * ( v * ( v - 2 ) - 1 ) ) );
}
}
}
}
Some aditional information: - if I add some debug output while making changes to the node from the animation curve editor, I get the following results for tangent$$anonymous$$ode: "Free" is 1, "Linear" is 21, and "Constant" is 31. attempting to actually set tangent$$anonymous$$ode to one of these values via code seems to have no effect.
Debug.Log( i + "] Before: " + testCurve.keys[ i ].tangent$$anonymous$$ode );
testCurve.keys[ i ].tangent$$anonymous$$ode = 21;
Debug.Log( i + "] After: " + testCurve.keys[ i ].tangent$$anonymous$$ode );
result is 0 before and 0 after.
Some more info.. starting to dig into uncharted territory here it seems. Using reflection I discovered that there's actually an internal Tangent$$anonymous$$odes enum:
internal enum Tangent$$anonymous$$ode
{
Editable,
Smooth,
Linear,
Stepped
}
I can only assume that UT haven't deemed all of this ready for prime-time yet? For now I'm trying to dig through reflector to find the UnityEditor call that sets the tangent mode- looks like it's
UnityEditor.Curve$$anonymous$$enu$$anonymous$$anager.SetBoth(Tangent$$anonymous$$ode, List<$$anonymous$$eyIdentifier>) : Void
Depends On
mscorlib
UnityEditor
UnityEngine
Used By
UnityEditor.Curve$$anonymous$$enu$$anonymous$$anager.SetEditable(Object) : Void
UnityEditor.Curve$$anonymous$$enu$$anonymous$$anager.SetFlat(Object) : Void
UnityEditor.Curve$$anonymous$$enu$$anonymous$$anager.SetSmooth(Object) : Void
Alright, after much screwing around with this I'm calling it a bust. So far as I can tell, the setter for tangent$$anonymous$$ode isn't actually hooked up to SetTangent$$anonymous$$ey$$anonymous$$ode, which lives in CurveUtility (internal unityeditor class). I was able to derive how the tangent mode is being calculated though- it appears to be a bitmask based on the left/right hand tangency of any given node.
the left side is calculated with:
m_Tangent$$anonymous$$ode &= -7;
m_Tangent$$anonymous$$ode |= ( ( int ) mode ) << 1;
and the right side is calculated with:
m_Tangent$$anonymous$$ode &= -25;
m_Tangent$$anonymous$$ode |= ( ( int ) mode ) << 3;
where mode is a Tangent$$anonymous$$ode (enum posted up above)
Long story short, the editor seems to be calling SetTangent$$anonymous$$ey$$anonymous$$ode to update the tangents, but the setter for the $$anonymous$$eyframe struct isn't. Seems like a bug to me.. And due to boxing/unboxing, using reflection to set the private member variable manually isn't an option in this case.
Oh well... guess I'll have to wait for official support for this :\
Answer by testure · Sep 05, 2012 at 07:35 PM
Okay, figured out a way to solve this issue. Though bear in mind that this relies on an undocumented private member of struct, and hidden functionality of an internal class, so use at your own risk. Personally, I'm developing a tool that will be in development for quite a while, so I can just make note of it and replace the functionality with the proper method when UT officially supports it. I'm only posting it here because there is ZERO information about this on the internet, and thus no solutions- so I've done all the exhausting research/testing necessary so the next guy doesn't have to :P
Anyway, here it is:
using UnityEngine;
using System;
using System.Collections;
using System.Reflection;
/// <summary>
/// Static utility class to work around lack of support for Keyframe.tangentMode
/// This utility class mimics the functionality that happens behind the scenes in UnityEditor when you manipulate an AnimationCurve. All of this information
/// was discovered via .net reflection, and thus relies on reflection to work
/// --testure 09/05/2012
/// </summary>
public static class AnimationCurveUtility : System.Object
{
public enum TangentMode
{
Editable,
Smooth,
Linear,
Stepped
}
public enum TangentDirection
{
Left,
Right
}
public static void SetLinear( ref AnimationCurve curve )
{
Type t = typeof( UnityEngine.Keyframe );
FieldInfo field = t.GetField( "m_TangentMode", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );
for ( int i = 0; i < curve.length; i++ )
{
object boxed = curve.keys[ i ]; // getting around the fact that Keyframe is a struct by pre-boxing
field.SetValue( boxed, GetNewTangentKeyMode( ( int ) field.GetValue( boxed ), TangentDirection.Left, TangentMode.Linear ) );
field.SetValue( boxed, GetNewTangentKeyMode( ( int ) field.GetValue( boxed ), TangentDirection.Right, TangentMode.Linear ) );
curve.MoveKey( i, ( Keyframe ) boxed );
curve.SmoothTangents( i, 0f );
}
}
public static int GetNewTangentKeyMode( int currentTangentMode, TangentDirection leftRight, TangentMode mode )
{
int output = currentTangentMode;
if ( leftRight == TangentDirection.Left )
{
output &= -7;
output |= ( ( int ) mode ) << 1;
}
else
{
output &= -25;
output |= ( ( int ) mode ) << 3;
}
return output;
}
}
And for those wondering- I'm aware that you can simply set the in/out tangent of a keyframe manually by calculating the correct tangency via atan(), but that isn't a good solution in my case since I need the actual tangent mode to change. Brute-forcing the tangent angle would still leave tangent handles, so if a user opens the curve editor and moves a keyframe the handles will be there and the keyframe will not be linear.
So, kind of a specialized problem with a specialized solution, but there you have it.
Hey thanks for all this work! I wanted to set all my scale curves to linear. If you have time, I would appreciate the help.
This script doesn't crash, but nothing happens. I saved your code to a file called AnimationCurveUtility.cs, and then in my own script I have this:
static void LinearScale$$anonymous$$eys()
{
// GET SELECTED ANI$$anonymous$$ATIONCLIP
GameObject obj = Selection.activeObject as GameObject;
if(obj.animation == null)
{
Debug.Log("No Animations on Active Transform");
return;
}
foreach (AnimationClip anim in AnimationUtility.GetAnimationClips(obj.animation))
{
AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(anim);
for (int i = 0; i < curveDatas.Length; i++)
{
if(curveDatas[i]!=null)
{
if(curveDatas[i].propertyName.ToLower().Contains("scale"))
{
AnimationCurveUtility.SetLinear(ref curveDatas[i].curve);
}
}
}
}
}
I don't see anything immediately obvious as to what's not working- you're getting Selection.activeObject so I'm assu$$anonymous$$g that this is an editor script? What's calling LinearScale$$anonymous$$eys? Have you tried putting any debug output in to see where it's failing?
Thanks for the help. I figured out the problem a little while ago. After calling your SetLinear function, I needed to re-apply the curve to the object using AnimationUtility.SetEditorCurve. I had assumed that AniamtionUtility.GetAllCurves was a live connection to the object.
This is really a great piece of work it seems, but I just can't get it to work at all. I am trying to set a curve to be stepped and it just isn't working. The curve editor in the inspector isn't showing the curve as stepped, and also when I do a Debug.Log(curve.evaluate) in code the returned values are not stepped. Has something changed about this?
Answer by nicloay · Jan 13, 2014 at 06:16 PM
Hope that this page will help you http://wiki.unity3d.com/index.php/EditorAnimationCurveExtension
testure Script is almost ok, except that for linear tangent he is using SmoothTangents which, probably, should not be there, It must be left and right tangent manual calculation like this 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));
}
}
}
and then you can use like this
yourLinearCurve.UpdateAllLinearTangents();
Answer by maciej.struzyna · Nov 04, 2014 at 08:53 AM
To get effect of "constant" tangent like in the default AnimationCurve editor window the tangentMode value is not needed. You have to set the tangent to float.PositiveInfinity. To get effect of "Linera" tangent you have to calculate the tangent accordingly like Jan wrote.
Therefore the tangentMode value is just informative (probably for the editor window) which mode for the tangent the user chosen. That is why this is not documented.
Your answer
Follow this Question
Related Questions
Remove unused animation curves? (Animation.Update() optimizing) 1 Answer
Animator parameter does not pick the value of the animation curve with the same name 2 Answers
Controlling speed of anim replay through an animation curve 1 Answer
I can't find some property of material after upgrade 4.3 in animation curve editor. 0 Answers