1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 10:12:54 +08:00

Merge pull request #20570 from ekrctb/scrolling-lifetime

Compute lifetime start from entry for scrolling hit objects
This commit is contained in:
Dan Balasescu 2022-10-05 20:45:43 +09:00 committed by GitHub
commit c52ddb0e50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 50 deletions

View File

@ -54,10 +54,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
protected override void UpdateInitialTransforms()
{
}
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
}
}

View File

@ -30,14 +30,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public bool UpdateResult() => base.UpdateResult(true);
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
// This hitobject should never expire, so this is just a safe maximum.
LifetimeEnd = LifetimeStart + 30000;
}
protected override void UpdateHitStateTransforms(ArmedState state)
{
// suppress the base call explicitly.

View File

@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
// Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms.
// Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1.
protected override double InitialLifetimeOffset => 30000;
[Resolved(canBeNull: true)]
private ManiaPlayfield playfield { get; set; }

View File

@ -11,8 +11,10 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
@ -167,14 +169,39 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
private void addHitObject(double time)
[Test]
public void TestVeryFlowScroll()
{
const double long_time_range = 100000;
var manualClock = new ManualClock();
AddStep("set manual clock", () =>
{
manualClock.CurrentTime = 0;
scrollContainers.ForEach(c => c.Clock = new FramedClock(manualClock));
setScrollAlgorithm(ScrollVisualisationMethod.Constant);
scrollContainers.ForEach(c => c.TimeRange = long_time_range);
});
AddStep("add hit objects", () =>
{
addHitObject(long_time_range);
addHitObject(long_time_range + 100, 250);
});
AddAssert("hit objects are alive", () => playfields.All(p => p.HitObjectContainer.AliveObjects.Count() == 2));
}
private void addHitObject(double time, float size = 75)
{
playfields.ForEach(p =>
{
var hitObject = new TestDrawableHitObject(time);
setAnchor(hitObject, p);
var hitObject = new TestHitObject(size) { StartTime = time };
var drawable = new TestDrawableHitObject(hitObject);
p.Add(hitObject);
setAnchor(drawable, p);
p.Add(drawable);
});
}
@ -248,6 +275,8 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
}
protected override ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new TestScrollingHitObjectContainer();
}
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
@ -281,22 +310,41 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
private class TestDrawableHitObject : DrawableHitObject<HitObject>
private class TestHitObject : HitObject
{
public TestDrawableHitObject(double time)
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Custom;
OriginPosition = new Vector2(75 / 4.0f);
public readonly float Size;
AutoSizeAxes = Axes.Both;
public TestHitObject(float size)
{
Size = size;
}
}
private class TestDrawableHitObject : DrawableHitObject<TestHitObject>
{
public TestDrawableHitObject(TestHitObject hitObject)
: base(hitObject)
{
Origin = Anchor.Centre;
Size = new Vector2(hitObject.Size);
AddInternal(new Box
{
Size = new Vector2(75),
RelativeSizeAxes = Axes.Both,
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
});
}
}
private class TestScrollingHitObjectContainer : ScrollingHitObjectContainer
{
protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry)
{
if (entry.HitObject is TestHitObject testObject)
return new RectangleF().Inflate(testObject.Size / 2);
return base.GetConservativeBoundingBox(entry);
}
}
}
}

View File

@ -5,10 +5,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Layout;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@ -127,6 +127,16 @@ namespace osu.Game.Rulesets.UI.Scrolling
private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight;
public override void Add(HitObjectLifetimeEntry entry)
{
// Scroll info is not available until loaded.
// The lifetime of all entries will be updated in the first Update.
if (IsLoaded)
setComputedLifetimeStart(entry);
base.Add(entry);
}
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
{
base.AddDrawable(entry, drawable);
@ -145,7 +155,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
private void invalidateHitObject(DrawableHitObject hitObject)
{
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
layoutComputed.Remove(hitObject);
}
@ -157,10 +166,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
layoutComputed.Clear();
// Reset lifetime to the conservative estimation.
// If a drawable becomes alive by this lifetime, its lifetime will be updated to a more precise lifetime in the next update.
foreach (var entry in Entries)
entry.SetInitialLifetime();
setComputedLifetimeStart(entry);
scrollingInfo.Algorithm.Reset();
@ -187,38 +194,46 @@ namespace osu.Game.Rulesets.UI.Scrolling
}
}
private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject)
/// <summary>
/// Get a conservative maximum bounding box of a <see cref="DrawableHitObject"/> corresponding to <paramref name="entry"/>.
/// It is used to calculate when the hit object appears.
/// </summary>
protected virtual RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100);
private double computeDisplayStartTime(HitObjectLifetimeEntry entry)
{
// Origin position may be relative to the parent size
Debug.Assert(hitObject.Parent != null);
RectangleF boundingBox = GetConservativeBoundingBox(entry);
float startOffset = 0;
float originAdjustment = 0.0f;
// calculate the dimension of the part of the hitobject that should already be visible
// when the hitobject origin first appears inside the scrolling container
switch (direction.Value)
{
case ScrollingDirection.Up:
originAdjustment = hitObject.OriginPosition.Y;
case ScrollingDirection.Right:
startOffset = boundingBox.Right;
break;
case ScrollingDirection.Down:
originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y;
startOffset = boundingBox.Bottom;
break;
case ScrollingDirection.Left:
originAdjustment = hitObject.OriginPosition.X;
startOffset = -boundingBox.Left;
break;
case ScrollingDirection.Right:
originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X;
case ScrollingDirection.Up:
startOffset = -boundingBox.Top;
break;
}
double computedStartTime = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength);
return scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength);
}
private void setComputedLifetimeStart(HitObjectLifetimeEntry entry)
{
double computedStartTime = computeDisplayStartTime(entry);
// always load the hitobject before its first judgement offset
return Math.Min(hitObject.HitObject.StartTime - hitObject.MaximumJudgementOffset, computedStartTime);
double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0;
entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime);
}
private void updateLayoutRecursive(DrawableHitObject hitObject)
@ -236,8 +251,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
{
updateLayoutRecursive(obj);
// Nested hitobjects don't need to scroll, but they do need accurate positions
// Nested hitobjects don't need to scroll, but they do need accurate positions and start lifetime
updatePosition(obj, hitObject.HitObject.StartTime);
setComputedLifetimeStart(obj.Entry);
}
}

View File

@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// </summary>
public virtual Vector2 ScreenSpacePositionAtTime(double time) => HitObjectContainer.ScreenSpacePositionAtTime(time);
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer();
protected sealed override HitObjectContainer CreateHitObjectContainer() => CreateScrollingHitObjectContainer();
protected virtual ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new ScrollingHitObjectContainer();
}
}