1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 11:42:55 +08:00

Merge branch 'master' into fix-hittarget-layering

This commit is contained in:
Dean Herbert 2020-08-25 15:44:38 +09:00 committed by GitHub
commit 940b4acf0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 147 additions and 15 deletions

View File

@ -285,8 +285,6 @@ namespace osu.Game.Rulesets.Catch.UI
private void runHyperDashStateTransition(bool hyperDashing) private void runHyperDashStateTransition(bool hyperDashing)
{ {
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
updateTrailVisibility(); updateTrailVisibility();
if (hyperDashing) if (hyperDashing)
@ -403,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ?? skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
hyperDashColour; hyperDashColour;
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
runHyperDashStateTransition(HyperDashing); runHyperDashStateTransition(HyperDashing);
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly Container<CatcherTrailSprite> hyperDashTrails; private readonly Container<CatcherTrailSprite> hyperDashTrails;
private readonly Container<CatcherTrailSprite> endGlowSprites; private readonly Container<CatcherTrailSprite> endGlowSprites;
private Color4 hyperDashTrailsColour; private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
public Color4 HyperDashTrailsColour public Color4 HyperDashTrailsColour
{ {
@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Catch.UI
return; return;
hyperDashTrailsColour = value; hyperDashTrailsColour = value;
hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); hyperDashTrails.Colour = hyperDashTrailsColour;
} }
} }
private Color4 endGlowSpritesColour; private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
public Color4 EndGlowSpritesColour public Color4 EndGlowSpritesColour
{ {
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI
return; return;
endGlowSpritesColour = value; endGlowSpritesColour = value;
endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); endGlowSprites.Colour = endGlowSpritesColour;
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@ -32,6 +33,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container<DrawableHoldNoteTail> tailContainer; private readonly Container<DrawableHoldNoteTail> tailContainer;
private readonly Container<DrawableHoldNoteTick> tickContainer; private readonly Container<DrawableHoldNoteTick> tickContainer;
/// <summary>
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
/// </summary>
private readonly Container sizingContainer;
/// <summary>
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
/// </summary>
private readonly Container maskingContainer;
private readonly SkinnableDrawable bodyPiece; private readonly SkinnableDrawable bodyPiece;
/// <summary> /// <summary>
@ -44,24 +55,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
public bool HasBroken { get; private set; } public bool HasBroken { get; private set; }
/// <summary>
/// Whether the hold note has been released potentially without having caused a break.
/// </summary>
private double? releaseTime;
public DrawableHoldNote(HoldNote hitObject) public DrawableHoldNote(HoldNote hitObject)
: base(hitObject) : base(hitObject)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Container maskedContents;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
sizingContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
maskingContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = maskedContents = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
}
},
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
}
},
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both,
}) })
{ {
RelativeSizeAxes = Axes.X RelativeSizeAxes = Axes.X
}, },
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both }, tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both }, tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
}); });
maskedContents.AddRange(new[]
{
bodyPiece.CreateProxy(),
tickContainer.CreateProxy(),
tailContainer.CreateProxy(),
});
} }
protected override void AddNestedHitObject(DrawableHitObject hitObject) protected override void AddNestedHitObject(DrawableHitObject hitObject)
@ -127,7 +168,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
base.OnDirectionChanged(e); base.OnDirectionChanged(e);
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; if (e.NewValue == ScrollingDirection.Up)
{
bodyPiece.Anchor = bodyPiece.Origin = Anchor.TopLeft;
sizingContainer.Anchor = sizingContainer.Origin = Anchor.BottomLeft;
}
else
{
bodyPiece.Anchor = bodyPiece.Origin = Anchor.BottomLeft;
sizingContainer.Anchor = sizingContainer.Origin = Anchor.TopLeft;
}
} }
public override void PlaySamples() public override void PlaySamples()
@ -145,9 +195,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
base.Update(); base.Update();
// Make the body piece not lie under the head note if (Time.Current < releaseTime)
releaseTime = null;
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
// This is required for the tail to not be masked away, since it lies outside the bounds of the hold note.
sizingContainer.Padding = new MarginPadding
{
Top = Direction.Value == ScrollingDirection.Down ? -Tail.Height : 0,
Bottom = Direction.Value == ScrollingDirection.Up ? -Tail.Height : 0,
};
// Pad the masking container to the starting position of the body piece (half-way under the head).
// This is required to make the body start getting masked immediately as soon as the note is held.
maskingContainer.Padding = new MarginPadding
{
Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0,
Bottom = Direction.Value == ScrollingDirection.Down ? Head.Height / 2 : 0,
};
// Position and resize the body to lie half-way under the head and the tail notes.
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
// As the note is being held, adjust the size of the sizing container. This has two effects:
// 1. The contained masking container will mask the body and ticks.
// 2. The head note will move along with the new "head position" in the container.
if (Head.IsHit && releaseTime == null)
{
// How far past the hit target this hold note is. Always a positive value.
float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y);
sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1);
}
} }
protected override void UpdateStateTransforms(ArmedState state) protected override void UpdateStateTransforms(ArmedState state)
@ -212,6 +291,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// If the key has been released too early, the user should not receive full score for the release // If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit) if (!Tail.IsHit)
HasBroken = true; HasBroken = true;
releaseTime = Time.Current;
} }
private void endHold() private void endHold()

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
/// <summary> /// <summary>
@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true); public void UpdateResult() => base.UpdateResult(true);
protected override void UpdateStateTransforms(ArmedState state)
{
// This hitobject should never expire, so this is just a safe maximum.
LifetimeEnd = LifetimeStart + 30000;
}
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
public override void OnReleased(ManiaAction action) public override void OnReleased(ManiaAction action)

