Refactor BeatSyncedContainer to support GameplayClock

This commit is contained in:
Dean Herbert 2021-07-15 14:30:38 +09:00
parent 8cc2d2e79e
commit a3129ad00e
2 changed files with 66 additions and 31 deletions

View File

@ -11,12 +11,12 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
@ -25,7 +25,8 @@ namespace osu.Game.Tests.Visual.UserInterface
public class TestSceneBeatSyncedContainer : OsuTestScene public class TestSceneBeatSyncedContainer : OsuTestScene
{ {
private BeatContainer beatContainer; private BeatContainer beatContainer;
private DecoupleableInterpolatingFramedClock decoupledClock;
private MasterGameplayClockContainer gameplayClockContainer;
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
@ -39,21 +40,18 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
beatContainer = new BeatContainer gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0)
{ {
Clock = decoupledClock = new DecoupleableInterpolatingFramedClock Child = beatContainer = new BeatContainer
{ {
IsCoupled = false,
},
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
}, },
}
}; };
decoupledClock.ChangeSource(Beatmap.Value.Track);
}); });
AddStep("Start playback", () => decoupledClock.Start()); AddStep("Start playback", () => gameplayClockContainer.Start());
} }
[Test] [Test]
@ -61,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
AddStep("Set time before zero", () => AddStep("Set time before zero", () =>
{ {
decoupledClock.Seek(-1000); gameplayClockContainer.Seek(-1000);
}); });
AddStep("bind event", () => AddStep("bind event", () =>
@ -150,8 +148,13 @@ namespace osu.Game.Tests.Visual.UserInterface
} }
} }
}; };
}
Beatmap.ValueChanged += delegate protected override void LoadComplete()
{
base.LoadComplete();
Beatmap.BindValueChanged(_ =>
{ {
timingPointCount.Value = 0; timingPointCount.Value = 0;
currentTimingPoint.Value = 0; currentTimingPoint.Value = 0;
@ -161,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface
adjustedBeatLength.Value = 0; adjustedBeatLength.Value = 0;
timeUntilNextBeat.Value = 0; timeUntilNextBeat.Value = 0;
timeSinceLastBeat.Value = 0; timeSinceLastBeat.Value = 0;
}; }, true);
} }
private List<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ToList(); private List<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ToList();
@ -181,7 +184,7 @@ namespace osu.Game.Tests.Visual.UserInterface
if (timingPoints.Count == 0) return 0; if (timingPoints.Count == 0) return 0;
if (timingPoints[^1] == current) if (timingPoints[^1] == current)
return (int)Math.Ceiling((Clock.CurrentTime - current.Time) / current.BeatLength); return (int)Math.Ceiling((BeatSyncClock.CurrentTime - current.Time) / current.BeatLength);
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
} }
@ -191,7 +194,7 @@ namespace osu.Game.Tests.Visual.UserInterface
base.Update(); base.Update();
timeUntilNextBeat.Value = TimeUntilNextBeat; timeUntilNextBeat.Value = TimeUntilNextBeat;
timeSinceLastBeat.Value = TimeSinceLastBeat; timeSinceLastBeat.Value = TimeSinceLastBeat;
currentTime.Value = Clock.CurrentTime; currentTime.Value = BeatSyncClock.CurrentTime;
} }
public Action<int, TimingControlPoint, EffectControlPoint, ChannelAmplitudes> NewBeat; public Action<int, TimingControlPoint, EffectControlPoint, ChannelAmplitudes> NewBeat;

View File

@ -1,19 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Screens.Play;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
/// <summary>
/// A container which fires a callback when a new beat is reached.
/// Consumes a parent <see cref="GameplayClock"/> or <see cref="Beatmap"/> (whichever is first available).
/// </summary>
/// <remarks>
/// This container does not set its own clock to the source used for beat matching.
/// This means that if the beat source clock is playing faster or slower, animations may unexpectedly overlap.
/// Make sure this container's Clock is also set to the expected source (or within a parent element which provides this).
/// </remarks>
public class BeatSyncedContainer : Container public class BeatSyncedContainer : Container
{ {
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private int lastBeat; private int lastBeat;
private TimingControlPoint lastTimingPoint; private TimingControlPoint lastTimingPoint;
@ -45,15 +55,45 @@ namespace osu.Game.Graphics.Containers
protected bool IsBeatSyncedWithTrack { get; private set; } protected bool IsBeatSyncedWithTrack { get; private set; }
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
}
[Resolved]
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
[Resolved(canBeNull: true)]
protected GameplayClock GameplayClock { get; private set; }
protected IClock BeatSyncClock
{
get
{
if (GameplayClock != null)
return GameplayClock;
if (Beatmap.Value.TrackLoaded)
return Beatmap.Value.Track;
return null;
}
}
protected override void Update() protected override void Update()
{ {
ITrack track = null; ITrack track = null;
IBeatmap beatmap = null; IBeatmap beatmap = null;
double currentTrackTime = 0; double currentTrackTime = 0;
TimingControlPoint timingPoint = null; TimingControlPoint timingPoint = null;
EffectControlPoint effectPoint = null; EffectControlPoint effectPoint = null;
var clock = BeatSyncClock;
if (clock == null)
return;
if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded)
{ {
track = Beatmap.Value.Track; track = Beatmap.Value.Track;
@ -62,7 +102,7 @@ namespace osu.Game.Graphics.Containers
if (track != null && beatmap != null && track.IsRunning && track.Length > 0) if (track != null && beatmap != null && track.IsRunning && track.Length > 0)
{ {
currentTrackTime = track.CurrentTime + EarlyActivationMilliseconds; currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds;
timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
@ -70,13 +110,15 @@ namespace osu.Game.Graphics.Containers
IsBeatSyncedWithTrack = timingPoint?.BeatLength > 0; IsBeatSyncedWithTrack = timingPoint?.BeatLength > 0;
if (timingPoint == null || !IsBeatSyncedWithTrack) if (!IsBeatSyncedWithTrack)
{ {
currentTrackTime = Clock.CurrentTime; currentTrackTime = clock.CurrentTime;
timingPoint = TimingControlPoint.DEFAULT; timingPoint = TimingControlPoint.DEFAULT;
effectPoint = EffectControlPoint.DEFAULT; effectPoint = EffectControlPoint.DEFAULT;
} }
Debug.Assert(timingPoint != null);
double beatLength = timingPoint.BeatLength / Divisor; double beatLength = timingPoint.BeatLength / Divisor;
while (beatLength < MinimumBeatLength) while (beatLength < MinimumBeatLength)
@ -103,15 +145,5 @@ namespace osu.Game.Graphics.Containers
lastBeat = beatIndex; lastBeat = beatIndex;
lastTimingPoint = timingPoint; lastTimingPoint = timingPoint;
} }
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap)
{
Beatmap.BindTo(beatmap);
}
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
}
} }
} }