1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 16:27:26 +08:00

Merge branch 'master' into reduce-fruit-spread

This commit is contained in:
Dan Balasescu 2020-03-27 19:19:23 +09:00 committed by GitHub
commit 378a957b33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 166 additions and 114 deletions

View File

@ -3,6 +3,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using osu.Framework.Testing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime)); AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType<BreakTracker>().First().Breaks.First().StartTime));
AddUntilStep("wait for seek to complete", () => AddUntilStep("wait for seek to complete", () =>
Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime); Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -12,14 +13,16 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[TestFixture] [TestFixture]
public class TestSceneBreakOverlay : OsuTestScene public class TestSceneBreakTracker : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(BreakOverlay), typeof(BreakOverlay),
}; };
private readonly TestBreakOverlay breakOverlay; private readonly BreakOverlay breakOverlay;
private readonly TestBreakTracker breakTracker;
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod> private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{ {
@ -35,9 +38,23 @@ namespace osu.Game.Tests.Visual.Gameplay
}, },
}; };
public TestSceneBreakOverlay() public TestSceneBreakTracker()
{ {
Add(breakOverlay = new TestBreakOverlay(true)); AddRange(new Drawable[]
{
breakTracker = new TestBreakTracker(),
breakOverlay = new BreakOverlay(true)
{
ProcessCustomClock = false,
}
});
}
protected override void Update()
{
base.Update();
breakOverlay.Clock = breakTracker.Clock;
} }
[Test] [Test]
@ -88,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay
loadBreaksStep("multiple breaks", testBreaks); loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true); seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1); AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true); seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false); seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
@ -110,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds) private void addShowBreakStep(double seconds)
{ {
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod> AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
{ {
new BreakPeriod new BreakPeriod
{ {
@ -122,12 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void setClock(bool useManual) private void setClock(bool useManual)
{ {
AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual)); AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual));
} }
private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks) private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks)
{ {
AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks); AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false); seekAndAssertBreak("seek back to 0", 0, false);
} }
@ -151,17 +168,18 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak) private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{ {
AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time); AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () => AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{ {
breakOverlay.ProgressTime(); breakTracker.ProgressTime();
return breakOverlay.IsBreakTime.Value == shouldBeBreak; return breakTracker.IsBreakTime.Value == shouldBeBreak;
}); });
} }
private class TestBreakOverlay : BreakOverlay private class TestBreakTracker : BreakTracker
{ {
private readonly FramedClock framedManualClock; public readonly FramedClock FramedManualClock;
private readonly ManualClock manualClock; private readonly ManualClock manualClock;
private IFrameBasedClock originalClock; private IFrameBasedClock originalClock;
@ -173,20 +191,19 @@ namespace osu.Game.Tests.Visual.Gameplay
set => manualClock.CurrentTime = value; set => manualClock.CurrentTime = value;
} }
public TestBreakOverlay(bool letterboxing) public TestBreakTracker()
: base(letterboxing)
{ {
framedManualClock = new FramedClock(manualClock = new ManualClock()); FramedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false; ProcessCustomClock = false;
} }
public void ProgressTime() public void ProgressTime()
{ {
framedManualClock.ProcessFrame(); FramedManualClock.ProcessFrame();
Update(); Update();
} }
public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock; public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock;
protected override void LoadComplete() protected override void LoadComplete()
{ {

View File

@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers
/// <summary> /// <summary>
/// Whether player is in break time. /// Whether player is in break time.
/// Must be bound to <see cref="BreakOverlay.IsBreakTime"/> to allow for dim adjustments in gameplay. /// Must be bound to <see cref="BreakTracker.IsBreakTime"/> to allow for dim adjustments in gameplay.
/// </summary> /// </summary>
public readonly IBindable<bool> IsBreakTime = new Bindable<bool>(); public readonly IBindable<bool> IsBreakTime = new Bindable<bool>();

View File

@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public override Playfield Playfield => playfield.Value; public override Playfield Playfield => playfield.Value;
private Container overlays; public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both };
public override Container Overlays => overlays; public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
@ -187,11 +187,12 @@ namespace osu.Game.Rulesets.UI
FrameStablePlayback = FrameStablePlayback, FrameStablePlayback = FrameStablePlayback,
Children = new Drawable[] Children = new Drawable[]
{ {
FrameStableComponents,
KeyBindingInputManager KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield) .WithChild(Playfield)
), ),
overlays = new Container { RelativeSizeAxes = Axes.Both } Overlays,
} }
}, },
}; };
@ -406,10 +407,15 @@ namespace osu.Game.Rulesets.UI
public abstract Playfield Playfield { get; } public abstract Playfield Playfield { get; }
/// <summary> /// <summary>
/// Place to put drawables above hit objects but below UI. /// Content to be placed above hitobjects. Will be affected by frame stability.
/// </summary> /// </summary>
public abstract Container Overlays { get; } public abstract Container Overlays { get; }
/// <summary>
/// Components to be run potentially multiple times in line with frame-stable gameplay.
/// </summary>
public abstract Container FrameStableComponents { get; }
/// <summary> /// <summary>
/// The frame-stable clock which is being used for playfield display. /// The frame-stable clock which is being used for playfield display.
/// </summary> /// </summary>

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI
{ {
/// <summary> /// <summary>
/// A container which consumes a parent gameplay clock and standardises frame counts for children. /// A container which consumes a parent gameplay clock and standardises frame counts for children.
/// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
/// </summary> /// </summary>
public class FrameStabilityContainer : Container, IHasReplayHandler public class FrameStabilityContainer : Container, IHasReplayHandler
{ {

View File

@ -2,8 +2,6 @@
// 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.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -16,8 +14,6 @@ namespace osu.Game.Screens.Play
{ {
public class BreakOverlay : Container public class BreakOverlay : Container
{ {
private readonly ScoreProcessor scoreProcessor;
/// <summary> /// <summary>
/// The duration of the break overlay fading. /// The duration of the break overlay fading.
/// </summary> /// </summary>
@ -37,10 +33,6 @@ namespace osu.Game.Screens.Play
{ {
breaks = value; breaks = value;
// reset index in case the new breaks list is smaller than last one
isBreakTime.Value = false;
CurrentBreakIndex = 0;
if (IsLoaded) if (IsLoaded)
initializeBreaks(); initializeBreaks();
} }
@ -48,27 +40,17 @@ namespace osu.Game.Screens.Play
public override bool RemoveCompletedTransforms => false; public override bool RemoveCompletedTransforms => false;
/// <summary>
/// Whether the gameplay is currently in a break.
/// </summary>
public IBindable<bool> IsBreakTime => isBreakTime;
protected int CurrentBreakIndex;
private readonly BindableBool isBreakTime = new BindableBool();
private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeAdjustmentBox;
private readonly Container remainingTimeBox; private readonly Container remainingTimeBox;
private readonly RemainingTimeCounter remainingTimeCounter; private readonly RemainingTimeCounter remainingTimeCounter;
private readonly BreakInfo info;
private readonly BreakArrows breakArrows; private readonly BreakArrows breakArrows;
private readonly double gameplayStartTime;
public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null)
{ {
this.gameplayStartTime = gameplayStartTime;
this.scoreProcessor = scoreProcessor;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
BreakInfo info;
Child = fadeContainer = new Container Child = fadeContainer = new Container
{ {
Alpha = 0, Alpha = 0,
@ -119,13 +101,11 @@ namespace osu.Game.Screens.Play
} }
}; };
if (scoreProcessor != null) bindProcessor(scoreProcessor); if (scoreProcessor != null)
} {
info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
[BackgroundDependencyLoader(true)] info.GradeDisplay.Current.BindTo(scoreProcessor.Rank);
private void load(GameplayClock clock) }
{
if (clock != null) Clock = clock;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -134,42 +114,6 @@ namespace osu.Game.Screens.Play
initializeBreaks(); initializeBreaks();
} }
protected override void Update()
{
base.Update();
updateBreakTimeBindable();
}
private void updateBreakTimeBindable() =>
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|| Clock.CurrentTime < gameplayStartTime
|| scoreProcessor?.HasCompleted == true;
private BreakPeriod getCurrentBreak()
{
if (breaks?.Count > 0)
{
var time = Clock.CurrentTime;
if (time > breaks[CurrentBreakIndex].EndTime)
{
while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
CurrentBreakIndex++;
}
else
{
while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
CurrentBreakIndex--;
}
var closest = breaks[CurrentBreakIndex];
return closest.Contains(time) ? closest : null;
}
return null;
}
private void initializeBreaks() private void initializeBreaks()
{ {
FinishTransforms(true); FinishTransforms(true);
@ -207,11 +151,5 @@ namespace osu.Game.Screens.Play
} }
} }
} }
private void bindProcessor(ScoreProcessor processor)
{
info.AccuracyDisplay.Current.BindTo(processor.Accuracy);
info.GradeDisplay.Current.BindTo(processor.Rank);
}
} }
} }

