diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs new file mode 100644 index 0000000000..8698ba3abd --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestScenePlayfieldCoveringContainer : OsuTestScene + { + private readonly ScrollingTestContainer scrollingContainer; + private readonly PlayfieldCoveringWrapper cover; + + public TestScenePlayfieldCoveringContainer() + { + Child = scrollingContainer = new ScrollingTestContainer(ScrollingDirection.Down) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 500), + Child = cover = new PlayfieldCoveringWrapper(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Orange + }) + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + [Test] + public void TestScrollingDownwards() + { + AddStep("set down scroll", () => scrollingContainer.Direction = ScrollingDirection.Down); + AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f); + AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f); + AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f); + } + + [Test] + public void TestScrollingUpwards() + { + AddStep("set up scroll", () => scrollingContainer.Direction = ScrollingDirection.Up); + AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f); + AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f); + AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index bdc8cb31e5..cbdcd49c5b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Mods { @@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Acronym => "FI"; public override IconUsage? Icon => OsuIcon.ModHidden; public override string Description => @"Keys appear out of nowhere!"; + + protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 66b90984b4..4bdb15526f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -2,15 +2,44 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModHidden : ModHidden + public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset { public override string Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + + /// + /// The direction in which the cover should expand. + /// + protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield; + + foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns)) + { + HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer; + Container hocParent = (Container)hoc.Parent; + + hocParent.Remove(hoc); + hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c => + { + c.RelativeSizeAxes = Axes.Both; + c.Direction = ExpandDirection; + c.Coverage = 0.5f; + })); + } + } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 511d6c8623..642353bd0b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); - private readonly ColumnHitObjectArea hitObjectArea; + public readonly ColumnHitObjectArea HitObjectArea; internal readonly Container TopLevelContainer; - public Container UnderlayElements => hitObjectArea.UnderlayElements; + public Container UnderlayElements => HitObjectArea.UnderlayElements; public Column(int index) { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.UI { // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), - hitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, + HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; - TopLevelContainer.Add(hitObjectArea.Explosions.CreateProxy()); + TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); } public override Axes RelativeSizeAxes => Axes.Y; @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }; - hitObjectArea.Explosions.Add(explosion); + HitObjectArea.Explosions.Add(explosion); explosion.Delay(200).Expire(true); } diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index ba5281a1a2..8f7880dafa 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -14,12 +15,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components public class HitObjectArea : SkinReloadableDrawable { protected readonly IBindable Direction = new Bindable(); + public readonly HitObjectContainer HitObjectContainer; public HitObjectArea(HitObjectContainer hitObjectContainer) { - InternalChildren = new[] + InternalChild = new Container { - hitObjectContainer, + RelativeSizeAxes = Axes.Both, + Child = HitObjectContainer = hitObjectContainer }; } diff --git a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs new file mode 100644 index 0000000000..15d216e8c5 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs @@ -0,0 +1,133 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.UI +{ + /// + /// A that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield. + /// + public class PlayfieldCoveringWrapper : CompositeDrawable + { + /// + /// The complete cover, including gradient and fill. + /// + private readonly Drawable cover; + + /// + /// The gradient portion of the cover. + /// + private readonly Box gradient; + + /// + /// The fully-opaque portion of the cover. + /// + private readonly Box filled; + + private readonly IBindable scrollDirection = new Bindable(); + + public PlayfieldCoveringWrapper(Drawable content) + { + InternalChild = new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + content, + cover = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Blending = new BlendingParameters + { + // Don't change the destination colour. + RGBEquation = BlendingEquation.Add, + Source = BlendingType.Zero, + Destination = BlendingType.One, + // Subtract the cover's alpha from the destination (points with alpha 1 should make the destination completely transparent). + AlphaEquation = BlendingEquation.Add, + SourceAlpha = BlendingType.Zero, + DestinationAlpha = BlendingType.OneMinusSrcAlpha + }, + Children = new Drawable[] + { + gradient = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Height = 0.25f, + Colour = ColourInfo.GradientVertical( + Color4.White.Opacity(0f), + Color4.White.Opacity(1f) + ) + }, + filled = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = 0 + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + scrollDirection.BindTo(scrollingInfo.Direction); + scrollDirection.BindValueChanged(onScrollDirectionChanged, true); + } + + private void onScrollDirectionChanged(ValueChangedEvent direction) + => cover.Rotation = direction.NewValue == ScrollingDirection.Up ? 0 : 180f; + + /// + /// The relative area that should be completely covered. This does not include the fade. + /// + public float Coverage + { + set + { + filled.Height = value; + gradient.Y = -value; + } + } + + /// + /// The direction in which the cover expands. + /// + public CoverExpandDirection Direction + { + set => cover.Scale = value == CoverExpandDirection.AlongScroll ? Vector2.One : new Vector2(1, -1); + } + } + + public enum CoverExpandDirection + { + /// + /// The cover expands along the scrolling direction. + /// + AlongScroll, + + /// + /// The cover expands against the scrolling direction. + /// + AgainstScroll + } +}