diff --git a/osu.Desktop.Tests/Visual/TestCaseManiaHitObjects.cs b/osu.Desktop.Tests/Visual/TestCaseManiaHitObjects.cs index 76235bbf19..d855e86aa0 100644 --- a/osu.Desktop.Tests/Visual/TestCaseManiaHitObjects.cs +++ b/osu.Desktop.Tests/Visual/TestCaseManiaHitObjects.cs @@ -41,8 +41,20 @@ namespace osu.Desktop.Tests.Visual RelativeChildSize = new Vector2(1, 10000), Children = new[] { - new DrawableNote(new Note { StartTime = 5000 }, ManiaAction.Key1) { AccentColour = Color4.Red }, - new DrawableNote(new Note { StartTime = 6000 }, ManiaAction.Key1) { AccentColour = Color4.Red } + new DrawableNote(new Note(), ManiaAction.Key1) + { + Y = 5000, + LifetimeStart = double.MinValue, + LifetimeEnd = double.MaxValue, + AccentColour = Color4.Red + }, + new DrawableNote(new Note(), ManiaAction.Key1) + { + Y = 6000, + LifetimeStart = double.MinValue, + LifetimeEnd = double.MaxValue, + AccentColour = Color4.Red + } } } } @@ -63,11 +75,14 @@ namespace osu.Desktop.Tests.Visual RelativeChildSize = new Vector2(1, 10000), Children = new[] { - new DrawableHoldNote(new HoldNote + new DrawableHoldNote(new HoldNote(), ManiaAction.Key1) { - StartTime = 5000, - Duration = 1000 - }, ManiaAction.Key1) { AccentColour = Color4.Red } + Y = 5000, + Height = 1000, + LifetimeStart = double.MinValue, + LifetimeEnd = double.MaxValue, + AccentColour = Color4.Red + } } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index e06f71cb64..3b801cba01 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Extensions.Color4Extensions; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -9,6 +10,7 @@ using OpenTK; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Judgements; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -23,6 +25,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly BodyPiece bodyPiece; private readonly Container tickContainer; + private readonly Container glowContainer; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -42,13 +45,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddRange(new Drawable[] { - // For now the body piece covers the entire height of the container - // whereas possibly in the future we don't want to extend under the head/tail. - // This will be fixed when new designs are given or the current design is finalized. bodyPiece = new BodyPiece { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, }, tickContainer = new Container { @@ -65,6 +66,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { Anchor = Anchor.BottomCentre, Origin = Anchor.TopCentre + }, + // The hit object itself cannot be used for the glow because the tail overshoots it + // So a specialized container that is updated to contain the tail height is used + glowContainer = new Container + { + RelativeSizeAxes = Axes.X, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } } }); @@ -83,6 +97,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddNested(tail); } + protected override void LoadComplete() + { + base.LoadComplete(); + + updateGlow(); + } + public override Color4 AccentColour { get { return base.AccentColour; } @@ -97,13 +118,42 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.AccentColour = value; head.AccentColour = value; tail.AccentColour = value; + + updateGlow(); } } + private void updateGlow() + { + if (!IsLoaded) + return; + + glowContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Opacity(0.5f), + Radius = 10, + Hollow = true + }; + } + protected override void UpdateState(ArmedState state) { } + protected override void Update() + { + base.Update(); + + // Make the body piece not lie under the head note + bodyPiece.Y = head.Height; + bodyPiece.Height = DrawHeight - head.Height; + + // Make the glowContainer "contain" the height of the tail note, keeping in mind + // that the tail note overshoots the height of this hit object + glowContainer.Height = DrawHeight + tail.Height; + } + public bool OnPressed(ManiaAction action) { // Make sure the action happened within the body of the hold note @@ -153,6 +203,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativePositionAxes = Axes.None; Y = 0; + + // Life time managed by the parent DrawableHoldNote + LifetimeStart = double.MinValue; + LifetimeEnd = double.MaxValue; + + ApplyGlow = false; } public override bool OnPressed(ManiaAction action) @@ -190,6 +246,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativePositionAxes = Axes.None; Y = 0; + + // Life time managed by the parent DrawableHoldNote + LifetimeStart = double.MinValue; + LifetimeEnd = double.MaxValue; + + ApplyGlow = false; } protected override ManiaJudgement CreateJudgement() => new HoldNoteTailJudgement(); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index a074715faf..74f775b49f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -23,11 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public Func HoldStartTime; - /// - /// References whether the user is currently holding the hold note. - /// - public Func IsHolding; - private readonly Container glowContainer; public DrawableHoldNoteTick(HoldNoteTick hitObject) @@ -36,9 +31,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; + Y = (float)HitObject.StartTime; + RelativeSizeAxes = Axes.X; Size = new Vector2(1); + // Life time managed by the parent DrawableHoldNote + LifetimeStart = double.MinValue; + LifetimeEnd = double.MaxValue; + Children = new[] { glowContainer = new CircularContainer @@ -109,7 +110,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Judgement.Result != HitResult.None) return; - if (IsHolding?.Invoke() != true) + if (HoldStartTime?.Invoke() == null) return; UpdateJudgement(true); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index bfef05ea07..f6129602a8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; using OpenTK.Graphics; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Objects.Drawables; @@ -20,6 +21,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null) : base(hitObject) { + RelativePositionAxes = Axes.Y; HitObject = hitObject; if (action != null) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index f0b622de17..11d3f970c3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -2,8 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Extensions.Color4Extensions; using OpenTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -16,13 +18,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { + /// + /// Gets or sets whether this should apply glow to itself. + /// + protected bool ApplyGlow = true; + private readonly NotePiece headPiece; public DrawableNote(Note hitObject, ManiaAction action) : base(hitObject, action) { RelativeSizeAxes = Axes.X; - Height = 100; + AutoSizeAxes = Axes.Y; + Masking = true; Add(headPiece = new NotePiece { @@ -31,6 +39,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + updateGlow(); + } + public override Color4 AccentColour { get { return base.AccentColour; } @@ -41,9 +56,28 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables base.AccentColour = value; headPiece.AccentColour = value; + + updateGlow(); } } + private void updateGlow() + { + if (!IsLoaded) + return; + + if (!ApplyGlow) + return; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Opacity(0.5f), + Radius = 10, + Hollow = true + }; + } + protected override void CheckJudgement(bool userTriggered) { if (!userTriggered) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 04e8df4ae2..fe40532e42 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -1,7 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using osu.Framework.Caching; using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,22 +17,61 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// internal class BodyPiece : Container, IHasAccentColour { - private readonly Box box; + private readonly Container subtractionLayer; + + private readonly Drawable background; + private readonly BufferedContainer foreground; + private readonly BufferedContainer subtractionContainer; public BodyPiece() { - RelativeSizeAxes = Axes.Both; + Blending = BlendingMode.Additive; Children = new[] { - box = new Box + background = new Box { RelativeSizeAxes = Axes.Both }, + foreground = new BufferedContainer { RelativeSizeAxes = Axes.Both, - Alpha = 0.3f + CacheDrawnFrameBuffer = true, + Children = new Drawable[] + { + new Box { RelativeSizeAxes = Axes.Both }, + subtractionContainer = new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + // This is needed because we're blending with another object + BackgroundColour = Color4.White.Opacity(0), + CacheDrawnFrameBuffer = true, + // The 'hole' is achieved by subtracting the result of this container with the parent + Blending = new BlendingParameters { AlphaEquation = BlendingEquation.ReverseSubtract }, + Child = subtractionLayer = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + // Height computed in Update + Width = 1, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + } } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + updateAccentColour(); + } + private Color4 accentColour; public Color4 AccentColour { @@ -40,8 +82,51 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces return; accentColour = value; - box.Colour = accentColour; + updateAccentColour(); } } + + private Cached subtractionCache = new Cached(); + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.DrawSize) > 0) + subtractionCache.Invalidate(); + + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + base.Update(); + + if (!subtractionCache.IsValid) + { + subtractionLayer.Width = 5; + subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth); + subtractionLayer.EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.White, + Type = EdgeEffectType.Glow, + Radius = DrawWidth + }; + + foreground.ForceRedraw(); + subtractionContainer.ForceRedraw(); + + subtractionCache.Validate(); + } + } + + private void updateAccentColour() + { + if (!IsLoaded) + return; + + foreground.Colour = AccentColour.Opacity(0.4f); + background.Colour = AccentColour.Opacity(0.2f); + + subtractionCache.Invalidate(); + } } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 4797f438d0..e6a26baa66 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -87,7 +87,9 @@ namespace osu.Game.Beatmaps { if (track != null) return track; - track = GetTrack(); + // we want to ensure that we always have a track, even if it's a fake one. + track = GetTrack() ?? new TrackVirtual(); + applyRateAdjustments(); return track; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ed852b1c25..7c09139302 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -12,10 +12,11 @@ using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Audio; using System.Linq; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { - public abstract class DrawableHitObject : Container + public abstract class DrawableHitObject : Container, IHasAccentColour { public readonly HitObject HitObject;