Update animated sprite so the constructor api is similar to original MGE (#906)

* Update animated sprite so the constructor api is similar to original MGE

* Fix tests from changes
This commit is contained in:
Christopher Whitley
2024-07-01 12:33:23 -04:00
committed by GitHub
parent 98ee05f58a
commit 2ad7a9d3db
12 changed files with 315 additions and 273 deletions
@@ -8,10 +8,10 @@ namespace MonoGame.Extended.Animations
public AnimationComponent(Game game)
: base(game)
{
Animations = new List<Animation>();
Animations = new List<AnimationController>();
}
public List<Animation> Animations { get; }
public List<AnimationController> Animations { get; }
public override void Update(GameTime gameTime)
{
@@ -8,12 +8,12 @@ using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Animations;
/// <summary>
/// Represents an animation with various control features such as play, pause, stop, looping, reversing, and
/// ping-pong effects.
/// Represents an animation controller with features to play, pause, stop, reset, and set the state of
/// animation playback such as looping, reversing, and ping-pong effects.
/// </summary>
public class Animation : IAnimation
public class AnimationController : IAnimationController
{
private readonly IAnimationDefinition _definition;
private readonly IAnimation _definition;
private int _direction;
/// <summary>
@@ -44,7 +44,7 @@ public class Animation : IAnimation
public double Speed { get; set; }
/// <inheritdoc />
public event Action<IAnimation, AnimationEventTrigger> OnAnimationEvent;
public event Action<IAnimationController, AnimationEventTrigger> OnAnimationEvent;
/// <inheritdoc />
public TimeSpan CurrentFrameTimeRemaining { get; private set; }
@@ -56,10 +56,10 @@ public class Animation : IAnimation
public int FrameCount => _definition.FrameCount;
/// <summary>
/// Initializes a new instance of the <see cref="Animation"/> class with the specified definition.
/// Initializes a new instance of the <see cref="AnimationController"/> class with the specified definition.
/// </summary>
/// <param name="definition">The definition of the animation.</param>
public Animation(IAnimationDefinition definition)
public AnimationController(IAnimation definition)
{
_definition = definition;
@@ -101,7 +101,7 @@ public class Animation : IAnimation
{
if (startingFrame < 0 || startingFrame >= _definition.FrameCount)
{
throw new ArgumentOutOfRangeException(nameof(startingFrame), $"{nameof(startingFrame)} cannot be less than zero or greater than or equal to the total number of frames in this {nameof(Animation)}");
throw new ArgumentOutOfRangeException(nameof(startingFrame), $"{nameof(startingFrame)} cannot be less than zero or greater than or equal to the total number of frames in this {nameof(AnimationController)}");
}
// Cannot play something that is already playing
@@ -134,7 +134,7 @@ public class Animation : IAnimation
{
if (index < 0 || index >= _definition.FrameCount)
{
throw new ArgumentOutOfRangeException(nameof(index), $"{nameof(index)} cannot be less than zero or greater than or equal to the total number of frames in this {nameof(Animation)}");
throw new ArgumentOutOfRangeException(nameof(index), $"{nameof(index)} cannot be less than zero or greater than or equal to the total number of frames in this {nameof(AnimationController)}");
}
CurrentFrame = index;
@@ -12,14 +12,14 @@ namespace MonoGame.Extended.Animations;
public class AnimationEvent : EventArgs
{
/// <summary>
/// Gets the animation associated with the event.
/// Gets the animation controller associated with the event.
/// </summary>
public IAnimation Animation { get; }
public IAnimationController Animation { get; }
/// <summary>
/// Gets the trigger that caused the event.
/// </summary>
public AnimationEventTrigger Trigger { get; }
internal AnimationEvent(IAnimation animation, AnimationEventTrigger trigger) => (Animation, Trigger) = (animation, trigger);
internal AnimationEvent(IAnimationController animation, AnimationEventTrigger trigger) => (Animation, Trigger) = (animation, trigger);
}
+14 -118
View File
@@ -3,60 +3,25 @@
// See LICENSE file in the project root for full license information.
using System;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Graphics;
namespace MonoGame.Extended.Animations;
/// <summary>
/// Defines the interface for an animation with various control features such as play, pause, stop, looping, reversing, and ping-pong effects.
/// Defines the interface for an animation, specifying properties of the animation such as frames, looping, reversing,
/// and ping-pong effects.
/// </summary>
public interface IAnimation : IDisposable
public interface IAnimation
{
/// <summary>
/// Gets a value indicating whether the animation is paused.
/// Gets the name of the animation.
/// </summary>
bool IsPaused { get; }
string Name { get; }
/// <summary>
/// Gets a value indicating whether the animation is currently animating.
/// Gets the read-only collection of frames in the animation.
/// </summary>
bool IsAnimating { get; }
/// <summary>
/// Gets or sets a value indicating whether the animation should loop.
/// </summary>
bool IsLooping { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the animation is reversed.
/// </summary>
bool IsReversed { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the animation should ping-pong (reverse direction at the ends).
/// </summary>
bool IsPingPong { get; set; }
/// <summary>
/// Gets or sets the speed of the animation.
/// </summary>
/// <value>The speed cannot be less than zero.</value>
double Speed { get; set; }
/// <summary>
/// Gets or sets the action to perform when an animation event is triggered.
/// </summary>
event Action<IAnimation, AnimationEventTrigger> OnAnimationEvent;
/// <summary>
/// Gets the time remaining for the current frame.
/// </summary>
TimeSpan CurrentFrameTimeRemaining { get; }
/// <summary>
/// Gets the index of the current frame of the animation.
/// </summary>
int CurrentFrame { get; }
ReadOnlySpan<IAnimationFrame> Frames { get; }
/// <summary>
/// Gets the total number of frames in the animation.
@@ -64,86 +29,17 @@ public interface IAnimation : IDisposable
int FrameCount { get; }
/// <summary>
/// Sets the animation to a specified frame.
/// Gets a value indicating whether the animation should loop.
/// </summary>
/// <param name="index">The index of the frame to set.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the <paramref name="index"/> parameter is less than zero or greater than or equal to the total
/// number of frames.
/// </exception>
void SetFrame(int index);
bool IsLooping { get; }
/// <summary>
/// Plays the animation from the beginning.
/// Gets a value indicating whether the animation is reversed.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully started; otherwise, <see langword="false"/>.
/// </returns>
bool Play();
bool IsReversed { get; }
/// <summary>
/// Plays the animation from a specified starting frame.
/// Gets a value indicating whether the animation should ping-pong (reverse direction at the ends).
/// </summary>
/// <param name="startingFrame">The frame to start the animation from.</param>
/// <returns>
/// <see langword="true"/> if the animation was successfully started; otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the <paramref name="startingFrame"/> parameter is less than zero or greater than or equal to the
/// total number of frames.
/// </exception>
bool Play(int startingFrame);
/// <summary>
/// Pauses the animation.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully paused; otherwise, <see langword="false"/>.
/// </returns>
bool Pause();
/// <summary>
/// Pauses the animation.
/// </summary>
/// <param name="resetFrameDuration">If set to <see langword="true"/>, resets the frame duration.</param>
/// <returns>
/// <see langword="true"/> if the animation was successfully paused; otherwise, <see langword="false"/>.
/// </returns>
bool Pause(bool resetFrameDuration);
/// <summary>
/// Unpauses the animation.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully unpaused; otherwise, <see langword="false"/>.
/// </returns>
bool Unpause();
/// <summary>
/// Unpauses the animation.
/// </summary>
/// <param name="advanceToNextFrame">If set to <see langword="true"/>, advances to the next frame.</param>
/// <returns>
/// <see langword="true"/> if the animation was successfully unpaused; otherwise, <see langword="false"/>.
/// </returns>
bool Unpause(bool advanceToNextFrame);
/// <summary>
/// Updates the animation.
/// </summary>
/// <param name="gameTime">A snapshot of the timing values for the current update cycle.</param>
void Update(GameTime gameTime);
/// <summary>
/// Stops the animation.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully stopped; otherwise, <see langword="false"/>.
/// </returns>
bool Stop();
/// <summary>
/// Resets the animation to its initial state.
/// </summary>
void Reset();
bool IsPingPong { get; }
}
@@ -0,0 +1,155 @@
// Copyright (c) Craftwork Games. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Animations;
/// <summary>
/// Defines the interface for an animation controller with features to play, pause, stop, reset, and set the state of
/// animation playback such as looping, reversing, and ping-pong effects.
/// </summary>
public interface IAnimationController : IDisposable
{
/// <summary>
/// Gets a value indicating whether this animation controller has been disposed of.
/// </summary>
bool IsDisposed { get; }
/// <summary>
/// Gets a value indicating whether the animation is paused.
/// </summary>
bool IsPaused { get; }
/// <summary>
/// Gets a value indicating whether the animation is currently animating.
/// </summary>
bool IsAnimating { get; }
/// <summary>
/// Gets or sets a value indicating whether the animation should loop.
/// </summary>
bool IsLooping { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the animation is reversed.
/// </summary>
bool IsReversed { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the animation should ping-pong (reverse direction at the ends).
/// </summary>
bool IsPingPong { get; set; }
/// <summary>
/// Gets or sets the speed of the animation.
/// </summary>
/// <value>The speed cannot be less than zero.</value>
double Speed { get; set; }
/// <summary>
/// Gets or sets the action to perform when an animation event is triggered.
/// </summary>
event Action<IAnimationController, AnimationEventTrigger> OnAnimationEvent;
/// <summary>
/// Gets the time remaining for the current frame.
/// </summary>
TimeSpan CurrentFrameTimeRemaining { get; }
/// <summary>
/// Gets the index of the current frame of the animation.
/// </summary>
int CurrentFrame { get; }
/// <summary>
/// Gets the total number of frames in the animation.
/// </summary>
int FrameCount { get; }
/// <summary>
/// Sets the animation to a specified frame.
/// </summary>
/// <param name="index">The index of the frame to set.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the <paramref name="index"/> parameter is less than zero or greater than or equal to the total
/// number of frames.
/// </exception>
void SetFrame(int index);
/// <summary>
/// Plays the animation from the beginning.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully started; otherwise, <see langword="false"/>.
/// </returns>
bool Play();
/// <summary>
/// Plays the animation from a specified starting frame.
/// </summary>
/// <param name="startingFrame">The frame to start the animation from.</param>
/// <returns>
/// <see langword="true"/> if the animation was successfully started; otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the <paramref name="startingFrame"/> parameter is less than zero or greater than or equal to the
/// total number of frames.
/// </exception>
bool Play(int startingFrame);
/// <summary>
/// Pauses the animation.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully paused; otherwise, <see langword="false"/>.
/// </returns>
bool Pause();
/// <summary>
/// Pauses the animation.
/// </summary>
/// <param name="resetFrameDuration">If set to <see langword="true"/>, resets the frame duration.</param>
/// <returns>
/// <see langword="true"/> if the animation was successfully paused; otherwise, <see langword="false"/>.
/// </returns>
bool Pause(bool resetFrameDuration);
/// <summary>
/// Unpauses the animation.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully unpaused; otherwise, <see langword="false"/>.
/// </returns>
bool Unpause();
/// <summary>
/// Unpauses the animation.
/// </summary>
/// <param name="advanceToNextFrame">If set to <see langword="true"/>, advances to the next frame.</param>
/// <returns>
/// <see langword="true"/> if the animation was successfully unpaused; otherwise, <see langword="false"/>.
/// </returns>
bool Unpause(bool advanceToNextFrame);
/// <summary>
/// Updates the animation.
/// </summary>
/// <param name="gameTime">A snapshot of the timing values for the current update cycle.</param>
void Update(GameTime gameTime);
/// <summary>
/// Stops the animation.
/// </summary>
/// <returns>
/// <see langword="true"/> if the animation was successfully stopped; otherwise, <see langword="false"/>.
/// </returns>
bool Stop();
/// <summary>
/// Resets the animation to its initial state.
/// </summary>
void Reset();
}
@@ -1,44 +0,0 @@
// Copyright (c) Craftwork Games. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System;
namespace MonoGame.Extended.Animations;
/// <summary>
/// Defines the interface for an animation definition, specifying properties of the animation such as frames, looping,
/// reversing, and ping-pong effects.
/// </summary>
public interface IAnimationDefinition
{
/// <summary>
/// Gets the name of the animation.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the read-only collection of frames in the animation.
/// </summary>
ReadOnlySpan<IAnimationFrame> Frames { get; }
/// <summary>
/// Gets the total number of frames in the animation.
/// </summary>
int FrameCount { get; }
/// <summary>
/// Gets a value indicating whether the animation should loop.
/// </summary>
bool IsLooping { get; }
/// <summary>
/// Gets a value indicating whether the animation is reversed.
/// </summary>
bool IsReversed { get; }
/// <summary>
/// Gets a value indicating whether the animation should ping-pong (reverse direction at the ends).
/// </summary>
bool IsPingPong { get; }
}
@@ -2,6 +2,8 @@
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Animations;
@@ -12,30 +14,66 @@ namespace MonoGame.Extended.Graphics;
/// </summary>
public class AnimatedSprite : Sprite
{
private readonly SpriteSheet _spriteSheet;
private IAnimation _animation;
private readonly Texture2DRegion[] _regions;
/// <summary>
/// Gets the animation used by this animated sprite.
/// Gets the animation controller used to control the current animation of this animated sprite.
/// </summary>
public IAnimation Animation { get; }
public IAnimationController Controller { get; private set; }
internal AnimatedSprite(SpriteSheetAnimationDefinition definition, Texture2DRegion[] regions)
: base(regions[0])
/// <summary>
/// Initializes a new instance of the <see cref="AnimatedSprite"/> class with the specified
/// <see cref="SpriteSheet"/>.
/// </summary>
/// <param name="spriteSheet">The <see cref="SpriteSheet"/> that contains the animations.</param>
public AnimatedSprite(SpriteSheet spriteSheet)
: base(spriteSheet.TextureAtlas[0])
{
Animation = new Animation(definition);
_regions = regions;
ArgumentNullException.ThrowIfNull(spriteSheet);
_spriteSheet = spriteSheet;
}
/// <summary>
/// Initializes a new instance of the <see cref="AnimatedSprite"/> class with the specified
/// <see cref="SpriteSheet"/>.
/// </summary>
/// <param name="spriteSheet">The <see cref="SpriteSheet"/> that contains the animations.</param>
/// <param name="initialAnimation">The initial animation to play</param>
public AnimatedSprite(SpriteSheet spriteSheet, string initialAnimation) :this(spriteSheet)
{
SpriteSheetAnimation definition = spriteSheet.GetAnimation(initialAnimation);
Controller = new AnimationController(definition);
}
/// <summary>
/// Sets the animation to use for this animated sprite.
/// </summary>
/// <param name="name">The name of the animation.</param>
/// <returns>The <see cref="IAnimationController"/> of the animation.</returns>
/// <exception cref="KeyNotFoundException">
/// Thrown if the source spritesheet does not contain an animation a name that matches the <paramref name="name"/> parameter.
/// </exception>
public IAnimationController SetAnimation(string name)
{
_animation = _spriteSheet.GetAnimation(name);
Controller = new AnimationController(_animation);
return Controller;
}
/// <inheritdoc />
public void Update(GameTime gameTime)
{
int index = Animation.CurrentFrame;
Animation.Update(gameTime);
int index = Controller.CurrentFrame;
Controller.Update(gameTime);
// If the current frame changed during the update, change the texture region
if (index != Animation.CurrentFrame)
if (index != Controller.CurrentFrame)
{
TextureRegion = _regions[Animation.CurrentFrame];
int regionIndex = _animation.Frames[Controller.CurrentFrame].FrameIndex;
TextureRegion = _spriteSheet.TextureAtlas[regionIndex];
}
}
}
@@ -12,7 +12,7 @@ namespace MonoGame.Extended.Graphics;
/// </summary>
public class SpriteSheet
{
private readonly Dictionary<string, SpriteSheetAnimationDefinition> _animations = new Dictionary<string, SpriteSheetAnimationDefinition>();
private readonly Dictionary<string, SpriteSheetAnimation> _animations = new Dictionary<string, SpriteSheetAnimation>();
/// <summary>
/// Gets the number of animations defined in the sprite sheet.
@@ -57,31 +57,22 @@ public class SpriteSheet
/// <returns>A new <see cref="Sprite"/> instance.</returns>
public Sprite CreateSprite(string regionName) => TextureAtlas.CreateSprite(regionName);
/// <summary>
/// Creates an animated sprite from the specified animation name.
/// </summary>
/// <param name="animationName">The name of the animation.</param>
/// <returns>A new <see cref="AnimatedSprite"/> instance.</returns>
public AnimatedSprite CreateAnimatedSprite(string animationName)
{
SpriteSheetAnimationDefinition animationDefinition = _animations[animationName];
Texture2DRegion[] regions = TextureAtlas.GetRegions(animationDefinition.Frames);
return new AnimatedSprite(animationDefinition, regions);
}
/// <summary>
/// Defines a new animation for the sprite sheet.
/// </summary>
/// <param name="name">The name of the animation.</param>
/// <param name="buildAction">The action to build the animation definition.</param>
public void DefineAnimation(string name, Action<SpriteSheetAnimationDefinitionBuilder> buildAction)
public void DefineAnimation(string name, Action<SpriteSheetAnimationBuilder> buildAction)
{
SpriteSheetAnimationDefinitionBuilder builder = new SpriteSheetAnimationDefinitionBuilder(name, this);
SpriteSheetAnimationBuilder builder = new SpriteSheetAnimationBuilder(name, this);
buildAction(builder);
SpriteSheetAnimationDefinition definition = builder.Build();
SpriteSheetAnimation definition = builder.Build();
_animations.Add(name, definition);
}
public SpriteSheetAnimation GetAnimation(string name) => _animations[name];
/// <summary>
/// Removes the animation definition with the specified name.
/// </summary>
@@ -7,7 +7,7 @@ using MonoGame.Extended.Animations;
namespace MonoGame.Extended.Graphics;
internal class SpriteSheetAnimationDefinition : IAnimationDefinition
public class SpriteSheetAnimation : IAnimation
{
private readonly SpriteSheetAnimationFrame[] _frames;
@@ -18,7 +18,7 @@ internal class SpriteSheetAnimationDefinition : IAnimationDefinition
public bool IsReversed { get; }
public bool IsPingPong { get; }
internal SpriteSheetAnimationDefinition(string name, SpriteSheetAnimationFrame[] frames, bool isLooping, bool isReversed, bool isPingPong)
internal SpriteSheetAnimation(string name, SpriteSheetAnimationFrame[] frames, bool isLooping, bool isReversed, bool isPingPong)
{
Name = name;
IsLooping = isLooping;
@@ -8,9 +8,9 @@ using System.Collections.Generic;
namespace MonoGame.Extended.Graphics;
/// <summary>
/// A builder class for creating <see cref="SpriteSheetAnimationDefinition"/> instances.
/// A builder class for creating <see cref="SpriteSheetAnimation"/> instances.
/// </summary>
public sealed class SpriteSheetAnimationDefinitionBuilder
public sealed class SpriteSheetAnimationBuilder
{
private readonly string _name;
private readonly SpriteSheet _spriteSheet;
@@ -19,7 +19,7 @@ public sealed class SpriteSheetAnimationDefinitionBuilder
private bool _isReversed;
private bool _isPingPong;
internal SpriteSheetAnimationDefinitionBuilder(string name, SpriteSheet spriteSheet)
internal SpriteSheetAnimationBuilder(string name, SpriteSheet spriteSheet)
{
_name = name;
_spriteSheet = spriteSheet;
@@ -30,8 +30,8 @@ public sealed class SpriteSheetAnimationDefinitionBuilder
/// </summary>
/// <param name="regionIndex">The index of the region in the sprite sheet.</param>
/// <param name="duration">The duration of the frame.</param>
/// <returns>The <see cref="SpriteSheetAnimationDefinitionBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationDefinitionBuilder AddFrame(int regionIndex, TimeSpan duration)
/// <returns>The <see cref="SpriteSheetAnimationBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationBuilder AddFrame(int regionIndex, TimeSpan duration)
{
SpriteSheetAnimationFrame frame = new SpriteSheetAnimationFrame(regionIndex, duration);
_frames.Add(frame);
@@ -43,8 +43,8 @@ public sealed class SpriteSheetAnimationDefinitionBuilder
/// </summary>
/// <param name="regionName">The name of the region in the sprite sheet.</param>
/// <param name="duration">The duration of the frame.</param>
/// <returns>The <see cref="SpriteSheetAnimationDefinitionBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationDefinitionBuilder AddFrame(string regionName, TimeSpan duration)
/// <returns>The <see cref="SpriteSheetAnimationBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationBuilder AddFrame(string regionName, TimeSpan duration)
{
int index = _spriteSheet.TextureAtlas.GetIndexOfRegion(regionName);
return AddFrame(index, duration);
@@ -54,8 +54,8 @@ public sealed class SpriteSheetAnimationDefinitionBuilder
/// Sets whether the animation should loop.
/// </summary>
/// <param name="isLooping">If set to <c>true</c>, the animation will loop.</param>
/// <returns>The <see cref="SpriteSheetAnimationDefinitionBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationDefinitionBuilder IsLooping(bool isLooping)
/// <returns>The <see cref="SpriteSheetAnimationBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationBuilder IsLooping(bool isLooping)
{
_isLooping = isLooping;
return this;
@@ -65,8 +65,8 @@ public sealed class SpriteSheetAnimationDefinitionBuilder
/// Sets whether the animation should be reversed.
/// </summary>
/// <param name="isReversed">If set to <c>true</c>, the animation will play in reverse.</param>
/// <returns>The <see cref="SpriteSheetAnimationDefinitionBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationDefinitionBuilder IsReversed(bool isReversed)
/// <returns>The <see cref="SpriteSheetAnimationBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationBuilder IsReversed(bool isReversed)
{
_isReversed = isReversed;
return this;
@@ -76,13 +76,13 @@ public sealed class SpriteSheetAnimationDefinitionBuilder
/// Sets whether the animation should ping-pong (reverse direction at the ends).
/// </summary>
/// <param name="isPingPong">If set to <c>true</c>, the animation will ping-pong.</param>
/// <returns>The <see cref="SpriteSheetAnimationDefinitionBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationDefinitionBuilder IsPingPong(bool isPingPong)
/// <returns>The <see cref="SpriteSheetAnimationBuilder"/> instance for chaining.</returns>
public SpriteSheetAnimationBuilder IsPingPong(bool isPingPong)
{
_isPingPong = isPingPong;
return this;
}
internal SpriteSheetAnimationDefinition Build() =>
new SpriteSheetAnimationDefinition(_name, _frames.ToArray(), _isLooping, _isReversed, _isPingPong);
internal SpriteSheetAnimation Build() =>
new SpriteSheetAnimation(_name, _frames.ToArray(), _isLooping, _isReversed, _isPingPong);
}
+5
View File
@@ -169,6 +169,11 @@ namespace MonoGame.Extended
return Subtract(first, second);
}
public static Vector2 operator -(Vector2 first, SizeF second)
{
return Subtract(first, second);
}
public static SizeF operator /(SizeF size, float value)
{
return new SizeF(size.Width / value, size.Height / value);
@@ -18,7 +18,7 @@ public class AnimationTests
public TimeSpan Duration { get; set; }
}
private class TestAnimationDefinition : IAnimationDefinition
private class TestAnimation : IAnimation
{
private readonly IAnimationFrame[] _frames;
public string Name { get; set; }
@@ -28,11 +28,11 @@ public class AnimationTests
public bool IsReversed { get; set; }
public bool IsPingPong { get; set; }
public TestAnimationDefinition(IAnimationFrame[] frames) => _frames = frames;
public TestAnimation(IAnimationFrame[] frames) => _frames = frames;
}
private readonly IAnimationDefinition _definition;
private readonly Animation _animation;
private readonly IAnimation _animation;
private readonly IAnimationController _animationController;
public AnimationTests()
{
@@ -41,94 +41,95 @@ public class AnimationTests
new TestAnimationFrame { FrameIndex = 0, Duration = TimeSpan.FromSeconds(1) },
new TestAnimationFrame { FrameIndex = 1, Duration = TimeSpan.FromSeconds(1) }
};
_definition = new TestAnimationDefinition(frames)
_animation = new TestAnimation(frames)
{
FrameCount = frames.Length,
IsLooping = false,
IsReversed = false,
IsPingPong = false
};
_animation = new Animation(_definition);
_animationController = new AnimationController(_animation);
}
[Fact]
public void Play_ShouldStartAnimation()
{
var result = _animation.Play();
var result = _animationController.Play();
Assert.True(result);
Assert.True(_animation.IsAnimating);
Assert.Equal(0, _animation.CurrentFrame);
Assert.True(_animationController.IsAnimating);
Assert.Equal(0, _animationController.CurrentFrame);
}
[Fact]
public void Play_ShouldThrowException_ForInvalidFrame()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _animation.Play(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => _animation.Play(10));
Assert.Throws<ArgumentOutOfRangeException>(() => _animationController.Play(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => _animationController.Play(10));
}
[Fact]
public void Pause_ShouldPauseAnimation()
{
_animation.Play();
var result = _animation.Pause();
_animationController.Play();
var result = _animationController.Pause();
Assert.True(result);
Assert.True(_animation.IsPaused);
Assert.True(_animationController.IsPaused);
}
[Fact]
public void Pause_ShouldResetFrameDuration_WhenSpecified()
{
_animation.Play();
_animation.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0.5)));
_animationController.Play();
_animationController.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0.5)));
var result = _animation.Pause(true);
var result = _animationController.Pause(true);
Assert.True(result);
Assert.Equal(_definition.Frames[0].Duration, _animation.CurrentFrameTimeRemaining);
Assert.Equal(_animation.Frames[0].Duration, _animationController.CurrentFrameTimeRemaining);
}
[Fact]
public void UnPause_ShouldResumeAnimation()
{
_animation.Play();
_animation.Pause();
var result = _animation.Unpause();
_animationController.Play();
_animationController.Pause();
var result = _animationController.Unpause();
Assert.True(result);
Assert.False(_animation.IsPaused);
Assert.False(_animationController.IsPaused);
}
[Fact]
public void UnPause_ShouldAdvanceFrame_WhenSpecified()
{
_animation.Play();
_animation.Pause();
var result = _animation.Unpause(true);
_animationController.Play();
_animationController.Pause();
var result = _animationController.Unpause(true);
Assert.True(result);
Assert.Equal(1, _animation.CurrentFrame);
Assert.Equal(1, _animationController.CurrentFrame);
}
[Fact]
public void Stop_ShouldStopAnimation()
{
_animation.Play();
var result = _animation.Stop();
_animationController.Play();
var result = _animationController.Stop();
Assert.True(result);
Assert.False(_animation.IsAnimating);
Assert.False(_animationController.IsAnimating);
}
[Fact]
public void Update_ShouldAdvanceFrame()
{
_animation.Play();
_animation.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1.1)));
_animationController.Play();
_animationController.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1.1)));
Assert.Equal(1, _animation.CurrentFrame);
Assert.Equal(1, _animationController.CurrentFrame);
}
[Fact]
@@ -139,7 +140,7 @@ public class AnimationTests
bool animationLoopTriggered = false;
bool animationCompletedTriggered = false;
_animation.OnAnimationEvent += (anim, trigger) =>
_animationController.OnAnimationEvent += (anim, trigger) =>
{
switch (trigger)
{
@@ -158,63 +159,63 @@ public class AnimationTests
}
};
_animation.Play();
_animation.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1.1)));
_animationController.Play();
_animationController.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1.1)));
Assert.True(frameBeginTriggered);
Assert.True(frameEndTriggered);
_animation.IsLooping = true;
_animation.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1.1)));
_animationController.IsLooping = true;
_animationController.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1.1)));
Assert.True(animationLoopTriggered);
_animation.IsLooping = false;
_animation.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(2)));
_animationController.IsLooping = false;
_animationController.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(2)));
Assert.True(animationCompletedTriggered);
}
[Fact]
public void Update_ShouldLoopAnimation()
{
_animation.IsLooping = true;
_animation.Play();
_animation.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(2.1)));
_animationController.IsLooping = true;
_animationController.Play();
_animationController.Update(new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(2.1)));
Assert.Equal(0, _animation.CurrentFrame);
Assert.Equal(0, _animationController.CurrentFrame);
}
[Fact]
public void Reset_ShouldResetAnimation()
{
_animation.Play();
_animation.Reset();
_animationController.Play();
_animationController.Reset();
Assert.False(_animation.IsAnimating);
Assert.True(_animation.IsPaused);
Assert.Equal(0, _animation.CurrentFrame);
Assert.False(_animationController.IsAnimating);
Assert.True(_animationController.IsPaused);
Assert.Equal(0, _animationController.CurrentFrame);
}
[Fact]
public void SetFrame_ShouldChangeCurrentFrame()
{
_animation.SetFrame(1);
_animationController.SetFrame(1);
Assert.Equal(1, _animation.CurrentFrame);
Assert.Equal(_definition.Frames[1].Duration, _animation.CurrentFrameTimeRemaining);
Assert.Equal(1, _animationController.CurrentFrame);
Assert.Equal(_animation.Frames[1].Duration, _animationController.CurrentFrameTimeRemaining);
}
[Fact]
public void SetFrame_ShouldThrowException_ForInvalidFrame()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _animation.SetFrame(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => _animation.SetFrame(10));
Assert.Throws<ArgumentOutOfRangeException>(() => _animationController.SetFrame(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => _animationController.SetFrame(10));
}
[Fact]
public void Dispose_ShouldDisposeAnimation()
{
_animation.Dispose();
_animationController.Dispose();
Assert.True(_animation.IsDisposed);
Assert.True(_animationController.IsDisposed);
}
}