1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Merge pull request #26455 from peppy/health-less-value-changed

Remove bindable overheads of health displays
This commit is contained in:
Bartłomiej Dach 2024-01-11 11:01:53 +01:00 committed by GitHub
commit da29faffd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 142 additions and 132 deletions

View File

@ -7,6 +7,7 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
@ -159,5 +160,15 @@ namespace osu.Game.Tests.Visual.Gameplay
Type = HitResult.Perfect
});
}
[Test]
public void TestSimulateDrain()
{
ScheduledDelegate del = null!;
AddStep("simulate drain", () => del = Scheduler.AddDelayed(() => healthProcessor.Health.Value -= 0.00025f * Time.Elapsed, 0, true));
AddUntilStep("wait until zero", () => healthProcessor.Health.Value == 0);
AddStep("cancel drain", () => del.Cancel());
}
}
}

View File

@ -22,6 +22,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly Bindable<bool> showHealth = new Bindable<bool>();
private HealthProcessor healthProcessor;
[Resolved]
private OsuConfigManager config { get; set; }
@ -29,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("create layer", () =>
{
Child = new HealthProcessorContainer(healthProcessor)
Child = new HealthProcessorContainer(this.healthProcessor = healthProcessor)
{
RelativeSizeAxes = Axes.Both,
Child = layer = new FailingLayer()
@ -50,12 +52,12 @@ namespace osu.Game.Tests.Visual.Gameplay
AddSliderStep("current health", 0.0, 1.0, 1.0, val =>
{
if (layer != null)
layer.Current.Value = val;
healthProcessor.Health.Value = val;
});
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddUntilStep("layer fade is visible", () => layer.ChildrenOfType<Container>().First().Alpha > 0.1f);
AddStep("set health to 1", () => layer.Current.Value = 1f);
AddStep("set health to 1", () => healthProcessor.Health.Value = 1f);
AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType<Container>().First().IsPresent);
}
@ -65,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay
create(new DrainingHealthProcessor(0));
AddUntilStep("layer is visible", () => layer.IsPresent);
AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}
@ -74,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
create(new AccumulatingHealthProcessor(1));
AddUntilStep("layer is not visible", () => !layer.IsPresent);
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}
@ -82,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestLayerVisibilityWithDrainingProcessor()
{
create(new DrainingHealthProcessor(0));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddWaitStep("wait for potential fade", 10);
AddAssert("layer is still visible", () => layer.IsPresent);
}
@ -92,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
create(new DrainingHealthProcessor(0));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddStep("don't show health", () => showHealth.Value = false);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 1f };
protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 600, UseRelativeSize = { Value = false } };
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };
@ -35,6 +35,13 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
protected override void Update()
{
base.Update();
healthProcessor.Health.Value -= 0.0001f * Time.Elapsed;
}
[Test]
public void TestHealthDisplayIncrementing()
{

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
@ -59,39 +58,12 @@ namespace osu.Game.Screens.Play.HUD
private bool displayingMiss => resetMissBarDelegate != null;
private readonly List<Vector2> missBarVertices = new List<Vector2>();
private readonly List<Vector2> healthBarVertices = new List<Vector2>();
private readonly List<Vector2> vertices = new List<Vector2>();
private double glowBarValue;
public double GlowBarValue
{
get => glowBarValue;
set
{
if (Precision.AlmostEquals(glowBarValue, value, 0.0001))
return;
glowBarValue = value;
pathVerticesCache.Invalidate();
}
}
private double healthBarValue;
public double HealthBarValue
{
get => healthBarValue;
set
{
if (Precision.AlmostEquals(healthBarValue, value, 0.0001))
return;
healthBarValue = value;
pathVerticesCache.Invalidate();
}
}
public const float MAIN_PATH_RADIUS = 10f;
private const float curve_start_offset = 70;
@ -161,7 +133,6 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete();
HealthProcessor.NewJudgement += onNewJudgement;
Current.BindValueChanged(onCurrentChanged, true);
// we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`.
// setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same,
@ -176,31 +147,6 @@ namespace osu.Game.Screens.Play.HUD
private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit;
private void onCurrentChanged(ValueChangedEvent<double> valueChangedEvent)
// schedule display updates one frame later to ensure we know the judgement result causing this change (if there is one).
=> Scheduler.AddOnce(updateDisplay);
private void updateDisplay()
{
double newHealth = Current.Value;
if (newHealth >= GlowBarValue)
finishMissDisplay();
double time = newHealth > GlowBarValue ? 500 : 250;
// TODO: this should probably use interpolation in update.
this.TransformTo(nameof(HealthBarValue), newHealth, time, Easing.OutQuint);
if (pendingMissAnimation && newHealth < GlowBarValue)
triggerMissDisplay();
pendingMissAnimation = false;
if (!displayingMiss)
this.TransformTo(nameof(GlowBarValue), newHealth, time, Easing.OutQuint);
}
protected override void Update()
{
base.Update();
@ -211,33 +157,44 @@ namespace osu.Game.Screens.Play.HUD
drawSizeLayout.Validate();
}
if (!pathVerticesCache.IsValid)
updatePathVertices();
healthBarValue = Interpolation.DampContinuously(healthBarValue, Current.Value, 50, Time.Elapsed);
if (!displayingMiss)
glowBarValue = Interpolation.DampContinuously(glowBarValue, Current.Value, 50, Time.Elapsed);
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);
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, glowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);
updatePathVertices();
}
protected override void HealthChanged(bool increase)
{
if (Current.Value >= glowBarValue)
finishMissDisplay();
if (pendingMissAnimation)
{
triggerMissDisplay();
pendingMissAnimation = false;
}
base.HealthChanged(increase);
}
protected override void FinishInitialAnimation(double value)
{
base.FinishInitialAnimation(value);
this.TransformTo(nameof(HealthBarValue), value, 500, Easing.OutQuint);
this.TransformTo(nameof(GlowBarValue), value, 250, Easing.OutQuint);
this.TransformTo(nameof(healthBarValue), value, 500, Easing.OutQuint);
this.TransformTo(nameof(glowBarValue), value, 250, Easing.OutQuint);
}
protected override void Flash()
{
base.Flash();
mainBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f))
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
if (!displayingMiss)
{
glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 30, Easing.OutQuint)
.Then()
.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 1000, Easing.OutQuint);
// TODO: REMOVE THIS. It's recreating textures.
glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint)
.Then()
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
@ -251,13 +208,15 @@ namespace osu.Game.Screens.Play.HUD
this.Delay(500).Schedule(() =>
{
this.TransformTo(nameof(GlowBarValue), Current.Value, 300, Easing.OutQuint);
this.TransformTo(nameof(glowBarValue), Current.Value, 300, Easing.OutQuint);
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);
// 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);
}
@ -269,6 +228,7 @@ 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);
}
@ -308,7 +268,6 @@ namespace osu.Game.Screens.Play.HUD
if (DrawWidth - padding < rescale_cutoff)
rescalePathProportionally();
List<Vector2> vertices = new List<Vector2>();
barPath.GetPathToProgress(vertices, 0.0, 1.0);
background.Vertices = vertices;
@ -338,20 +297,23 @@ namespace osu.Game.Screens.Play.HUD
private void updatePathVertices()
{
barPath.GetPathToProgress(healthBarVertices, 0.0, healthBarValue);
barPath.GetPathToProgress(missBarVertices, healthBarValue, Math.Max(glowBarValue, healthBarValue));
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;
if (healthBarVertices.Count == 0)
healthBarVertices.Add(Vector2.Zero);
mainBar.Vertices = vertices;
mainBar.Position = initialVertex;
if (missBarVertices.Count == 0)
missBarVertices.Add(Vector2.Zero);
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 = missBarVertices.Select(v => v - missBarVertices[0]).ToList();
glowBar.Position = missBarVertices[0];
mainBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList();
mainBar.Position = healthBarVertices[0];
glowBar.Vertices = vertices;
glowBar.Position = initialVertex;
pathVerticesCache.Validate();
}
@ -366,14 +328,17 @@ namespace osu.Game.Screens.Play.HUD
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 Color4.White.Opacity(0.8f);
return colour_white;
return Interpolation.ValueAt(position,
Color4.White.Opacity(0.8f),
Color4.Black.Opacity(0.2f),
colour_white,
colour_black,
-0.5f, 1f, Easing.OutQuint);
}
}
@ -412,12 +377,14 @@ namespace osu.Game.Screens.Play.HUD
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, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion, Easing.InQuint);
return Interpolation.ValueAt(position, transparent_black, GlowColour, 0.0, GlowPortion, Easing.InQuint);
}
}
}

