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. ///