mirror of
https://github.com/ppy/osu.git
synced 2025-03-28 20:47:22 +08:00
Merge pull request #9988 from bdach/unstable-rate
Add basic hit unstable rate display to results screen
This commit is contained in:
commit
65759e1d1e
@ -326,6 +326,16 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
Height = 250
|
Height = 250
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
|
{
|
||||||
|
new UnstableRate(score.HitEvents)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -193,30 +193,46 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
new StatisticRow
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
|
||||||
|
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
Columns = new[]
|
new StatisticRow
|
||||||
{
|
{
|
||||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList())
|
Columns = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new StatisticItem("Timing Distribution",
|
||||||
Height = 250
|
new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
}),
|
{
|
||||||
}
|
RelativeSizeAxes = Axes.X,
|
||||||
},
|
Height = 250
|
||||||
new StatisticRow
|
}),
|
||||||
{
|
}
|
||||||
Columns = new[]
|
},
|
||||||
|
new StatisticRow
|
||||||
{
|
{
|
||||||
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
Columns = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
||||||
Height = 250
|
{
|
||||||
}),
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
|
{
|
||||||
|
new UnstableRate(timedHitEvents)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,19 +161,34 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
new StatisticRow
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
|
||||||
|
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
Columns = new[]
|
new StatisticRow
|
||||||
{
|
{
|
||||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList())
|
Columns = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
Height = 250
|
{
|
||||||
}),
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
|
{
|
||||||
|
new UnstableRate(timedHitEvents)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ using osu.Game.Screens.Ranking.Statistics;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Ranking
|
namespace osu.Game.Tests.Visual.Ranking
|
||||||
{
|
{
|
||||||
public class TestSceneSimpleStatisticRow : OsuTestScene
|
public class TestSceneSimpleStatisticTable : OsuTestScene
|
||||||
{
|
{
|
||||||
private Container container;
|
private Container container;
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
public void TestEmpty()
|
public void TestEmpty()
|
||||||
{
|
{
|
||||||
AddStep("create with no items",
|
AddStep("create with no items",
|
||||||
() => container.Add(new SimpleStatisticRow(2, Enumerable.Empty<SimpleStatisticItem>())));
|
() => container.Add(new SimpleStatisticTable(2, Enumerable.Empty<SimpleStatisticItem>())));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Value = RNG.Next(100)
|
Value = RNG.Next(100)
|
||||||
});
|
});
|
||||||
|
|
||||||
container.Add(new SimpleStatisticRow(columnCount, items));
|
container.Add(new SimpleStatisticTable(columnCount, items));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,13 +41,14 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
{
|
{
|
||||||
Text = Name,
|
Text = Name,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft
|
Origin = Anchor.CentreLeft,
|
||||||
|
Font = OsuFont.GetFont(size: 14)
|
||||||
},
|
},
|
||||||
value = new OsuSpriteText
|
value = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold)
|
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -13,10 +14,10 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
namespace osu.Game.Screens.Ranking.Statistics
|
namespace osu.Game.Screens.Ranking.Statistics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a statistic row with simple statistics (ones that only need textual display).
|
/// Represents a table with simple statistics (ones that only need textual display).
|
||||||
/// Richer visualisations should be done with <see cref="StatisticRow"/>s and <see cref="StatisticItem"/>s.
|
/// Richer visualisations should be done with <see cref="StatisticRow"/>s and <see cref="StatisticItem"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SimpleStatisticRow : CompositeDrawable
|
public class SimpleStatisticTable : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly SimpleStatisticItem[] items;
|
private readonly SimpleStatisticItem[] items;
|
||||||
private readonly int columnCount;
|
private readonly int columnCount;
|
||||||
@ -28,7 +29,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="columnCount">The number of columns to layout the <paramref name="items"/> into.</param>
|
/// <param name="columnCount">The number of columns to layout the <paramref name="items"/> into.</param>
|
||||||
/// <param name="items">The <see cref="SimpleStatisticItem"/>s to display in this row.</param>
|
/// <param name="items">The <see cref="SimpleStatisticItem"/>s to display in this row.</param>
|
||||||
public SimpleStatisticRow(int columnCount, IEnumerable<SimpleStatisticItem> items)
|
public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable<SimpleStatisticItem> items)
|
||||||
{
|
{
|
||||||
if (columnCount < 1)
|
if (columnCount < 1)
|
||||||
throw new ArgumentOutOfRangeException(nameof(columnCount));
|
throw new ArgumentOutOfRangeException(nameof(columnCount));
|
@ -32,33 +32,9 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
createHeader(item)
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(5, 0),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Circle
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Height = 9,
|
|
||||||
Width = 4,
|
|
||||||
Colour = Color4Extensions.FromHex("#00FFAA")
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Text = item.Name,
|
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
@ -78,5 +54,37 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Drawable createHeader(StatisticItem item)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(item.Name))
|
||||||
|
return Empty();
|
||||||
|
|
||||||
|
return new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5, 0),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Height = 9,
|
||||||
|
Width = 4,
|
||||||
|
Colour = Color4Extensions.FromHex("#00FFAA")
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Text = item.Name,
|
||||||
|
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="StatisticItem"/>, to be displayed inside a <see cref="StatisticRow"/> in the results screen.
|
/// Creates a new <see cref="StatisticItem"/>, to be displayed inside a <see cref="StatisticRow"/> in the results screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the item.</param>
|
/// <param name="name">The name of the item. Can be <see cref="string.Empty"/> to hide the item header.</param>
|
||||||
/// <param name="content">The <see cref="Drawable"/> content to be displayed.</param>
|
/// <param name="content">The <see cref="Drawable"/> content to be displayed.</param>
|
||||||
/// <param name="dimension">The <see cref="Dimension"/> of this item. This can be thought of as the column dimension of an encompassing <see cref="GridContainer"/>.</param>
|
/// <param name="dimension">The <see cref="Dimension"/> of this item. This can be thought of as the column dimension of an encompassing <see cref="GridContainer"/>.</param>
|
||||||
public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null)
|
public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null)
|
||||||
|
@ -94,14 +94,15 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(30, 15),
|
Spacing = new Vector2(30, 15),
|
||||||
|
Alpha = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap))
|
foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap))
|
||||||
{
|
{
|
||||||
rows.Add(new GridContainer
|
rows.Add(new GridContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Content = new[]
|
Content = new[]
|
||||||
@ -125,6 +126,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
|
|
||||||
spinner.Hide();
|
spinner.Hide();
|
||||||
content.Add(d);
|
content.Add(d);
|
||||||
|
d.FadeIn(250, Easing.OutQuint);
|
||||||
}, localCancellationSource.Token);
|
}, localCancellationSource.Token);
|
||||||
}), localCancellationSource.Token);
|
}), localCancellationSource.Token);
|
||||||
}
|
}
|
||||||
|
39
osu.Game/Screens/Ranking/Statistics/UnstableRate.cs
Normal file
39
osu.Game/Screens/Ranking/Statistics/UnstableRate.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays the unstable rate statistic for a given play.
|
||||||
|
/// </summary>
|
||||||
|
public class UnstableRate : SimpleStatisticItem<double>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates and computes an <see cref="UnstableRate"/> statistic.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitEvents">Sequence of <see cref="HitEvent"/>s to calculate the unstable rate based on.</param>
|
||||||
|
public UnstableRate(IEnumerable<HitEvent> hitEvents)
|
||||||
|
: base("Unstable Rate")
|
||||||
|
{
|
||||||
|
var timeOffsets = hitEvents.Select(ev => ev.TimeOffset).ToArray();
|
||||||
|
Value = 10 * standardDeviation(timeOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double standardDeviation(double[] timeOffsets)
|
||||||
|
{
|
||||||
|
if (timeOffsets.Length == 0)
|
||||||
|
return double.NaN;
|
||||||
|
|
||||||
|
var mean = timeOffsets.Average();
|
||||||
|
var squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
|
||||||
|
return Math.Sqrt(squares / timeOffsets.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user