Merge branch 'master' into beatmap-carousel-refactor

This commit is contained in:
Dean Herbert
2020-10-19 13:17:11 +09:00
committed by GitHub
125 changed files with 2238 additions and 884 deletions

View File

@ -79,9 +79,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementNewCombo()
{
if (currentPlacement == null) return;
if (currentPlacement.HitObject is IHasComboInformation c)
if (currentPlacement?.HitObject is IHasComboInformation c)
c.NewCombo = NewCombo.Value == TernaryState.True;
}

View File

@ -597,10 +597,20 @@ namespace osu.Game.Screens.Edit
{
double amount = e.ShiftPressed ? 4 : 1;
bool trackPlaying = clock.IsRunning;
if (trackPlaying)
{
// generally users are not looking to perform tiny seeks when the track is playing,
// so seeks should always be by one full beat, bypassing the beatDivisor.
// this multiplication undoes the division that will be applied in the underlying seek operation.
amount *= beatDivisor.Value;
}
if (direction < 1)
clock.SeekBackward(!clock.IsRunning, amount);
clock.SeekBackward(!trackPlaying, amount);
else
clock.SeekForward(!clock.IsRunning, amount);
clock.SeekForward(!trackPlaying, amount);
}
private void exportBeatmap()

View File

@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
public class GameplayClock : IFrameBasedClock, ISamplePlaybackDisabler
public class GameplayClock : IFrameBasedClock
{
private readonly IFrameBasedClock underlyingClock;
@ -28,8 +28,6 @@ namespace osu.Game.Screens.Play
/// </summary>
public virtual IEnumerable<Bindable<double>> NonGameplayAdjustments => Enumerable.Empty<Bindable<double>>();
private readonly Bindable<bool> samplePlaybackDisabled = new Bindable<bool>();
public GameplayClock(IFrameBasedClock underlyingClock)
{
this.underlyingClock = underlyingClock;
@ -66,13 +64,11 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Whether nested samples supporting the <see cref="ISamplePlaybackDisabler"/> interface should be paused.
/// </summary>
protected virtual bool ShouldDisableSamplePlayback => IsPaused.Value;
public virtual bool ShouldDisableSamplePlayback => IsPaused.Value;
public void ProcessFrame()
{
// intentionally not updating the underlying clock (handled externally).
samplePlaybackDisabled.Value = ShouldDisableSamplePlayback;
}
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
@ -82,7 +78,5 @@ namespace osu.Game.Screens.Play
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
public IClock Source => underlyingClock;
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
}
}

View File

@ -54,7 +54,6 @@ namespace osu.Game.Screens.Play
public GameplayClock GameplayClock => localGameplayClock;
[Cached(typeof(GameplayClock))]
[Cached(typeof(ISamplePlaybackDisabler))]
private readonly LocalGameplayClock localGameplayClock;
private Bindable<double> userAudioOffset;

View File

