mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 23:12:55 +08:00
move judgement + state logic up to DrawableHitObject
This commit is contained in:
parent
b9f4dee801
commit
70fc09f81e
@ -29,37 +29,167 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Color4 AccentColour { get; set; } = Color4.Gray;
|
public virtual Color4 AccentColour { get; set; } = Color4.Gray;
|
||||||
|
|
||||||
|
private List<DrawableHitObject> nestedHitObjects;
|
||||||
|
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
|
||||||
|
|
||||||
|
public event Action<DrawableHitObject, Judgement> OnJudgement;
|
||||||
|
public event Action<DrawableHitObject, Judgement> OnJudgementRemoved;
|
||||||
|
|
||||||
|
public IReadOnlyList<Judgement> Judgements => judgements;
|
||||||
|
private readonly List<Judgement> judgements = new List<Judgement>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether a visible judgement should be displayed when this representation is hit.
|
/// Whether a visible judgement should be displayed when this representation is hit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool DisplayJudgement => true;
|
public virtual bool DisplayJudgement => true;
|
||||||
|
|
||||||
public override bool RemoveCompletedTransforms => false;
|
/// <summary>
|
||||||
|
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="DrawableHitObject"/> can be judged.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool ProvidesJudgement => true;
|
||||||
|
|
||||||
|
private bool judgementOccurred;
|
||||||
|
private bool judgementFinalized => judgements.LastOrDefault()?.Final == true;
|
||||||
|
|
||||||
|
public bool Interactive = true;
|
||||||
|
public override bool HandleInput => Interactive;
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
public override bool RemoveCompletedTransforms => false;
|
||||||
protected override bool RequiresChildrenUpdate => true;
|
protected override bool RequiresChildrenUpdate => true;
|
||||||
|
|
||||||
public virtual bool AllJudged => false;
|
public readonly Bindable<ArmedState> State = new Bindable<ArmedState>();
|
||||||
|
|
||||||
protected DrawableHitObject(HitObject hitObject)
|
protected DrawableHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
State.ValueChanged += state =>
|
||||||
|
{
|
||||||
|
UpdateState(state);
|
||||||
|
|
||||||
|
// apply any custom state overrides
|
||||||
|
ApplyCustomUpdateState?.Invoke(this, state);
|
||||||
|
};
|
||||||
|
|
||||||
|
State.TriggerChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void UpdateState(ArmedState state);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind to apply a custom state which can override the default implementation.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
||||||
|
|
||||||
|
while (judgements.Count > 0)
|
||||||
|
{
|
||||||
|
var lastJudgement = judgements[judgements.Count - 1];
|
||||||
|
if (lastJudgement.TimeOffset + endTime <= Time.Current)
|
||||||
|
break;
|
||||||
|
|
||||||
|
judgements.RemoveAt(judgements.Count - 1);
|
||||||
|
State.Value = ArmedState.Idle;
|
||||||
|
|
||||||
|
OnJudgementRemoved?.Invoke(this, lastJudgement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
|
UpdateJudgement(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void AddNested(DrawableHitObject h)
|
||||||
|
{
|
||||||
|
if (nestedHitObjects == null)
|
||||||
|
nestedHitObjects = new List<DrawableHitObject>();
|
||||||
|
nestedHitObjects.Add(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notifies that a new judgement has occurred for this <see cref="DrawableHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="judgement">The <see cref="Judgement"/>.</param>
|
||||||
|
protected void AddJudgement(Judgement judgement)
|
||||||
|
{
|
||||||
|
judgementOccurred = true;
|
||||||
|
|
||||||
|
// Ensure that the judgement is given a valid time offset, because this may not get set by the caller
|
||||||
|
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
||||||
|
judgement.TimeOffset = Time.Current - endTime;
|
||||||
|
|
||||||
|
judgements.Add(judgement);
|
||||||
|
|
||||||
|
switch (judgement.Result)
|
||||||
|
{
|
||||||
|
case HitResult.None:
|
||||||
|
break;
|
||||||
|
case HitResult.Miss:
|
||||||
|
State.Value = ArmedState.Miss;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
State.Value = ArmedState.Hit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnJudgement?.Invoke(this, judgement);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes this <see cref="DrawableHitObject"/>, checking if any judgements have occurred.
|
/// Processes this <see cref="DrawableHitObject"/>, checking if any judgements have occurred.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userTriggered">Whether the user triggered this process.</param>
|
/// <param name="userTriggered">Whether the user triggered this process.</param>
|
||||||
/// <returns>Whether a judgement has occurred from this <see cref="DrawableHitObject"/> or any nested <see cref="DrawableHitObject"/>s.</returns>
|
/// <returns>Whether a judgement has occurred from this <see cref="DrawableHitObject"/> or any nested <see cref="DrawableHitObject"/>s.</returns>
|
||||||
protected internal virtual bool UpdateJudgement(bool userTriggered) => false;
|
protected internal bool UpdateJudgement(bool userTriggered)
|
||||||
|
{
|
||||||
private List<DrawableHitObject> nestedHitObjects;
|
judgementOccurred = false;
|
||||||
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
|
|
||||||
|
if (AllJudged)
|
||||||
protected virtual void AddNested(DrawableHitObject h)
|
return false;
|
||||||
|
|
||||||
|
if (NestedHitObjects != null)
|
||||||
|
{
|
||||||
|
foreach (var d in NestedHitObjects)
|
||||||
|
judgementOccurred |= d.UpdateJudgement(userTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ProvidesJudgement || judgementFinalized || judgementOccurred)
|
||||||
|
return judgementOccurred;
|
||||||
|
|
||||||
|
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
||||||
|
CheckForJudgements(userTriggered, Time.Current - endTime);
|
||||||
|
|
||||||
|
return judgementOccurred;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any judgements have occurred for this <see cref="DrawableHitObject"/>. This method must construct
|
||||||
|
/// all <see cref="Judgement"/>s and notify of them through <see cref="AddJudgement"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userTriggered">Whether the user triggered this check.</param>
|
||||||
|
/// <param name="timeOffset">The offset from the <see cref="HitObject"/> end time at which this check occurred. A <paramref name="timeOffset"/> > 0
|
||||||
|
/// implies that this check occurred after the end time of <see cref="HitObject"/>. </param>
|
||||||
|
protected virtual void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (nestedHitObjects == null)
|
|
||||||
nestedHitObjects = new List<DrawableHitObject>();
|
|
||||||
nestedHitObjects.Add(h);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -76,30 +206,14 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
public abstract class DrawableHitObject<TObject> : DrawableHitObject
|
public abstract class DrawableHitObject<TObject> : DrawableHitObject
|
||||||
where TObject : HitObject
|
where TObject : HitObject
|
||||||
{
|
{
|
||||||
public event Action<DrawableHitObject, Judgement> OnJudgement;
|
|
||||||
public event Action<DrawableHitObject, Judgement> OnJudgementRemoved;
|
|
||||||
|
|
||||||
public new readonly TObject HitObject;
|
public new readonly TObject HitObject;
|
||||||
|
|
||||||
public override bool HandleInput => Interactive;
|
|
||||||
public bool Interactive = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this <see cref="DrawableHitObject"/> can be judged.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual bool ProvidesJudgement => true;
|
|
||||||
|
|
||||||
private readonly List<Judgement> judgements = new List<Judgement>();
|
|
||||||
public IReadOnlyList<Judgement> Judgements => judgements;
|
|
||||||
|
|
||||||
protected List<SampleChannel> Samples = new List<SampleChannel>();
|
protected List<SampleChannel> Samples = new List<SampleChannel>();
|
||||||
protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples;
|
protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples;
|
||||||
|
|
||||||
// Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
|
// Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
|
||||||
protected virtual string SampleNamespace => null;
|
protected virtual string SampleNamespace => null;
|
||||||
|
|
||||||
public readonly Bindable<ArmedState> State = new Bindable<ArmedState>();
|
|
||||||
|
|
||||||
protected DrawableHitObject(TObject hitObject)
|
protected DrawableHitObject(TObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -141,139 +255,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
State.ValueChanged += state =>
|
State.ValueChanged += state =>
|
||||||
{
|
{
|
||||||
UpdateState(state);
|
|
||||||
|
|
||||||
// apply any custom state overrides
|
|
||||||
ApplyCustomUpdateState?.Invoke(this, state);
|
|
||||||
|
|
||||||
if (State == ArmedState.Hit)
|
if (State == ArmedState.Hit)
|
||||||
PlaySamples();
|
PlaySamples();
|
||||||
};
|
};
|
||||||
|
|
||||||
State.TriggerChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void PlaySamples()
|
protected void PlaySamples() => Samples.ForEach(s => s?.Play());
|
||||||
{
|
|
||||||
Samples.ForEach(s => s?.Play());
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool judgementOccurred;
|
|
||||||
private bool judgementFinalized => judgements.LastOrDefault()?.Final == true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
|
|
||||||
/// </summary>
|
|
||||||
public sealed override bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies that a new judgement has occurred for this <see cref="DrawableHitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="judgement">The <see cref="Judgement"/>.</param>
|
|
||||||
protected void AddJudgement(Judgement judgement)
|
|
||||||
{
|
|
||||||
judgementOccurred = true;
|
|
||||||
|
|
||||||
// Ensure that the judgement is given a valid time offset, because this may not get set by the caller
|
|
||||||
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
|
||||||
judgement.TimeOffset = Time.Current - endTime;
|
|
||||||
|
|
||||||
judgements.Add(judgement);
|
|
||||||
|
|
||||||
switch (judgement.Result)
|
|
||||||
{
|
|
||||||
case HitResult.None:
|
|
||||||
break;
|
|
||||||
case HitResult.Miss:
|
|
||||||
State.Value = ArmedState.Miss;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
State.Value = ArmedState.Hit;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnJudgement?.Invoke(this, judgement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes this <see cref="DrawableHitObject"/>, checking if any judgements have occurred.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="userTriggered">Whether the user triggered this process.</param>
|
|
||||||
/// <returns>Whether a judgement has occurred from this <see cref="DrawableHitObject"/> or any nested <see cref="DrawableHitObject"/>s.</returns>
|
|
||||||
protected internal sealed override bool UpdateJudgement(bool userTriggered)
|
|
||||||
{
|
|
||||||
judgementOccurred = false;
|
|
||||||
|
|
||||||
if (AllJudged)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (NestedHitObjects != null)
|
|
||||||
{
|
|
||||||
foreach (var d in NestedHitObjects)
|
|
||||||
judgementOccurred |= d.UpdateJudgement(userTriggered);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ProvidesJudgement || judgementFinalized || judgementOccurred)
|
|
||||||
return judgementOccurred;
|
|
||||||
|
|
||||||
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
|
||||||
CheckForJudgements(userTriggered, Time.Current - endTime);
|
|
||||||
|
|
||||||
return judgementOccurred;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if any judgements have occurred for this <see cref="DrawableHitObject"/>. This method must construct
|
|
||||||
/// all <see cref="Judgement"/>s and notify of them through <see cref="AddJudgement"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="userTriggered">Whether the user triggered this check.</param>
|
|
||||||
/// <param name="timeOffset">The offset from the <see cref="HitObject"/> end time at which this check occurred. A <paramref name="timeOffset"/> > 0
|
|
||||||
/// implies that this check occurred after the end time of <see cref="HitObject"/>. </param>
|
|
||||||
protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) { }
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
|
||||||
|
|
||||||
while (judgements.Count > 0)
|
|
||||||
{
|
|
||||||
var lastJudgement = judgements[judgements.Count - 1];
|
|
||||||
if (lastJudgement.TimeOffset + endTime <= Time.Current)
|
|
||||||
break;
|
|
||||||
|
|
||||||
judgements.RemoveAt(judgements.Count - 1);
|
|
||||||
State.Value = ArmedState.Idle;
|
|
||||||
|
|
||||||
OnJudgementRemoved?.Invoke(this, lastJudgement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
|
||||||
{
|
|
||||||
base.UpdateAfterChildren();
|
|
||||||
|
|
||||||
UpdateJudgement(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void AddNested(DrawableHitObject h)
|
|
||||||
{
|
|
||||||
base.AddNested(h);
|
|
||||||
|
|
||||||
if (!(h is DrawableHitObject<TObject> hWithJudgement))
|
|
||||||
return;
|
|
||||||
|
|
||||||
hWithJudgement.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
|
|
||||||
hWithJudgement.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
|
|
||||||
hWithJudgement.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bind to apply a custom state which can override the default implementation.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
|
|
||||||
|
|
||||||
protected abstract void UpdateState(ArmedState state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user