Fix looping animations incorrectly completing when an update contains a large elapsed time.

Ping pong waits until frame delay is complete on re-entry of the first frame before firing Complete rather than firing Complete immediately upon entering the frame.  This makes it consistent with a non ping pong animation which waits for the frame delay of the last frame before it fires Complete.
Ping Pong now finishes on the first frame rather than potentially finishing on a different frame if the elapsed time since last update was larger than the time remaining for the animation to become complete.
Add unit tests for SpriteSheetAnimation.
This commit is contained in:
David Fidge
2023-03-31 14:08:14 +10:30
parent 54faadbe9a
commit 8e2be47e4c
2 changed files with 922 additions and 7 deletions
@@ -41,7 +41,7 @@ namespace MonoGame.Extended.Sprites
public new bool IsComplete => CurrentTime >= AnimationDuration;
public float AnimationDuration => IsPingPong
? (KeyFrames.Length*2 - 2)*FrameDuration
? (KeyFrames.Length*2 - (IsLooping ? 2 : 1))*FrameDuration
: KeyFrames.Length*FrameDuration;
public TextureRegion2D CurrentFrame => KeyFrames[CurrentFrameIndex];
@@ -59,10 +59,10 @@ namespace MonoGame.Extended.Sprites
{
if (IsComplete)
{
OnCompleted?.Invoke();
if (IsLooping)
CurrentTime -= AnimationDuration;
CurrentTime %= AnimationDuration;
else
OnCompleted?.Invoke();
}
if (KeyFrames.Length == 1)
@@ -76,10 +76,15 @@ namespace MonoGame.Extended.Sprites
if (IsPingPong)
{
frameIndex = frameIndex%(length*2 - 2);
if (IsComplete)
frameIndex = 0;
else
{
frameIndex = frameIndex % (length * 2 - 2);
if (frameIndex >= length)
frameIndex = length - 2 - (frameIndex - length);
if (frameIndex >= length)
frameIndex = length - 2 - (frameIndex - length);
}
}
if (IsLooping)
@@ -0,0 +1,910 @@
using System;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Sprites;
using MonoGame.Extended.TextureAtlases;
using Xunit;
namespace MonoGame.Extended.Tests.Sprites
{
public class SpriteSheetAnimationTests
{
[Theory]
[InlineData(0, 0)]
[InlineData(0, 0.9f)]
[InlineData(1, 1f)]
[InlineData(1, 1.9f)]
[InlineData(0, 2f)]
[InlineData(0, 2.9f)]
[InlineData(1, 3f)]
[InlineData(0, 4f)]
[InlineData(1, 5f)]
public void Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Fact]
public void Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(0, 0.9f)]
[InlineData(1, 1f)]
[InlineData(1, 1.1f)]
[InlineData(1, 1.9f)]
public void Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(1, 2f)]
[InlineData(1, 3f)]
[InlineData(1, 4f)]
public void Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
}
[Fact]
public void Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
isCompleteFired = false; // Reset isCompleteFired for next execution
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired); // Event is not fired again as animation was already completed
}
[Theory]
[InlineData(1, 0)]
[InlineData(1, 0.9f)]
[InlineData(0, 1f)]
[InlineData(0, 1.9f)]
[InlineData(1, 2f)]
[InlineData(1, 2.9f)]
[InlineData(0, 3f)]
[InlineData(1, 4f)]
[InlineData(0, 5f)]
public void Reversed_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Fact]
public void Reversed_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(1, 0.9f)]
[InlineData(0, 1f)]
[InlineData(0, 1.1f)]
[InlineData(0, 1.9f)]
public void Reversed_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(0, 2f)]
[InlineData(0, 3f)]
[InlineData(0, 4f)]
public void Reversed_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
}
[Fact]
public void Reversed_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
isCompleteFired = false; // Reset isCompleteFired for next execution
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired); // Event is not fired again as animation was already completed;
}
[Theory]
[InlineData(0, 0)]
[InlineData(0, 0.9f)]
[InlineData(1, 1f)]
[InlineData(1, 1.9f)]
[InlineData(0, 2f)]
[InlineData(0, 2.9f)]
[InlineData(1, 3f)]
[InlineData(0, 4f)]
[InlineData(1, 5f)]
[InlineData(0, 6f)]
[InlineData(1, 7f)]
[InlineData(0, 8f)]
public void PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Fact]
public void PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(0, 0.9f)]
[InlineData(1, 1f)]
[InlineData(1, 1.9f)]
[InlineData(0, 2f)]
[InlineData(0, 2.9f)]
public void PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(0, 3f)]
[InlineData(0, 4f)]
[InlineData(0, 5f)]
public void PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
}
[Fact]
public void PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegion2D3 = new TextureRegion2D("Region 3", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2, textureRegion2D3 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1, 2 },
1,
false,
false,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
isCompleteFired = false; // Reset isCompleteFired for next execution
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired); // Event is not fired again as animation was already completed
}
[Theory]
[InlineData(1, 0)]
[InlineData(1, 0.9f)]
[InlineData(0, 1f)]
[InlineData(0, 1.9f)]
[InlineData(1, 2f)]
[InlineData(1, 2.9f)]
[InlineData(0, 3f)]
[InlineData(1, 4f)]
[InlineData(0, 5f)]
[InlineData(1, 6f)]
[InlineData(0, 7f)]
[InlineData(1, 8f)]
public void Reversed_PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true,
true,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Fact]
public void Reversed_PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
true,
true,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(1, 0.9f)]
[InlineData(0, 1f)]
[InlineData(0, 1.9f)]
[InlineData(1, 2f)]
[InlineData(1, 2.9f)]
public void Reversed_PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false,
true,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
}
[Theory]
[InlineData(1, 3f)]
[InlineData(1, 4f)]
[InlineData(1, 5f)]
public void Reversed_PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1 },
1,
false,
true,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
}
[Fact]
public void Reversed_PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
{
var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
var textureRegion2D3 = new TextureRegion2D("Region 3", null, new Rectangle());
var textureRegions = new[] { textureRegion2D1, textureRegion2D2, textureRegion2D3 };
var spriteSheetAnimationData = new SpriteSheetAnimationData(
new[] { 0, 1, 2 },
1,
false,
true,
true
);
var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
var isCompleteFired = false;
spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
spriteSheetAnimation.Play();
var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
Assert.False(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired);
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.True(isCompleteFired);
isCompleteFired = false; // Reset isCompleteFired for next execution
gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
spriteSheetAnimation.Update(gameTime);
Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
Assert.True(spriteSheetAnimation.IsComplete);
Assert.False(isCompleteFired); // Event is not fired again as animation was already completed
}
}
}