@ -1,200 +0,0 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Screens.Play.HUD
{
public abstract class ComboCounter : Container
{
public BindableInt Current = new BindableInt
{
MinValue = 0,
};
public bool IsRolling { get; protected set; }
protected SpriteText PopOutCount;
protected virtual double PopOutDuration => 150;
protected virtual float PopOutScale => 2.0f;
protected virtual Easing PopOutEasing => Easing.None;
protected virtual float PopOutInitialAlpha => 0.75f;
protected virtual double FadeOutDuration => 100;
/// <summary>
/// Duration in milliseconds for the counter roll-up animation for each element.
/// </summary>
protected virtual double RollingDuration => 20;
/// <summary>
/// Easing for the counter rollover animation.
/// </summary>
protected Easing RollingEasing => Easing.None;
protected SpriteText DisplayedCountSpriteText;
private int previousValue;
/// <summary>
/// Base of all combo counters.
/// </summary>
protected ComboCounter()
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
DisplayedCountSpriteText = new OsuSpriteText
{
Alpha = 0,
},
PopOutCount = new OsuSpriteText
{
Alpha = 0,
Margin = new MarginPadding(0.05f),
}
};
TextSize = 80;
Current.ValueChanged += combo => updateCount(combo.NewValue == 0);
}
protected override void LoadComplete()
{
base.LoadComplete();
DisplayedCountSpriteText.Text = FormatCount(Current.Value);
DisplayedCountSpriteText.Anchor = Anchor;
DisplayedCountSpriteText.Origin = Origin;
StopRolling();
}
private int displayedCount;
/// <summary>
/// Value shown at the current moment.
/// </summary>
public virtual int DisplayedCount
{
get => displayedCount;
protected set
{
if (displayedCount.Equals(value))
return;
updateDisplayedCount(displayedCount, value, IsRolling);
}
}
private float textSize;
public float TextSize
{
get => textSize;
set
{
textSize = value;
DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: TextSize);
PopOutCount.Font = PopOutCount.Font.With(size: TextSize);
}
}
/// <summary>
/// Increments the combo by an amount.
/// </summary>
/// <param name="amount"></param>
public void Increment(int amount = 1)
{
Current.Value += amount;
}
/// <summary>
/// Stops rollover animation, forcing the displayed count to be the actual count.
/// </summary>
public void StopRolling()
{
updateCount(false);
}
protected virtual string FormatCount(int count)
{
return count.ToString();
}
protected virtual void OnCountRolling(int currentValue, int newValue)
{
transformRoll(currentValue, newValue);
}
protected virtual void OnCountIncrement(int currentValue, int newValue)
{
DisplayedCount = newValue;
}
protected virtual void OnCountChange(int currentValue, int newValue)
{
DisplayedCount = newValue;
}
private double getProportionalDuration(int currentValue, int newValue)
{
double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue;
return difference * RollingDuration;
}
private void updateDisplayedCount(int currentValue, int newValue, bool rolling)
{
displayedCount = newValue;
if (rolling)
OnDisplayedCountRolling(currentValue, newValue);
else if (currentValue + 1 == newValue)
OnDisplayedCountIncrement(newValue);
else
OnDisplayedCountChange(newValue);
}
private void updateCount(bool rolling)
{
int prev = previousValue;
previousValue = Current.Value;
if (!IsLoaded)
return;
if (!rolling)
{
FinishTransforms(false, nameof(DisplayedCount));
IsRolling = false;
DisplayedCount = prev;
if (prev + 1 == Current.Value)
OnCountIncrement(prev, Current.Value);
else
OnCountChange(prev, Current.Value);
}
else
{
OnCountRolling(displayedCount, Current.Value);
IsRolling = true;
}
}
private void transformRoll(int currentValue, int newValue)
{
this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), RollingEasing);
}
protected abstract void OnDisplayedCountRolling(int currentValue, int newValue);
protected abstract void OnDisplayedCountIncrement(int newValue);
protected abstract void OnDisplayedCountChange(int newValue);
}
}

View File

@ -0,0 +1,43 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter
{
private readonly Vector2 offset = new Vector2(-20, 5);
public DefaultAccuracyCounter()
{
Origin = Anchor.TopRight;
Anchor = Anchor.TopRight;
}
[Resolved(canBeNull: true)]
private HUDOverlay hud { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.BlueLighter;
}
protected override void Update()
{
base.Update();
if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score)
{
// for now align with the score counter. eventually this will be user customisable.
Anchor = Anchor.TopLeft;
Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopLeft) + offset;
}
}
}
}

View File

@ -0,0 +1,55 @@
// 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;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class DefaultComboCounter : RollingCounter<int>, IComboCounter
{
private readonly Vector2 offset = new Vector2(20, 5);
protected override double RollingDuration => 750;
[Resolved(canBeNull: true)]
private HUDOverlay hud { get; set; }
public DefaultComboCounter()
{
Current.Value = DisplayedCount = 0;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours) => Colour = colours.BlueLighter;
protected override void Update()
{
base.Update();
if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score)
{
// for now align with the score counter. eventually this will be user customisable.
Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopRight) + offset;
}
}
protected override string FormatCount(int count)
{
return $@"{count}x";
}
protected override double GetProportionalDuration(int currentValue, int newValue)
{
return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f;
}
protected override OsuSpriteText CreateSpriteText()
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f));
}
}

View File

