1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-23 18:07:25 +08:00
osu-lazer/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs

338 lines
13 KiB
C#
Raw Normal View History

2019-08-20 01:44:06 +08:00
// 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 System.Linq;
2019-08-30 17:46:42 +08:00
using osu.Framework.Allocation;
using osu.Framework.Bindables;
2019-08-30 17:46:42 +08:00
using osu.Framework.Extensions.Color4Extensions;
2019-08-20 01:44:06 +08:00
using osu.Framework.Graphics;
2019-08-30 17:46:42 +08:00
using osu.Framework.Graphics.Colour;
2019-08-20 01:44:06 +08:00
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
2019-08-30 17:46:42 +08:00
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
2019-08-30 17:46:42 +08:00
using osuTK;
2019-08-20 01:44:06 +08:00
2019-08-30 17:46:42 +08:00
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
2019-08-20 01:44:06 +08:00
{
public class BarHitErrorMeter : HitErrorMeter
2019-08-20 01:44:06 +08:00
{
2022-03-14 18:27:53 +08:00
private const int judgement_line_width = 14;
[SettingSource("Judgement line thickness", "How thick the individual lines should be.")]
public BindableNumber<float> JudgementLineThickness { get; } = new BindableNumber<float>(4)
{
MinValue = 1,
MaxValue = 8,
Precision = 0.1f,
};
[SettingSource("Show moving average arrow", "Whether an arrow should move beneath the bar showing the average error.")]
public Bindable<bool> ShowMovingAverage { get; } = new BindableBool(true);
private SpriteIcon arrow;
private SpriteIcon iconEarly;
private SpriteIcon iconLate;
private Container colourBarsEarly;
private Container colourBarsLate;
private Container judgementsContainer;
private double maxHitWindow;
2019-08-20 01:44:06 +08:00
private double floatingAverage;
private Container colourBars;
private Container arrowContainer;
private const int max_concurrent_judgements = 50;
public BarHitErrorMeter()
2019-08-20 01:44:06 +08:00
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
2022-03-14 18:27:53 +08:00
const int centre_marker_size = 8;
2022-03-14 18:18:47 +08:00
const int bar_height = 200;
const int bar_width = 2;
const float chevron_size = 8;
const float icon_size = 14;
var hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
InternalChild = new Container
2019-08-20 01:44:06 +08:00
{
AutoSizeAxes = Axes.X,
Height = bar_height,
Margin = new MarginPadding(2),
2019-08-20 01:44:06 +08:00
Children = new Drawable[]
{
colourBars = new Container
2019-08-20 01:44:06 +08:00
{
Name = "colour axis",
X = chevron_size,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = judgement_line_width,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
iconEarly = new SpriteIcon
2019-08-30 18:14:07 +08:00
{
Y = -10,
2022-03-14 18:18:47 +08:00
Size = new Vector2(icon_size),
2019-08-30 18:14:07 +08:00
Icon = FontAwesome.Solid.ShippingFast,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
2019-08-30 18:14:07 +08:00
},
iconLate = new SpriteIcon
2019-08-30 18:14:07 +08:00
{
Y = 10,
2022-03-14 18:18:47 +08:00
Size = new Vector2(icon_size),
2019-08-30 18:14:07 +08:00
Icon = FontAwesome.Solid.Bicycle,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
},
colourBarsEarly = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Width = bar_width,
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Scale = new Vector2(1, -1),
},
colourBarsLate = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Width = bar_width,
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
},
new Circle
{
Name = "middle marker behind",
Colour = GetColourForHitResult(hitWindows.Last().result),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(centre_marker_size),
},
2022-03-14 18:27:53 +08:00
judgementsContainer = new Container
{
Name = "judgements",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Width = judgement_line_width,
},
new Circle
{
Name = "middle marker in front",
2022-03-14 18:27:53 +08:00
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(centre_marker_size),
Scale = new Vector2(0.5f),
},
}
2019-08-20 01:44:06 +08:00
},
arrowContainer = new Container
2019-08-20 01:44:06 +08:00
{
Name = "average chevron",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreRight,
Width = chevron_size,
X = chevron_size,
2019-08-20 01:44:06 +08:00
RelativeSizeAxes = Axes.Y,
Alpha = 0,
Scale = new Vector2(0, 1),
Child = arrow = new SpriteIcon
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Y,
Y = 0.5f,
Icon = FontAwesome.Solid.ChevronRight,
Size = new Vector2(chevron_size),
}
2019-08-20 01:44:06 +08:00
},
}
};
2019-08-20 01:44:06 +08:00
createColourBars(hitWindows);
2019-08-20 01:44:06 +08:00
}
protected override void LoadComplete()
{
base.LoadComplete();
colourBars.Height = 0;
colourBars.ResizeHeightTo(1, 800, Easing.OutQuint);
// delay the arrow appearance animation for only the initial appearance.
using (arrowContainer.BeginDelayedSequence(250))
{
ShowMovingAverage.BindValueChanged(visible =>
{
arrowContainer.FadeTo(visible.NewValue ? 1 : 0, 250, Easing.OutQuint);
arrowContainer.ScaleTo(visible.NewValue ? new Vector2(1) : new Vector2(0, 1), 250, Easing.OutQuint);
}, true);
}
}
protected override void Update()
{
base.Update();
// undo any layout rotation to display icons in the correct orientation
iconEarly.Rotation = -Rotation;
iconLate.Rotation = -Rotation;
}
private void createColourBars((HitResult result, double length)[] windows)
2019-08-20 13:00:09 +08:00
{
// max to avoid div-by-zero.
maxHitWindow = Math.Max(1, windows.First().length);
for (int i = 0; i < windows.Length; i++)
{
(var result, double length) = windows[i];
float hitWindow = (float)(length / maxHitWindow);
colourBarsEarly.Add(createColourBar(result, hitWindow, i == 0));
colourBarsLate.Add(createColourBar(result, hitWindow, i == 0));
}
Drawable createColourBar(HitResult result, float height, bool requireGradient = false)
{
2019-12-21 19:52:53 +08:00
var colour = GetColourForHitResult(result);
if (requireGradient)
{
// the first bar needs gradient rendering.
const float gradient_start = 0.8f;
return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
2019-12-21 19:52:53 +08:00
Colour = colour,
Height = height * gradient_start
},
new Box
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(colour, colour.Opacity(0)),
Y = gradient_start,
Height = height * (1 - gradient_start)
},
}
};
}
return new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
Height = height
};
}
}
2019-08-20 13:00:09 +08:00
protected override void OnNewJudgement(JudgementResult judgement)
2019-08-20 01:44:06 +08:00
{
2022-03-14 18:27:53 +08:00
const int arrow_move_duration = 800;
2022-03-14 18:18:47 +08:00
if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0)
2019-08-20 01:44:06 +08:00
return;
if (!judgement.Type.IsScorable() || judgement.Type.IsBonus())
return;
if (judgementsContainer.Count > max_concurrent_judgements)
{
const double quick_fade_time = 100;
// check with a bit of lenience to avoid precision error in comparison.
var old = judgementsContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1);
if (old != null)
{
old.ClearTransforms();
old.FadeOut(quick_fade_time).Expire();
}
}
judgementsContainer.Add(new JudgementLine
{
JudgementLineThickness = { BindTarget = JudgementLineThickness },
Y = getRelativeJudgementPosition(judgement.TimeOffset),
Colour = GetColourForHitResult(judgement.Type),
});
2019-08-20 01:44:06 +08:00
arrow.MoveToY(
getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1)
2022-03-14 18:27:53 +08:00
, arrow_move_duration, Easing.OutQuint);
2019-08-20 01:44:06 +08:00
}
private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1);
2019-08-20 01:44:06 +08:00
internal class JudgementLine : CompositeDrawable
2019-08-20 01:44:06 +08:00
{
public readonly BindableNumber<float> JudgementLineThickness = new BindableFloat();
public JudgementLine()
{
RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y;
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
Anchor = Anchor.TopCentre;
InternalChild = new Circle
{
RelativeSizeAxes = Axes.Both,
};
}
2019-08-20 01:44:06 +08:00
protected override void LoadComplete()
{
const int judgement_fade_in_duration = 100;
const int judgement_fade_out_duration = 5000;
base.LoadComplete();
2019-08-20 01:44:06 +08:00
Alpha = 0;
Width = 0;
JudgementLineThickness.BindValueChanged(thickness => Height = thickness.NewValue, true);
this
2022-03-14 18:27:53 +08:00
.FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint)
.ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint)
.Then()
2022-03-14 18:27:53 +08:00
.FadeOut(judgement_fade_out_duration)
.ResizeWidthTo(0, judgement_fade_out_duration, Easing.InQuint)
.Expire();
}
2019-08-20 01:44:06 +08:00
}
2021-09-10 04:21:51 +08:00
2021-09-20 22:22:36 +08:00
public override void Clear() => judgementsContainer.Clear();
2019-08-20 01:44:06 +08:00
}
}