Skip to content

Commit

Permalink
Merge pull request #29616 from peppy/break-overlay-animation
Browse files Browse the repository at this point in the history
Add beat-synced animation to break overlay
  • Loading branch information
peppy authored Sep 4, 2024
2 parents ef1add3 + 7cd24ba commit ca09028
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 57 deletions.
9 changes: 4 additions & 5 deletions osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public TestSceneBreakTracker()
breakOverlay = new BreakOverlay(true, new ScoreProcessor(new OsuRuleset()))
{
ProcessCustomClock = false,
BreakTracker = breakTracker,
}
};
}
Expand All @@ -57,9 +58,6 @@ protected override void Update()
[Test]
public void TestShowBreaks()
{
setClock(false);

addShowBreakStep(2);
addShowBreakStep(5);
addShowBreakStep(15);
}
Expand Down Expand Up @@ -124,7 +122,7 @@ private void addShowBreakStep(double seconds)
{
AddStep($"show '{seconds}s' break", () =>
{
breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
breakTracker.Breaks = new List<BreakPeriod>
{
new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000)
};
Expand All @@ -138,7 +136,7 @@ private void setClock(bool useManual)

private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks)
{
AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks);
AddStep($"load {breakDescription}", () => breakTracker.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false);
}

Expand Down Expand Up @@ -184,6 +182,7 @@ public double ManualClockTime
}

public TestBreakTracker()
: base(0, new ScoreProcessor(new OsuRuleset()))
{
FramedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false;
Expand Down
108 changes: 66 additions & 42 deletions osu.Game/Screens/Play/BreakOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,61 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.Break;
using osu.Game.Utils;

namespace osu.Game.Screens.Play
{
public partial class BreakOverlay : Container
public partial class BreakOverlay : BeatSyncedContainer
{
/// <summary>
/// The duration of the break overlay fading.
/// </summary>
public const double BREAK_FADE_DURATION = BreakPeriod.MIN_BREAK_DURATION / 2;

private const float remaining_time_container_max_size = 0.3f;
private const int vertical_margin = 25;
private const int vertical_margin = 15;

private readonly Container fadeContainer;

private IReadOnlyList<BreakPeriod> breaks = Array.Empty<BreakPeriod>();

public IReadOnlyList<BreakPeriod> Breaks
{
get => breaks;
set
{
breaks = value;

if (IsLoaded)
initializeBreaks();
}
}

public override bool RemoveCompletedTransforms => false;

public BreakTracker BreakTracker { get; init; } = null!;

private readonly Container remainingTimeAdjustmentBox;
private readonly Container remainingTimeBox;
private readonly RemainingTimeCounter remainingTimeCounter;
private readonly BreakArrows breakArrows;
private readonly ScoreProcessor scoreProcessor;
private readonly BreakInfo info;

private readonly IBindable<Period?> currentPeriod = new Bindable<Period?>();

public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
{
this.scoreProcessor = scoreProcessor;
RelativeSizeAxes = Axes.Both;

MinimumBeatLength = 200;

// Doesn't play well with pause/unpause.
// This might mean that some beats don't animate if the user is running <60fps, but we'll deal with that if anyone notices.
AllowMistimedEventFiring = false;

Child = fadeContainer = new Container
{
Alpha = 0,
Expand Down Expand Up @@ -114,13 +113,13 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
{
Anchor = Anchor.Centre,
Origin = Anchor.BottomCentre,
Margin = new MarginPadding { Bottom = vertical_margin },
Y = -vertical_margin,
},
info = new BreakInfo
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = vertical_margin },
Y = vertical_margin,
},
breakArrows = new BreakArrows
{
Expand All @@ -134,51 +133,76 @@ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
protected override void LoadComplete()
{
base.LoadComplete();
initializeBreaks();

info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
((IBindable<ScoreRank>)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank);

currentPeriod.BindTo(BreakTracker.CurrentPeriod);
currentPeriod.BindValueChanged(updateDisplay, true);
}

private float remainingTimeForCurrentPeriod =>
currentPeriod.Value == null ? 0 : (float)Math.Max(0, (currentPeriod.Value.Value.End - Time.Current - BREAK_FADE_DURATION) / currentPeriod.Value.Value.Duration);

protected override void Update()
{
base.Update();

remainingTimeBox.Height = Math.Min(8, remainingTimeBox.DrawWidth);

// Keep things simple by resetting beat synced transforms on a rewind.
if (Clock.ElapsedFrameTime < 0)
{
remainingTimeBox.ClearTransforms(targetMember: nameof(Width));
remainingTimeBox.Width = remainingTimeForCurrentPeriod;
}
}

protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);

if (currentPeriod.Value == null)
return;

float timeBoxTargetWidth = (float)Math.Max(0, (remainingTimeForCurrentPeriod - timingPoint.BeatLength / currentPeriod.Value.Value.Duration));
remainingTimeBox.ResizeWidthTo(timeBoxTargetWidth, timingPoint.BeatLength * 2, Easing.OutQuint);
}

private void initializeBreaks()
private void updateDisplay(ValueChangedEvent<Period?> period)
{
FinishTransforms(true);
Scheduler.CancelDelayedTasks();

foreach (var b in breaks)
if (period.NewValue == null)
return;

var b = period.NewValue.Value;

using (BeginAbsoluteSequence(b.Start))
{
if (!b.HasEffect)
continue;
fadeContainer.FadeIn(BREAK_FADE_DURATION);
breakArrows.Show(BREAK_FADE_DURATION);

using (BeginAbsoluteSequence(b.StartTime))
{
fadeContainer.FadeIn(BREAK_FADE_DURATION);
breakArrows.Show(BREAK_FADE_DURATION);
remainingTimeAdjustmentBox
.ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint)
.Delay(b.Duration - BREAK_FADE_DURATION)
.ResizeWidthTo(0);

remainingTimeAdjustmentBox
.ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint)
.Delay(b.Duration - BREAK_FADE_DURATION)
.ResizeWidthTo(0);
remainingTimeBox.ResizeWidthTo(remainingTimeForCurrentPeriod);

remainingTimeBox
.ResizeWidthTo(0, b.Duration - BREAK_FADE_DURATION)
.Then()
.ResizeWidthTo(1);
remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration);

remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration);
remainingTimeCounter.MoveToX(-50)
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);

