• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
Question by jceipek · Oct 26, 2014 at 11:25 AM · blendingkeyframeprocedural animation

Replicating Wolfire's Procedural Animation Approach in Unity?

What's the best way to blend between single animation keyframes with custom curves in Unity?

I'm trying to see how feasible it would be to use the procedural animation approach described here: http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach inside Unity, and I haven't been able to find a way to blend between individual keyframes using arbitrary curves for the influence of each keyframe. However, I could easily be missing something because I'm not very familiar with Unity's animation facilities and the legacy animation system (which I would probably need to use instead of Mecanim?) is poorly documented.

Does Unity's animation system support this usecase at all, or would it be necessary to write an animation importer and then use that as the basis for a fully-custom animation blending system that manually moves bones in LateUpdate? This option might work, but it seems horribly inefficient (is it?) and duplicates much of Unity's existing animation functionality.

Comment
vexe
JakubNei
theride19

People who like this

3 Show 1
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image vexe · Jun 16, 2015 at 06:35 AM 0
Share

Bump. Would be interested in an answer myself.

1 Reply

  • Sort: 
avatar image

Answer by Tenser · Nov 03, 2015 at 05:24 PM

I know this thread is one year old, but i recently tried to do the same thing and i found a possible solution.

It might not be perfect, but it can help someone who is trying to do something similar

( Note : This uses the Legacy Animation system and you need to create 1 frame long static animations for each pose )

The PoseSequence class used below :

 using UnityEngine;
 
 
 [System.Serializable]
 public class PoseSequence
 {
 
     public string Name = "";
     public string[] Poses = new string[0];
 
 
     public AnimationCurve Curve;
 
 }
 

