diff --git a/osu.Game.Rulesets.Rush/Mods/RushModFadeIn.cs b/osu.Game.Rulesets.Rush/Mods/RushModFadeIn.cs new file mode 100644 index 0000000..10f4b45 --- /dev/null +++ b/osu.Game.Rulesets.Rush/Mods/RushModFadeIn.cs @@ -0,0 +1,18 @@ +// Copyright (c) Shane Woolcock. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Rush.UI; + +namespace osu.Game.Rulesets.Rush.Mods +{ + public class RushModFadeIn : RushModPlayfieldCover + { + public override string Name => "Fade In"; + public override string Acronym => "FI"; + + public override string Description => @"Keys fade in before you hit them!"; + public override double ScoreMultiplier => 1; + + protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; + } +} diff --git a/osu.Game.Rulesets.Rush/Mods/RushModHidden.cs b/osu.Game.Rulesets.Rush/Mods/RushModHidden.cs new file mode 100644 index 0000000..262d586 --- /dev/null +++ b/osu.Game.Rulesets.Rush/Mods/RushModHidden.cs @@ -0,0 +1,15 @@ +// Copyright (c) Shane Woolcock. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Rush.UI; + +namespace osu.Game.Rulesets.Rush.Mods +{ + public class RushModHidden : RushModPlayfieldCover + { + public override string Description => @"Keys fade out before you hit them!"; + public override double ScoreMultiplier => 1; + + protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + } +} diff --git a/osu.Game.Rulesets.Rush/Mods/RushModPlayfieldCover.cs b/osu.Game.Rulesets.Rush/Mods/RushModPlayfieldCover.cs new file mode 100644 index 0000000..cac494c --- /dev/null +++ b/osu.Game.Rulesets.Rush/Mods/RushModPlayfieldCover.cs @@ -0,0 +1,50 @@ +// Copyright (c) Shane Woolcock. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Rush.Objects; +using osu.Game.Rulesets.Rush.UI; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Rush.Mods +{ + public abstract class RushModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset + { + public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + + /// + /// The direction in which the cover should expand. + /// + protected abstract CoverExpandDirection ExpandDirection { get; } + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + RushPlayfield rushPlayfield = (RushPlayfield)drawableRuleset.Playfield; + + Container hitObjectArea = rushPlayfield.HitObjectArea; + Container hocParent = (Container)hitObjectArea.Parent; + hocParent.Remove(hitObjectArea); + + PlayfieldCoveringWrapper wrapper = new PlayfieldCoveringWrapper(hitObjectArea) + { + RelativeSizeAxes = Axes.Both, + Direction = ExpandDirection, + Coverage = 0.5f, + }; + + hocParent.Add(wrapper); + } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + } +} diff --git a/osu.Game.Rulesets.Rush/RushRuleset.cs b/osu.Game.Rulesets.Rush/RushRuleset.cs index 3783f9d..add283e 100644 --- a/osu.Game.Rulesets.Rush/RushRuleset.cs +++ b/osu.Game.Rulesets.Rush/RushRuleset.cs @@ -70,6 +70,7 @@ public override IEnumerable GetModsFor(ModType type) { new MultiMod(new RushModSuddenDeath(), new RushModPerfect()), new MultiMod(new RushModDoubleTime(), new RushModNightcore()), + new MultiMod(new RushModHidden(), new RushModFadeIn()), new RushModFlashlight() }; diff --git a/osu.Game.Rulesets.Rush/UI/LanePlayfield.cs b/osu.Game.Rulesets.Rush/UI/LanePlayfield.cs index de3176b..317cd06 100644 --- a/osu.Game.Rulesets.Rush/UI/LanePlayfield.cs +++ b/osu.Game.Rulesets.Rush/UI/LanePlayfield.cs @@ -5,11 +5,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Rush.Objects; using osu.Game.Rulesets.Rush.Objects.Drawables; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -18,9 +16,6 @@ namespace osu.Game.Rulesets.Rush.UI { public class LanePlayfield : ScrollingPlayfield { - private readonly JudgementContainer judgementContainer; - private readonly Container effectsContainer; - private readonly LanedHitLane lane; public LanePlayfield(LanedHitLane type) @@ -30,9 +25,10 @@ public LanePlayfield(LanedHitLane type) Name = $"{(isAirLane ? "Air" : "Ground")} Playfield"; Padding = new MarginPadding { Left = RushPlayfield.HIT_TARGET_OFFSET }; - Anchor = Origin = isAirLane ? Anchor.TopCentre : Anchor.BottomCentre; + Anchor = isAirLane ? Anchor.TopLeft : Anchor.BottomLeft; + Origin = Anchor.CentreLeft; RelativeSizeAxes = Axes.Both; - Size = new Vector2(1, 0); + Size = new Vector2(1, 0.5f); AddRangeInternal(new Drawable[] { @@ -47,8 +43,6 @@ public LanePlayfield(LanedHitLane type) RelativeSizeAxes = Axes.Both, }, confineMode: ConfineMode.ScaleToFit), }, - effectsContainer = new Container(), - judgementContainer = new JudgementContainer(), HitObjectContainer, }); } @@ -81,9 +75,6 @@ protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObje RegisterPool(new DrawableLanedObjectPool(lane, initialSize, maximumSize)); } - public void AddExplosion(Drawable drawable) => effectsContainer.Add(drawable); - public void AddJudgement(DrawableRushJudgement judgement) => judgementContainer.Add(judgement); - // This pool pre-initializes created DrawableLanedObjects with a predefined lane value // The lane value needs to be set beforehand so that the pieces (Minion, etc) can load using the correct information private class DrawableLanedObjectPool : DrawablePool where T : PoolableDrawable, IDrawableLanedHit, new() diff --git a/osu.Game.Rulesets.Rush/UI/RushPlayfield.cs b/osu.Game.Rulesets.Rush/UI/RushPlayfield.cs index 100b9ba..93b2e03 100644 --- a/osu.Game.Rulesets.Rush/UI/RushPlayfield.cs +++ b/osu.Game.Rulesets.Rush/UI/RushPlayfield.cs @@ -39,9 +39,17 @@ public class RushPlayfield : ScrollingPlayfield, IKeyBindingHandler private readonly Container halfPaddingOverEffectContainer; internal readonly Container OverPlayerEffectsContainer; + // The container housing all HitObjectAreas + public readonly Container HitObjectArea; + + // playfield for each lanes private readonly LanePlayfield airLane; private readonly LanePlayfield groundLane; + // These are used to hold onto judgement and effect drawables + private readonly Container airLaneEffectContainer; + private readonly Container groundLaneEffectContainer; + public IEnumerable AllAliveHitObjects { get @@ -77,15 +85,50 @@ public RushPlayfield() Anchor = Origin = Anchor.Centre; InternalChildren = new Drawable[] { - airLane = new LanePlayfield(LanedHitLane.Air), - groundLane = new LanePlayfield(LanedHitLane.Ground), - // Contains miniboss and duals for now new Container { - Name = "Hit Objects", + RelativeSizeAxes = Axes.Both, + Child = HitObjectArea = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + airLane = new LanePlayfield(LanedHitLane.Air), + groundLane = new LanePlayfield(LanedHitLane.Ground), + // Contains miniboss and duals for now + new Container + { + Name = "Hit Objects", + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Name = "Hit Objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Child = HitObjectContainer + }, + }, + } + } + }, + new Container{ + Name = "Judgement and Effects container", RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Child = HitObjectContainer + Children = new Drawable[]{ + airLaneEffectContainer = new Container() + { + Name = "Top Container", + Anchor = Anchor.TopLeft, + Origin = Anchor.CentreLeft + }, + groundLaneEffectContainer = new Container() + { + Name = "Bottom Container", + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft + } + } }, halfPaddingOverEffectContainer = new Container { @@ -184,7 +227,10 @@ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) }; if (rushJudgedObject is IDrawableLanedHit laned) - playfieldForLane(laned.Lane).AddExplosion(explosion); + { + var effectsContainer = laned.Lane == LanedHitLane.Air ? airLaneEffectContainer : groundLaneEffectContainer; + effectsContainer.Add(explosion); + } } // Display health point difference if the judgement result implies it. @@ -215,7 +261,9 @@ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) break; } - playfieldForLane(judgementLane).AddJudgement(judgementDrawable); + var judgementContainer = judgementLane == LanedHitLane.Air ? airLaneEffectContainer : groundLaneEffectContainer; + + judgementContainer.Add(judgementDrawable); } } diff --git a/osu.Game.Rulesets.Rush/UI/RushPlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Rush/UI/RushPlayfieldCoveringWrapper.cs new file mode 100644 index 0000000..869d75f --- /dev/null +++ b/osu.Game.Rulesets.Rush/UI/RushPlayfieldCoveringWrapper.cs @@ -0,0 +1,128 @@ +// Copyright (c) Shane Woolcock. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +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 osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Rush.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; + + public PlayfieldCoveringWrapper(Drawable content) + { + InternalChild = new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 2, + Children = new[] + { + new Container{ + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 0.5f, + Child = 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.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.25f, + Colour = ColourInfo.GradientHorizontal( + Color4.White.Opacity(1f), + Color4.White.Opacity(0f) + ) + }, + filled = new Box + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Width = 0 + } + } + } + } + }; + } + + + /// + /// The relative area that should be completely covered. This does not include the fade. + /// + public float Coverage + { + set + { + filled.Width = value; + gradient.X = value; + } + } + + /// + /// The direction in which the cover expands. + /// + public CoverExpandDirection Direction + { + set => cover.Scale = value == CoverExpandDirection.AlongScroll ? new Vector2(-1, 1) : Vector2.One; + } + } + + public enum CoverExpandDirection + { + /// + /// The cover expands along the scrolling direction. + /// + AlongScroll, + + /// + /// The cover expands against the scrolling direction. + /// + AgainstScroll + } +}