1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 03:27:24 +08:00

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.
This commit is contained in:
smoogipooo 2017-06-16 19:21:54 +09:00
parent ea87aca032
commit 4afe83e74e
7 changed files with 103 additions and 56 deletions

View File

@ -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;
}
}
}
}
}

View File

@ -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; }

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableNote(Note hitObject, Bindable<Key> key = null)
: base(hitObject, key)
{
RelativeSizeAxes = Axes.Both;
RelativeSizeAxes = Axes.X;
Height = 100;
Add(headPiece = new NotePiece

View File

@ -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;
/// <summary>
@ -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;

View File

@ -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();
/// <summary>
/// Time offset before <see cref="HitObject.StartTime"/> at which this <see cref="DrawableHitObject"/> becomes visible and the time offset
/// after <see cref="HitObject.StartTime"/> or <see cref="IHasEndTime.EndTime"/> at which it expires.
///
/// <para>
/// This provides only a default life time range, however classes inheriting from <see cref="DrawableHitObject"/> should expire their state through
/// <see cref="DrawableHitObject{TObject, TJudgement}.UpdateState(ArmedState)"/> if more tight control over the life time is desired.
/// </para>
/// </summary>
public BindableDouble LifetimeOffset
{
get { return lifetimeOffset; }
set { lifetimeOffset.BindTo(value); }
}
public readonly HitObject HitObject;
/// <summary>
@ -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<TObject> : DrawableHitObject
@ -190,6 +223,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
nestedHitObjects = new List<DrawableHitObject<TObject, TJudgement>>();
h.OnJudgement += d => OnJudgement?.Invoke(d);
h.LifetimeOffset = LifetimeOffset;
nestedHitObjects.Add(h);
}

View File

@ -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<Drawable> DepthComparer => new HitObjectReverseStartTimeComparer();
/// <summary>
/// Axes through which this timing section scrolls. This is set from <see cref="SpeedAdjustmentContainer"/>.
/// Axes through which this timing section scrolls. This is set by the <see cref="SpeedAdjustmentContainer"/>.
/// </summary>
internal Axes ScrollingAxes;
private Cached layout = new Cached();
/// <summary>
/// The control point that provides the speed adjustments for this container. This is set by the <see cref="SpeedAdjustmentContainer"/>.
/// </summary>
internal MultiplierControlPoint ControlPoint;
protected override IComparer<Drawable> DepthComparer => new HitObjectReverseStartTimeComparer();
/// <summary>
/// Creates a new <see cref="DrawableTimingSection"/>.
@ -71,41 +76,54 @@ namespace osu.Game.Rulesets.Timing
return;
}
layout.Invalidate();
durationBacking.Invalidate();
base.InvalidateFromChild(invalidation);
}
protected override void UpdateAfterChildren()
private Cached<double> durationBacking = new Cached<double>();
/// <summary>
/// The maximum duration of any one hit object inside this <see cref="DrawableTimingSection"/>. This is calculated as the maximum
/// end time between all hit objects relative to this <see cref="DrawableTimingSection"/>'s <see cref="ControlPoint.StartTime"/>.
/// </summary>
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;
}
}
}

View File

@ -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;
/// <summary>
/// Creates a new <see cref="SpeedAdjustmentContainer"/>.
/// </summary>
@ -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);
}
/// <summary>
/// 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.
/// </summary>