@ -16,7 +16,7 @@ using osu.Framework.Utils;
namespace osu.Game.Screens.Play.HUD
{
public class StandardHealthDisplay : HealthDisplay, IHasAccentColour
public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour
{
/// <summary>
/// The base opacity of the glow.
@ -71,8 +71,12 @@ namespace osu.Game.Screens.Play.HUD
}
}
public StandardHealthDisplay()
public DefaultHealthDisplay()
{
Size = new Vector2(1, 5);
RelativeSizeAxes = Axes.X;
Margin = new MarginPadding { Top = 20 };
Children = new Drawable[]
{
new Box
@ -103,13 +107,7 @@ namespace osu.Game.Screens.Play.HUD
GlowColour = colours.BlueDarker;
}
public void Flash(JudgementResult result)
{
if (!result.IsHit)
return;
Scheduler.AddOnce(flash);
}
public override void Flash(JudgementResult result) => Scheduler.AddOnce(flash);
private void flash()
{

View File

@ -0,0 +1,35 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Play.HUD
{
public class DefaultScoreCounter : ScoreCounter
{
public DefaultScoreCounter()
: base(6)
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
}
[Resolved(canBeNull: true)]
private HUDOverlay hud { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.BlueLighter;
// todo: check if default once health display is skinnable
hud?.ShowHealthbar.BindValueChanged(healthBar =>
{
this.MoveToY(healthBar.NewValue ? 30 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING);
}, true);
}
}
}

View File

@ -3,6 +3,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@ -12,14 +13,18 @@ namespace osu.Game.Screens.Play.HUD
/// A container for components displaying the current player health.
/// Gets bound automatically to the <see cref="HealthProcessor"/> when inserted to <see cref="DrawableRuleset.Overlays"/> hierarchy.
/// </summary>
public abstract class HealthDisplay : Container
public abstract class HealthDisplay : Container, IHealthDisplay
{
public readonly BindableDouble Current = new BindableDouble(1)
public Bindable<double> Current { get; } = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
public virtual void Flash(JudgementResult result)
{
}
/// <summary>
/// Bind the tracked fields of <see cref="HealthProcessor"/> to this health display.
/// </summary>

View File

@ -66,54 +66,69 @@ namespace osu.Game.Screens.Play.HUD
switch (type.NewValue)
{
case ScoreMeterType.HitErrorBoth:
createBar(false);
createBar(true);
createBar(Anchor.CentreLeft);
createBar(Anchor.CentreRight);
break;
case ScoreMeterType.HitErrorLeft:
createBar(false);
createBar(Anchor.CentreLeft);
break;
case ScoreMeterType.HitErrorRight:
createBar(true);
createBar(Anchor.CentreRight);
break;
case ScoreMeterType.HitErrorBottom:
createBar(Anchor.BottomCentre);
break;
case ScoreMeterType.ColourBoth:
createColour(false);
createColour(true);
createColour(Anchor.CentreLeft);
createColour(Anchor.CentreRight);
break;
case ScoreMeterType.ColourLeft:
createColour(false);
createColour(Anchor.CentreLeft);
break;
case ScoreMeterType.ColourRight:
createColour(true);
createColour(Anchor.CentreRight);
break;
case ScoreMeterType.ColourBottom:
createColour(Anchor.BottomCentre);
break;
}
}
private void createBar(bool rightAligned)
private void createBar(Anchor anchor)
{
bool rightAligned = (anchor & Anchor.x2) > 0;
bool bottomAligned = (anchor & Anchor.y2) > 0;
var display = new BarHitErrorMeter(hitWindows, rightAligned)
{
Margin = new MarginPadding(margin),
Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft,
Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft,
Anchor = anchor,
Origin = bottomAligned ? Anchor.CentreLeft : anchor,
Alpha = 0,
Rotation = bottomAligned ? 270 : 0
};
completeDisplayLoading(display);
}
private void createColour(bool rightAligned)
private void createColour(Anchor anchor)
{
bool bottomAligned = (anchor & Anchor.y2) > 0;
var display = new ColourHitErrorMeter(hitWindows)
{
Margin = new MarginPadding(margin),
Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft,
Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft,
Anchor = anchor,
Origin = bottomAligned ? Anchor.CentreLeft : anchor,
Alpha = 0,
Rotation = bottomAligned ? 270 : 0
};
completeDisplayLoading(display);

View File

@ -99,7 +99,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
Size = new Vector2(10),
Icon = FontAwesome.Solid.ShippingFast,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Origin = Anchor.Centre,
// undo any layout rotation to display the icon the correct orientation
Rotation = -Rotation,
},
new SpriteIcon
{
@ -107,7 +109,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
Size = new Vector2(10),
Icon = FontAwesome.Solid.Bicycle,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Origin = Anchor.Centre,
// undo any layout rotation to display the icon the correct orientation
Rotation = -Rotation,
}
}
},

View File

