using Friflo.Engine.ECS; using Microsoft.Xna.Framework; using MrGameEng.Core; using MrGameEng.Graphics; using Xunit; namespace MrGameEng.Graphics.Tests; public class SpriteAnimationTests { private static Texture2DRegion Region(int x) => new(null!, new Rectangle(x, 0, 16, 16)); private static SpriteAnimationClip Clip(bool loop, params int[] xs) => new(Array.ConvertAll(xs, Region), framesPerSecond: 10f, loop); [Fact] public void FrameAt_LoopingClip_WrapsAround() { var clip = Clip(loop: true, 0, 1, 2); Assert.Equal(0, clip.FrameAt(0.00f).Bounds.X); Assert.Equal(1, clip.FrameAt(0.10f).Bounds.X); Assert.Equal(2, clip.FrameAt(0.25f).Bounds.X); Assert.Equal(0, clip.FrameAt(0.30f).Bounds.X); // wrapped } [Fact] public void FrameAt_NonLoopingClip_ClampsToLastFrame() { var clip = Clip(loop: false, 0, 1); Assert.Equal(1, clip.FrameAt(10f).Bounds.X); } private sealed class AnimScene : Scene { public Entity Animated; public SpriteAnimationClip Clip = SpriteAnimationTests.Clip(loop: false, 0, 1, 2); protected override void OnLoad() { this.UseSpriteAnimation(); Animated = Store.CreateEntity( new Sprite { Color = Color.White }, new SpriteAnimator(Clip)); } } [Fact] public void AnimationSystem_AdvancesFrames_AndStopsAtEnd() { var context = new EngineContext(); var scene = new AnimScene(); context.Scenes.Switch(scene); context.Clock.Advance(0.15f); // 10 fps → frame 1 context.Scenes.Update(context.Clock); Assert.Equal(1, scene.Animated.GetComponent().Region!.Bounds.X); context.Clock.Advance(1.0f); // far past the end of a non-looping clip context.Scenes.Update(context.Clock); Assert.Equal(2, scene.Animated.GetComponent().Region!.Bounds.X); Assert.False(scene.Animated.GetComponent().Playing); } }