1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 04:42:58 +08:00

Adjust GameplaySampleTriggerSource to only switch samples when close enough to the next hit object

Closes #23963.

To simplify things, I've removed the optimisation of using
`AliveObject`s because it would break the way this whole lookup works.
This commit is contained in:
Dean Herbert 2023-06-20 20:04:02 +09:00
parent c815f8cd23
commit 555ce7684b

View File

@ -7,7 +7,7 @@ using System.Linq;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Rulesets.UI namespace osu.Game.Rulesets.UI
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.UI
}; };
} }
private HitObjectLifetimeEntry fallbackObject; private HitObjectLifetimeEntry mostValidObject;
/// <summary> /// <summary>
/// Play the most appropriate hit sound for the current point in time. /// Play the most appropriate hit sound for the current point in time.
@ -67,56 +67,38 @@ namespace osu.Game.Rulesets.UI
protected HitObject GetMostValidObject() protected HitObject GetMostValidObject()
{ {
// The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time. if (mostValidObject == null || isAlreadyHit(mostValidObject))
var drawableHitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.HasResult != true);
if (drawableHitObject != null)
{
// A hit object may have a more valid nested object.
drawableHitObject = getMostValidNestedDrawable(drawableHitObject);
return drawableHitObject.HitObject;
}
// In the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play.
// This lookup can be skipped if the last entry is still valid (in the future and not yet hit).
if (fallbackObject == null || fallbackObject.Result?.HasResult == true)
{ {
// We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty). // We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty).
// If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager.
fallbackObject = hitObjectContainer.Entries var candidate = hitObjectContainer.Entries.Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime);
.Where(e => e.Result?.HasResult != true).MinBy(e => e.HitObject.StartTime);
if (fallbackObject != null)
return getEarliestNestedObject(fallbackObject.HitObject);
// In the case there are no non-judged objects, the last hit object should be used instead. // In the case there are no non-judged objects, the last hit object should be used instead.
fallbackObject ??= hitObjectContainer.Entries.LastOrDefault(); if (candidate == null)
mostValidObject = hitObjectContainer.Entries.LastOrDefault();
else
{
if (isCloseEnoughToCurrentTime(candidate))
mostValidObject = candidate;
else
mostValidObject ??= hitObjectContainer.Entries.FirstOrDefault();
}
} }
if (fallbackObject == null) if (mostValidObject == null)
return null; return null;
bool fallbackHasResult = fallbackObject.Result?.HasResult == true;
// If the fallback has been judged then we want the sample from the object itself. // If the fallback has been judged then we want the sample from the object itself.
if (fallbackHasResult) if (isAlreadyHit(mostValidObject))
return fallbackObject.HitObject; return mostValidObject.HitObject;
// Else we want the earliest (including nested). // Else we want the earliest (including nested).
// In cases of nested objects, they will always have earlier sample data than their parent object. // In cases of nested objects, they will always have earlier sample data than their parent object.
return getEarliestNestedObject(fallbackObject.HitObject); return getEarliestNestedObject(mostValidObject.HitObject);
} }
private DrawableHitObject getMostValidNestedDrawable(DrawableHitObject o) private bool isAlreadyHit(HitObjectLifetimeEntry h) => h.Result?.HasResult == true;
{ private bool isCloseEnoughToCurrentTime(HitObjectLifetimeEntry h) => Time.Current > h.HitObject.StartTime - h.HitObject.HitWindows.WindowFor(HitResult.Miss) * 1.5;
var nestedWithoutResult = o.NestedHitObjects.FirstOrDefault(n => n.Result?.HasResult != true);
if (nestedWithoutResult == null)
return o;
return getMostValidNestedDrawable(nestedWithoutResult);
}
private HitObject getEarliestNestedObject(HitObject hitObject) private HitObject getEarliestNestedObject(HitObject hitObject)
{ {