@ -0,0 +1,19 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// An interface providing a set of methods to update a accuracy counter.
/// </summary>
public interface IAccuracyCounter : IDrawable
{
/// <summary>
/// The current accuracy to be displayed.
/// </summary>
Bindable<double> Current { get; }
}
}

View File

@ -0,0 +1,19 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// An interface providing a set of methods to update a combo counter.
/// </summary>
public interface IComboCounter : IDrawable
{
/// <summary>
/// The current combo to be displayed.
/// </summary>
Bindable<int> Current { get; }
}
}

View File

@ -0,0 +1,26 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// An interface providing a set of methods to update a health display.
/// </summary>
public interface IHealthDisplay : IDrawable
{
/// <summary>
/// The current health to be displayed.
/// </summary>
Bindable<double> Current { get; }
/// <summary>
/// Flash the display for a specified result type.
/// </summary>
/// <param name="result">The result type.</param>
void Flash(JudgementResult result);
}
}

View File

@ -0,0 +1,19 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// An interface providing a set of methods to update a score counter.
/// </summary>
public interface IScoreCounter : IDrawable
{
/// <summary>
/// The current score to be displayed.
/// </summary>
Bindable<double> Current { get; }
}
}

View File

@ -0,0 +1,252 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// Uses the 'x' symbol and has a pop-out effect while rolling over.
/// </summary>
public class LegacyComboCounter : CompositeDrawable, IComboCounter
{
public Bindable<int> Current { get; } = new BindableInt { MinValue = 0, };
private uint scheduledPopOutCurrentId;
private const double pop_out_duration = 150;
private const Easing pop_out_easing = Easing.None;
private const double fade_out_duration = 100;
/// <summary>
/// Duration in milliseconds for the counter roll-up animation for each element.
/// </summary>
private const double rolling_duration = 20;
private Drawable popOutCount;
private Drawable displayedCountSpriteText;
private int previousValue;
private int displayedCount;
private bool isRolling;
[Resolved]
private ISkinSource skin { get; set; }
public LegacyComboCounter()
{
AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomLeft;
Origin = Anchor.BottomLeft;
Margin = new MarginPadding(10);
Scale = new Vector2(1.2f);
}
/// <summary>
/// Value shown at the current moment.
/// </summary>
public virtual int DisplayedCount
{
get => displayedCount;
private set
{
if (displayedCount.Equals(value))
return;
if (isRolling)
onDisplayedCountRolling(displayedCount, value);
else if (displayedCount + 1 == value)
onDisplayedCountIncrement(value);
else
onDisplayedCountChange(value);
displayedCount = value;
}
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new[]
{
displayedCountSpriteText = createSpriteText().With(s =>
{
s.Alpha = 0;
}),
popOutCount = createSpriteText().With(s =>
{
s.Alpha = 0;
s.Margin = new MarginPadding(0.05f);
})
};
Current.ValueChanged += combo => updateCount(combo.NewValue == 0);
}
protected override void LoadComplete()
{
base.LoadComplete();
((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
displayedCountSpriteText.Anchor = Anchor;
displayedCountSpriteText.Origin = Origin;
popOutCount.Origin = Origin;
popOutCount.Anchor = Anchor;
updateCount(false);
}
private void updateCount(bool rolling)
{
int prev = previousValue;
previousValue = Current.Value;
if (!IsLoaded)
return;
if (!rolling)
{
FinishTransforms(false, nameof(DisplayedCount));
isRolling = false;
DisplayedCount = prev;
if (prev + 1 == Current.Value)
onCountIncrement(prev, Current.Value);
else
onCountChange(prev, Current.Value);
}
else
{
onCountRolling(displayedCount, Current.Value);
isRolling = true;
}
}
private void transformPopOut(int newValue)
{
((IHasText)popOutCount).Text = formatCount(newValue);
popOutCount.ScaleTo(1.6f);
popOutCount.FadeTo(0.75f);
popOutCount.MoveTo(Vector2.Zero);
popOutCount.ScaleTo(1, pop_out_duration, pop_out_easing);
popOutCount.FadeOut(pop_out_duration, pop_out_easing);
popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration, pop_out_easing);
}
private void transformNoPopOut(int newValue)
{
((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
displayedCountSpriteText.ScaleTo(1);
}
private void transformPopOutSmall(int newValue)
{
((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
displayedCountSpriteText.ScaleTo(1.1f);
displayedCountSpriteText.ScaleTo(1, pop_out_duration, pop_out_easing);
}
private void scheduledPopOutSmall(uint id)
{
// Too late; scheduled task invalidated
if (id != scheduledPopOutCurrentId)
return;
DisplayedCount++;
}
private void onCountIncrement(int currentValue, int newValue)
{
scheduledPopOutCurrentId++;
if (DisplayedCount < currentValue)
DisplayedCount++;
displayedCountSpriteText.Show();
transformPopOut(newValue);
uint newTaskId = scheduledPopOutCurrentId;
Scheduler.AddDelayed(delegate
{
scheduledPopOutSmall(newTaskId);
}, pop_out_duration);
}
private void onCountRolling(int currentValue, int newValue)
{
scheduledPopOutCurrentId++;
// Hides displayed count if was increasing from 0 to 1 but didn't finish
if (currentValue == 0 && newValue == 0)
displayedCountSpriteText.FadeOut(fade_out_duration);
transformRoll(currentValue, newValue);
}
private void onCountChange(int currentValue, int newValue)
{
scheduledPopOutCurrentId++;
if (newValue == 0)
displayedCountSpriteText.FadeOut();
DisplayedCount = newValue;
}
private void onDisplayedCountRolling(int currentValue, int newValue)
{
if (newValue == 0)
displayedCountSpriteText.FadeOut(fade_out_duration);
else
displayedCountSpriteText.Show();
transformNoPopOut(newValue);
}
private void onDisplayedCountChange(int newValue)
{
displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1);
transformNoPopOut(newValue);
}
private void onDisplayedCountIncrement(int newValue)
{
displayedCountSpriteText.Show();
transformPopOutSmall(newValue);
}
private void transformRoll(int currentValue, int newValue) =>
this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), Easing.None);
private string formatCount(int count) => $@"{count}x";
private double getProportionalDuration(int currentValue, int newValue)
{
double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue;
return difference * rolling_duration;
}
private OsuSpriteText createSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboText));
}
}

