‘Simple’ property animation in Unity

Recently, I’ve spent a few hours with Unity’s new animation system, Mecanim. I wasn’t working on any complex animations, but only wanted to implement fading of a few elements in my scene. This turned out to be a real desaster. Why?

  • For simply fading a property in an out, I needed two animation clips and four states, with pretty sensitive transition setup (I wanted the fading to be interruptible, etc.)
  • The animations cannot go from the current property value to the target at a certain speed. They will always animate from start to end.
  • Adding a delay would require either to modify the animation clip or do even more complex state setup.

So, after fiddling with this for a while, I came to the conclusion that this kind of animation is better done in code (which seems to be what people in the forums think, too). I’ve set up a simple class for fading a property value, added the logic to my MonoBehaviour script, and had it working the way I wanted within 20 minutes. Go figure.

For anyone interested, here is the code. It could probably be improved in various ways, but it’s doing what I want for my project. Feel free to play with it.

ValueFader.cs:

 public struct FadeParameters
 {
   public float initialValue;
   public float maxValue;
   public float minValue;
   public float fadeInDuration;
   public float fadeOutDuration;
   public float fadeInDelay;
     // applies only when going from Idle to FadingIn
     // (not e.g. FadingOut -> FadingIn)
 }
 
 public class ValueFader
 {
   private enum FadeState
   {
     FadingIn,
     FadingOut,
     Idle
   }
 
   private float _remainingDelay;
   private float _currentValue;
   private FadeParameters _fadeParameters;
   private FadeState _currentFadeState = FadeState.Idle;
   private float _fadeInIncrement;
   private float _fadeOutIncrement;
   public float currentValue { get { return _currentValue; } }
 
   public ValueFader(FadeParameters fp)
   {
     _fadeParameters = fp;
     _fadeInIncrement =
       (_fadeParameters.maxValue - _fadeParameters.minValue)
       / _fadeParameters.fadeInDuration;
     _fadeOutIncrement =
       (_fadeParameters.maxValue - _fadeParameters.minValue)
       / _fadeParameters.fadeOutDuration;
     _currentValue = fp.initialValue;
     _remainingDelay = fp.fadeInDelay;
   }
 
   public void FadeIn()
   {
     if (_currentValue < _fadeParameters.maxValue)
     {
       _currentFadeState = FadeState.FadingIn;
     }
     else
     {
       SetIdle();
     }
   }
 
   public void FadeOut()
   {
     if (_currentValue > _fadeParameters.minValue)
     {
       _currentFadeState = FadeState.FadingOut;
     }
     else
     {
       SetIdle(); // might have been in FadingIn, during delay
     }
   }
 
   // Returns true, if currentValue was changed by this method
   public bool Update(float deltaTime)
   {
     if (_currentFadeState == FadeState.Idle)
     {
       return false;
     }
 
     bool retVal = false;
 
     if (_currentFadeState == FadeState.FadingIn)
     {
       if (_remainingDelay <= 0.0f)
       {
         _currentValue += _fadeInIncrement * deltaTime;
         if (_currentValue >= _fadeParameters.maxValue)
         {
           _currentValue = _fadeParameters.maxValue;
           _currentFadeState = FadeState.Idle;
         }
 
         retVal = true;
       }
       else
       {
         _remainingDelay -= deltaTime;
       }
     }
     else //if (_currentFadeState == FadeState.FadingOut)
     {
       _currentValue -= _fadeOutIncrement * deltaTime;
       if (_currentValue <= _fadeParameters.minValue)
       {
         _currentValue = _fadeParameters.minValue;
         SetIdle();
       }
 
       retVal = true;
     }
 
     return retVal;
   }
 
   private void SetIdle()
   {
     _currentFadeState = FadeState.Idle;
     _remainingDelay = _fadeParameters.fadeInDelay;
   }
 }
 

And here is how you would use it, from within a MonoBehavior script.

Set up the animation parameters like this, and store the ValueFader in a class member:

FadeParameters imageFP;
imageFP.fadeInDuration = 1.0f;
imageFP.fadeOutDuration = 0.7f;
imageFP.initialValue = 0.0f;
imageFP.maxValue = 0.1f;
imageFP.minValue = 0.0f;
imageFP.fadeInDelay = 1.0f;

_imageFader = new ValueFader(imageFP);  

How you control fading values in or out, depends entirely on your logic. You might want to do it based on some event:

void OnSomethingHappened()
 {
   ...
   if (startFadingIn)
   {
     _imageFader.FadeIn();
   }
   else if (startFadingOut)
   {
     _imageFader.FadeOut();
   }
 } 

You get the idea… In Update(), apply the animated value to a material color, or whatever you want to animate:

void Update()
{
  if (_imageFader.Update(Time.deltaTime))
  {
    _imageMaterial.SetColor(
      "_Color", new Color(1.0f, 1.0f, 1.0f, _imageFader.currentValue));
  }
  ...
 } 

 And that’s all 🙂