diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 40450c672918..a5308f4cdeb1 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -24,7 +24,7 @@ public partial class CatchModFlashlight : ModFlashlight public override BindableBool ComboBasedSize { get; } = new BindableBool(true); - public override float DefaultFlashlightSize => 325; + public override float DefaultFlashlightSize => 203.125f; protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 496e7610ff6d..f4f7f9d44b7f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -32,26 +32,6 @@ public partial class TestSceneOsuModFlashlight : OsuModTestScene [Test] public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true }); - [Test] - public void TestPlayfieldBasedSize() - { - OsuModFlashlight flashlight; - CreateModTest(new ModTestData - { - Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()], - PassCondition = () => - { - var flashlightOverlay = Player.DrawableRuleset.Overlays - .ChildrenOfType.Flashlight>() - .First(); - - // the combo check is here because the flashlight radius decreases for the first time at 100 combo - // and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()` - return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100; - } - }); - } - [Test] public void TestSliderDimsOnlyAfterStartTime() { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 3009530b5051..a8c2508f809b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -41,7 +41,7 @@ public partial class OsuModFlashlight : ModFlashlight, IApplicable public override BindableBool ComboBasedSize { get; } = new BindableBool(true); - public override float DefaultFlashlightSize => 200; + public override float DefaultFlashlightSize => 125; private OsuFlashlight flashlight = null!; diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs index 05a408c6210a..6dbd3478f16c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs @@ -3,7 +3,10 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.UI; using osuTK; @@ -12,6 +15,34 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { public partial class TestSceneTaikoModFlashlight : TaikoModTestScene { + [Test] + public void TestAspectRatios([Values] bool withClassicMod) + { + if (withClassicMod) + CreateModTest(new ModTestData { Mods = new Mod[] { new TaikoModFlashlight(), new TaikoModClassic() }, PassCondition = () => true }); + else + CreateModTest(new ModTestData { Mod = new TaikoModFlashlight(), PassCondition = () => true }); + + AddStep("clear dim", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0.0)); + + AddStep("reset", () => Stack.FillMode = FillMode.Stretch); + AddStep("set to 16:9", () => + { + Stack.FillAspectRatio = 16 / 9f; + Stack.FillMode = FillMode.Fit; + }); + AddStep("set to 4:3", () => + { + Stack.FillAspectRatio = 4 / 3f; + Stack.FillMode = FillMode.Fit; + }); + AddSliderStep("aspect ratio", 0.01f, 5f, 1f, v => + { + Stack.FillAspectRatio = v; + Stack.FillMode = FillMode.Fit; + }); + } + [TestCase(1f)] [TestCase(0.5f)] [TestCase(1.25f)] diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 64f2f4c18a40..02c06850b744 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -47,28 +47,15 @@ public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPla { this.taikoPlayfield = taikoPlayfield; - FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize()); + FlashlightSize = new Vector2(0, GetSize()); FlashlightSmoothness = 1.4f; AddLayout(flashlightProperties); } - /// - /// Returns the aspect ratio-adjusted size of the flashlight. - /// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments. - /// - /// - /// The size of the flashlight. - /// The value provided here should always come from . - /// - private Vector2 adjustSizeForPlayfieldAspectRatio(float size) - { - return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y); - } - protected override void UpdateFlashlightSize(float size) { - this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; @@ -82,7 +69,7 @@ protected override void Update() FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre); ClearTransforms(targetMember: nameof(FlashlightSize)); - FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize()); + FlashlightSize = new Vector2(0, GetSize()); flashlightProperties.Validate(); } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index a88d714dced0..884066d7ab74 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,8 +13,8 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shaders.Types; using osu.Framework.Graphics.Sprites; +using osu.Framework.Layout; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.OpenGL.Vertices; @@ -85,7 +84,11 @@ public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) flashlight.Colour = Color4.Black; flashlight.Combo.BindTo(Combo); - flashlight.GetPlayfieldScale = () => drawableRuleset.PlayfieldAdjustmentContainer.Scale; + + var playfieldDrawInfoTracker = new PlayfieldDrawInfoTracker(); + + drawableRuleset.PlayfieldAdjustmentContainer.Add(playfieldDrawInfoTracker); + flashlight.PlayfieldDrawInfoTracker = playfieldDrawInfoTracker; drawableRuleset.Overlays.Add(new Container { @@ -111,7 +114,9 @@ public abstract partial class Flashlight : Drawable public override bool RemoveCompletedTransforms => false; - internal Func? GetPlayfieldScale; + internal PlayfieldDrawInfoTracker PlayfieldDrawInfoTracker { get; set; } = null!; + + private DrawInfo playfieldDrawInfo => PlayfieldDrawInfoTracker.DrawInfo; private readonly float defaultFlashlightSize; private readonly float sizeMultiplier; @@ -146,6 +151,8 @@ protected override void LoadComplete() isBreakTime.BindTo(player.IsBreakTime); isBreakTime.BindValueChanged(_ => UpdateFlashlightSize(GetSize()), true); } + + PlayfieldDrawInfoTracker.OnDrawInfoInvalidate += () => Invalidate(Invalidation.DrawNode); } protected abstract void UpdateFlashlightSize(float size); @@ -156,15 +163,6 @@ public float GetSize() { float size = defaultFlashlightSize * sizeMultiplier; - if (GetPlayfieldScale != null) - { - Vector2 playfieldScale = GetPlayfieldScale(); - - Debug.Assert(Precision.AlmostEquals(Math.Abs(playfieldScale.X), Math.Abs(playfieldScale.Y)), - @"Playfield has non-proportional scaling. Flashlight implementations should be revisited with regard to balance."); - size *= Math.Abs(playfieldScale.X); - } - if (isBreakTime.Value) size *= 2.5f; else if (comboBasedSize) @@ -265,7 +263,11 @@ public override void ApplyState() shader = Source.shader; screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); - flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy; + + // scale the flashlight based on the playfield to match gameplay components scale. + Vector2 drawInfoScale = Source.playfieldDrawInfo.Matrix.ExtractScale().Xy; + flashlightSize = Source.FlashlightSize * drawInfoScale; + flashlightDim = Source.FlashlightDim; flashlightSmoothness = Source.flashlightSmoothness; } @@ -321,5 +323,33 @@ private record struct FlashlightParameters } } } + + /// + /// The purpose of this component is to track any changes to Playfield.Parent.DrawInfo + /// (by being added to the content of ). + /// All in order for the flashlight to invalidate its draw node and read any changes in the playfield's scaling. + /// + internal partial class PlayfieldDrawInfoTracker : Component + { + private readonly LayoutValue drawInfoLayout = new LayoutValue(Invalidation.DrawInfo); + + public Action? OnDrawInfoInvalidate; + + public PlayfieldDrawInfoTracker() + { + AddLayout(drawInfoLayout); + } + + protected override void Update() + { + base.Update(); + + if (!drawInfoLayout.IsValid) + { + OnDrawInfoInvalidate?.Invoke(); + drawInfoLayout.Validate(); + } + } + } } }