1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 18:42:56 +08:00

Merge remote-tracking branch 'upstream/master' into beatmap-overlay-mod-selector-new

This commit is contained in:
Dean Herbert 2019-11-22 18:00:58 +09:00
commit 880f7ddca7
17 changed files with 260 additions and 72 deletions

View File

@ -19,6 +19,7 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osu.Game.Storyboards;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
@ -75,14 +76,14 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin);
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin);
public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
{
private readonly ISkinSource skin;
public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
: base(beatmap, frameBasedClock, audio)
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
: base(beatmap, storyboard, frameBasedClock, audio)
{
this.skin = skin;
}

View File

@ -15,6 +15,7 @@ using osu.Game.Tests.Visual;
using osuTK;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Storyboards;
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Rulesets.Osu.Tests
@ -28,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override bool Autoplay => true;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = (TrackVirtualManual)working.Track;
return working;
}

View File

@ -7,6 +7,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -29,9 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
var working = base.CreateWorkingBeatmap(beatmap);
var working = base.CreateWorkingBeatmap(beatmap, storyboard);
track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track;

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
@ -35,9 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private Track track;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = working.Track;
return working;
}

View File

@ -0,0 +1,113 @@
// 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.
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneLeadIn : RateAdjustedBeatmapTestScene
{
private LeadInPlayer player;
private const double lenience_ms = 10;
private const double first_hit_object = 2170;
[TestCase(1000, 0)]
[TestCase(2000, 0)]
[TestCase(3000, first_hit_object - 3000)]
[TestCase(10000, first_hit_object - 10000)]
public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime)
{
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo = { AudioLeadIn = leadIn }
});
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
[TestCase(1000, 0)]
[TestCase(0, 0)]
[TestCase(-1000, -1000)]
[TestCase(-10000, -10000)]
public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime)
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
AddStep("create player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard);
LoadScreen(player = new LeadInPlayer());
});
AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1);
}
private class LeadInPlayer : TestPlayer
{
public LeadInPlayer()
: base(false, false)
{
}
public double? FirstFrameClockTime;
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public double GameplayStartTime => DrawableRuleset.GameplayStartTime;
public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (!FirstFrameClockTime.HasValue)
{
FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime;
AddInternal(new OsuSpriteText
{
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
+ $"FirstHitObjectTime: {FirstHitObjectTime} "
+ $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} "
+ $"FirstFrameClockTime: {FirstFrameClockTime}"
});
}
}
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -42,9 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
var working = base.CreateWorkingBeatmap(beatmap);
var working = base.CreateWorkingBeatmap(beatmap, storyboard);
workingWeakReferences.Add(working);
return working;
}

View File

@ -4,8 +4,8 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Input;
@ -18,25 +18,37 @@ namespace osu.Game.Tests.Visual.Gameplay
private SkipOverlay skip;
private int requestCount;
private double increment;
private GameplayClockContainer gameplayClockContainer;
private GameplayClock gameplayClock;
private const double skip_time = 6000;
[SetUp]
public void SetUp() => Schedule(() =>
{
requestCount = 0;
Child = new Container
increment = skip_time;
Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0)
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedOffsetClock(Clock)
{
Offset = -Clock.CurrentTime,
},
Children = new Drawable[]
{
skip = new SkipOverlay(6000)
skip = new SkipOverlay(skip_time)
{
RequestSeek = _ => requestCount++
RequestSkip = () =>
{
requestCount++;
gameplayClockContainer.Seek(gameplayClock.CurrentTime + increment);
}
}
},
};
gameplayClockContainer.Start();
gameplayClock = gameplayClockContainer.GameplayClock;
});
[Test]
@ -64,19 +76,35 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestClickOnlyActuatesOnce()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () =>
{
increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2;
InputManager.Click(MouseButton.Left);
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
[Test]
public void TestClickOnlyActuatesMultipleTimes()
{
AddStep("set increment lower", () => increment = 3000);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(2);
}
[Test]
public void TestDoesntFadeOnMouseDown()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("wait for overlay disapper", () => !skip.IsAlive);
AddUntilStep("wait for overlay disappear", () => !skip.IsPresent);
AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0);

View File

@ -76,7 +76,7 @@ namespace osu.Game.Beatmaps
public string MD5Hash { get; set; }
// General
public int AudioLeadIn { get; set; }
public double AudioLeadIn { get; set; }
public bool Countdown { get; set; } = true;
public float StackLeniency { get; set; } = 0.7f;
public bool SpecialStyle { get; set; }

