A game framework written with osu! in mind.
at master 174 lines 6.1 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.Linq; 7using osu.Framework.Caching; 8 9namespace osu.Framework.Graphics.Animations 10{ 11 /// <summary> 12 /// Represents a generic, frame-based animation. Inherit this class if you need custom animations. 13 /// </summary> 14 /// <typeparam name="T">The type of content in the frames of the animation.</typeparam> 15 public abstract class Animation<T> : AnimationClockComposite, IFramedAnimation 16 { 17 /// <summary> 18 /// The duration in milliseconds of a newly added frame, if no duration is explicitly specified when adding the frame. 19 /// Defaults to 60fps. 20 /// </summary> 21 public double DefaultFrameLength = 1000.0 / 60.0; 22 23 private readonly List<FrameData<T>> frameData; 24 25 /// <summary> 26 /// The number of frames this animation has. 27 /// </summary> 28 public int FrameCount => frameData.Count; 29 30 public int CurrentFrameIndex { get; private set; } 31 32 public T CurrentFrame => frameData[CurrentFrameIndex].Content; 33 34 private readonly Cached currentFrameCache = new Cached(); 35 36 /// <summary> 37 /// Construct a new animation which loops by default. 38 /// </summary> 39 /// <param name="startAtCurrentTime">Whether the current clock time should be assumed as the 0th animation frame.</param> 40 protected Animation(bool startAtCurrentTime = true) 41 : base(startAtCurrentTime) 42 { 43 frameData = new List<FrameData<T>>(); 44 Loop = true; 45 } 46 47 /// <summary> 48 /// Displays the frame with the given zero-based frame index. 49 /// </summary> 50 /// <param name="frameIndex">The zero-based index of the frame to display.</param> 51 public void GotoFrame(int frameIndex) 52 { 53 Seek(frameData[Math.Clamp(frameIndex, 0, frameData.Count)].DisplayStartTime); 54 } 55 56 /// <summary> 57 /// Adds a new frame with the given content and display duration (in milliseconds) to this animation. 58 /// </summary> 59 /// <param name="content">The content of the new frame.</param> 60 /// <param name="displayDuration">The duration the new frame should be displayed for.</param> 61 public void AddFrame(T content, double? displayDuration = null) 62 { 63 AddFrame(new FrameData<T> 64 { 65 Duration = displayDuration ?? DefaultFrameLength, // 60 fps by default 66 Content = content, 67 }); 68 } 69 70 public void AddFrame(FrameData<T> frame) 71 { 72 var lastFrame = frameData.LastOrDefault(); 73 74 frame.DisplayStartTime = lastFrame.DisplayEndTime; 75 Duration += frame.Duration; 76 77 frameData.Add(frame); 78 79 OnFrameAdded(frame.Content, frame.Duration); 80 } 81 82 /// <summary> 83 /// Adds a new frame for each element in the given enumerable. Every frame will be displayed for 1/60th of a second. 84 /// </summary> 85 /// <param name="contents">The contents to use for creating new frames.</param> 86 public void AddFrames(IEnumerable<T> contents) 87 { 88 foreach (var t in contents) 89 AddFrame(t); 90 } 91 92 /// <summary> 93 /// Adds a new frame for each element in the given enumerable. Every frame will be displayed for the given number of milliseconds. 94 /// </summary> 95 /// <param name="frames">The contents and display durations to use for creating new frames.</param> 96 public void AddFrames(IEnumerable<FrameData<T>> frames) 97 { 98 foreach (var t in frames) 99 AddFrame(t.Content, t.Duration); 100 } 101 102 /// <summary> 103 /// Removes all frames from this animation. 104 /// </summary> 105 public void ClearFrames() 106 { 107 frameData.Clear(); 108 109 Duration = 0; 110 CurrentFrameIndex = 0; 111 } 112 113 /// <summary> 114 /// Displays the given contents. 115 /// </summary> 116 /// <remarks>This method will only be called after <see cref="OnFrameAdded(T, double)"/> has been called at least once.</remarks> 117 /// <param name="content">The content that will be displayed.</param> 118 protected abstract void DisplayFrame(T content); 119 120 /// <summary> 121 /// Called whenever a new frame was added to this animation. 122 /// </summary> 123 /// <param name="content">The content of the new frame.</param> 124 /// <param name="displayDuration">The display duration of the new frame.</param> 125 protected virtual void OnFrameAdded(T content, double displayDuration) 126 { 127 } 128 129 protected override void Update() 130 { 131 base.Update(); 132 133 if (frameData.Count == 0) return; 134 135 updateFrameIndex(); 136 137 if (!currentFrameCache.IsValid) 138 updateCurrentFrame(); 139 } 140 141 private void updateFrameIndex() 142 { 143 switch (PlaybackPosition.CompareTo(frameData[CurrentFrameIndex].DisplayStartTime)) 144 { 145 case -1: 146 while (CurrentFrameIndex > 0 && PlaybackPosition < frameData[CurrentFrameIndex].DisplayStartTime) 147 { 148 CurrentFrameIndex--; 149 currentFrameCache.Invalidate(); 150 } 151 152 break; 153 154 case 1: 155 while (CurrentFrameIndex < frameData.Count - 1 && PlaybackPosition >= frameData[CurrentFrameIndex].DisplayEndTime) 156 { 157 CurrentFrameIndex++; 158 currentFrameCache.Invalidate(); 159 } 160 161 break; 162 } 163 } 164 165 private void updateCurrentFrame() 166 { 167 DisplayFrame(CurrentFrame); 168 169 UpdateSizing(); 170 171 currentFrameCache.Validate(); 172 } 173 } 174}