diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 868ef9283d..9b91252a06 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -2,23 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts; using osu.Game.Skinning; using osuTK; @@ -41,16 +36,14 @@ namespace osu.Game.Screens.Play.HUD [SettingSource("Use relative size")] public BindableBool UseRelativeSize { get; } = new BindableBool(true); - private BarPath mainBar = null!; + private ArgonHealthDisplayBar mainBar = null!; /// /// Used to show a glow at the end of the main bar, or red "damage" area when missing. /// - private BarPath glowBar = null!; + private ArgonHealthDisplayBar glowBar = null!; - private BackgroundPath background = null!; - - private SliderPath barPath = null!; + private Container content = null!; private static readonly Colour4 main_bar_colour = Colour4.White; private static readonly Colour4 main_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.5f); @@ -59,23 +52,15 @@ namespace osu.Game.Screens.Play.HUD private bool displayingMiss => resetMissBarDelegate != null; - private readonly List vertices = new List(); - private double glowBarValue; private double healthBarValue; public const float MAIN_PATH_RADIUS = 10f; - - private const float curve_start_offset = 70; - private const float curve_end_offset = 40; private const float padding = MAIN_PATH_RADIUS * 2; - private const float curve_smoothness = 10; private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); - private readonly Cached pathVerticesCache = new Cached(); - public ArgonHealthDisplay() { AddLayout(drawSizeLayout); @@ -93,42 +78,40 @@ namespace osu.Game.Screens.Play.HUD { AutoSizeAxes = Axes.Y; - InternalChild = new Container + InternalChild = content = new Container { - AutoSizeAxes = Axes.Both, Children = new Drawable[] { - background = new BackgroundPath - { - PathRadius = MAIN_PATH_RADIUS, - Alpha = 0, - AlwaysPresent = true - }, new ArgonHealthDisplayBackground { RelativeSizeAxes = Axes.Both, PathRadius = MAIN_PATH_RADIUS, PathPadding = MAIN_PATH_RADIUS }, - glowBar = new BarPath + new Container { - BarColour = Color4.White, - GlowColour = main_bar_glow_colour, - Blending = BlendingParameters.Additive, - Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), - PathRadius = 40f, - // Kinda hacky, but results in correct positioning with increased path radius. - Margin = new MarginPadding(-30f), - GlowPortion = 0.9f, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(-30f), + Child = glowBar = new ArgonHealthDisplayBar + { + RelativeSizeAxes = Axes.Both, + BarColour = Color4.White, + GlowColour = main_bar_glow_colour, + Blending = BlendingParameters.Additive, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), + PathRadius = 40f, + PathPadding = 40f, + GlowPortion = 0.9f, + } }, - mainBar = new BarPath + mainBar = new ArgonHealthDisplayBar { - AutoSizeAxes = Axes.None, RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, BarColour = main_bar_colour, GlowColour = main_bar_glow_colour, PathRadius = MAIN_PATH_RADIUS, + PathPadding = MAIN_PATH_RADIUS, GlowPortion = 0.6f, } } @@ -151,7 +134,7 @@ namespace osu.Game.Screens.Play.HUD UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); Width = previousWidth; - BarHeight.BindValueChanged(_ => updatePath(), true); + BarHeight.BindValueChanged(_ => updateContentSize(), true); } private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit; @@ -162,7 +145,7 @@ namespace osu.Game.Screens.Play.HUD if (!drawSizeLayout.IsValid) { - updatePath(); + updateContentSize(); drawSizeLayout.Validate(); } @@ -173,7 +156,7 @@ namespace osu.Game.Screens.Play.HUD mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, glowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); - updatePathVertices(); + updatePathProgress(); } protected override void HealthChanged(bool increase) @@ -203,10 +186,9 @@ namespace osu.Game.Screens.Play.HUD if (!displayingMiss) { - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint) + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), Colour4.White, 30, Easing.OutQuint) .Then() - .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); + .TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); } } @@ -221,13 +203,11 @@ namespace osu.Game.Screens.Play.HUD finishMissDisplay(); }, out resetMissBarDelegate); - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() - .TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() + .TransformTo(nameof(ArgonHealthDisplayBar.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint); - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) - .TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) + .TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint); } private void finishMissDisplay() @@ -237,53 +217,22 @@ namespace osu.Game.Screens.Play.HUD if (Current.Value > 0) { - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In); - glowBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.BarColour), main_bar_colour, 300, Easing.In); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), main_bar_glow_colour, 300, Easing.In); } resetMissBarDelegate?.Cancel(); resetMissBarDelegate = null; } - private void updatePath() + private void updateContentSize() { float usableWidth = DrawWidth - padding; if (usableWidth < 0) enforceMinimumWidth(); - // the display starts curving at `curve_start_offset` units from the right and ends curving at `curve_end_offset`. - // to ensure that the curve is symmetric when it starts being narrow enough, add a `curve_end_offset` to the left side too. - const float rescale_cutoff = curve_start_offset + curve_end_offset; - - float barLength = Math.Max(DrawWidth - padding, rescale_cutoff); - float curveStart = barLength - curve_start_offset; - float curveEnd = barLength - curve_end_offset; - - Vector2 diagonalDir = (new Vector2(curveEnd, BarHeight.Value) - new Vector2(curveStart, 0)).Normalized(); - - barPath = new SliderPath(new[] - { - new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), - new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.BEZIER), - new PathControlPoint(new Vector2(curveStart, 0)), - new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.LINEAR), - new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.BEZIER), - new PathControlPoint(new Vector2(curveEnd, BarHeight.Value)), - new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.LINEAR), - new PathControlPoint(new Vector2(barLength, BarHeight.Value)), - }); - - if (DrawWidth - padding < rescale_cutoff) - rescalePathProportionally(); - - barPath.GetPathToProgress(vertices, 0.0, 1.0); - - background.Vertices = vertices; - mainBar.Vertices = vertices; - glowBar.Vertices = vertices; - - updatePathVertices(); + content.Size = new Vector2(DrawWidth, BarHeight.Value + padding); + updatePathProgress(); void enforceMinimumWidth() { @@ -296,35 +245,13 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = relativeAxes; } - - void rescalePathProportionally() - { - foreach (var point in barPath.ControlPoints) - point.Position = new Vector2(point.Position.X / barLength * (DrawWidth - padding), point.Position.Y); - } } - private void updatePathVertices() + private void updatePathProgress() { - barPath.GetPathToProgress(vertices, 0.0, healthBarValue); - if (vertices.Count == 0) vertices.Add(Vector2.Zero); - Vector2 initialVertex = vertices[0]; - for (int i = 0; i < vertices.Count; i++) - vertices[i] -= initialVertex; - - mainBar.Vertices = vertices; - mainBar.Position = initialVertex; - - barPath.GetPathToProgress(vertices, healthBarValue, Math.Max(glowBarValue, healthBarValue)); - if (vertices.Count == 0) vertices.Add(Vector2.Zero); - initialVertex = vertices[0]; - for (int i = 0; i < vertices.Count; i++) - vertices[i] -= initialVertex; - - glowBar.Vertices = vertices; - glowBar.Position = initialVertex; - - pathVerticesCache.Validate(); + mainBar.EndProgress = (float)healthBarValue; + glowBar.StartProgress = (float)healthBarValue; + glowBar.EndProgress = (float)Math.Max(glowBarValue, healthBarValue); } protected override void Dispose(bool isDisposing) @@ -334,67 +261,5 @@ namespace osu.Game.Screens.Play.HUD if (HealthProcessor.IsNotNull()) HealthProcessor.NewJudgement -= onNewJudgement; } - - private partial class BackgroundPath : SmoothPath - { - private static readonly Color4 colour_white = Color4.White.Opacity(0.8f); - private static readonly Color4 colour_black = Color4.Black.Opacity(0.2f); - - protected override Color4 ColourAt(float position) - { - if (position <= 0.16f) - return colour_white; - - return Interpolation.ValueAt(position, - colour_white, - colour_black, - -0.5f, 1f, Easing.OutQuint); - } - } - - private partial class BarPath : SmoothPath - { - private Colour4 barColour; - - public Colour4 BarColour - { - get => barColour; - set - { - if (barColour == value) - return; - - barColour = value; - InvalidateTexture(); - } - } - - private Colour4 glowColour; - - public Colour4 GlowColour - { - get => glowColour; - set - { - if (glowColour == value) - return; - - glowColour = value; - InvalidateTexture(); - } - } - - public float GlowPortion { get; init; } - - private static readonly Colour4 transparent_black = Colour4.Black.Opacity(0.0f); - - protected override Color4 ColourAt(float position) - { - if (position >= GlowPortion) - return BarColour; - - return Interpolation.ValueAt(position, transparent_black, GlowColour, 0.0, GlowPortion, Easing.InQuint); - } - } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs new file mode 100644 index 0000000000..6bc2232776 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs @@ -0,0 +1,227 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Runtime.InteropServices; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Shaders.Types; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts +{ + public partial class ArgonHealthDisplayBar : Box + { + private float endProgress = 1f; + + public float EndProgress + { + get => endProgress; + set + { + if (endProgress == value) + return; + + endProgress = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float startProgress = 0f; + + public float StartProgress + { + get => startProgress; + set + { + if (startProgress == value) + return; + + startProgress = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float radius = 10f; + + public float PathRadius + { + get => radius; + set + { + if (radius == value) + return; + + radius = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float padding = 10f; + + public float PathPadding + { + get => padding; + set + { + if (padding == value) + return; + + padding = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float glowPortion; + + public float GlowPortion + { + get => glowPortion; + set + { + if (glowPortion == value) + return; + + glowPortion = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Colour4 barColour = Color4.White; + + public Colour4 BarColour + { + get => barColour; + set + { + if (barColour == value) + return; + + barColour = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Colour4 glowColour = Color4.White.Opacity(0); + + public Colour4 GlowColour + { + get => glowColour; + set + { + if (glowColour == value) + return; + + glowColour = value; + Invalidate(Invalidation.DrawNode); + } + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "ArgonBarPath"); + } + + protected override void Update() + { + base.Update(); + Invalidate(Invalidation.DrawNode); + } + + protected override DrawNode CreateDrawNode() => new ArgonBarPathDrawNode(this); + + private class ArgonBarPathDrawNode : SpriteDrawNode + { + protected new ArgonHealthDisplayBar Source => (ArgonHealthDisplayBar)base.Source; + + public ArgonBarPathDrawNode(ArgonHealthDisplayBar source) + : base(source) + { + } + + private Vector2 size; + private float startProgress; + private float endProgress; + private float pathRadius; + private float padding; + private float glowPortion; + private Color4 barColour; + private Color4 glowColour; + + public override void ApplyState() + { + base.ApplyState(); + + size = Source.DrawSize; + endProgress = Source.endProgress; + startProgress = Math.Min(Source.startProgress, endProgress); + pathRadius = Source.PathRadius; + padding = Source.PathPadding; + glowPortion = Source.GlowPortion; + barColour = Source.barColour; + glowColour = Source.glowColour; + } + + protected override void Draw(IRenderer renderer) + { + if (pathRadius == 0) + return; + + base.Draw(renderer); + } + + private IUniformBuffer parametersBuffer; + + protected override void BindUniformResources(IShader shader, IRenderer renderer) + { + base.BindUniformResources(shader, renderer); + + parametersBuffer ??= renderer.CreateUniformBuffer(); + parametersBuffer.Data = new ArgonBarPathParameters + { + BarColour = new Vector4(barColour.R, barColour.G, barColour.B, barColour.A), + GlowColour = new Vector4(glowColour.R, glowColour.G, glowColour.B, glowColour.A), + GlowPortion = glowPortion, + Size = size, + StartProgress = startProgress, + EndProgress = endProgress, + PathRadius = pathRadius, + Padding = padding + }; + + shader.BindUniformBlock("m_ArgonBarPathParameters", parametersBuffer); + } + + protected override bool CanDrawOpaqueInterior => false; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + parametersBuffer?.Dispose(); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private record struct ArgonBarPathParameters + { + public UniformVector4 BarColour; + public UniformVector4 GlowColour; + public UniformVector2 Size; + public UniformFloat StartProgress; + public UniformFloat EndProgress; + public UniformFloat PathRadius; + public UniformFloat Padding; + public UniformFloat GlowPortion; + private UniformPadding4 pad; + } + } + } +}