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 🙂