1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-12 09:27:51 +08:00
osu-lazer/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs

485 lines
20 KiB
C#
Raw Normal View History

2020-03-17 15:37:56 +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;
2020-03-17 15:37:56 +08:00
using osu.Framework.Allocation;
2020-10-29 15:11:37 +08:00
using osu.Framework.Audio;
using osu.Framework.Bindables;
2020-03-17 15:37:56 +08:00
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
2020-03-17 15:37:56 +08:00
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
2020-03-17 15:37:56 +08:00
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
2020-03-17 15:37:56 +08:00
using osu.Game.Scoring;
using osu.Game.Skinning;
2020-03-17 15:37:56 +08:00
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// The component that displays the player's accuracy on the results screen.
/// </summary>
public class AccuracyCircle : CompositeDrawable
{
/// <summary>
/// Duration for the transforms causing this component to appear.
/// </summary>
public const double APPEAR_DURATION = 200;
/// <summary>
/// Delay before the accuracy circle starts filling.
/// </summary>
public const double ACCURACY_TRANSFORM_DELAY = 450;
/// <summary>
/// Duration for the accuracy circle fill.
/// </summary>
public const double ACCURACY_TRANSFORM_DURATION = 3000;
/// <summary>
/// Delay after <see cref="ACCURACY_TRANSFORM_DURATION"/> for the rank text (A/B/C/D/S/SS) to appear.
/// </summary>
public const double TEXT_APPEAR_DELAY = ACCURACY_TRANSFORM_DURATION / 2;
/// <summary>
/// Delay before the rank circles start filling.
/// </summary>
public const double RANK_CIRCLE_TRANSFORM_DELAY = 150;
/// <summary>
/// Duration for the rank circle fills.
/// </summary>
public const double RANK_CIRCLE_TRANSFORM_DURATION = 800;
/// <summary>
/// Relative width of the rank circles.
/// </summary>
public const float RANK_CIRCLE_RADIUS = 0.06f;
/// <summary>
/// Relative width of the circle showing the accuracy.
/// </summary>
private const float accuracy_circle_radius = 0.2f;
/// <summary>
/// SS is displayed as a 1% region, otherwise it would be invisible.
/// </summary>
private const double virtual_ss_percentage = 0.01;
/// <summary>
/// The easing for the circle filling transforms.
/// </summary>
public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10;
2021-06-02 11:49:00 +08:00
#region Sound Effect Playback Parameters
// swoosh-up
private const double sfx_swoosh_pre_delay = 443f;
private const double sfx_swoosh_volume = 0.4f;
// score ticks
private const double sfx_score_tick_debounce_rate_start = 18f;
private const double sfx_score_tick_debounce_rate_end = 300f;
private const Easing sfx_score_tick_debounce_rate_easing = Easing.OutSine;
private const double sfx_score_tick_volume_start = 0.6f;
private const double sfx_score_tick_volume_end = 1.0f;
private const Easing sfx_score_tick_volume_easing = Easing.OutSine;
private const Easing sfx_score_tick_pitch_easing = Easing.OutSine;
// badge dinks
private const double sfx_badge_dink_volume = 1f;
// impact
private const double sfx_rank_impact_volume = 1.0f;
// applause
private const bool sfx_applause_enabled = true;
private const double sfx_applause_pre_delay = 545f;
private const double sfx_applause_volume = 0.8f;
#endregion
2020-03-17 15:37:56 +08:00
private readonly ScoreInfo score;
private SmoothCircularProgress accuracyCircle;
private SmoothCircularProgress innerMask;
private Container<RankBadge> badges;
private RankText rankText;
private DrawableSample scoreTickSound;
private DrawableSample badgeTickSound;
private DrawableSample badgeMaxSound;
private DrawableSample swooshUpSound;
private DrawableSample rankDImpactSound;
private DrawableSample rankBImpactSound;
private DrawableSample rankCImpactSound;
private DrawableSample rankAImpactSound;
private DrawableSample rankSImpactSound;
private DrawableSample rankSSImpactSound;
private DrawableSample rankDApplauseSound;
private DrawableSample rankBApplauseSound;
private DrawableSample rankCApplauseSound;
private DrawableSample rankAApplauseSound;
private DrawableSample rankSApplauseSound;
private DrawableSample rankSSApplauseSound;
private Bindable<double> tickPlaybackRate = new Bindable<double>();
private double lastTickPlaybackTime;
private bool isTicking;
private readonly bool withFlair;
public AccuracyCircle(ScoreInfo score, bool withFlair)
2020-03-17 15:37:56 +08:00
{
this.score = score;
this.withFlair = withFlair;
}
2020-03-17 15:37:56 +08:00
[BackgroundDependencyLoader]
2021-06-02 11:49:00 +08:00
private void load(GameHost host, ISkinSource skin)
2020-03-17 15:37:56 +08:00
{
InternalChildren = new Drawable[]
{
new SmoothCircularProgress
{
Name = "Background circle",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(47),
Alpha = 0.5f,
InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle
Current = { Value = 1 },
},
accuracyCircle = new SmoothCircularProgress
{
Name = "Accuracy circle",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
InnerRadius = accuracy_circle_radius,
},
new BufferedContainer
{
Name = "Graded circles",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Padding = new MarginPadding(2),
Children = new Drawable[]
{
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.X),
2020-03-17 15:37:56 +08:00
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 1 }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.S),
2020-03-17 15:37:56 +08:00
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 1 - virtual_ss_percentage }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.A),
2020-03-17 15:37:56 +08:00
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.95f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.B),
2020-03-17 15:37:56 +08:00
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.9f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.C),
2020-03-17 15:37:56 +08:00
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.8f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.D),
2020-03-17 15:37:56 +08:00
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.7f }
},
new RankNotch(0),
new RankNotch((float)(1 - virtual_ss_percentage)),
new RankNotch(0.95f),
new RankNotch(0.9f),
new RankNotch(0.8f),
new RankNotch(0.7f),
new BufferedContainer
{
Name = "Graded circle mask",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(1),
Blending = new BlendingParameters
{
Source = BlendingType.DstColor,
Destination = BlendingType.OneMinusSrcAlpha,
SourceAlpha = BlendingType.One,
DestinationAlpha = BlendingType.SrcAlpha
},
Child = innerMask = new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
InnerRadius = RANK_CIRCLE_RADIUS - 0.01f,
}
}
}
},
badges = new Container<RankBadge>
{
Name = "Rank badges",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
Children = new[]
{
2020-07-03 03:35:32 +08:00
new RankBadge(1f, getRank(ScoreRank.X)),
new RankBadge(0.95f, getRank(ScoreRank.S)),
new RankBadge(0.9f, getRank(ScoreRank.A)),
new RankBadge(0.8f, getRank(ScoreRank.B)),
new RankBadge(0.7f, getRank(ScoreRank.C)),
new RankBadge(0.35f, getRank(ScoreRank.D)),
2020-03-17 15:37:56 +08:00
}
},
rankText = new RankText(score.Rank)
};
if (withFlair)
{
2021-06-02 11:49:00 +08:00
tickPlaybackRate = new Bindable<double>(sfx_score_tick_debounce_rate_start);
AddRangeInternal(new Drawable[]
{
scoreTickSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultScoreTick)) as DrawableSample,
badgeTickSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultBadgeTick)) as DrawableSample,
badgeMaxSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultBadgeTickMax)) as DrawableSample,
swooshUpSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultSwooshUp)) as DrawableSample,
rankDImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultRank_D)) as DrawableSample,
rankBImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultRank_B)) as DrawableSample,
rankCImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultRank_C)) as DrawableSample,
rankAImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultRank_A)) as DrawableSample,
rankSImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultRank_S)) as DrawableSample,
rankSSImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultRank_SS)) as DrawableSample,
rankDApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultApplause_D)) as DrawableSample,
rankBApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultApplause_B)) as DrawableSample,
rankCApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultApplause_C)) as DrawableSample,
rankAApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultApplause_A)) as DrawableSample,
rankSApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultApplause_S)) as DrawableSample,
rankSSApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent<GameplaySkinSamples>(GameplaySkinSamples.ResultApplause_SS)) as DrawableSample
});
}
2020-03-17 15:37:56 +08:00
}
2020-07-03 03:35:32 +08:00
private ScoreRank getRank(ScoreRank rank)
{
foreach (var mod in score.Mods.OfType<IApplicableToScoreProcessor>())
rank = mod.AdjustRank(rank, score.Accuracy);
return rank;
}
protected override void Update()
{
base.Update();
2021-06-02 11:49:00 +08:00
if (!isTicking) return;
bool enoughTimePassedSinceLastPlayback = Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value;
if (!enoughTimePassedSinceLastPlayback) return;
Schedule(() => scoreTickSound?.Play());
lastTickPlaybackTime = Clock.CurrentTime;
}
2020-03-17 15:37:56 +08:00
protected override void LoadComplete()
{
base.LoadComplete();
this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint);
2021-06-02 11:49:00 +08:00
if (swooshUpSound != null)
{
2021-06-02 11:49:00 +08:00
this.Delay(sfx_swoosh_pre_delay).Schedule(() =>
{
2021-06-02 11:49:00 +08:00
swooshUpSound.VolumeTo(sfx_swoosh_volume);
swooshUpSound.Play();
});
}
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY))
2020-03-17 15:37:56 +08:00
innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
2020-03-17 15:37:56 +08:00
{
double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
Schedule(() =>
{
if (scoreTickSound != null)
{
// doesn't work
2021-06-02 11:49:00 +08:00
scoreTickSound.FrequencyTo(1).Then().FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_pitch_easing);
scoreTickSound.VolumeTo(sfx_score_tick_volume_start).Then().VolumeTo(sfx_score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_volume_easing);
this.TransformBindableTo(tickPlaybackRate, sfx_score_tick_debounce_rate_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_debounce_rate_easing);
}
isTicking = true;
});
int badgeNum = 0;
2020-03-17 15:37:56 +08:00
foreach (var badge in badges)
{
if (badge.Accuracy > score.Accuracy)
continue;
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true))
2020-10-29 15:11:37 +08:00
{
2020-03-17 15:37:56 +08:00
badge.Appear();
Schedule(() =>
{
DrawableSample dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound;
2021-06-02 11:49:00 +08:00
if (dink == null) return;
dink.FrequencyTo(1 + badgeNum++ * 0.05);
2021-06-02 11:49:00 +08:00
dink.VolumeTo(sfx_badge_dink_volume);
dink.Play();
});
2020-10-29 15:11:37 +08:00
}
2020-03-17 15:37:56 +08:00
}
using (BeginDelayedSequence(TEXT_APPEAR_DELAY))
2020-10-29 15:11:37 +08:00
{
2020-03-17 15:37:56 +08:00
rankText.Appear();
Schedule(() =>
{
isTicking = false;
DrawableSample impact = null;
switch (score.Rank)
{
case ScoreRank.D:
impact = rankDImpactSound;
break;
case ScoreRank.C:
impact = rankCImpactSound;
break;
case ScoreRank.B:
impact = rankBImpactSound;
break;
case ScoreRank.A:
impact = rankAImpactSound;
break;
case ScoreRank.S:
case ScoreRank.SH:
impact = rankSImpactSound;
break;
case ScoreRank.X:
case ScoreRank.XH:
impact = rankSSImpactSound;
break;
}
if (impact == null) return;
2021-06-02 11:49:00 +08:00
impact.VolumeTo(sfx_rank_impact_volume);
impact.Play();
});
2021-06-02 11:49:00 +08:00
using (BeginDelayedSequence(sfx_applause_pre_delay))
{
2021-06-02 11:49:00 +08:00
if (!sfx_applause_enabled) return;
Schedule(() =>
{
DrawableSample applause = null;
switch (score.Rank)
{
case ScoreRank.D:
applause = rankDApplauseSound;
break;
case ScoreRank.C:
applause = rankCApplauseSound;
break;
case ScoreRank.B:
applause = rankBApplauseSound;
break;
case ScoreRank.A:
applause = rankAApplauseSound;
break;
case ScoreRank.S:
case ScoreRank.SH:
applause = rankSApplauseSound;
break;
case ScoreRank.X:
case ScoreRank.XH:
applause = rankSSApplauseSound;
break;
}
if (applause == null) return;
2021-06-02 11:49:00 +08:00
applause.VolumeTo(sfx_applause_volume);
applause.Play();
});
}
2020-10-29 15:11:37 +08:00
}
2020-03-17 15:37:56 +08:00
}
}
private double inverseEasing(Easing easing, double targetValue)
{
double test = 0;
double result = 0;
int count = 2;
while (Math.Abs(result - targetValue) > 0.005)
{
int dir = Math.Sign(targetValue - result);
test += dir * 1.0 / count;
result = Interpolation.ApplyEasing(easing, test);
count++;
}
return test;
}
}
}