@Bunny83 I got what I needed from PraetorBlue’s answer.
You can see how I use it below.
I don’t think what I’m doing is so fundamentally wrong though, it seems useful to me 
The Phase class and its variations :
using System;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;
[Serializable]
public abstract class Phase<T>
{
public void Reset()
{
ElapsedTime = 0f;
}
public float ElapsedTime { get; private set; }
public float ExecuteTime = 1f;
public float RemainingTime => ExecuteTime - ElapsedTime;
public float CompletionPercent => Mathf.Clamp01(ElapsedTime/ ExecuteTime);
public bool HasFinishedRun => ElapsedTime >= ExecuteTime;
public T DoRun(float _currentTime)
{
ElapsedTime = _currentTime;
return Run();
}
protected abstract T Run();
public void MasterEnter()
{
// Debug.Log("Enter");
ElapsedTime = 0f;
Enter();
}
public void MasterExit()
{
Exit();
}
protected virtual void Enter()
{
}
protected virtual void Exit()
{
// Debug.Log("Exit");
}
public virtual void Init()
{
}
}
[Serializable]
public class Phase_Bool : Phase<bool>
{
private bool Consumed;
protected override void Enter()
{
Consumed = false;
}
protected override bool Run()
{
var saved = !Consumed;
if (Consumed == false)
{
Consumed = true;
}
return saved;
}
}
[Serializable]
public class Phase_Float : Phase<float>
{
[HorizontalGroup("A")] public float Start;
[HorizontalGroup("A")] public float End;
public EasingFunctions.Functions Function;
private EasingFunctions.EasingFunc Func;
public override void Init()
{
Func = EasingFunctions.GetEasingFunction(Function);
}
protected override float Run()
{
return Func(Start, End, ElapsedTime / ExecuteTime);
}
}
The phaser (the class that plays each phases - I create them in the inspector)
// 8:13 PMFeast
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class Phaser<P, T> where P : Phase<T>
{
public float Delay = 0f;
public PlayMode Mode = PlayMode.Once;
public int CurrentPhaseIndex { get; private set; } = 0;
public List<P> MyPhases = new List<P>();
public float TotalTime { get; private set; }
public float PhaseTime { get; private set; }
public P CurrentPhase => MyPhases[CurrentPhaseIndex];
private bool IsInit;
public Phaser()
{
Init();
}
public enum PlayMode
{
Once = 0,
Loop = 1,
}
public void Init()
{
Reset();
if (IsInit == false)
{
IsInit = true;
foreach (var p in MyPhases)
{
p.Init();
}
}
}
public void Reset()
{
IsFirstRun = true;
TotalTime = 0f;
PhaseTime = 0f;
CurrentPhaseIndex = 0;
foreach (var p in MyPhases)
{
p.Reset();
}
}
private bool IsFirstRun = true;
/// <summary>
/// Returns isRunning.
/// </summary>
public bool Run(out T _result)
{
_result = default;
var phaseCount = MyPhases.Count;
switch (Mode)
{
case PlayMode.Once:
if (CurrentPhaseIndex >= phaseCount)
{
return false;
}
break;
case PlayMode.Loop:
CurrentPhaseIndex %= phaseCount;
break;
default:
throw new ArgumentOutOfRangeException();
}
if (IsInit == false || phaseCount == 0)
{
return false;
}
if (Delay > 0f && TotalTime <= Delay)
{
TotalTime += Time.deltaTime;
return true; // we're playing... just delayed
}
// When the first phase starts we should call it.
if (CurrentPhaseIndex == 0 && IsFirstRun)
{
IsFirstRun = false;
CurrentPhase.MasterEnter();
}
TotalTime += Time.deltaTime;
PhaseTime += Time.deltaTime;
if (CurrentPhase.HasFinishedRun)
{
CurrentPhase.MasterExit();
PhaseTime -= CurrentPhase.ExecuteTime;
CurrentPhaseIndex += 1;
// Debug.Log($"CurrentPhaseIndex: {CurrentPhaseIndex}");
if (CurrentPhaseIndex >= phaseCount && Mode != PlayMode.Loop)
{
return false;
}
if (Mode == PlayMode.Loop)
{
CurrentPhaseIndex %= phaseCount;
}
OnNewPhase();
CurrentPhase.MasterEnter();
}
_result = CurrentPhase.DoRun(PhaseTime);
return true;
}
public void OnNewPhase()
{
}
}
Here’s how I use it, in this case it’s to create a prefab every x seconds depending on the time I set in the inspector.
// 9:12 PMFeast
using System;
using UnityEngine;
public class Impl : MonoBehaviour
{
public Phaser<Phase_Bool, bool> Phaser = new Phaser<Phase_Bool, bool>();
public GameObject Prefab;
public Transform Target;
public bool DoUpdate;
private void Awake()
{
Phaser.Init();
}
void Update()
{
if (!DoUpdate) return;
var playing = Phaser.Run(out var phaseResult);
if (playing)
{
if (phaseResult)
Instantiate(Prefab, Target.position, Quaternion.identity);
Debug.Log($"myFloat: {phaseResult} @ {Phaser.PhaseTime} ------- Total time: {Phaser.TotalTime}");
}
else
{
Debug.LogError($"myFloat: {phaseResult} @ {Phaser.PhaseTime} ------- Total time: {Phaser.TotalTime}");
}
}
}
Another use case, animating the scale of a Transform:
public class Impl2 : MonoBehaviour
{
public Phaser<Phase_Float, float> Phaser = new Phaser<Phase_Float, float>();
public Transform Target;
private void Awake()
{
Phaser.Init();
}
void Update()
{
var playing = Phaser.Run(out var phaseResult);
if (playing)
{
Target.localScale = Vector3.one * phaseResult;
}
else
{
}
}
}