The PoseManager Class :

     using UnityEngine;
 using System.Collections.Generic;
 
 // This component manages pose transitions.
 // A pose is a 1 frame long static animation, it must be added to the 'Animation' component to be usable.
 // The curves used by this component can be any length but must start at time 0 and the weight must stay between 0 and 1.
 public class PoseManager : MonoBehaviour
 {
 
     // The Legacy Animation component, note that the BoseBlender component must be on the same GameObject
     Animation _animation;
 
     // A pose sequence array for sequences that will be used often and don't need to be tweaked.
     // It's totally optionnal so you can remove it, the corresponding 'StartSequence' and 'GetPreset' methods if you don't need it.
     public PoseSequence[] Presets = new PoseSequence[ 0 ];
 
     // The current timer in the sequence
     float _timer;
 
     // The duration of the sequence and the curve and a boolean indicating if they differ.
     float _sequenceDuration, _curveDuration;
     bool _isDurationOverriden;
 
     // Stored weight to check if the curve changed direction since last frame, indicating that a new transition started
     float _lastWeight;
 
     // The index of the fading in pose
     int _index;
     // The scale of the weight to apply to the currently fading in pose
     float _weightScale;
 
     // Stored boolean representing the current direction of the curve
     int _curveDirection;
 
     // Is the current sequence looping
     bool _looping;
 
     // The curve used by the current sequence. The curve should start at time 0 for and its weight must stay between 0 and 1. Note that if you are trying to loop the sequence, the curve should be looping too ( ending at the same weight it starts )
     AnimationCurve _curve;
 
     // The animations representing the poses used by the current sequence
     AnimationState[] _states;
 
     List<AnimationState> _fadingOutStates;
     AnimationState _fadingInState;
 
     public delegate void DelegateOnPoseChanged ( string poseName );
     // An event called every time a pose reached its maximum weight. 
     // It's optionnal, use it if you want to react to a pose that has finished fading in.
     public event DelegateOnPoseChanged OnPoseChanged;
 
 
     // Fetching the Animation component
     void Start ()
     {
         _animation = GetComponent<Animation> ();
     }
 
     void Update ()
     {
         if ( _states == null ) return;
 
         _timer += Time.deltaTime;
 
         float deltaWeight = EvaluateCurve ();
 
         // Updates the weight if the sequences is still running
         if ( _states != null )
             UpdateWeight ( deltaWeight );
 
         if ( _timer >= _sequenceDuration )
             NextTransition (); 
     }
 
 
     // Remove this if you removed the Presets array
     public void StartPresetSequence ( string name )
     {
         StartPresetSequence ( name, false, 0f );
     }
 
     // Remove this if you removed the Presets array
     public void StartPresetSequence ( string name, float overrideDuration )
     {
         StartPresetSequence ( name, false, overrideDuration );
     }
 
     // Remove this if you removed the Presets array
     public void StartLoopingPresetSequence ( string name )
     {
         StartPresetSequence ( name, true, 0f );
     }
 
     // Remove this if you removed the Presets array
     public void StartLoopingPresetSequence ( string name, float overrideDuration )
     {
         StartPresetSequence ( name, true, overrideDuration );
     }
 
     // The poses strings represent the AnimationClip name, just like in the Animation component
     public void StartSequence ( AnimationCurve curve, params string[] poses )
     {
         StartSequence ( poses, curve, false, 0f );
     }
 
     public void StartSequence ( AnimationCurve curve, float overrideDuration, params string[] poses )
     {
         StartSequence ( poses, curve, false, overrideDuration );
     }
 
     public void StartLoopingSequence ( AnimationCurve curve, params string[] poses )
     {
         StartSequence ( poses, curve, true, 0f );
     }
 
     public void StartLoopingSequence ( AnimationCurve curve, float overrideDuration, params string[] poses )
     {
         StartSequence ( poses, curve, true, overrideDuration );
     }
 
     // Remove this if you removed the Presets array
     void StartPresetSequence ( string name, bool looping, float newDuration )
     {
         PoseSequence preset = GetPreset ( name );
 
         if ( preset == null ) return;
 
         StartSequence ( preset.Poses, preset.Curve, looping, newDuration );
     }
 
     void StartSequence ( string[] poses, AnimationCurve curve, bool looping, float overrideDuration )
     {
         if ( curve.keys.Length != poses.Length + 1 )
             throw new System.Exception ( "The curve must have n+1 keys where n is the number of poses. Keys = " + curve.keys.Length + ", poses : " + poses.Length );
 
         _states = new AnimationState[ poses.Length ];
 
         // Get AnimationStates from the Animation component
         for ( int i = 0 ; i < poses.Length ; i++ )
             _states[ i ] = _animation[ poses[ i ] ];
 
         _curve = curve;
         _looping = looping;
 
         // Get the curve's last key time
         _curveDuration = GetNativeCurveLength ( curve );
 
         // Check if the override duration makes sense.
         if ( overrideDuration > 0f && overrideDuration != _curveDuration )
         {
             _sequenceDuration = overrideDuration;
             _isDurationOverriden = true;
         }
         else
         {
             _sequenceDuration = _curveDuration;
             _isDurationOverriden = false;
         }
 
         _timer = 0f;
 
         FirstTransition ();
     }
 
     // Remove this if you removed the Presets array
     PoseSequence GetPreset ( string name )
     {
         for ( int i = 0 ; i < Presets.Length ; i++ )
             if ( Presets[ i ].Name == name )
                 return Presets[ i ];
 
         return null;
     }
 
     float GetNativeCurveLength ( AnimationCurve curve )
     {
         return curve.keys[ curve.keys.Length - 1 ].time;
     }
 
     // Shortcut to evaluate the curve at the current timer
     float EvaluateCurve ()
     {
         float clampedTimer = Mathf.Clamp ( _isDurationOverriden ? ( _timer * _curveDuration / _sequenceDuration ) : _timer, 0f, _curveDuration );
 
         return EvaluateCurve ( clampedTimer, true );
     }
 
     // Check direction and weight variations since last evaluation
     // If the curve changed direction, start transitionning to next pose in the array.
     float EvaluateCurve ( float time, bool recordWeight )
     {
         float currentWeight = _curve.Evaluate ( time );
 
         float deltaWeight = Mathf.Abs ( currentWeight - _lastWeight );
 
         int direction;
 
         if ( currentWeight > _lastWeight )
             direction = 1;
         else if ( currentWeight < _lastWeight )
             direction = -1;
         else direction = 0;
 
         if ( recordWeight )
         _lastWeight = currentWeight;
 
         if ( CheckIfCurveChangedDirection ( direction ) )
         {
             _curveDirection = direction;
             NextTransition ();
         }
 
         return deltaWeight;
     }
 
 
     bool CheckIfCurveChangedDirection ( int currentDirection )
     {
         // if the current direction has not been found, it just accepts the new one
         if ( _curveDirection == 0 )
         {
             _curveDirection = currentDirection;
             return false;
         }
 
         // if the curve is currently flat, the direction does not change
         if ( currentDirection == 0 || _curveDirection == currentDirection )
             return false;
 
         return true;
     }
 
     void GetCurveInitialDirection ()
     {
         float predictionTimer = .05f;
 
         // Try to find the first slope in the curve, in case it's starting flat
         // If no slope is found, an exception is thrown
         while ( _curveDirection == 0 )
         {
             EvaluateCurve ( predictionTimer, false );
             predictionTimer += .05f;
 
             if ( predictionTimer > _curveDuration )
                 throw new System.Exception ( "Flat Curves are not supported." );
         }
     }
 
     void FirstTransition ()
     {
         _index = 0;
 
         _curveDirection = 0;
 
         // Get the starting weight of the first pose according to the curve initial weight
         var weight = _curve.Evaluate ( 0f );
         _lastWeight = weight;
 
         GetCurveInitialDirection ();
 
         _fadingOutStates = new List<AnimationState> ();
 
         // Get all the currently active state in the Animation component to fade them out.
         foreach ( AnimationState state in _animation )
             if ( state.enabled )
                 _fadingOutStates.Add ( state );
 
         DoTransition ();
     }
 
     // Goes to the next transition. If the current transition was the last one, stops the sequence or resets it if the sequence is looping
     void NextTransition ()
     {
         // Call the OnPoseChanged event if a listener is registered.
         if ( OnPoseChanged != null )
             OnPoseChanged ( _fadingInState.name );
 
         for ( int i = 0 ; i < _fadingOutStates.Count ; i++ )
         {
             _fadingOutStates[ i ].enabled = false;
             _fadingOutStates[ i ].weight = 1f;
         }
 
         _fadingOutStates = new List<AnimationState> ();
         _fadingOutStates.Add ( _fadingInState );
 
         _index++;
 
         if ( _index >= _states.Length )
         {
             if ( _looping )
             {
                 _timer -= _sequenceDuration;
                 FirstTransition ();
             }
             else
             {
                 Stop ();
                 return;
             }
         }
 
         DoTransition ();
     }
 
     void DoTransition ()
     {
         _fadingInState = _states[ _index ];
 
         _fadingInState.enabled = true;
         _fadingInState.weight = 0f;
 
         _weightScale = _curveDirection > 0 ? 1f - _lastWeight : _lastWeight;
     }
 
     void UpdateWeight ( float deltaWeight )
     {
         // Add weight to the fading in pose factored by the weight scale since the weight of the curve when the pose started might not be 0 or 1
         _fadingInState.weight += deltaWeight * _weightScale;
 
         float sharedWeight = ( 1f - _fadingInState.weight ) / _fadingOutStates.Count;
 
         // Remove weight from all the fading out poses. If their weight reach 0, disable them.
         for ( int i = 0 ; i < _fadingOutStates.Count ; i++ )
         {
             var state = _fadingOutStates[ i ];
 
             // No need to remove weight if the state is disabled or if, for some reason, it's the currently fading in pose.
             if ( !state.enabled || state == _fadingInState ) continue;
 
             state.weight -= deltaWeight;
 
             if ( state.weight <= 0f )
             {
                 Debug.Log ( "State faded off : " + state.name );
                 state.enabled = false;
                 state.weight = 1f;
                 _fadingOutStates.Remove ( state );
             }
         }
     }
 
     void Stop ()
     {
         _states = null;
     }
 
 }

