A game framework written with osu! in mind.
at master 120 lines 3.8 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 osu.Framework.Allocation; 6using osu.Framework.Graphics.Containers; 7using osu.Framework.Timing; 8 9namespace osu.Framework.Graphics.Animations 10{ 11 public abstract class AnimationClockComposite : CustomisableSizeCompositeDrawable, IAnimation 12 { 13 private readonly bool startAtCurrentTime; 14 15 private bool hasSeeked; 16 17 private readonly ManualClock manualClock = new ManualClock(); 18 19 /// <summary> 20 /// Construct a new animation. 21 /// </summary> 22 /// <param name="startAtCurrentTime">Whether the current clock time should be assumed as the 0th animation frame.</param> 23 protected AnimationClockComposite(bool startAtCurrentTime = true) 24 { 25 this.startAtCurrentTime = startAtCurrentTime; 26 } 27 28 [BackgroundDependencyLoader] 29 private void load() 30 { 31 base.AddInternal(new Container 32 { 33 RelativeSizeAxes = Axes.Both, 34 Clock = new FramedClock(manualClock), 35 Child = CreateContent() 36 }); 37 } 38 39 public override IFrameBasedClock Clock 40 { 41 get => base.Clock; 42 set 43 { 44 base.Clock = value; 45 consumeClockTime(); 46 } 47 } 48 49 protected override void LoadComplete() 50 { 51 base.LoadComplete(); 52 53 // always consume to zero out elapsed for update loop. 54 double elapsed = consumeClockTime(); 55 56 if (!startAtCurrentTime && !hasSeeked) 57 manualClock.CurrentTime += elapsed; 58 } 59 60 protected internal override void AddInternal(Drawable drawable) => throw new InvalidOperationException($"Use {nameof(CreateContent)} instead."); 61 62 /// <summary> 63 /// Create the content container for animation display. 64 /// </summary> 65 /// <returns>The container providing the content to be added into this <see cref="AnimationClockComposite"/>'s hierarchy.</returns> 66 public abstract Drawable CreateContent(); 67 68 private double lastConsumedTime; 69 70 protected override void Update() 71 { 72 base.Update(); 73 74 double consumedTime = consumeClockTime(); 75 if (IsPlaying) 76 manualClock.CurrentTime += consumedTime; 77 } 78 79 /// <summary> 80 /// The current playback position of the animation, in milliseconds. 81 /// </summary> 82 public double PlaybackPosition 83 { 84 get 85 { 86 double current = manualClock.CurrentTime; 87 88 if (Loop) current %= Duration; 89 90 return Math.Clamp(current, 0, Duration); 91 } 92 set 93 { 94 hasSeeked = true; 95 manualClock.CurrentTime = value; 96 97 // consume current clock to avoid additional jumps on top of the seek due to time naturally elapsing. 98 // there's no need to do this before we're loaded - LoadComplete() will also consume clock initially, 99 // and Time might not even be initialised yet during load 100 if (IsLoaded) 101 consumeClockTime(); 102 } 103 } 104 105 public double Duration { get; protected set; } 106 107 public bool IsPlaying { get; set; } = true; 108 109 public virtual bool Loop { get; set; } 110 111 public void Seek(double time) => PlaybackPosition = time; 112 113 private double consumeClockTime() 114 { 115 double elapsed = Time.Current - lastConsumedTime; 116 lastConsumedTime = Time.Current; 117 return elapsed; 118 } 119 } 120}