Calm, tuned motion out of the box
Every element has a show and a hide preset, chosen in the Elements tab. Entrances ease out, exits are shorter and ease in, and every show and hide fades, so nothing pops in or out.
The ten presets
Each card loops: show, hold, hide.
What makes them feel good
Base pose
Each object's resting position, scale and alpha are recorded once. Every preset starts from it and returns to it, so repeated or interrupted animations never drift.
No skipped frames
A long frame, like the one where a scene loads, slows an animation slightly instead of jumping past half of it.
One animation per channel
A new animation stops older ones driving the same thing (position, scale, alpha). A button pop doesn't stop a slide; a new show stops a running hide.
No flash
Right after its scene loads, an element is put in its show start pose, in the same frame as Populate.
Overlapping transitions
In a screen switch, the old state hides while the new one shows: no empty frame in between.
Unscaled time
UI keeps animating while the game is paused (Time.timeScale = 0).
Override the animation for one call
UiTransition replaces the presets for a single ShowState or HideUI call, without changing the elements' configuration. Shared elements stay still either way.
// Show the Store with a zoom this time, whatever its elements are configured to do.
await UiManager.ShowState<StoreUiState>(context, transition: UiTransition.ShowWith(TweenPresets.ScaleKey));
// Close the top state by sliding it out to the right.
await UiManager.HideUI(UiTransition.HideWith(TweenPresets.SlideRightKey));
// Both sides at once: the new screen enters moving left, the old screen leaves moving left.
await UiManager.ShowStateByKey("ProfileUiState", context, false,
new UiTransition(TweenPresets.SlideLeftKey, TweenPresets.SlideLeftKey));Recipe: tabs that slide in the direction you tap
// Moving to a tab on the right slides everything left (like swiping), and the other way round.
private static UiTransition GetTabTransition(ShowcaseTab from, ShowcaseTab to)
{
if (from == to)
{
return UiTransition.Default;
}
string preset = (int)to > (int)from ? TweenPresets.SlideLeftKey : TweenPresets.SlideRightKey;
return new UiTransition(preset, preset);
}Button feedback
The pops and shake also work on any transform, without fading, as feedback:
await TweenPresets.ClickPop(buyButton.transform); // quick 1.08x pop
await TweenPresets.SettlePop(coinIcon.transform); // gentle 1.04x pulse
await TweenPresets.ShakeLocal(buyButton.transform); // "no": not enough coinsYour own animation
ShowAsync, HideAsync and PrepareForShow are virtual. Run extra motion next to the preset, or replace it completely. Tween.RunAsync gives you the same interruptible, frame-capped loop the presets use.
public class StoreUiElement : UiElement
{
[SerializeField] private RectTransform[] cards;
public override void PrepareForShow()
{
base.PrepareForShow();
for (int i = 0; i < cards.Length; i++)
{
cards[i].localScale = Vector3.zero;
}
}
public override async Task ShowAsync(string presetKey = null)
{
Task screen = base.ShowAsync(presetKey);
for (int i = 0; i < cards.Length; i++)
{
RectTransform card = cards[i];
await Tween.RunAsync(card, 0.18f, TweenCurves.EaseOut, true, CancellationToken.None, null, eased =>
{
card.localScale = Vector3.one * eased;
});
}
await screen;
}
}The showcase app adds Inspector components on top of these hooks: RevealOnShow, StaggeredReveal, FillBar, CountUpText, LoopMotion, PressFeedback and more. Designers add them to objects and the element plays them with its preset. They live in the sample project, so copy the ones you like. See Showcase.