1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-14 19:53:08 +08:00

Merge pull request #27967 from peppy/storyboard-video-transforms-and-sizing

Fix storyboard videos not accepting transforms
This commit is contained in:
Bartłomiej Dach 2024-04-23 18:09:09 +02:00 committed by GitHub
commit c454dd2496
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 39 deletions

View File

@ -10,6 +10,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -40,7 +41,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("disallow all lookups", () => AddStep("disallow all lookups", () =>
{ {
storyboard.UseSkinSprites = false; storyboard.UseSkinSprites = false;
storyboard.AlwaysProvideTexture = false; storyboard.ProvideResources = false;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -55,7 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("allow storyboard lookup", () => AddStep("allow storyboard lookup", () =>
{ {
storyboard.UseSkinSprites = false; storyboard.UseSkinSprites = false;
storyboard.AlwaysProvideTexture = true; storyboard.ProvideResources = true;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -67,13 +68,48 @@ namespace osu.Game.Tests.Visual.Gameplay
assertStoryboardSourced(); assertStoryboardSourced();
} }
[TestCase(false)]
[TestCase(true)]
public void TestVideo(bool scaleTransformProvided)
{
AddStep("allow storyboard lookup", () =>
{
storyboard.ProvideResources = true;
});
AddStep("create video", () => SetContents(_ =>
{
var layer = storyboard.GetLayer("Video");
var sprite = new StoryboardVideo("Videos/test-video.mp4", Time.Current);
if (scaleTransformProvided)
{
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
}
layer.Elements.Clear();
layer.Add(sprite);
return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
storyboard.CreateDrawable()
}
};
}));
}
[Test] [Test]
public void TestSkinLookupPreferredOverStoryboard() public void TestSkinLookupPreferredOverStoryboard()
{ {
AddStep("allow all lookups", () => AddStep("allow all lookups", () =>
{ {
storyboard.UseSkinSprites = true; storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true; storyboard.ProvideResources = true;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -91,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("allow skin lookup", () => AddStep("allow skin lookup", () =>
{ {
storyboard.UseSkinSprites = true; storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = false; storyboard.ProvideResources = false;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -109,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("allow all lookups", () => AddStep("allow all lookups", () =>
{ {
storyboard.UseSkinSprites = true; storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true; storyboard.ProvideResources = true;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -127,7 +163,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("allow all lookups", () => AddStep("allow all lookups", () =>
{ {
storyboard.UseSkinSprites = true; storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true; storyboard.ProvideResources = true;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -142,7 +178,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("allow all lookups", () => AddStep("allow all lookups", () =>
{ {
storyboard.UseSkinSprites = true; storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true; storyboard.ProvideResources = true;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -156,7 +192,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("allow all lookups", () => AddStep("allow all lookups", () =>
{ {
storyboard.UseSkinSprites = true; storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true; storyboard.ProvideResources = true;
}); });
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
@ -170,7 +206,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft)); AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft));
} }
private DrawableStoryboard createSprite(string lookupName, Anchor origin, Vector2 initialPosition) private Drawable createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
{ {
var layer = storyboard.GetLayer("Background"); var layer = storyboard.GetLayer("Background");
@ -180,7 +216,14 @@ namespace osu.Game.Tests.Visual.Gameplay
layer.Elements.Clear(); layer.Elements.Clear();
layer.Add(sprite); layer.Add(sprite);
return storyboard.CreateDrawable().With(s => s.RelativeSizeAxes = Axes.Both); return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
storyboard.CreateDrawable()
}
};
} }
private void assertStoryboardSourced() private void assertStoryboardSourced()
@ -202,42 +245,52 @@ namespace osu.Game.Tests.Visual.Gameplay
return new TestDrawableStoryboard(this, mods); return new TestDrawableStoryboard(this, mods);
} }
public bool AlwaysProvideTexture { get; set; } public bool ProvideResources { get; set; }
public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty; public override string GetStoragePathFromStoryboardPath(string path) => ProvideResources ? path : string.Empty;
private partial class TestDrawableStoryboard : DrawableStoryboard private partial class TestDrawableStoryboard : DrawableStoryboard
{ {
private readonly bool alwaysProvideTexture; private readonly bool provideResources;
public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList<Mod>? mods) public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList<Mod>? mods)
: base(storyboard, mods) : base(storyboard, mods)
{ {
alwaysProvideTexture = storyboard.AlwaysProvideTexture; provideResources = storyboard.ProvideResources;
} }
protected override IResourceStore<byte[]> CreateResourceLookupStore() => alwaysProvideTexture protected override IResourceStore<byte[]> CreateResourceLookupStore() => provideResources
? new AlwaysReturnsTextureStore() ? new ResourcesTextureStore()
: new ResourceStore<byte[]>(); : new ResourceStore<byte[]>();
internal class AlwaysReturnsTextureStore : IResourceStore<byte[]> internal class ResourcesTextureStore : IResourceStore<byte[]>
{ {
private const string test_image = "Resources/Textures/test-image.png";
private readonly DllResourceStore store; private readonly DllResourceStore store;
public AlwaysReturnsTextureStore() public ResourcesTextureStore()
{ {
store = TestResources.GetStore(); store = TestResources.GetStore();
} }
public void Dispose() => store.Dispose(); public void Dispose() => store.Dispose();
public byte[] Get(string name) => store.Get(test_image); public byte[] Get(string name) => store.Get(map(name));
public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken); public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(map(name), cancellationToken);
public Stream GetStream(string name) => store.GetStream(test_image); public Stream GetStream(string name) => store.GetStream(map(name));
private string map(string name)
{
switch (name)
{
case lookup_name:
return "Resources/Textures/test-image.png";
default:
return $"Resources/{name}";
}
}
public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources(); public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
} }

View File

@ -114,7 +114,7 @@ namespace osu.Game.Beatmaps.Formats
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant())) if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant()))
break; break;
storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset)); storyboard.GetLayer("Video").Add(storyboardSprite = new StoryboardVideo(path, offset));
break; break;
} }

View File

@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video; using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps;
namespace osu.Game.Storyboards.Drawables namespace osu.Game.Storyboards.Drawables
{ {
@ -23,11 +21,21 @@ namespace osu.Game.Storyboards.Drawables
{ {
Video = video; Video = video;
RelativeSizeAxes = Axes.Both; // In osu-stable, a mapper can add a scale command for a storyboard video.
// This allows scaling based on the video's absolute size.
//
// If not specified we take up the full available space.
bool useRelative = !video.TimelineGroup.Scale.HasCommands;
RelativeSizeAxes = useRelative ? Axes.Both : Axes.None;
AutoSizeAxes = useRelative ? Axes.None : Axes.Both;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore) private void load(TextureStore textureStore)
{ {
var stream = textureStore.GetStream(Video.Path); var stream = textureStore.GetStream(Video.Path);
@ -36,12 +44,14 @@ namespace osu.Game.Storyboards.Drawables
InternalChild = drawableVideo = new Video(stream, false) InternalChild = drawableVideo = new Video(stream, false)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = RelativeSizeAxes,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Alpha = 0, Alpha = 0,
}; };
Video.ApplyTransforms(drawableVideo);
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -3,23 +3,20 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
using osuTK;
namespace osu.Game.Storyboards namespace osu.Game.Storyboards
{ {
public class StoryboardVideo : IStoryboardElement public class StoryboardVideo : StoryboardSprite
{ {
public string Path { get; }
public bool IsDrawable => true;
public double StartTime { get; }
public StoryboardVideo(string path, double offset) public StoryboardVideo(string path, double offset)
: base(path, Anchor.Centre, Vector2.Zero)
{ {
Path = path; // This is just required to get a valid StartTime based on the incoming offset.
StartTime = offset; // Actual fades are handled inside DrawableStoryboardVideo for now.
TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 0);
} }
public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); public override Drawable CreateDrawable() => new DrawableStoryboardVideo(this);
} }
} }