View File

@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
break; break;
case ArmedState.Hit: case ArmedState.Hit:
this.FadeOut(150, Easing.OutQuint); this.FadeOut();
break; break;
} }
} }

View File

@ -7,8 +7,10 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Details;
@ -72,6 +74,32 @@ namespace osu.Game.Tests.Visual.Online
}; };
} }
[Test]
public void TestOnlyFailMetrics()
{
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
{
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).ToArray(),
}
});
AddAssert("graph max values correct",
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 100));
}
[Test]
public void TestEmptyMetrics()
{
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
{
Metrics = new BeatmapMetrics()
});
AddAssert("graph max values correct",
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 0));
}
private class GraphExposingSuccessRate : SuccessRate private class GraphExposingSuccessRate : SuccessRate
{ {
public new FailRetryGraph Graph => base.Graph; public new FailRetryGraph Graph => base.Graph;

View File

@ -236,7 +236,7 @@ namespace osu.Game.Screens.Select
private void updateMetrics() private void updateMetrics()
{ {
var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false;
var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) && (beatmap?.Metrics.Fails?.Any() ?? false); var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false);
if (hasRatings) if (hasRatings)
{ {

View File

@ -29,16 +29,30 @@ namespace osu.Game.Screens.Select.Details
var retries = Metrics?.Retries ?? Array.Empty<int>(); var retries = Metrics?.Retries ?? Array.Empty<int>();
var fails = Metrics?.Fails ?? Array.Empty<int>(); var fails = Metrics?.Fails ?? Array.Empty<int>();
var retriesAndFails = sumRetriesAndFails(retries, fails);
float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; float maxValue = retriesAndFails.Any() ? retriesAndFails.Max() : 0;
failGraph.MaxValue = maxValue; failGraph.MaxValue = maxValue;
retryGraph.MaxValue = maxValue; retryGraph.MaxValue = maxValue;
failGraph.Values = fails.Select(f => (float)f); failGraph.Values = fails.Select(v => (float)v);
retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); retryGraph.Values = retriesAndFails.Select(v => (float)v);
} }
} }
private int[] sumRetriesAndFails(int[] retries, int[] fails)
{
var result = new int[Math.Max(retries.Length, fails.Length)];
for (int i = 0; i < retries.Length; ++i)
result[i] = retries[i];
for (int i = 0; i < fails.Length; ++i)
result[i] += fails[i];
return result;
}
public FailRetryGraph() public FailRetryGraph()
{ {
Children = new[] Children = new[]