View File

@ -0,0 +1,82 @@
// 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.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Screens.Play
{
public class BreakTracker : Component
{
private readonly ScoreProcessor scoreProcessor;
private readonly double gameplayStartTime;
/// <summary>
/// Whether the gameplay is currently in a break.
/// </summary>
public IBindable<bool> IsBreakTime => isBreakTime;
protected int CurrentBreakIndex;
private readonly BindableBool isBreakTime = new BindableBool();
private IReadOnlyList<BreakPeriod> breaks;
public IReadOnlyList<BreakPeriod> Breaks
{
get => breaks;
set
{
breaks = value;
// reset index in case the new breaks list is smaller than last one
isBreakTime.Value = false;
CurrentBreakIndex = 0;
}
}
public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
{
this.gameplayStartTime = gameplayStartTime;
this.scoreProcessor = scoreProcessor;
}
protected override void Update()
{
base.Update();
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|| Clock.CurrentTime < gameplayStartTime
|| scoreProcessor?.HasCompleted == true;
}
private BreakPeriod getCurrentBreak()
{
if (breaks?.Count > 0)
{
var time = Clock.CurrentTime;
if (time > breaks[CurrentBreakIndex].EndTime)
{
while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
CurrentBreakIndex++;
}
else
{
while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
CurrentBreakIndex--;
}
var closest = breaks[CurrentBreakIndex];
return closest.Contains(time) ? closest : null;
}
return null;
}
}
}

