SlowMo.cs
using System;
namespace SquishyTerry;
/// <summary>
/// Game-wide slow motion for big moments. <see cref="Trigger"/> dips time to <see cref="MinScale"/>
/// almost instantly, holds it briefly, then eases back to normal speed.
/// <para>
/// Consumers read <see cref="Scale"/> (1 = normal): the face simulation runs that fraction of its
/// steps and the combo timer runs at that rate, so slow motion never costs the player their combo.
/// (The audio is a live synth stream, so it isn't pitched: that would drift its buffer and warble.)
/// </para>
/// </summary>
public static class SlowMo
{
const float MinScale = 0.25f;
const float DropTime = 0.06f; // snap into slow motion
const float HoldTime = 0.28f; // stay there
const float RecoverTime = 0.4f; // ease back to full speed
static float _t = 999f;
static float _lastUpdate = -1f;
/// <summary>Current time scale, 1 = normal speed.</summary>
public static float Scale { get; private set; } = 1f;
/// <summary>0 at normal speed, 1 at full slow motion (for pitch and effects).</summary>
public static float Amount => (1f - Scale) / (1f - MinScale);
public static void Trigger() => _t = 0f;
/// <summary>Advance by real time; safe to call from several components in the same frame.</summary>
public static void Update( float realNow, float realDelta )
{
if ( realNow == _lastUpdate ) return;
_lastUpdate = realNow;
_t += realDelta;
float slow;
if ( _t < DropTime ) slow = _t / DropTime;
else if ( _t < DropTime + HoldTime ) slow = 1f;
else
{
float x = Math.Clamp( (_t - DropTime - HoldTime) / RecoverTime, 0f, 1f );
slow = 1f - x * x * (3f - 2f * x);
}
Scale = 1f - (1f - MinScale) * slow;
}
}