mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 11:20:24 +08:00
95648a3d27
TL;DR the clock was being set too late, causing more transforms to be created than necessary. This solves the issue by way of a refactor (sorry). Overall this should simplify handling of things as more of the logic is shared with the known good-state `BeatmapBackgroundWithStoryboard`. I did try without a refactor (just delaying the creation until the clock arrives) but this version made more sense because background generally expect to do their main load in BDL to aid in smooth transitions. And we can't get the clock by there. (although arguably we could just use a similar method to `BeatmapBackgroundWithStoryboard` and forego using the editor clock). Of note, I removed the black background overdraw hack. There are edge cases where it will lead to weird transitions, but these are far and few between. Basically you need a storyboard which sets the flag to hide the beatmap background and has transparency in it. This is no longer as flagrantly bad as things used to be (which led to the inline fix) as far as I can tell, but feel free to prove me wrong. If this is a blocker I'll probably just add a permanent black box (which does fix this). Closes #36875.
137 lines
4.6 KiB
C#
137 lines
4.6 KiB
C#
// 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.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Timing;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Overlays;
|
|
using osu.Game.Rulesets.Mods;
|
|
using osu.Game.Screens;
|
|
using osu.Game.Storyboards.Drawables;
|
|
|
|
namespace osu.Game.Graphics.Backgrounds
|
|
{
|
|
public partial class BeatmapBackgroundWithStoryboard : BeatmapBackground
|
|
{
|
|
private readonly InterpolatingFramedClock storyboardClock;
|
|
|
|
public readonly AudioContainer Storyboard;
|
|
|
|
private DrawableStoryboard? drawableStoryboard;
|
|
private CancellationTokenSource? loadCancellationSource = new CancellationTokenSource();
|
|
|
|
public Action? StoryboardLoaded { get; set; }
|
|
|
|
[Resolved(CanBeNull = true)]
|
|
private MusicController? musicController { get; set; }
|
|
|
|
[Resolved]
|
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
|
|
|
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
|
: base(beatmap, fallbackTextureName)
|
|
{
|
|
storyboardClock = new InterpolatingFramedClock();
|
|
|
|
AddInternal(Storyboard = new AudioContainer
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Volume = { Value = 0 },
|
|
});
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
LoadStoryboard(false);
|
|
}
|
|
|
|
public void LoadStoryboard(bool async = true)
|
|
{
|
|
Debug.Assert(drawableStoryboard == null);
|
|
|
|
if (!Beatmap.Storyboard.HasDrawable)
|
|
return;
|
|
|
|
drawableStoryboard = new DrawableStoryboard(Beatmap.Storyboard, mods.Value)
|
|
{
|
|
Clock = storyboardClock
|
|
};
|
|
|
|
if (async)
|
|
LoadComponentAsync(drawableStoryboard, finishLoad, (loadCancellationSource = new CancellationTokenSource()).Token);
|
|
else
|
|
{
|
|
LoadComponent(drawableStoryboard);
|
|
finishLoad(drawableStoryboard);
|
|
}
|
|
|
|
void finishLoad(DrawableStoryboard s)
|
|
{
|
|
if (Beatmap.Storyboard.ReplacesBackground)
|
|
Sprite.FadeOut(BackgroundScreen.TRANSITION_LENGTH, Easing.InQuint);
|
|
|
|
Storyboard.FadeInFromZero(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint);
|
|
Storyboard.Add(s);
|
|
|
|
StoryboardLoaded?.Invoke();
|
|
}
|
|
}
|
|
|
|
public void UnloadStoryboard()
|
|
{
|
|
if (drawableStoryboard == null)
|
|
return;
|
|
|
|
loadCancellationSource?.Cancel();
|
|
loadCancellationSource = null;
|
|
|
|
// clear is intentionally used here for the storyboard to be disposed asynchronously.
|
|
Storyboard.Clear();
|
|
|
|
drawableStoryboard = null;
|
|
|
|
Sprite.Alpha = 1f;
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
if (musicController != null)
|
|
musicController.TrackChanged += onTrackChanged;
|
|
|
|
updateStoryboardClockSource(Beatmap);
|
|
}
|
|
|
|
private void onTrackChanged(WorkingBeatmap newBeatmap, TrackChangeDirection _) => updateStoryboardClockSource(newBeatmap);
|
|
|
|
private void updateStoryboardClockSource(WorkingBeatmap newBeatmap)
|
|
{
|
|
if (newBeatmap != Beatmap)
|
|
return;
|
|
|
|
// `MusicController` will sometimes reload the track, even when the working beatmap technically hasn't changed.
|
|
// ensure that the storyboard's clock is always using the latest track instance.
|
|
storyboardClock.ChangeSource(newBeatmap.Track);
|
|
// more often than not, the previous source track's time will be in the future relative to the new source track.
|
|
// explicitly process a single frame so that `InterpolatingFramedClock`'s interpolation logic is bypassed
|
|
// and the storyboard clock is correctly rewound to the source track's time exactly.
|
|
storyboardClock.ProcessFrame();
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
base.Dispose(isDisposing);
|
|
if (musicController != null)
|
|
musicController.TrackChanged -= onTrackChanged;
|
|
}
|
|
}
|
|
}
|