Comment

People who like this

0 Show 6 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Obsurveyor · Nov 21, 2015 at 06:55 PM 0
Share

Tenser, did you forget to post a component called "PoseBlender"? Your code works but with no blending, it doesn't seem be to complete.

avatar image Tenser · Nov 22, 2015 at 12:54 AM 0
Share

The PoseBlender component is the PoseManager, i renamed it but forgot to update the comments

avatar image Obsurveyor Tenser · Nov 22, 2015 at 01:00 AM 0
Share

Ahh. Well, I ended up figuring it out on my own. Maybe it's Unity 5.2.2f1 but your code, as-is, wouldn't blend between two poses. I had to change the Start() code slightly:

 void Start()
 {
     _animation = GetComponent<Animation>();
 
     if(_animation == null)
     {
         Debug.Log("Couldn't find Animation component");
         return;
     }
 
     foreach(AnimationState s in _animation)
     {
         s.enabled = false;
         s.speed = 0.0f;
         s.weight = 0.0f;
     }
 }

Thanks for posting, I think I can do something of my own, now that this has taught me how to blend 1 frame poses.

avatar image Tenser Obsurveyor · Nov 22, 2015 at 02:12 AM 0
Share

It's weird, I tested it ( well, maybe not as much as i should have ) and it was working.

The point of the start method was to be able to fade out of the current pose, which you can't with the fix you made

I'll try to look into it but i think i can make something better without using the animation component and those 1 frame animations so it would be more straight forward

Show more comments
avatar image Windan · Oct 08, 2016 at 05:42 PM 0
Share

Did you ever get this to work properly? Or do you know of any resources that would help me learn about this technique more in order to implement it myself?

Unity Answers is in Read-Only mode

Unity Answers content will be migrated to a new Community platform and we are aiming to launch a public beta by June 9. Please note, Unity Answers is now in read-only so we can prepare for the final data migration.

For more information and updates, please read our full announcement thread in the Unity Forum.

Follow this Question

Answers Answers and Comments

31 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Using a DLL with mecanim 0 Answers

Creating keyframes from poses in the scene view (with pictures for clarity) -1 Answers

Animation Window: How to scale keyframes to increase time? 9 Answers

Animation KeyFrames not showing in AnimationEditor 1 Answer

About keyframes,,, 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges