1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 03:25:11 +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)
{
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
updateTrailVisibility();
if (hyperDashing)
@ -403,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
hyperDashColour;
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
runHyperDashStateTransition(HyperDashing);
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly Container<CatcherTrailSprite> hyperDashTrails;
private readonly Container<CatcherTrailSprite> endGlowSprites;
private Color4 hyperDashTrailsColour;
private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
public Color4 HyperDashTrailsColour
{
@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Catch.UI
return;
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
{
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI
return;
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.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
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<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;
/// <summary>
@ -44,24 +55,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
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)
: base(hitObject)
{
RelativeSizeAxes = Axes.X;
Container maskedContents;
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
{
RelativeSizeAxes = Axes.Both
RelativeSizeAxes = Axes.Both,
})
{
RelativeSizeAxes = Axes.X
},
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
headContainer = new Container<DrawableHoldNoteHead> { 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)
@ -127,7 +168,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
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()
@ -145,9 +195,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
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.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)
@ -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 (!Tail.IsHit)
HasBroken = true;
releaseTime = Time.Current;
}
private void endHold()

View File

@ -1,6 +1,8 @@
// 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.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
/// <summary>
@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
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 void OnReleased(ManiaAction action)

View File

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

View File

@ -7,8 +7,10 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
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
{
public new FailRetryGraph Graph => base.Graph;

View File

@ -236,7 +236,7 @@ namespace osu.Game.Screens.Select
private void updateMetrics()
{
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)
{

View File

@ -29,16 +29,30 @@ namespace osu.Game.Screens.Select.Details
var retries = Metrics?.Retries ?? 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;
retryGraph.MaxValue = maxValue;
failGraph.Values = fails.Select(f => (float)f);
retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue));
failGraph.Values = fails.Select(v => (float)v);
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()
{
Children = new[]