A game framework written with osu! in mind.
at master 319 lines 11 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 NUnit.Framework; 6using osu.Framework.Allocation; 7using osu.Framework.Graphics; 8using osu.Framework.Graphics.Animations; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.Sprites; 11using osu.Framework.Graphics.Textures; 12using osu.Framework.IO.Stores; 13using osu.Framework.Testing; 14using osu.Framework.Timing; 15 16namespace osu.Framework.Tests.Visual.Sprites 17{ 18 public class TestSceneAnimation : FrameworkTestScene 19 { 20 private SpriteText timeText; 21 22 private ManualClock clock; 23 24 private TestAnimation animation; 25 private Container animationContainer; 26 27 [Resolved] 28 private FontStore fontStore { get; set; } 29 30 [SetUpSteps] 31 public void SetUpSteps() 32 { 33 AddStep("load container", () => 34 { 35 Children = new Drawable[] 36 { 37 animationContainer = new Container 38 { 39 RelativeSizeAxes = Axes.Both, 40 Clock = new FramedClock(clock = new ManualClock()), 41 }, 42 timeText = new SpriteText { Text = "Animation is loading..." } 43 }; 44 }); 45 46 loadNewAnimation(); 47 48 AddStep("Reset clock", () => clock.CurrentTime = 0); 49 } 50 51 [Test] 52 public void TestFrameSeeking() 53 { 54 AddAssert("frame count is correct", () => animation.FrameCount == TestAnimation.LOADABLE_FRAMES); 55 AddUntilStep("wait for frames to pass", () => animation.CurrentFrameIndex > 10); 56 AddStep("stop animation", () => animation.Stop()); 57 AddAssert("is stopped", () => !animation.IsPlaying); 58 59 AddStep("goto frame 60", () => animation.GotoFrame(60)); 60 AddAssert("is at frame 60", () => animation.CurrentFrameIndex == 60); 61 62 AddStep("goto frame 30", () => animation.GotoFrame(30)); 63 AddAssert("is at frame 30", () => animation.CurrentFrameIndex == 30); 64 65 AddStep("goto frame 60", () => animation.GotoFrame(60)); 66 AddAssert("is at frame 60", () => animation.CurrentFrameIndex == 60); 67 68 AddStep("start animation", () => animation.Play()); 69 AddUntilStep("continues to frame 70", () => animation.CurrentFrameIndex == 70); 70 } 71 72 [Test] 73 public void TestStartFromCurrentTime() 74 { 75 AddAssert("Animation is near start", () => animation.PlaybackPosition < 1000); 76 77 AddWaitStep("Wait some", 20); 78 79 loadNewAnimation(); 80 81 AddAssert("Animation is near start", () => animation.PlaybackPosition < 1000); 82 } 83 84 [Test] 85 public void TestStoppedAnimationIsAtZero() 86 { 87 loadNewAnimation(postLoadAction: a => a.Stop()); 88 AddAssert("Animation is at start", () => animation.PlaybackPosition == 0); 89 } 90 91 [Test] 92 public void TestStoppedAnimationIsAtSpecifiedFrame() 93 { 94 loadNewAnimation(postLoadAction: a => a.GotoAndStop(2)); 95 AddAssert("Animation is at specific frame", () => animation.PlaybackPosition == 500); 96 } 97 98 [Test] 99 public void TestPauseThenResume() 100 { 101 loadNewAnimation(false, postLoadAction: a => a.Stop()); 102 103 AddWaitStep("wait some", 10); 104 105 AddStep("play", () => animation.Play()); 106 107 AddAssert("time is near start", () => animation.CurrentFrameIndex < 2); 108 } 109 110 [Test] 111 public void TestStartFromOngoingTime() 112 { 113 AddWaitStep("Wait some", 20); 114 115 loadNewAnimation(false); 116 117 AddAssert("Animation is not near start", () => animation.PlaybackPosition > 1000); 118 } 119 120 [Test] 121 public void TestSetCustomClockWithCurrentTime() 122 { 123 AddAssert("Animation is near start", () => animation.PlaybackPosition < 1000); 124 125 AddUntilStep("Animation is not near start", () => animation.PlaybackPosition > 1000); 126 127 double posBefore = 0; 128 129 AddStep("store position", () => posBefore = animation.PlaybackPosition); 130 131 AddStep("Set custom clock", () => animation.Clock = new FramedOffsetClock(null) { Offset = 10000 }); 132 133 AddAssert("Animation continued playing at current position", () => animation.PlaybackPosition - posBefore < 1000); 134 } 135 136 [Test] 137 public void TestSetCustomClockWithOngoingTime() 138 { 139 loadNewAnimation(false); 140 141 AddAssert("Animation is near start", () => animation.PlaybackPosition < 1000); 142 143 AddUntilStep("Animation is not near start", () => animation.PlaybackPosition > 1000); 144 145 AddStep("Set custom clock", () => animation.Clock = new FramedOffsetClock(null) { Offset = 10000 }); 146 147 AddAssert("Animation is not near start", () => animation.PlaybackPosition > 1000); 148 } 149 150 [Test] 151 public void TestJumpForward() 152 { 153 AddStep("Jump ahead by 10 seconds", () => clock.CurrentTime += 10000); 154 AddUntilStep("Animation seeked", () => animation.PlaybackPosition >= 10000); 155 } 156 157 [Test] 158 public void TestJumpBack() 159 { 160 AddStep("Jump ahead by 10 seconds", () => clock.CurrentTime += 10000); 161 AddUntilStep("Animation seeked", () => animation.PlaybackPosition >= 10000); 162 163 AddStep("Jump back by 10 seconds", () => clock.CurrentTime -= 10000); 164 AddUntilStep("Animation seeked", () => animation.PlaybackPosition < 10000); 165 } 166 167 [Test] 168 public void TestAnimationDoesNotLoopIfDisabled() 169 { 170 AddStep("Seek to end", () => clock.CurrentTime = animation.Duration); 171 AddUntilStep("Animation seeked", () => animation.PlaybackPosition >= animation.Duration - 1000); 172 173 AddWaitStep("Wait for playback", 10); 174 AddAssert("Not looped", () => animation.PlaybackPosition >= animation.Duration - 1000); 175 } 176 177 [Test] 178 public void TestAnimationLoopsIfEnabled() 179 { 180 AddStep("Set looping", () => animation.Loop = true); 181 AddStep("Seek to end", () => clock.CurrentTime = animation.Duration - 2000); 182 AddUntilStep("Animation seeked", () => animation.PlaybackPosition >= animation.Duration - 1000); 183 184 AddWaitStep("Wait for playback", 10); 185 AddUntilStep("Looped", () => animation.PlaybackPosition < animation.Duration - 1000); 186 } 187 188 [Test] 189 public void TestTransformBeforeLoaded() 190 { 191 AddStep("set time to future", () => clock.CurrentTime = 10000); 192 193 loadNewAnimation(postLoadAction: a => 194 { 195 a.Alpha = 0; 196 a.FadeInFromZero(10).Then().FadeOutFromOne(1000); 197 }); 198 199 AddAssert("Is visible", () => animation.Alpha > 0); 200 } 201 202 [Test] 203 public void TestStartFromFutureTimeWithInitialSeek() 204 { 205 AddStep("set time to future", () => clock.CurrentTime = 10000); 206 207 loadNewAnimation(false, a => 208 { 209 a.PlaybackPosition = -10000; 210 }); 211 212 AddAssert("Animation is at beginning", () => animation.PlaybackPosition < 1000); 213 } 214 215 [Test] 216 public void TestGotoZeroOnFirstFrameVisible() 217 { 218 loadNewAnimation(); 219 220 AddStep("set time to 1000", () => clock.CurrentTime = 1000); 221 AddStep("hide animation", () => animation.Hide()); 222 223 AddStep("set time = 2000", () => clock.CurrentTime = 2000); 224 AddStep("goto(0) and show", () => 225 { 226 animation.GotoFrame(0); 227 animation.Show(); 228 }); 229 230 // Note: We won't get PlaybackPosition=0 here because the test runner increments the clock by at least 200ms per step, so 1000 is a safe value. 231 AddAssert("animation restarted from 0", () => animation.PlaybackPosition < 1000); 232 } 233 234 [TestCase(0)] 235 [TestCase(48)] 236 public void TestGotoFrameBeforeLoaded(int frame) 237 { 238 AddStep("create new animation", () => animation = new TestAnimation(true, fontStore) 239 { 240 Loop = false 241 }); 242 AddStep($"go to frame {frame}", () => animation.GotoFrame(frame)); 243 244 AddStep("load animation", () => animationContainer.Child = animation); 245 246 AddAssert($"animation is at frame {frame}", () => animation.CurrentFrameIndex == frame); 247 } 248 249 [Test] 250 public void TestClearFrames() 251 { 252 loadNewAnimation(); 253 254 AddUntilStep("animation is playing", () => animation.CurrentFrameIndex > 0); 255 256 AddStep("clear frames", () => animation.ClearFrames()); 257 AddAssert("animation duration is 0", () => animation.Duration == 0); 258 AddAssert("animation is at start", () => animation.CurrentFrameIndex == 0); 259 } 260 261 private void loadNewAnimation(bool startFromCurrent = true, Action<TestAnimation> postLoadAction = null) 262 { 263 AddStep("load animation", () => 264 { 265 animationContainer.Child = animation = new TestAnimation(startFromCurrent, fontStore) 266 { 267 Loop = false, 268 }; 269 270 postLoadAction?.Invoke(animation); 271 }); 272 273 AddUntilStep("Wait for animation to load", () => animation.IsLoaded); 274 } 275 276 protected override void Update() 277 { 278 base.Update(); 279 280 if (clock != null) 281 clock.CurrentTime += Clock.ElapsedFrameTime; 282 283 if (animation != null) 284 { 285 timeText.Text = $"playback: {animation.PlaybackPosition:N0} current frame: {animation.CurrentFrameIndex} total frames: {animation.FramesProcessed}"; 286 } 287 } 288 289 private class TestAnimation : TextureAnimation 290 { 291 public const int LOADABLE_FRAMES = 72; 292 293 public int FramesProcessed; 294 295 // fontStore passed in via ctor to be able to test scenarios where an animation 296 // already has frames before load 297 public TestAnimation(bool startFromCurrent, FontStore fontStore) 298 : base(startFromCurrent) 299 { 300 Anchor = Anchor.Centre; 301 Origin = Anchor.Centre; 302 303 for (int i = 0; i < LOADABLE_FRAMES; i++) 304 { 305 AddFrame(new Texture(fontStore.Get(null, (char)('0' + i))?.Texture.TextureGL) 306 { 307 ScaleAdjust = 1 + i / 40f, 308 }, 250); 309 } 310 } 311 312 protected override void DisplayFrame(Texture content) 313 { 314 FramesProcessed++; 315 base.DisplayFrame(content); 316 } 317 } 318 } 319}