View File

@ -48,22 +48,29 @@ namespace osu.Game.Screens.Play.HUD
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
Child = new FillFlowContainer
{
iconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
iconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
},
unrankedText = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = @"/ UNRANKED /",
Font = OsuFont.Numeric.With(size: 12)
}
},
unrankedText = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Text = @"/ UNRANKED /",
Font = OsuFont.Numeric.With(size: 12)
}
};
Current.ValueChanged += mods =>

View File

@ -20,14 +20,13 @@ namespace osu.Game.Screens.Play.HUD
public readonly VisualSettings VisualSettings;
//public readonly CollectionSettings CollectionSettings;
//public readonly DiscussionSettings DiscussionSettings;
public PlayerSettingsOverlay()
{
AlwaysPresent = true;
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
AutoSizeAxes = Axes.Both;
Child = new FillFlowContainer<PlayerSettingsGroup>
{
@ -36,7 +35,6 @@ namespace osu.Game.Screens.Play.HUD
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Margin = new MarginPadding { Top = 100, Right = 10 },
Children = new PlayerSettingsGroup[]
{
//CollectionSettings = new CollectionSettings(),

View File

@ -0,0 +1,29 @@
// 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 osu.Framework.Bindables;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public class SkinnableAccuracyCounter : SkinnableDrawable, IAccuracyCounter
{
public Bindable<double> Current { get; } = new Bindable<double>();
public SkinnableAccuracyCounter()
: base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter())
{
CentreComponent = false;
}
private IAccuracyCounter skinnedCounter;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
skinnedCounter = Drawable as IAccuracyCounter;
skinnedCounter?.Current.BindTo(Current);
}
}
}

View File

@ -0,0 +1,29 @@
// 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 osu.Framework.Bindables;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public class SkinnableComboCounter : SkinnableDrawable, IComboCounter
{
public Bindable<int> Current { get; } = new Bindable<int>();
public SkinnableComboCounter()
: base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter())
{
CentreComponent = false;
}
private IComboCounter skinnedCounter;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
skinnedCounter = Drawable as IComboCounter;
skinnedCounter?.Current.BindTo(Current);
}
}
}

View File

@ -0,0 +1,29 @@
// 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 osu.Framework.Bindables;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public class SkinnableScoreCounter : SkinnableDrawable, IScoreCounter
{
public Bindable<double> Current { get; } = new Bindable<double>();
public SkinnableScoreCounter()
: base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter())
{
CentreComponent = false;
}
private IScoreCounter skinnedCounter;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
skinnedCounter = Drawable as IScoreCounter;
skinnedCounter?.Current.BindTo(Current);
}
}
}

