1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-20 06:12:55 +08:00

Add hitobject lifetime support

This commit is contained in:
smoogipoo 2020-11-10 20:16:52 +09:00
parent 45e9f16f6b
commit 6f3f6dc28b
6 changed files with 86 additions and 16 deletions

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
Position = new Vector2(128, 128),
ComboIndex = 1,
})));
}), null));
}
private HitCircle prepareObject(HitCircle circle)

View File

@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(300, 0),
}),
RepeatCount = 1
})));
}), null));
}
private Slider prepareObject(Slider slider)

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(256, 192),
ComboIndex = 1,
Duration = 1000,
})));
}), null));
}
private Spinner prepareObject(Spinner circle)

View File

@ -120,6 +120,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary>
private bool hasHitObjectApplied;
/// <summary>
/// The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of the currently-attached <see cref="HitObject"/>.
/// </summary>
[CanBeNull]
private HitObjectLifetimeEntry lifetimeEntry;
/// <summary>
/// Creates a new <see cref="DrawableHitObject"/>.
/// </summary>
@ -143,7 +149,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
base.LoadAsyncComplete();
if (HitObject != null)
Apply(HitObject);
Apply(HitObject, lifetimeEntry);
}
protected override void LoadComplete()
@ -160,16 +166,33 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Applies a new <see cref="HitObject"/> to be represented by this <see cref="DrawableHitObject"/>.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to apply.</param>
public void Apply(HitObject hitObject)
/// <param name="lifetimeEntry">The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of <paramref name="hitObject"/>.</param>
public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
{
free();
HitObject = hitObject ?? throw new InvalidOperationException($"Cannot apply a null {nameof(HitObject)}.");
this.lifetimeEntry = lifetimeEntry;
if (lifetimeEntry != null)
{
// Transfer lifetime from the entry.
LifetimeStart = lifetimeEntry.LifetimeStart;
LifetimeEnd = lifetimeEntry.LifetimeEnd;
// Copy any existing result from the entry (required for rewind / judgement revert).
Result = lifetimeEntry.Result;
}
// Ensure this DHO has a result.
Result ??= CreateResult(HitObject.CreateJudgement())
?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
// Copy back the result to the entry for potential future retrieval.
if (lifetimeEntry != null)
lifetimeEntry.Result = Result;
foreach (var h in HitObject.NestedHitObjects)
{
var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
@ -302,7 +325,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
private void onDefaultsApplied(HitObject hitObject)
{
Apply(hitObject);
Apply(hitObject, lifetimeEntry);
DefaultsApplied?.Invoke(this);
}
@ -549,15 +572,27 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </remarks>
protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action);
private double? lifetimeStart;
public override double LifetimeStart
{
get => lifetimeStart ?? (HitObject.StartTime - InitialLifetimeOffset);
set
get => base.LifetimeStart;
set => setLifetime(value, LifetimeEnd);
}
public override double LifetimeEnd
{
get => base.LifetimeEnd;
set => setLifetime(LifetimeStart, value);
}
private void setLifetime(double lifetimeStart, double lifetimeEnd)
{
base.LifetimeStart = lifetimeStart;
base.LifetimeEnd = lifetimeEnd;
if (lifetimeEntry != null)
{
lifetimeStart = value;
base.LifetimeStart = value;
lifetimeEntry.LifetimeStart = lifetimeStart;
lifetimeEntry.LifetimeEnd = lifetimeEnd;
}
}

View File

@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics.Performance;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Objects
@ -16,6 +18,14 @@ namespace osu.Game.Rulesets.Objects
/// </summary>
public readonly HitObject HitObject;
/// <summary>
/// The result that <see cref="HitObject"/> was judged with.
/// This is set by the accompanying <see cref="DrawableHitObject"/>, and reused when required for rewinding.
/// </summary>
internal JudgementResult Result;
private readonly IBindable<double> startTimeBindable = new BindableDouble();
/// <summary>
/// Creates a new <see cref="HitObjectLifetimeEntry"/>.
/// </summary>
@ -23,7 +33,9 @@ namespace osu.Game.Rulesets.Objects
public HitObjectLifetimeEntry(HitObject hitObject)
{
HitObject = hitObject;
ResetLifetimeStart();
startTimeBindable.BindTo(HitObject.StartTimeBindable);
startTimeBindable.BindValueChanged(onStartTimeChanged, true);
}
// The lifetime start, as set by the hitobject.
@ -91,8 +103,8 @@ namespace osu.Game.Rulesets.Objects
protected virtual double InitialLifetimeOffset => 10000;
/// <summary>
/// Resets <see cref="LifetimeStart"/> according to the start time of the <see cref="HitObject"/>.
/// Resets <see cref="LifetimeStart"/> according to the change in start time of the <see cref="HitObject"/>.
/// </summary>
internal void ResetLifetimeStart() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
private void onStartTimeChanged(ValueChangedEvent<double> startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
}
}

View File

@ -16,6 +16,7 @@ using System.Threading;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input;
@ -246,6 +247,16 @@ namespace osu.Game.Rulesets.UI
Playfield.Add(drawableObject);
}
protected sealed override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject)
{
if (!(hitObject is TObject tHitObject))
throw new InvalidOperationException($"Unexpected hitobject type: {hitObject.GetType().ReadableName()}");
return CreateLifetimeEntry(tHitObject);
}
protected virtual HitObjectLifetimeEntry CreateLifetimeEntry(TObject hitObject) => new HitObjectLifetimeEntry(hitObject);
public override void SetRecordTarget(Replay recordingReplay)
{
if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager))
@ -564,9 +575,21 @@ namespace osu.Game.Rulesets.UI
m.ApplyToDrawableHitObjects(dho.Yield());
}
dho.Apply(hitObject);
dho.Apply(hitObject, GetLifetimeEntry(hitObject));
});
}
protected abstract HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject);
private readonly Dictionary<HitObject, HitObjectLifetimeEntry> lifetimeEntries = new Dictionary<HitObject, HitObjectLifetimeEntry>();
protected HitObjectLifetimeEntry GetLifetimeEntry(HitObject hitObject)
{
if (lifetimeEntries.TryGetValue(hitObject, out var entry))
return entry;
return lifetimeEntries[hitObject] = CreateLifetimeEntry(hitObject);
}
}
public class BeatmapInvalidForRulesetException : ArgumentException