From 4afe83e74e9eb8978fc5fe77bae67badb1bff5b8 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Fri, 16 Jun 2017 19:21:54 +0900 Subject: [PATCH] Rework DrawableHitObject to provide default life times and proper DrawableTimingSection autosizing. This exposes LifetimeOffset from DrawableHitObject which is used by the XSRG rulesets to adjust the life time range by the VisibleTimeRange. --- .../Tests/TestCaseScrollingHitObjects.cs | 16 +--- .../Drawables/DrawableManiaHitObject.cs | 7 -- .../Objects/Drawables/DrawableNote.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 8 +- .../Objects/Drawables/DrawableHitObject.cs | 34 +++++++++ .../Rulesets/Timing/DrawableTimingSection.cs | 76 ++++++++++++------- .../Timing/SpeedAdjustmentContainer.cs | 16 +++- 7 files changed, 103 insertions(+), 56 deletions(-) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseScrollingHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseScrollingHitObjects.cs index 5a4b3deb05..2d30d5603a 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseScrollingHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseScrollingHitObjects.cs @@ -125,6 +125,8 @@ namespace osu.Desktop.VisualTests.Tests private class TestSpeedAdjustmentContainer : SpeedAdjustmentContainer { + public override bool RemoveWhenNotAlive => false; + public TestSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) : base(controlPoint) { @@ -195,25 +197,11 @@ namespace osu.Desktop.VisualTests.Tests FadeInFromZero(250, EasingTypes.OutQuint); } - private bool hasExpired; protected override void Update() { base.Update(); if (Time.Current >= HitObject.StartTime) - { background.Colour = Color4.Red; - - if (!hasExpired) - { - using (BeginDelayedSequence(200)) - { - FadeOut(200); - Expire(); - } - - hasExpired = true; - } - } } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index e32e953404..c0eea3ce22 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -33,13 +33,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Y = (float)HitObject.StartTime; } - protected override void LoadComplete() - { - base.LoadComplete(); - - LifetimeStart = HitObject.StartTime - ManiaPlayfield.TIME_SPAN_MAX; - } - public override Color4 AccentColour { get { return base.AccentColour; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 658d409bb8..9322fed3eb 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public DrawableNote(Note hitObject, Bindable key = null) : base(hitObject, key) { - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; Height = 100; Add(headPiece = new NotePiece diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 5f33ac2cf1..205f8e152c 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Mania.UI public const float HIT_TARGET_POSITION = 50; private const double time_span_default = 1500; - public const double TIME_SPAN_MIN = 50; - public const double TIME_SPAN_MAX = 10000; + private const double time_span_min = 50; + private const double time_span_max = 10000; private const double time_span_step = 50; /// @@ -60,8 +60,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly BindableDouble visibleTimeRange = new BindableDouble(time_span_default) { - MinValue = TIME_SPAN_MIN, - MaxValue = TIME_SPAN_MAX + MinValue = time_span_min, + MaxValue = time_span_max }; private readonly SpeedAdjustmentCollection barLineContainer; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index fcdcf672d5..68d2023109 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -12,11 +12,28 @@ using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Audio; using System.Linq; +using osu.Framework.Configuration; namespace osu.Game.Rulesets.Objects.Drawables { public abstract class DrawableHitObject : Container { + private readonly BindableDouble lifetimeOffset = new BindableDouble(); + /// + /// Time offset before at which this becomes visible and the time offset + /// after or at which it expires. + /// + /// + /// This provides only a default life time range, however classes inheriting from should expire their state through + /// if more tight control over the life time is desired. + /// + /// + public BindableDouble LifetimeOffset + { + get { return lifetimeOffset; } + set { lifetimeOffset.BindTo(value); } + } + public readonly HitObject HitObject; /// @@ -28,6 +45,22 @@ namespace osu.Game.Rulesets.Objects.Drawables { HitObject = hitObject; } + + public override double LifetimeStart + { + get { return Math.Min(HitObject.StartTime - lifetimeOffset, base.LifetimeStart); } + set { base.LifetimeStart = value; } + } + + public override double LifetimeEnd + { + get + { + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + return Math.Max(endTime + LifetimeOffset, base.LifetimeEnd); + } + set { base.LifetimeEnd = value; } + } } public abstract class DrawableHitObject : DrawableHitObject @@ -190,6 +223,7 @@ namespace osu.Game.Rulesets.Objects.Drawables nestedHitObjects = new List>(); h.OnJudgement += d => OnJudgement?.Invoke(d); + h.LifetimeOffset = LifetimeOffset; nestedHitObjects.Add(h); } diff --git a/osu.Game/Rulesets/Timing/DrawableTimingSection.cs b/osu.Game/Rulesets/Timing/DrawableTimingSection.cs index ef5daf0de8..1f1abc9bb4 100644 --- a/osu.Game/Rulesets/Timing/DrawableTimingSection.cs +++ b/osu.Game/Rulesets/Timing/DrawableTimingSection.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; +using osu.Game.Rulesets.Objects.Types; +using System; namespace osu.Game.Rulesets.Timing { @@ -44,14 +46,17 @@ namespace osu.Game.Rulesets.Timing set { visibleTimeRange.BindTo(value); } } - protected override IComparer DepthComparer => new HitObjectReverseStartTimeComparer(); - /// - /// Axes through which this timing section scrolls. This is set from . + /// Axes through which this timing section scrolls. This is set by the . /// internal Axes ScrollingAxes; - private Cached layout = new Cached(); + /// + /// The control point that provides the speed adjustments for this container. This is set by the . + /// + internal MultiplierControlPoint ControlPoint; + + protected override IComparer DepthComparer => new HitObjectReverseStartTimeComparer(); /// /// Creates a new . @@ -71,41 +76,54 @@ namespace osu.Game.Rulesets.Timing return; } - layout.Invalidate(); + durationBacking.Invalidate(); base.InvalidateFromChild(invalidation); } - protected override void UpdateAfterChildren() + private Cached durationBacking = new Cached(); + /// + /// The maximum duration of any one hit object inside this . This is calculated as the maximum + /// end time between all hit objects relative to this 's . + /// + public double Duration => durationBacking.EnsureValid() + ? durationBacking.Value + : durationBacking.Refresh(() => { - base.UpdateAfterChildren(); + if (!Children.Any()) + return 0; - if (!layout.EnsureValid()) - { - layout.Refresh(() => - { - if (!Children.Any()) - return; + double baseDuration = Children.Max(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime) - ControlPoint.StartTime; - //double maxDuration = Children.Select(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime).Max(); - //float width = (float)maxDuration - RelativeChildOffset.X; - //float height = (float)maxDuration - RelativeChildOffset.Y; + if (baseDuration == 0) + baseDuration = 1000; + // Scrolling rulesets typically have anchors/origins set to their start time, but if an object has no end time and lies on the control point + // then the baseDuration above will be 0. This will cause problems with masking when it is set as the value for Size in Update(). + // + // Thus we _want_ the timing section to completely contain the hit object, and to do with we'll need to find a duration that corresponds + // to the absolute size of the element that extrudes beyond our bounds. For simplicity, we can approximate this by just using the largest + // absolute size available from our children. + float maxAbsoluteSize = Children.Where(c => (c.RelativeSizeAxes & ScrollingAxes) == 0) + .Select(c => (ScrollingAxes & Axes.X) > 0 ? c.Width : c.Height) + .DefaultIfEmpty().Max(); - // Auto-size to the total size of our children - // This ends up being the total duration of our children, however for now this is a more sure-fire way to calculate this - // than the above due to some undesired masking optimisations causing some hit objects to be culled... - // Todo: When this is investigated more we should use the above method as it is a little more exact - // Todo: This is not working correctly in the case that hit objects are absolutely-sized - needs a proper looking into in osu!framework - float width = Children.Select(child => child.X + child.Width).Max() - RelativeChildOffset.X; - float height = Children.Select(child => child.Y + child.Height).Max() - RelativeChildOffset.Y; + float ourAbsoluteSize = (ScrollingAxes & Axes.X) > 0 ? DrawWidth : DrawHeight; - // Consider that width/height are time values. To have ourselves span these time values 1:1, we first need to set our size - Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? width : Size.X, (ScrollingAxes & Axes.Y) > 0 ? height : Size.Y); - // Then to make our position-space be time values again, we need our relative child size to follow our size - RelativeChildSize = Size; - }); - } + // Add the extra duration to account for the absolute size + baseDuration *= 1 + maxAbsoluteSize / ourAbsoluteSize; + + return baseDuration; + }); + + protected override void Update() + { + base.Update(); + + // Consider that width/height are time values. To have ourselves span these time values 1:1, we first need to set our size + Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)Duration : Size.X, (ScrollingAxes & Axes.Y) > 0 ? (float)Duration : Size.Y); + // Then to make our position-space be time values again, we need our relative child size to follow our size + RelativeChildSize = Size; } } } diff --git a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs index af9a79adb5..b5973099d2 100644 --- a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs +++ b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; +using System.Linq; +using System; namespace osu.Game.Rulesets.Timing { @@ -42,6 +44,8 @@ namespace osu.Game.Rulesets.Timing public readonly MultiplierControlPoint ControlPoint; + private DrawableTimingSection timingSection; + /// /// Creates a new . /// @@ -56,9 +60,10 @@ namespace osu.Game.Rulesets.Timing [BackgroundDependencyLoader] private void load() { - DrawableTimingSection timingSection = CreateTimingSection(); + timingSection = CreateTimingSection(); timingSection.ScrollingAxes = ScrollingAxes; + timingSection.ControlPoint = ControlPoint; timingSection.VisibleTimeRange.BindTo(VisibleTimeRange); timingSection.RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)ControlPoint.StartTime : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)ControlPoint.StartTime : 0); @@ -74,6 +79,15 @@ namespace osu.Game.Rulesets.Timing RelativeChildSize = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)VisibleTimeRange : 1, (ScrollingAxes & Axes.Y) > 0 ? (float)VisibleTimeRange : 1); } + public override double LifetimeStart => ControlPoint.StartTime - VisibleTimeRange; + public override double LifetimeEnd => ControlPoint.StartTime + timingSection.Duration + VisibleTimeRange; + + public override void Add(DrawableHitObject drawable) + { + drawable.LifetimeOffset.BindTo(VisibleTimeRange); + base.Add(drawable); + } + /// /// Whether this speed adjustment can contain a hit object. This is true if the hit object occurs after this speed adjustment with respect to time. ///