View File

@ -1,141 +0,0 @@
// 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 osuTK;
using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// Uses the 'x' symbol and has a pop-out effect while rolling over.
/// </summary>
public class StandardComboCounter : ComboCounter
{
protected uint ScheduledPopOutCurrentId;
protected virtual float PopOutSmallScale => 1.1f;
protected virtual bool CanPopOutWhileRolling => false;
public new Vector2 PopOutScale = new Vector2(1.6f);
protected override void LoadComplete()
{
base.LoadComplete();
PopOutCount.Origin = Origin;
PopOutCount.Anchor = Anchor;
}
protected override string FormatCount(int count)
{
return $@"{count}x";
}
protected virtual void TransformPopOut(int newValue)
{
PopOutCount.Text = FormatCount(newValue);
PopOutCount.ScaleTo(PopOutScale);
PopOutCount.FadeTo(PopOutInitialAlpha);
PopOutCount.MoveTo(Vector2.Zero);
PopOutCount.ScaleTo(1, PopOutDuration, PopOutEasing);
PopOutCount.FadeOut(PopOutDuration, PopOutEasing);
PopOutCount.MoveTo(DisplayedCountSpriteText.Position, PopOutDuration, PopOutEasing);
}
protected virtual void TransformPopOutRolling(int newValue)
{
TransformPopOut(newValue);
TransformPopOutSmall(newValue);
}
protected virtual void TransformNoPopOut(int newValue)
{
DisplayedCountSpriteText.Text = FormatCount(newValue);
DisplayedCountSpriteText.ScaleTo(1);
}
protected virtual void TransformPopOutSmall(int newValue)
{
DisplayedCountSpriteText.Text = FormatCount(newValue);
DisplayedCountSpriteText.ScaleTo(PopOutSmallScale);
DisplayedCountSpriteText.ScaleTo(1, PopOutDuration, PopOutEasing);
}
protected virtual void ScheduledPopOutSmall(uint id)
{
// Too late; scheduled task invalidated
if (id != ScheduledPopOutCurrentId)
return;
DisplayedCount++;
}
protected override void OnCountRolling(int currentValue, int newValue)
{
ScheduledPopOutCurrentId++;
// Hides displayed count if was increasing from 0 to 1 but didn't finish
if (currentValue == 0 && newValue == 0)
DisplayedCountSpriteText.FadeOut(FadeOutDuration);
base.OnCountRolling(currentValue, newValue);
}
protected override void OnCountIncrement(int currentValue, int newValue)
{
ScheduledPopOutCurrentId++;
if (DisplayedCount < currentValue)
DisplayedCount++;
DisplayedCountSpriteText.Show();
TransformPopOut(newValue);
uint newTaskId = ScheduledPopOutCurrentId;
Scheduler.AddDelayed(delegate
{
ScheduledPopOutSmall(newTaskId);
}, PopOutDuration);
}
protected override void OnCountChange(int currentValue, int newValue)
{
ScheduledPopOutCurrentId++;
if (newValue == 0)
DisplayedCountSpriteText.FadeOut();
base.OnCountChange(currentValue, newValue);
}
protected override void OnDisplayedCountRolling(int currentValue, int newValue)
{
if (newValue == 0)
DisplayedCountSpriteText.FadeOut(FadeOutDuration);
else
DisplayedCountSpriteText.Show();
if (CanPopOutWhileRolling)
TransformPopOutRolling(newValue);
else
TransformNoPopOut(newValue);
}
protected override void OnDisplayedCountChange(int newValue)
{
DisplayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1);
TransformNoPopOut(newValue);
}
protected override void OnDisplayedCountIncrement(int newValue)
{
DisplayedCountSpriteText.Show();
TransformPopOutSmall(newValue);
}
}
}

View File