View File

@ -34,7 +34,6 @@ namespace osu.Game.Screens.Play
public void ProcessFrame()
{
// we do not want to process the underlying clock.
// this is handled by PauseContainer.
}
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;

View File

@ -41,6 +41,8 @@ namespace osu.Game.Screens.Play
private readonly double gameplayStartTime;
private readonly double firstHitObjectTime;
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
{
Default = 1,
@ -66,6 +68,7 @@ namespace osu.Game.Screens.Play
this.beatmap = beatmap;
this.mods = mods;
this.gameplayStartTime = gameplayStartTime;
this.firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime;
RelativeSizeAxes = Axes.Both;
@ -89,6 +92,11 @@ namespace osu.Game.Screens.Play
private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset;
/// <summary>
/// Duration before gameplay start time required before skip button displays.
/// </summary>
public const double MINIMUM_SKIP_TIME = 1000;
private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1);
[BackgroundDependencyLoader]
@ -97,9 +105,22 @@ namespace osu.Game.Screens.Play
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true);
UserPlaybackRate.ValueChanged += _ => updateRate();
// sane default provided by ruleset.
double startTime = Math.Min(0, gameplayStartTime);
Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime));
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.
if (beatmap.BeatmapInfo.AudioLeadIn > 0)
startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn);
Seek(startTime);
adjustableClock.ProcessFrame();
UserPlaybackRate.ValueChanged += _ => updateRate();
}
public void Restart()
@ -130,6 +151,23 @@ namespace osu.Game.Screens.Play
this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
}
/// <summary>
/// Skip forward to the next valid skip point.
/// </summary>
public void Skip()
{
if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME)
return;
double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME;
if (GameplayClock.CurrentTime < 0 && skipTarget > 6000)
// double skip exception for storyboards with very long intros
skipTarget = 0;
Seek(skipTarget);
}
/// <summary>
/// Seek to a specific time in gameplay.
/// <remarks>

View File

@ -203,7 +203,7 @@ namespace osu.Game.Screens.Play
},
new SkipOverlay(DrawableRuleset.GameplayStartTime)
{
RequestSeek = GameplayClockContainer.Seek
RequestSkip = GameplayClockContainer.Skip
},
FailOverlay = new FailOverlay
{

View File

@ -19,6 +19,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.MathUtils;
using osu.Game.Input.Bindings;
namespace osu.Game.Screens.Play
@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play
{
private readonly double startTime;
public Action<double> RequestSeek;
public Action RequestSkip;
private Button button;
private Box remainingTimeBox;
@ -35,6 +36,9 @@ namespace osu.Game.Screens.Play
private FadeContainer fadeContainer;
private double displayTime;
[Resolved]
private GameplayClock gameplayClock { get; set; }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
/// <summary>
@ -45,8 +49,6 @@ namespace osu.Game.Screens.Play
{
this.startTime = startTime;
Show();
RelativePositionAxes = Axes.Both;
RelativeSizeAxes = Axes.X;
@ -57,13 +59,8 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
private void load(OsuColour colours)
{
var baseClock = Clock;
if (clock != null)
Clock = clock;
Children = new Drawable[]
{
fadeContainer = new FadeContainer
@ -73,7 +70,6 @@ namespace osu.Game.Screens.Play
{
button = new Button
{
Clock = baseClock,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@ -90,46 +86,42 @@ namespace osu.Game.Screens.Play
};
}
/// <summary>
/// Duration before gameplay start time required before skip button displays.
/// </summary>
private const double skip_buffer = 1000;
private const double fade_time = 300;
private double beginFadeTime => startTime - fade_time;
private double fadeOutBeginTime => startTime - GameplayClockContainer.MINIMUM_SKIP_TIME;
protected override void LoadComplete()
{
base.LoadComplete();
// skip is not required if there is no extra "empty" time to skip.
if (Clock.CurrentTime > beginFadeTime - skip_buffer)
// we may need to remove this if rewinding before the initial player load position becomes a thing.
if (fadeOutBeginTime < gameplayClock.CurrentTime)
{
Alpha = 0;
Expire();
return;
}
this.FadeInFromZero(fade_time);
using (BeginAbsoluteSequence(beginFadeTime))
this.FadeOut(fade_time);
button.Action = () => RequestSkip?.Invoke();
displayTime = gameplayClock.CurrentTime;
button.Action = () => RequestSeek?.Invoke(beginFadeTime);
displayTime = Time.Current;
Expire();
Show();
}
protected override void PopIn() => this.FadeIn();
protected override void PopIn() => this.FadeIn(fade_time);
protected override void PopOut() => this.FadeOut();
protected override void PopOut() => this.FadeOut(fade_time);
protected override void Update()
{
base.Update();
remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint);
var progress = Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime));
remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1));
button.Enabled.Value = progress > 0;
State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden;
}
protected override bool OnMouseMove(MouseMoveEvent e)
@ -335,13 +327,7 @@ namespace osu.Game.Screens.Play
box.FlashColour(Color4.White, 500, Easing.OutQuint);
aspect.ScaleTo(1.2f, 2000, Easing.OutQuint);
bool result = base.OnClick(e);
// for now, let's disable the skip button after the first press.
// this will likely need to be contextual in the future (bound from external components).
Enabled.Value = false;
return result;
return base.OnClick(e);
}
}
}

