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
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 ) ) );
}
}
}
}
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
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;
}
}
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));
}
}
}
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.