using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION))
{
fadeContainer.FadeOut(BREAK_FADE_DURATION);
breakArrows.Hide(BREAK_FADE_DURATION);
}
info.MoveToX(50)
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);

using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION))
{
fadeContainer.FadeOut(BREAK_FADE_DURATION);
breakArrows.Hide(BREAK_FADE_DURATION);
}
}
}
Expand Down
21 changes: 14 additions & 7 deletions osu.Game/Screens/Play/BreakTracker.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
Expand All @@ -18,7 +16,7 @@ public partial class BreakTracker : Component
private readonly ScoreProcessor scoreProcessor;
private readonly double gameplayStartTime;

private PeriodTracker breaks;
private PeriodTracker breaks = new PeriodTracker(Enumerable.Empty<Period>());

/// <summary>
/// Whether the gameplay is currently in a break.
Expand All @@ -27,6 +25,8 @@ public partial class BreakTracker : Component

private readonly BindableBool isBreakTime = new BindableBool(true);

public readonly Bindable<Period?> CurrentPeriod = new Bindable<Period?>();

public IReadOnlyList<BreakPeriod> Breaks
{
set
Expand All @@ -39,7 +39,7 @@ public IReadOnlyList<BreakPeriod> Breaks
}
}

public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
public BreakTracker(double gameplayStartTime, ScoreProcessor scoreProcessor)
{
this.gameplayStartTime = gameplayStartTime;
this.scoreProcessor = scoreProcessor;
Expand All @@ -55,9 +55,16 @@ private void updateBreakTime()
{
double time = Clock.CurrentTime;

isBreakTime.Value = breaks?.IsInAny(time) == true
|| time < gameplayStartTime
|| scoreProcessor?.HasCompleted.Value == true;
if (breaks.IsInAny(time, out var currentBreak))
{
CurrentPeriod.Value = currentBreak;
isBreakTime.Value = true;
}
else
{
CurrentPeriod.Value = null;
isBreakTime.Value = time < gameplayStartTime || scoreProcessor.HasCompleted.Value;
}
}
}
}
2 changes: 1 addition & 1 deletion osu.Game/Screens/Play/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ private Drawable createOverlayComponents(IWorkingBeatmap working)
{
Clock = DrawableRuleset.FrameStableClock,
ProcessCustomClock = false,
Breaks = working.Beatmap.Breaks
BreakTracker = breakTracker,
},
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
Expand Down
24 changes: 22 additions & 2 deletions osu.Game/Utils/PeriodTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace osu.Game.Utils
Expand All @@ -24,8 +25,17 @@ public PeriodTracker(IEnumerable<Period> periods)
/// Whether the provided time is in any of the added periods.
/// </summary>
/// <param name="time">The time value to check.</param>
public bool IsInAny(double time)
public bool IsInAny(double time) => IsInAny(time, out _);

/// <summary>
/// Whether the provided time is in any of the added periods.
/// </summary>
/// <param name="time">The time value to check.</param>
/// <param name="period">The period which matched.</param>
public bool IsInAny(double time, [NotNullWhen(true)] out Period? period)
{
period = null;

if (periods.Count == 0)
return false;

Expand All @@ -41,7 +51,15 @@ public bool IsInAny(double time)
}

var nearest = periods[nearestIndex];
return time >= nearest.Start && time <= nearest.End;
bool isInAny = time >= nearest.Start && time <= nearest.End;

if (isInAny)
{
period = nearest;
return true;
}

return false;
}
}

Expand All @@ -57,6 +75,8 @@ public readonly struct Period
/// </summary>
public readonly double End;

public double Duration => End - Start;

public Period(double start, double end)
{
if (start >= end)
Expand Down

0 comments on commit ca09028

Please sign in to comment.