@ -10,28 +10,30 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osuTK;
using osuTK.Input;
namespace osu.Game.Screens.Play
{
[Cached]
public class HUDOverlay : Container
{
private const float fade_duration = 400;
private const Easing fade_easing = Easing.Out;
public const float FADE_DURATION = 400;
public const Easing FADE_EASING = Easing.Out;
public readonly KeyCounterDisplay KeyCounter;
public readonly RollingCounter<int> ComboCounter;
public readonly ScoreCounter ScoreCounter;
public readonly RollingCounter<double> AccuracyCounter;
public readonly HealthDisplay HealthDisplay;
public readonly SkinnableComboCounter ComboCounter;
public readonly SkinnableScoreCounter ScoreCounter;
public readonly SkinnableAccuracyCounter AccuracyCounter;
public readonly SkinnableHealthDisplay HealthDisplay;
public readonly SongProgress Progress;
public readonly ModDisplay ModDisplay;
public readonly HitErrorDisplay HitErrorDisplay;
@ -61,7 +63,10 @@ namespace osu.Game.Screens.Play
public Action<double> RequestSeek;
private readonly Container topScoreContainer;
private readonly FillFlowContainer bottomRightElements;
private readonly FillFlowContainer topRightElements;
private readonly Container mainUIElements;
private IEnumerable<Drawable> hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
@ -80,35 +85,61 @@ namespace osu.Game.Screens.Play
visibilityContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
mainUIElements = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
HealthDisplay = CreateHealthDisplay(),
AccuracyCounter = CreateAccuracyCounter(),
ScoreCounter = CreateScoreCounter(),
ComboCounter = CreateComboCounter(),
HitErrorDisplay = CreateHitErrorDisplayOverlay(),
}
},
},
new Drawable[]
{
Progress = CreateProgress(),
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
}
},
},
topRightElements = new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Margin = new MarginPadding(10),
Spacing = new Vector2(10),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
HealthDisplay = CreateHealthDisplay(),
topScoreContainer = new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
AccuracyCounter = CreateAccuracyCounter(),
ScoreCounter = CreateScoreCounter(),
ComboCounter = CreateComboCounter(),
},
},
Progress = CreateProgress(),
ModDisplay = CreateModsContainer(),
HitErrorDisplay = CreateHitErrorDisplayOverlay(),
PlayerSettingsOverlay = CreatePlayerSettingsOverlay(),
}
},
new FillFlowContainer
bottomRightElements = new FillFlowContainer
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y),
Margin = new MarginPadding(10),
Spacing = new Vector2(10),
AutoSizeAxes = Axes.Both,
LayoutDuration = fade_duration / 2,
LayoutEasing = fade_easing,
LayoutDuration = FADE_DURATION / 2,
LayoutEasing = FADE_EASING,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
@ -161,21 +192,8 @@ namespace osu.Game.Screens.Play
{
base.LoadComplete();
ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, fade_duration, fade_easing)));
ShowHealthbar.BindValueChanged(healthBar =>
{
if (healthBar.NewValue)
{
HealthDisplay.FadeIn(fade_duration, fade_easing);
topScoreContainer.MoveToY(30, fade_duration, fade_easing);
}
else
{
HealthDisplay.FadeOut(fade_duration, fade_easing);
topScoreContainer.MoveToY(0, fade_duration, fade_easing);
}
}, true);
ShowHealthbar.BindValueChanged(healthBar => HealthDisplay.FadeTo(healthBar.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING), true);
ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING)));
configShowHud.BindValueChanged(visible =>
{
@ -186,6 +204,24 @@ namespace osu.Game.Screens.Play
replayLoaded.BindValueChanged(replayLoadedValueChanged, true);
}
protected override void Update()
{
base.Update();
float topRightOffset = 0;
// fetch the bottom-most position of any main ui element that is anchored to the top of the screen.
// consider this kind of temporary.
foreach (var d in mainUIElements)
{
if (d is SkinnableDrawable sd && (sd.Drawable.Anchor & Anchor.y0) > 0)
topRightOffset = Math.Max(sd.Drawable.ScreenSpaceDrawQuad.BottomRight.Y, topRightOffset);
}
topRightElements.Y = ToLocalSpace(new Vector2(0, topRightOffset)).Y;
bottomRightElements.Y = -Progress.Height;
}
private void replayLoadedValueChanged(ValueChangedEvent<bool> e)
{
PlayerSettingsOverlay.ReplayLoaded = e.NewValue;
@ -230,34 +266,13 @@ namespace osu.Game.Screens.Play
return base.OnKeyDown(e);
}
protected virtual RollingCounter<double> CreateAccuracyCounter() => new PercentageCounter
{
BypassAutoSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopRight,
Margin = new MarginPadding { Top = 5, Right = 20 },
};
protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter();
protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
};
protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter();
protected virtual RollingCounter<int> CreateComboCounter() => new SimpleComboCounter
{
BypassAutoSizeAxes = Axes.X,
Anchor = Anchor.TopRight,
Origin = Anchor.TopLeft,
Margin = new MarginPadding { Top = 5, Left = 20 },
};
protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter();
protected virtual HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay
{
Size = new Vector2(1, 5),
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Top = 20 }
};
protected virtual SkinnableHealthDisplay CreateHealthDisplay() => new SkinnableHealthDisplay();
protected virtual FailingLayer CreateFailingLayer() => new FailingLayer
{
@ -268,7 +283,6 @@ namespace osu.Game.Screens.Play
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding(10),
};
protected virtual SongProgress CreateProgress() => new SongProgress
@ -289,7 +303,6 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20, Right = 20 },
};
protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows);
@ -302,8 +315,14 @@ namespace osu.Game.Screens.Play
AccuracyCounter?.Current.BindTo(processor.Accuracy);
ComboCounter?.Current.BindTo(processor.Combo);
if (HealthDisplay is StandardHealthDisplay shd)
processor.NewJudgement += shd.Flash;
if (HealthDisplay is IHealthDisplay shd)
{
processor.NewJudgement += judgement =>
{
if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit)
shd.Flash(judgement);
};
}
}
protected virtual void BindHealthProcessor(HealthProcessor processor)