View File

@ -640,10 +640,19 @@ namespace osu.Game.Screens.Select
itemsCache.Validate();
}
private bool firstScroll = true;
private void updateScrollPosition()
{
if (scrollTarget != null)
{
if (firstScroll)
{
// reduce movement when first displaying the carousel.
scroll.ScrollTo(scrollTarget.Value - 200, false);
firstScroll = false;
}
scroll.ScrollTo(scrollTarget.Value);
scrollPositionCache.Validate();
}

View File

@ -66,7 +66,7 @@ namespace osu.Game.Storyboards.Drawables
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
{
var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
if (path == null)
return;

View File

@ -17,6 +17,8 @@ namespace osu.Game.Storyboards
public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable));
public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0);
public Storyboard()
{
layers.Add("Background", new StoryboardLayer("Background", 3));

View File

@ -5,25 +5,31 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Beatmaps
{
public class TestWorkingBeatmap : WorkingBeatmap
{
private readonly IBeatmap beatmap;
private readonly Storyboard storyboard;
/// <summary>
/// Create an instance which provides the <see cref="IBeatmap"/> when requested.
/// </summary>
/// <param name="beatmap">The beatmap</param>
public TestWorkingBeatmap(IBeatmap beatmap)
/// <param name="beatmap">The beatmap.</param>
/// <param name="storyboard">An optional storyboard.</param>
public TestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
: base(beatmap.BeatmapInfo, null)
{
this.beatmap = beatmap;
this.storyboard = storyboard;
}
protected override IBeatmap GetBeatmap() => beatmap;
protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard();
protected override Texture GetBackground() => null;
protected override VideoSprite GetVideo() => null;

View File

@ -21,6 +21,7 @@ using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
@ -119,10 +120,10 @@ namespace osu.Game.Tests.Visual
protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset);
protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) =>
CreateWorkingBeatmap(CreateBeatmap(ruleset));
CreateWorkingBeatmap(CreateBeatmap(ruleset), null);
protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) =>
new ClockBackedTestWorkingBeatmap(beatmap, Clock, audio);
protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, audio);
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
@ -168,7 +169,7 @@ namespace osu.Game.Tests.Visual
/// <param name="referenceClock">A clock which should be used instead of a stopwatch for virtual time progression.</param>
/// <param name="audio">Audio manager. Required if a reference clock isn't provided.</param>
public ClockBackedTestWorkingBeatmap(RulesetInfo ruleset, IFrameBasedClock referenceClock, AudioManager audio)
: this(new TestBeatmap(ruleset), referenceClock, audio)
: this(new TestBeatmap(ruleset), null, referenceClock, audio)
{
}
@ -176,11 +177,12 @@ namespace osu.Game.Tests.Visual
/// Create an instance which provides the <see cref="IBeatmap"/> when requested.
/// </summary>
/// <param name="beatmap">The beatmap</param>
/// <param name="storyboard">The storyboard.</param>
/// <param name="referenceClock">An optional clock which should be used instead of a stopwatch for virtual time progression.</param>
/// <param name="audio">Audio manager. Required if a reference clock isn't provided.</param>
/// <param name="length">The length of the returned virtual track.</param>
public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000)
: base(beatmap)
public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000)
: base(beatmap, storyboard)
{
if (referenceClock != null)
{