View File

@ -100,11 +100,11 @@ namespace osu.Game.Screens.Play.HUD
protected override void Update()
{
base.Update();
double target = Math.Clamp(max_alpha * (1 - Current.Value / low_health_threshold), 0, max_alpha);
boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f);
base.Update();
}
}
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@ -35,6 +36,8 @@ namespace osu.Game.Screens.Play.HUD
private BindableNumber<double> health = null!;
protected bool InitialAnimationPlaying => initialIncrease != null;
private ScheduledDelegate? initialIncrease;
/// <summary>
@ -56,13 +59,6 @@ namespace osu.Game.Screens.Play.HUD
// Don't bind directly so we can animate the startup procedure.
health = HealthProcessor.Health.GetBoundCopy();
health.BindValueChanged(h =>
{
if (initialIncrease != null)
FinishInitialAnimation(h.OldValue);
Current.Value = h.NewValue;
});
if (hudOverlay != null)
showHealthBar.BindTo(hudOverlay.ShowHealthBar);
@ -70,12 +66,42 @@ namespace osu.Game.Screens.Play.HUD
// this probably shouldn't be operating on `this.`
showHealthBar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true);
initialHealthValue = health.Value;
if (PlayInitialIncreaseAnimation)
startInitialAnimation();
else
Current.Value = health.Value;
}
private double lastValue;
private double initialHealthValue;
protected override void Update()
{
base.Update();
if (!InitialAnimationPlaying || health.Value != initialHealthValue)
{
Current.Value = health.Value;
if (initialIncrease != null)
FinishInitialAnimation(Current.Value);
}
// Health changes every frame in draining situations.
// Manually handle value changes to avoid bindable event flow overhead.
if (!Precision.AlmostEquals(lastValue, Current.Value, 0.001f))
{
HealthChanged(Current.Value > lastValue);
lastValue = Current.Value;
}
}
protected virtual void HealthChanged(bool increase)
{
}
private void startInitialAnimation()
{
if (Current.Value >= health.Value)

View File

@ -79,7 +79,14 @@ namespace osu.Game.Skinning
marker.Position = fill.Position + new Vector2(fill.DrawWidth, isNewStyle ? fill.DrawHeight / 2 : 0);
}
protected override void Flash() => marker.Flash();
protected override void HealthChanged(bool increase)
{
if (increase)
marker.Bulge();
base.HealthChanged(increase);
}
protected override void Flash() => marker.Flash(Current.Value >= epic_cutoff);
private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"scorebar-{name}");
@ -113,19 +120,16 @@ namespace osu.Game.Skinning
Origin = Anchor.Centre,
};
protected override void LoadComplete()
protected override void Update()
{
base.LoadComplete();
base.Update();
Current.BindValueChanged(hp =>
{
if (hp.NewValue < 0.2f)
Main.Texture = superDangerTexture;
else if (hp.NewValue < epic_cutoff)
Main.Texture = dangerTexture;
else
Main.Texture = normalTexture;
});
if (Current.Value < 0.2f)
Main.Texture = superDangerTexture;
else if (Current.Value < epic_cutoff)
Main.Texture = dangerTexture;
else
Main.Texture = normalTexture;
}
}
@ -226,37 +230,30 @@ namespace osu.Game.Skinning
public abstract Sprite CreateSprite();
protected override void LoadComplete()
public override void Flash(bool isEpic)
{
base.LoadComplete();
Current.BindValueChanged(val =>
{
if (val.NewValue > val.OldValue)
bulgeMain();
});
}
public override void Flash()
{
bulgeMain();
bool isEpic = Current.Value >= epic_cutoff;
Bulge();
explode.Blending = isEpic ? BlendingParameters.Additive : BlendingParameters.Inherit;
explode.ScaleTo(1).Then().ScaleTo(isEpic ? 2 : 1.6f, 120);
explode.FadeOutFromOne(120);
}
private void bulgeMain() =>
public override void Bulge()
{
base.Bulge();
Main.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out);
}
}
public partial class LegacyHealthPiece : CompositeDrawable
{
public Bindable<double> Current { get; } = new Bindable<double>();
public virtual void Flash()
public virtual void Bulge()
{
}
public virtual void Flash(bool isEpic)
{
}
}