osukey/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
Dean Herbert cbc4e5319d Fix DrawableStoryboardAnimation to handle skin fallback frame count similar to stable
Reasoning is explained in the inline comment (basically, stable doesn't
care what the user specifies as the frame count when falling back to
skin resources).

This change also removes on to two layers of drawables, which should be
a win in heavy storyboards.
2022-04-07 17:35:56 +09:00

154 lines
5.0 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.IO;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Storyboards.Drawables
{
public class DrawableStoryboardAnimation : DrawableAnimation, IFlippable, IVectorScalable
{
public StoryboardAnimation Animation { get; }
private bool flipH;
public bool FlipH
{
get => flipH;
set
{
if (flipH == value)
return;
flipH = value;
Invalidate(Invalidation.MiscGeometry);
}
}
private bool flipV;
public bool FlipV
{
get => flipV;
set
{
if (flipV == value)
return;
flipV = value;
Invalidate(Invalidation.MiscGeometry);
}
}
private Vector2 vectorScale = Vector2.One;
public Vector2 VectorScale
{
get => vectorScale;
set
{
if (Math.Abs(value.X) < Precision.FLOAT_EPSILON)
value.X = Precision.FLOAT_EPSILON;
if (Math.Abs(value.Y) < Precision.FLOAT_EPSILON)
value.Y = Precision.FLOAT_EPSILON;
if (vectorScale == value)
return;
if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(VectorScale)} must be finite, but is {value}.");
vectorScale = value;
Invalidate(Invalidation.MiscGeometry);
}
}
public override bool RemoveWhenNotAlive => false;
protected override Vector2 DrawScale
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale;
public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV);
public override bool IsPresent
=> !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent;
public DrawableStoryboardAnimation(StoryboardAnimation animation)
{
Animation = animation;
Origin = animation.Origin;
Position = animation.InitialPosition;
Loop = animation.LoopType == AnimationLoopType.LoopForever;
LifetimeStart = animation.StartTime;
LifetimeEnd = animation.EndTime;
}
protected override Vector2 GetCurrentDisplaySize()
{
Texture texture = (CurrentFrame as Sprite)?.Texture
?? ((CurrentFrame as SkinnableSprite)?.Drawable as Sprite)?.Texture;
return new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0);
}
[Resolved]
private ISkinSource skin { get; set; }
[BackgroundDependencyLoader]
private void load(TextureStore textureStore, Storyboard storyboard)
{
int frameIndex = 0;
Texture frameTexture = storyboard.GetTextureFromPath(getFramePath(frameIndex), textureStore);
if (frameTexture != null)
{
// sourcing from storyboard.
for (frameIndex = 0; frameIndex < Animation.FrameCount; frameIndex++)
{
frameTexture = storyboard.GetTextureFromPath(getFramePath(frameIndex), textureStore);
AddFrame(new Sprite { Texture = frameTexture }, Animation.FrameDelay);
}
}
else if (storyboard.UseSkinSprites)
{
// fallback to skin if required.
skin.SourceChanged += skinSourceChanged;
skinSourceChanged();
}
Animation.ApplyTransforms(this);
}
private void skinSourceChanged()
{
ClearFrames();
// ClearFrames doesn't clear the last displayed frame.
// Clear manually for now, in case the skin doesn't provide any frames.
DisplayFrame(Empty());
// When reading from a skin, we match stables weird behaviour where `FrameCount` is ignored
// and resources are retrieved until the end of the animation.
foreach (var texture in skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path), default, default, true, string.Empty, out _))
AddFrame(new Sprite { Texture = texture }, Animation.FrameDelay);
}
private string getFramePath(int i) => Animation.Path.Replace(".", $"{i}.");
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
skin.SourceChanged -= skinSourceChanged;
}
}
}