-
Notifications
You must be signed in to change notification settings - Fork 420
Transforms and tweening
A relatively extensive toolchain exists for applying transforms to Drawable
s. This includes not only the ability to perform common visual adjustments (fade, scale, rotate, colour), but also the ability to arbitrarily perform transforms on any field/property belonging to the Drawable
.
Internally, transforms are a state machine which is build using TransformSequence
. Multiple transforms can be chained and nested. This page attempts to provide a starting point for understanding how transforms can be used, but there should be plenty of room for exploration beyond this guide via experimentation.
A simple example which will fade a box into existence:
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(500);
Transforms can be chained in various ways. To run multiple transforms of different types at the same time value, simply chain the calls:
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(500)
.ScaleTo(new Vector2(2))
.RotateTo(90); // all three of these will be run in parallel.
To play one transform after another finishes, use .Then()
:
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(500).Then().FadeOut(500); // fade in then out, over 1 second.
To add a delay to a sequence, you can use .Delay(ms)
:
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(500).Then().Delay(200).FadeOut(500); // fade in then out, over 1.2 seconds, with a pause at full opacity.
For more complex cases, BeginDelayedSequence
and BeginAbsoluteSequence
can help to organise things:
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
using (box.BeginAbsoluteSequence(2000)) // nested calls will run from absolute clock time of 2000ms. by default this applies to all children too.
{
box.FadeIn(1000);
using (box.BeginDelayedSequence(1000)) // nested calls will be delayed by 1000ms. by default this applies to all children too.
box.FadeOut(1000);
}
To loop a certain transform sequence, append either of .Loop(millisecondsPause)
, or .Loop(millisecondsPause, times)
, depending on how you want the loop to behave:
This will loop the transform sequence indefinitely, with a defined pause between each iteration of the loop, until it's interrupted by one of the interruption methods.
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
// indefinitely resizes the box to 75px, then to 50px, with a pause of 250ms at the end of each iteration
box.ResizeTo(new Vector2(75), 500).Then().ResizeTo(new Vector2(50), 250).Loop(250);
Just like the above, except that it will loop for a defined number of times, not indefinitely.
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
// for 5 times, resize the box to 75px, then to 50px, with a pause of 250ms at the end of each iteration
box.ResizeTo(new Vector2(75), 500).Then().ResizeTo(new Vector2(50), 250).Loop(250, 5);
All specific transform methods such as .FadeIn(...)
/.FadeColour(...)
are methods defined in extension classes that delegate to the core method .TransformTo()
, which accepts the name of the member to transform/tween, and the remaining arguments for transforming (duration, easing, etc.).
And that doesn't apply only to the Drawable
base class, you can apply transforms to any member belonging to your class as long as it inherits Drawable
, and as long as the member is not static
, and not a readonly
field or getter-only/setter-only property:
public class SpecialBox : Box
{
public double SpecialProperty { get; set }
}
var box = new SpecialBox { Size = new Vector2(50), SpecialProperty = 1.0 }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.TransformTo(nameof(SpecialProperty), 10.0, 1000); // transform SpecialProperty from current value to value 10.0, in 1 second.
For wrapping it in an extensions class:
public static class SpecialBoxExtensions
{
public static TransformSequence<T> TweenSpecialPropertyTo<T>(this T specialBox, double newValue, double duration, Easing easing = Easing.None)
where T : SpecialBox
=> specialBox.TweenSpecialPropertyTo(newValue, duration, new DefaultEasingFunction(easing));
public static TransformSequence<T> TweenSpecialPropertyTo<T>(this TransformSequence<T> t, double newValue, double duration, Easing easing = Easing.None)
where T : SpecialBox
=> specialBox.TweenSpecialPropertyTo(newValue, duration, new DefaultEasingFunction(easing));
public static TransformSequence<T> TweenSpecialPropertyTo<T, TEasing>(this T specialBox, double newValue, double duration, in TEasing easing)
where T : SpecialBox
where TEasing : IEasingFunction
=> specialBox.TransformTo(nameof(SpecialBox.SpecialProperty), newValue, duration, easing);
public static TransformSequence<T> TweenSpecialPropertyTo<T, TEasing>(this TransformSequence<T> t, double newValue, double duration, in TEasing easing)
where T : SpecialBox
where TEasing : IEasingFunction
=> t.Append(o => o.TweenSpecialPropertyTo(newValue, duration, easing));
}
box.TweenSpecialPropertyTo(10.0, 1000);
box.FadeInFromZero(500)
.TweenSpecialPropertyTo(10.0, 1000)
.Then().FadeOut(500);
In cases where transforms are to be run every frame, it is highly recommended to use interpolation instead:
public override void Update()
{
// useful if targetWidth is a moving target which need to be tracked, for example.
box.Width = Interpolation.ValueAt(Clock.ElapsedFrameTime, box.Width, targetWidth, 0, 200, Easing.OutQuint);
}
Transforms can be interrupted during their application, by finishing them immediately on current time, or removing them and leaving the transformed property at its current state, or rewinding it back to the beginning of the transform.
To finish transforms immediately, use FinishTransforms
on the drawable whose transforms are still being processed.
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(1000);
// after 0.5 seconds, finish transforms applied to the box.
Scheduler.AddDelay(() =>
{
// by default, this only applies to transforms applied to the box itself,
// pass `true` to finish transforms of children as well.
box.FinishTransforms();
}, 500);
To remove transforms, use ClearTransforms
on the drawable whose transforms are still being processed.
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(1000);
// after 0.5 seconds, remove transforms from the box.
Scheduler.AddDelay(() =>
{
// by default, this only applies to transforms applied to the box itself,
// pass `true` to clear transforms of children as well.
box.ClearTransforms();
}, 500);
To remove transforms starting at a certain time, use ClearTransformsAfter
instead.
box.FadeInFromZero(500).Then().FadeOut(500);
// after 0.5 seconds, remove transforms at current time from the box.
Scheduler.AddDelay(() =>
{
// by default, this only applies to transforms applied to the box itself,
// pass `true` to clear transforms of children as well.
box.ClearTransformsAfter(Time.Current);
}
- Note that
ClearTransformsAfter(time)
actually removes transforms starting at the given time, not after it. This was a mistake in naming and will be resolved soon.
Removing transforms will not revert them, the drawable will remain at its current state.
To revert the transform applied to the Drawable
, or apply them from a given absolute clock time before removing, use ApplyTransformsAt
.
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(1000);
// after 0.5 seconds, revert the transform and remove it from the box.
Scheduler.AddDelay(() =>
{
// by default, this only applies to transforms applied to the box itself,
// pass `true` to clear transforms of children as well.
box.ApplyTransformsAt(Time.Current - 500);
box.ClearTransforms();
}, 500);
// TODO
In cases the default Easing
functions aren't what you want for some transforms, you can define your own IEasingFunction
and pass it to the transforms:
public readonly struct SpecialEasingFunction : IEasingFunction
{
public double ApplyEasing(double time) => time;
}
var box = new Box { Size = new Vector2(50) }
Add(box); // the target of transforms must always be in the draw hierarchy and loaded before operating on it.
box.FadeInFromZero(1000, new SpecialEasingFunction());
-
IEasingFunction.ApplyEasing
accepts a time value in the range [0-1], and returns an eased value of it. For examples, you can refer to theDefaultEasingFunction
implementation.
Some basic things to note:
- Transforming before a drawable is loaded (ie.
LoadComplete
) will cause the transforms to play out immediately. This is due to the drawable not yet having a clock to work with. If you must queue transforms before load, make sure to use aSchedule(() => {})
call. - Transforms are not free and should not be run every frame. Please use interpolation for such cases.
- Generally, we recommend not operating on oneself with transforms (ie.
this.FadeIn()
). This is due to the potential of conflicts between internal and external calls, which could overwrite or cause unexpected behaviour. Create a private nested container and operate on that instead.
- Create your first project
- Learning framework key bindings
- Adding resource stores
- Adding custom key bindings
- Adding custom fonts