View File

@ -76,6 +76,8 @@ namespace osu.Game.Screens.Play
public BreakOverlay BreakOverlay; public BreakOverlay BreakOverlay;
private BreakTracker breakTracker;
protected ScoreProcessor ScoreProcessor { get; private set; } protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; }
@ -204,7 +206,7 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>()) foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>())
mod.ApplyToHealthProcessor(HealthProcessor); mod.ApplyToHealthProcessor(HealthProcessor);
BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
} }
private void addUnderlayComponents(Container target) private void addUnderlayComponents(Container target)
@ -231,6 +233,18 @@ namespace osu.Game.Screens.Play
DrawableRuleset, DrawableRuleset,
new ComboEffects(ScoreProcessor) new ComboEffects(ScoreProcessor)
}); });
DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
{
ScoreProcessor,
HealthProcessor,
breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
{
Breaks = working.Beatmap.Breaks
}
});
HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime);
} }
private void addOverlayComponents(Container target, WorkingBeatmap working) private void addOverlayComponents(Container target, WorkingBeatmap working)
@ -293,20 +307,14 @@ namespace osu.Game.Screens.Play
performImmediateExit(); performImmediateExit();
}, },
}, },
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks)
{
Clock = DrawableRuleset.FrameStableClock,
ProcessCustomClock = false,
Breaks = working.Beatmap.Breaks
},
}); });
DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Breaks = working.Beatmap.Breaks
});
DrawableRuleset.Overlays.Add(ScoreProcessor);
DrawableRuleset.Overlays.Add(HealthProcessor);
HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
} }
private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime) private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime)
@ -318,7 +326,7 @@ namespace osu.Game.Screens.Play
private void updatePauseOnFocusLostState() => private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.HasReplayLoaded.Value
&& !BreakOverlay.IsBreakTime.Value; && !breakTracker.IsBreakTime.Value;
private IBeatmap loadPlayableBeatmap() private IBeatmap loadPlayableBeatmap()
{ {
@ -540,7 +548,7 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide(); PauseOverlay.Hide();
// breaks and time-based conditions may allow instant resume. // breaks and time-based conditions may allow instant resume.
if (BreakOverlay.IsBreakTime.Value) if (breakTracker.IsBreakTime.Value)
completeResume(); completeResume();
else else
DrawableRuleset.RequestResume(completeResume); DrawableRuleset.RequestResume(completeResume);
@ -574,8 +582,8 @@ namespace osu.Game.Screens.Play
Background.BlurAmount.Value = 0; Background.BlurAmount.Value = 0;
// bind component bindables. // bind component bindables.
Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); Background.IsBreakTime.BindTo(breakTracker.IsBreakTime);
DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);