Skip to content
Damnae edited this page Dec 6, 2020 · 3 revisions

Keyframes Basics

osu! uses Commands to animate sprites. These have the format start time, end time, start value, end value. When you have multiple commands in a row, this translates into repeating yourself a lot:

sprite.Fade(0, 1000, 0, .5f);
sprite.Fade(1000, 2000, .5f, .7f);
sprite.Fade(2000, 3000, .7f, .3f);
sprite.Fade(3000, 4000, .3f, .1f);
sprite.Fade(4000, 5000, .1f, 0);

Keyframes are simpler and just use time, value. The same set of commands would look like this:

opacity.Add(0, 0)
    .Add(1000, .5f)
    .Add(2000, .7f)
    .Add(3000, .3f)
    .Add(4000, .1f)
    .Add(5000, 0);

Once you have defined a set of keyframes, you'll want to convert them to commands. This is what the ForEachPair method is for:

opacity.ForEachPair((start, end) => sprite.Fade(start.Time, end.Time, start.Value, end.Value));

Initialization

The constructor to create keyframed values looks like this:

var opacity = new KeyframedValue<float>(interpolate);

The interpolate parameter tells the KeyframedValue how to interpolate values between keyframes. It can be left to null if you aren't going to call the ValueAt(double time) method.

Interpolating functions can be found in StorybrewCommon.Animations.InterpolatingFunctions:

var opacity = new KeyframedValue<float>(InterpolatingFunctions.Float);

Simplifying Keyframes

Keyframes are mainly used to simplify large amount of commands. You can see an example of that in the spectrum scripts, Spectrum and RadialSpectrum.

This simplification is based on the Ramer–Douglas–Peucker algorithm; the main idea is to take the value furthest away from the line, as long as it is farther than a threshold. Values that are close enough can be removed:

This is done by the methods Simplify1dKeyframes, Simplify2dKeyframes, Simplify3dKeyframes. The one you want to use depends on how many parameters the command would take:

  • MoveX, MoveY, Rotate, Scale and Fade takes one value so you'd use Simplify1dKeyframes
  • Move and ScaleVec take two so you'd use Simplify2dKeyframes;
  • Color takes three, so you'd use Simplify3dKeyframes

Simplifying the keyframes defined in the example above would look like this:

opacity.Simplify1dKeyframes(tolerance, v => v);

The tolerance parameter is the distance threshold from which keyframes can be removed.

The v => v part is used to convert the keyframes values to a float that the method works with. Since the values are already floats, there is nothing to do. The same thing usually applies to Simplify2dKeyframes, where the values needed are already Vector2. If you were simplifying colors, you'd have to convert from Color4 to Vector3:

colors.Simplify3dKeyframes(tolerance, v => new Vector3(v.R, v.G, v.B));

Complete Opacity Example

using StorybrewCommon.Animations;
using StorybrewCommon.Scripting;

namespace StorybrewScripts
{
    public class OpacitySample : StoryboardObjectGenerator
    {
        public override void Generate()
        {
            var sprite = GetLayer("").CreateSprite("image.png");
            var opacity = new KeyframedValue<float>(null);
            opacity.Add(0, 0)
                .Add(1000, .5f)
                .Add(2000, .7f)
                .Add(3000, .3f)
                .Add(4000, .1f)
                .Add(5000, 0);
            opacity.Simplify1dKeyframes(.3f, v => v);
            opacity.ForEachPair((start, end) => sprite.Fade(start.Time, end.Time, start.Value, end.Value));
        }
    }
}