Describing Animations
Milease provides a series of extension functions for object
, with function names following the format: M<EasingFunctionName><EasingTypeName>
.
For example, a commonly used easing function is Quad
, and if you use the Out
easing type, the function name would be MQuadOut
.
Notably, when the easing type is In
, the function name only reflects the easing function itself, such as MQuad
.
For more information on easing functions and types, refer to other articles.
Constructing an Animator for Any Member
[offset] + [duration] / object.MQuad(x => x.member, startValue, endValue) - [blendingMode];
This is the basic statement to describe an animation. Here, offset
(delay), duration
, and blendingMode
are optional parameters.
Here's a simple example: suppose we want to gradually increase the AudioSource
volume from 0 to 1 over 0.5 seconds:
0.5f / MyAudioSource.MQuad(x => x.volume, 0f, 1f);
This line describes the animation operation. The first parameter is a MemberExpression
used to select which member of the object MyAudioSource
to operate on.
Likewise, to move the object ball
from one point to another after 1 second, taking 0.5 seconds to do so:
1f + 0.5f / ball.MQuad(x => x.position, Vector3.zero, Vector3.one);
Then, you can play the animation using any of the following methods:
- Call the
Play
function on the generated animator to insert it into the animation task list, which will play starting the next frame:
(1f + 0.5f / ball.MQuad(x => x.position, Vector3.zero, Vector3.one)).Play();
- If you need the properties to be updated immediately upon playing, use:
(1f + 0.5f / ball.MQuad(x => x.position, Vector3.zero, Vector3.one)).PlayImmidiate();
- If the above one-liner feels too long, you can also write:
MAni.Make(
1f + 0.5f / ball.MQuad(x => x.position, Vector3.zero, Vector3.one)
).Play();
Nested Animations
Each animation statement returns a MilInstantAnimator
. Through its fluent interface, animations can be combined and nested.
For example, consider this sequence of animations:
- The ball gradually changes from transparent to white.
- While the ball is changing color, the music volume fades in from 0 to 1.
- Once both operations are complete, the ball moves.
- Midway through the movement, the ball scales up.
- After all of the above, perform these in sequence: the camera's
fov
decreases, and the camera rotates.
You could nest them like this:
MAni.Make(
1f / ballSpriteRender.MQuad(x => x.color, Color.clear, Color.white),
1f / music.MQuad(x => x.volume, 0f, 1f)
).Then(
2f / ballTransform.MQuad(x => x.position, Vector3.zero, Vector3.one * 3f),
1f + 0.5f / ballTransform.MQuad(x => x.localScale, Vector3.one, Vector3.one * 2f)
).ThenOneByOne(
1f / camera.MQuad(x => x.fieldOfView, 6f, 4.5f),
1f / camera.MQuad(x => x.localEulerAngles, Vector3.zero, new Vector3(0, 0, 20f))
).Play();
The Then
method means the next group of animations will execute in parallel, but only after all previous animations have completed. The ThenOneByOne
method means the next animations will execute sequentially, again after previous ones finish.
There's also a method called And
, not shown in the example, which means the animations that follow will run in parallel with the previous group.
This structure mirrors natural language logic. For example:
The ball appeared and the music started, then the ball moved and got bigger, then the camera approached, then the camera rotated.
MilInstantAnimator
is a reference type. This means you can append new animation content to it later. However, this is not recommended — modifying an animator while it’s playing is undefined behavior. It may also increase your project’s maintenance burden.
Animations Without Start Values
At this point, you might wonder: if we want to animate the ball from its current position to a target, is the previous syntax too verbose?
That’s where a new animation description method comes in:
[offset] + [duration] / object.MQuad(x => x.member, targetValue.ToThis());
For example, if the ball’s current position is random, but we want it to move to (0, 0, 0):
(0.5f / ball.MQuad(x => x.position, Vector3.zero.ToThis())).Play();
Or if the ball’s current color is random, but we want it to become fully transparent:
(0.5f / ball.MQuad(x => x.color, Color.clear.ToThis())).Play();
Though this may seem equivalent to:
(0.5f / ball.MQuad(x => x.position, ball.position, Vector3.zero)).Play();
Using that version requires reconstructing the animator repeatedly. In contrast, using ToThis()
allows the animator to be reused continuously.