View File

@ -35,7 +35,8 @@ using osu.Game.Users;
namespace osu.Game.Screens.Play
{
[Cached]
public class Player : ScreenWithBeatmapBackground
[Cached(typeof(ISamplePlaybackDisabler))]
public class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler
{
/// <summary>
/// The delay upon completion of the beatmap before displaying the results screen.
@ -55,6 +56,8 @@ namespace osu.Game.Screens.Play
// We are managing our own adjustments (see OnEntering/OnExiting).
public override bool AllowRateAdjustments => false;
private readonly Bindable<bool> samplePlaybackDisabled = new Bindable<bool>();
/// <summary>
/// Whether gameplay should pause when the game window focus is lost.
/// </summary>
@ -218,8 +221,12 @@ namespace osu.Game.Screens.Play
createGameplayComponents(Beatmap.Value, playableBeatmap)
});
// also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
var hudRulesetContainer = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value));
GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value)));
if (!DrawableRuleset.AllowGameplayOverlays)
{
@ -229,7 +236,11 @@ namespace osu.Game.Screens.Play
skipOverlay.Hide();
}
DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState());
DrawableRuleset.IsPaused.BindValueChanged(paused =>
{
updateGameplayState();
samplePlaybackDisabled.Value = paused.NewValue;
});
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
@ -752,5 +763,7 @@ namespace osu.Game.Screens.Play
}
#endregion
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
}
}

View File

@ -0,0 +1,51 @@
// 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;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play
{
public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay
{
public Bindable<double> Current { get; } = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
public void Flash(JudgementResult result) => skinnedCounter?.Flash(result);
private HealthProcessor processor;
public void BindHealthProcessor(HealthProcessor processor)
{
if (this.processor != null)
throw new InvalidOperationException("Can't bind to a processor more than once");
this.processor = processor;
Current.BindTo(processor.Health);
}
public SkinnableHealthDisplay()
: base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay())
{
CentreComponent = false;
}
private IHealthDisplay skinnedCounter;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
skinnedCounter = Drawable as IHealthDisplay;
skinnedCounter?.Current.BindTo(Current);
}
}
}

View File

@ -70,7 +70,6 @@ namespace osu.Game.Screens.Play
public SongProgress()
{
Masking = true;
Height = bottom_bar_height + graph_height + handle_size.Y + info_height;
Children = new Drawable[]
{
@ -148,6 +147,8 @@ namespace osu.Game.Screens.Play
bar.CurrentTime = gameplayTime;
graph.Progress = (int)(graph.ColumnCount * progress);
Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y;
}
private void updateBarVisibility()

View File

@ -79,10 +79,10 @@ namespace osu.Game.Screens.Select
}
private static int getLengthScale(string value) =>
value.EndsWith("ms") ? 1 :
value.EndsWith("s") ? 1000 :
value.EndsWith("m") ? 60000 :
value.EndsWith("h") ? 3600000 : 1000;
value.EndsWith("ms", StringComparison.Ordinal) ? 1 :
value.EndsWith('s') ? 1000 :
value.EndsWith('m') ? 60000 :
value.EndsWith('h') ? 3600000 : 1000;
private static bool parseFloatWithPoint(string value, out float result) =>
float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result);