|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Collections.ObjectModel; |
| 4 | +using MLEM.Maths; |
| 5 | +using MLEM.Misc; |
| 6 | + |
| 7 | +namespace MLEM.Animations { |
| 8 | + /// <summary> |
| 9 | + /// A step animator is a simple class to facilitate multi-step, timed, and optionally looping "animations" in the form of event callbacks. |
| 10 | + /// A step animator supports multiple <see cref="Steps"/> which are executed in order in <see cref="Update"/>. |
| 11 | + /// </summary> |
| 12 | + /// <typeparam name="TObject">The object to animate, which is passed to all relevant events.</typeparam> |
| 13 | + public class StepAnimator<TObject> : GenericDataHolder { |
| 14 | + |
| 15 | + /// <summary> |
| 16 | + /// This step animator's steps. |
| 17 | + /// </summary> |
| 18 | + public readonly ReadOnlyCollection<Step> Steps; |
| 19 | + /// <summary> |
| 20 | + /// Whether this step animator is looping. |
| 21 | + /// </summary> |
| 22 | + public readonly bool Loop; |
| 23 | + |
| 24 | + /// <summary> |
| 25 | + /// The index of the <see cref="CurrentStep"/> in the <see cref="Steps"/> collection. |
| 26 | + /// This is -1 if the animator has not started yet or is finished. |
| 27 | + /// </summary> |
| 28 | + public int CurrentStepIndex { get; private set; } |
| 29 | + /// <summary> |
| 30 | + /// The currently executed step, or <see langword="null"/> if the animator has not started yet or is finished. |
| 31 | + /// </summary> |
| 32 | + public Step CurrentStep => this.CurrentStepIndex >= 0 && this.CurrentStepIndex < this.Steps.Count ? this.Steps[this.CurrentStepIndex] : null; |
| 33 | + /// <summary> |
| 34 | + /// The time into the <see cref="CurrentStep"/>. |
| 35 | + /// </summary> |
| 36 | + public TimeSpan CurrentStepTime { get; private set; } |
| 37 | + /// <summary> |
| 38 | + /// The total time that the <see cref="CurrentStep"/> should play for. |
| 39 | + /// </summary> |
| 40 | + public TimeSpan CurrentStepTotalTime { get; private set; } |
| 41 | + |
| 42 | + private readonly Random random; |
| 43 | + |
| 44 | + /// <summary> |
| 45 | + /// Creates a new step animator with the given settings. |
| 46 | + /// </summary> |
| 47 | + /// <param name="random">The random instance to use for determining the time for each <see cref="Step"/> with differing <see cref="Step.MinTime"/> and <see cref="Step.MaxTime"/> to play for.</param> |
| 48 | + /// <param name="steps">The steps to play.</param> |
| 49 | + /// <param name="loop">Whether the <paramref name="steps"/> should loop.</param> |
| 50 | + public StepAnimator(Random random, IList<Step> steps, bool loop = false) { |
| 51 | + this.random = random; |
| 52 | + this.Steps = new ReadOnlyCollection<Step>(steps); |
| 53 | + this.Loop = loop; |
| 54 | + this.CurrentStepIndex = -1; |
| 55 | + } |
| 56 | + |
| 57 | + /// <summary> |
| 58 | + /// Creates a new step animator with the given settings. |
| 59 | + /// </summary> |
| 60 | + /// <param name="steps">The steps to play.</param> |
| 61 | + /// <param name="loop">Whether the <paramref name="steps"/> should loop.</param> |
| 62 | + public StepAnimator(IList<Step> steps, bool loop = false) : this(new Random(), steps, loop) {} |
| 63 | + |
| 64 | + /// <summary> |
| 65 | + /// Creates a new step animator with the given settings. |
| 66 | + /// </summary> |
| 67 | + /// <param name="steps">The steps to play.</param> |
| 68 | + public StepAnimator(params Step[] steps) : this(steps, false) {} |
| 69 | + |
| 70 | + /// <summary> |
| 71 | + /// Updates this step animator, starting, updating and finishing its <see cref="Steps"/>. |
| 72 | + /// If this method returns <see langword="true"/>, this step animator has finished playing (which will never occur if there are any steps and <see cref="Loop"/> is <see langword="true"/>). |
| 73 | + /// </summary> |
| 74 | + /// <param name="obj">The object to animate, which is passed to all relevant events.</param> |
| 75 | + /// <param name="passed">The amount of time that has passed since the last time this method was called.</param> |
| 76 | + /// <returns>Whether this step animator has finished playing.</returns> |
| 77 | + public bool Update(TObject obj, TimeSpan passed) { |
| 78 | + if (this.CurrentStepIndex >= this.Steps.Count) |
| 79 | + return true; |
| 80 | + |
| 81 | + if (this.CurrentStepIndex < 0) |
| 82 | + this.Advance(obj); |
| 83 | + |
| 84 | + this.CurrentStepTime += passed; |
| 85 | + |
| 86 | + var timePercentage = Math.Min(1, this.CurrentStepTime.Ticks / (float) this.CurrentStepTotalTime.Ticks); |
| 87 | + this.CurrentStep?.Animation?.Invoke(this, this.CurrentStep, obj, timePercentage); |
| 88 | + |
| 89 | + return timePercentage >= 1 && this.Advance(obj); |
| 90 | + } |
| 91 | + |
| 92 | + /// <summary> |
| 93 | + /// Resets this step animator, returning back to the first <see cref="Step"/> in its <see cref="Steps"/> collection. |
| 94 | + /// </summary> |
| 95 | + /// <param name="obj">The object to animate, which is passed to all relevant events.</param> |
| 96 | + public void Reset(TObject obj) { |
| 97 | + this.CurrentStep?.Finished?.Invoke(this, this.CurrentStep, obj); |
| 98 | + this.CurrentStepIndex = -1; |
| 99 | + } |
| 100 | + |
| 101 | + private bool Advance(TObject obj) { |
| 102 | + this.CurrentStep?.Finished?.Invoke(this, this.CurrentStep, obj); |
| 103 | + |
| 104 | + this.CurrentStepIndex++; |
| 105 | + if (this.CurrentStepIndex >= this.Steps.Count) { |
| 106 | + if (!this.Loop) |
| 107 | + return true; |
| 108 | + this.CurrentStepIndex = 0; |
| 109 | + } |
| 110 | + |
| 111 | + this.CurrentStepTime = TimeSpan.Zero; |
| 112 | + this.CurrentStepTotalTime = this.CurrentStep == null ? TimeSpan.Zero : |
| 113 | + this.CurrentStep.MinTime == this.CurrentStep.MaxTime ? this.CurrentStep.MinTime : |
| 114 | + TimeSpan.FromTicks((long) this.random.NextSingle(this.CurrentStep.MinTime.Ticks, this.CurrentStep.MaxTime.Ticks)); |
| 115 | + |
| 116 | + this.CurrentStep?.Started?.Invoke(this, this.CurrentStep, obj); |
| 117 | + |
| 118 | + return false; |
| 119 | + } |
| 120 | + |
| 121 | + /// <summary> |
| 122 | + /// A step in a <see cref="StepAnimator{TObject}"/>. |
| 123 | + /// </summary> |
| 124 | + public class Step : GenericDataHolder { |
| 125 | + |
| 126 | + /// <summary> |
| 127 | + /// The minimum time that this step should be able to play for. |
| 128 | + /// If this is different from <see cref="MaxTime"/>, a random time between the two is selected each time the step is played. |
| 129 | + /// </summary> |
| 130 | + public readonly TimeSpan MinTime; |
| 131 | + /// <summary> |
| 132 | + /// The maximum time that this step should be able to play for. |
| 133 | + /// If this is different from <see cref="MinTime"/>, a random time between the two is selected each time the step is played. |
| 134 | + /// </summary> |
| 135 | + public readonly TimeSpan MaxTime; |
| 136 | + |
| 137 | + /// <summary> |
| 138 | + /// The animation function that is called every <see cref="Update"/> while this step is active. |
| 139 | + /// </summary> |
| 140 | + public AnimationFunction Animation; |
| 141 | + /// <summary> |
| 142 | + /// A function that is called when this step starts, before the first time <see cref="Animation"/> is called. |
| 143 | + /// </summary> |
| 144 | + public Action<StepAnimator<TObject>, Step, TObject> Started; |
| 145 | + /// <summary> |
| 146 | + /// A function that is called when this step finishes, after the last time <see cref="Animation"/> is called, or when this step is currently playing while the <see cref="StepAnimator{TObject}"/> is <see cref="Reset"/>. |
| 147 | + /// </summary> |
| 148 | + public Action<StepAnimator<TObject>, Step, TObject> Finished; |
| 149 | + |
| 150 | + /// <summary> |
| 151 | + /// Creates a new animator step with the given settings. |
| 152 | + /// </summary> |
| 153 | + /// <param name="time">The time that this step should play for.</param> |
| 154 | + /// <param name="varianceFactor">An optional variance factor, which is used to determine a <see cref="MinTime"/> and <see cref="MaxTime"/> by multiplying the <paramref name="time"/> with it and subtracting the result from the <see cref="MinTime"/> and adding the result to the <see cref="MaxTime"/>.</param> |
| 155 | + /// <param name="animation">The animation function that is called every <see cref="Update"/> while this step is active.</param> |
| 156 | + public Step(TimeSpan time, float varianceFactor, AnimationFunction animation = null) : this( |
| 157 | + time - TimeSpan.FromTicks((long) (time.Ticks * varianceFactor)), |
| 158 | + time + TimeSpan.FromTicks((long) (time.Ticks * varianceFactor)), |
| 159 | + animation) {} |
| 160 | + |
| 161 | + /// <summary> |
| 162 | + /// Creates a new animator step with the given settings. |
| 163 | + /// </summary> |
| 164 | + /// <param name="time">The time that this step should play for.</param> |
| 165 | + /// <param name="animation">The animation function that is called every <see cref="Update"/> while this step is active.</param> |
| 166 | + public Step(TimeSpan time, AnimationFunction animation = null) : this(time, time, animation) {} |
| 167 | + |
| 168 | + /// <summary> |
| 169 | + /// Creates a new animator step with the given settings. |
| 170 | + /// </summary> |
| 171 | + /// <param name="minTime">The minimum time that this step should be able to play for. If this is different from <paramref name="maxTime"/>, a random time between the two is selected each time the step is played.</param> |
| 172 | + /// <param name="maxTime">The maximum time that this step should be able to play for. If this is different from <paramref name="minTime"/>, a random time between the two is selected each time the step is played.</param> |
| 173 | + /// <param name="animation">The animation function that is called every <see cref="Update"/> while this step is active.</param> |
| 174 | + public Step(TimeSpan minTime, TimeSpan maxTime, AnimationFunction animation = null) { |
| 175 | + this.MinTime = minTime; |
| 176 | + this.MaxTime = maxTime; |
| 177 | + this.Animation = animation; |
| 178 | + } |
| 179 | + |
| 180 | + } |
| 181 | + |
| 182 | + /// <summary> |
| 183 | + /// An animation function used by <see cref="Step"/>. |
| 184 | + /// The animation function that is called every <see cref="Update"/> while a <see cref="Step"/> is active. |
| 185 | + /// </summary> |
| 186 | + public delegate void AnimationFunction(StepAnimator<TObject> animator, Step step, TObject obj, float timePercentage); |
| 187 | + |
| 188 | + } |
| 189 | +} |
0 commit comments