1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 16:37: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] [Test]
public void TestCorrectAnimationStartTime() 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 bool IsDrawable => true;
public double StartTime => double.MinValue; public double StartTime => double.MinValue;
public double EndTime => double.MaxValue; public double EndTime => double.MaxValue;
public double EndTimeForDisplay => double.MaxValue;
public Drawable CreateDrawable() => new DrawableTestStoryboardElement(); public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
} }

View File

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

View File

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

View File

@ -12,9 +12,17 @@ namespace osu.Game.Storyboards
{ {
/// <summary> /// <summary>
/// The time at which the <see cref="IStoryboardElement"/> ends. /// 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> /// </summary>
double EndTime { get; } 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> /// <summary>
/// The duration of the StoryboardElement. /// The duration of the StoryboardElement.
/// </summary> /// </summary>

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osuTK; using osuTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;

View File

@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
using osuTK; 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); public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands);
private delegate void DrawablePropertyInitializer<in T>(Drawable drawable, T value); private delegate void DrawablePropertyInitializer<in T>(Drawable drawable, T value);
@ -114,7 +124,7 @@ namespace osu.Game.Storyboards
public virtual Drawable CreateDrawable() public virtual Drawable CreateDrawable()
=> new DrawableStoryboardSprite(this); => 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. // 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 // 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) foreach (var command in commands)
{ {
DrawablePropertyInitializer<T> initFunc = null; DrawablePropertyInitializer<T>? initFunc = null;
if (!initialized) 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); var commands = TimelineGroup.GetCommands(timelineSelector);
foreach (var loop in loops) foreach (var loop in loops)
@ -198,11 +208,11 @@ namespace osu.Game.Storyboards
{ {
public double StartTime => command.StartTime; public double StartTime => command.StartTime;
private readonly DrawablePropertyInitializer<T> initializeProperty; private readonly DrawablePropertyInitializer<T>? initializeProperty;
private readonly DrawableTransformer<T> transform; private readonly DrawableTransformer<T> transform;
private readonly CommandTimeline<T>.TypedCommand command; 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.command = command;
this.initializeProperty = initializeProperty; this.initializeProperty = initializeProperty;