1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 12:17:26 +08:00

Merge pull request #23307 from peppy/fix-storyboard-loop-end-time

Fix some storyboard loops not playing for as long as expected
This commit is contained in:
Bartłomiej Dach 2023-04-26 22:42:45 +02:00 committed by GitHub
commit 1959c2daeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 56 additions and 12 deletions

View File

@ -95,6 +95,27 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[Test]
public void TestLoopWithoutExplicitFadeOut()
{
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("animation-loop-no-explicit-end-time.osb"))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
Assert.AreEqual(1, background.Elements.Count);
Assert.AreEqual(2000, background.Elements[0].StartTime);
Assert.AreEqual(2000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
Assert.AreEqual(3000, (background.Elements[0] as StoryboardAnimation)?.GetEndTime());
Assert.AreEqual(12000, (background.Elements[0] as StoryboardAnimation)?.EndTimeForDisplay);
}
}
[Test]
public void TestCorrectAnimationStartTime()
{

View File

@ -0,0 +1,6 @@
[Events]
//Storyboard Layer 0 (Background)
Animation,Background,Centre,"img.jpg",320,240,2,150,LoopForever
F,0,2000,,0,1
L,2000,10
F,18,0,1000,1,0

View File

@ -311,6 +311,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsDrawable => true;
public double StartTime => double.MinValue;
public double EndTime => double.MaxValue;
public double EndTimeForDisplay => double.MaxValue;
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
}

View File

@ -85,7 +85,7 @@ namespace osu.Game.Storyboards.Drawables
Loop = animation.LoopType == AnimationLoopType.LoopForever;
LifetimeStart = animation.StartTime;
LifetimeEnd = animation.EndTime;
LifetimeEnd = animation.EndTimeForDisplay;
}
[Resolved]

View File

@ -82,7 +82,7 @@ namespace osu.Game.Storyboards.Drawables
Position = sprite.InitialPosition;
LifetimeStart = sprite.StartTime;
LifetimeEnd = sprite.EndTime;
LifetimeEnd = sprite.EndTimeForDisplay;
}
[Resolved]

View File

@ -12,9 +12,17 @@ namespace osu.Game.Storyboards
{
/// <summary>
/// The time at which the <see cref="IStoryboardElement"/> ends.
/// This is consumed to extend the length of a storyboard to ensure all visuals are played to completion.
/// </summary>
double EndTime { get; }
/// <summary>
/// The time this element displays until.
/// This is used for lifetime purposes, and includes long playing animations which don't necessarily extend
/// a storyboard's play time.
/// </summary>
double EndTimeForDisplay { get; }
/// <summary>
/// The duration of the StoryboardElement.
/// </summary>

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using osuTK;
using osu.Framework.Graphics;
using osu.Game.Storyboards.Drawables;

View File

@ -1,12 +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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Game.Storyboards.Drawables;
using osuTK;
@ -84,6 +81,19 @@ namespace osu.Game.Storyboards
}
}
public double EndTimeForDisplay
{
get
{
double latestEndTime = TimelineGroup.EndTime;
foreach (var l in loops)
latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations);
return latestEndTime;
}
}
public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands);
private delegate void DrawablePropertyInitializer<in T>(Drawable drawable, T value);
@ -114,7 +124,7 @@ namespace osu.Game.Storyboards
public virtual Drawable CreateDrawable()
=> new DrawableStoryboardSprite(this);
public void ApplyTransforms(Drawable drawable, IEnumerable<Tuple<CommandTimelineGroup, double>> triggeredGroups = null)
public void ApplyTransforms(Drawable drawable, IEnumerable<Tuple<CommandTimelineGroup, double>>? triggeredGroups = null)
{
// For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
// To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
@ -156,7 +166,7 @@ namespace osu.Game.Storyboards
foreach (var command in commands)
{
DrawablePropertyInitializer<T> initFunc = null;
DrawablePropertyInitializer<T>? initFunc = null;
if (!initialized)
{
@ -169,7 +179,7 @@ namespace osu.Game.Storyboards
}
}
private IEnumerable<CommandTimeline<T>.TypedCommand> getCommands<T>(CommandTimelineSelector<T> timelineSelector, IEnumerable<Tuple<CommandTimelineGroup, double>> triggeredGroups)
private IEnumerable<CommandTimeline<T>.TypedCommand> getCommands<T>(CommandTimelineSelector<T> timelineSelector, IEnumerable<Tuple<CommandTimelineGroup, double>>? triggeredGroups)
{
var commands = TimelineGroup.GetCommands(timelineSelector);
foreach (var loop in loops)
@ -198,11 +208,11 @@ namespace osu.Game.Storyboards
{
public double StartTime => command.StartTime;
private readonly DrawablePropertyInitializer<T> initializeProperty;
private readonly DrawablePropertyInitializer<T>? initializeProperty;
private readonly DrawableTransformer<T> transform;
private readonly CommandTimeline<T>.TypedCommand command;
public GeneratedCommand([NotNull] CommandTimeline<T>.TypedCommand command, [CanBeNull] DrawablePropertyInitializer<T> initializeProperty, [NotNull] DrawableTransformer<T> transform)
public GeneratedCommand(CommandTimeline<T>.TypedCommand command, DrawablePropertyInitializer<T>? initializeProperty, DrawableTransformer<T> transform)
{
this.command = command;
this.initializeProperty = initializeProperty;