using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Misc; using MLEM.Textures; namespace MLEM.Animations { /// /// A sprite animation that allows for any number of frames that each last any number of seconds /// public class SpriteAnimation : GenericDataHolder { private readonly AnimationFrame[] frames; /// /// Returns the at the given index. /// Index ordering is based on the order that animation frames were added in. /// /// The index in the list of animation frames public AnimationFrame this[int index] => this.frames[index]; /// /// The frame that the animation is currently on. /// public AnimationFrame CurrentFrame { get { // we might have overshot the end time by a little bit, so just return the last frame if (this.TimeIntoAnimation >= this.TotalTime) return this.frames[this.frames.Length - 1]; var accum = 0D; foreach (var frame in this.frames) { accum += frame.Seconds; if (accum >= this.TimeIntoAnimation) return frame; } // if we're here then the time is negative for some reason, so just return the first frame return this.frames[0]; } } /// /// The texture region that the animation's has /// public TextureRegion CurrentRegion => this.CurrentFrame.Region; /// /// The total amount of time that this animation has. /// This is auatomatically calculated based on the frame time of each frame. /// public readonly double TotalTime; /// /// The amount of seconds that the animation has been going on for. /// If is reached, this value resets to 0. /// public double TimeIntoAnimation { get; private set; } /// /// The finished state of this animation. /// This is only true for longer than a frame if is false. /// public bool IsFinished { get; private set; } /// /// The name of this animation. This is useful if used in combination with . /// public string Name; /// /// The speed multiplier that this animation should run with. /// Numbers higher than 1 will increase the speed. /// public float SpeedMultiplier = 1; /// /// Set to false to stop this animation from looping. /// To check if the animation has finished playing, see . /// public bool IsLooping = true; /// /// A callback that gets fired when the animation completes. /// public event Completed OnCompleted; /// /// Set this to true to pause the playback of the animation. /// will not continue and the will not change. /// public bool IsPaused; /// /// Creates a new sprite animation that contains the given frames. /// /// The frames this animation should have public SpriteAnimation(params AnimationFrame[] frames) { this.frames = frames; foreach (var frame in frames) this.TotalTime += frame.Seconds; } /// /// Creates a new sprite animation that contains the given texture regions as frames. /// /// The amount of time that each frame should last for /// The texture regions that should make up this animation public SpriteAnimation(double timePerFrame, params TextureRegion[] regions) : this(Array.ConvertAll(regions, region => new AnimationFrame(region, timePerFrame))) { } /// /// Creates a new sprite animation based on the given texture regions in rectangle-based format. /// /// The amount of time that each frame should last for /// The texture that the regions should come from /// The texture regions that should make up this animation public SpriteAnimation(double timePerFrame, Texture2D texture, params Rectangle[] regions) : this(timePerFrame, Array.ConvertAll(regions, region => new TextureRegion(texture, region))) { } /// /// Updates this animation, causing to be increased and the to be updated. /// /// The game's time public void Update(GameTime time) { this.Update(time.ElapsedGameTime); } /// /// Updates this animation, causing to be increased and the to be updated. /// /// The amount of time that has passed public void Update(TimeSpan elapsed) { if (this.IsFinished || this.IsPaused) return; this.TimeIntoAnimation += elapsed.TotalSeconds * this.SpeedMultiplier; if (this.TimeIntoAnimation >= this.TotalTime) { if (!this.IsLooping) { this.IsFinished = true; } else { this.Restart(); } this.OnCompleted?.Invoke(this); } } /// /// Restarts this animation from the first frame. /// public void Restart() { this.TimeIntoAnimation = 0; this.IsFinished = false; this.IsPaused = false; } /// /// A callback for when a sprite animation is completed. /// /// The animation that has completed public delegate void Completed(SpriteAnimation animation); } /// /// Represents a single frame of a /// public class AnimationFrame { /// /// The texture region that this frame should render /// public readonly TextureRegion Region; /// /// The total amount of seconds that this frame should last for /// public readonly double Seconds; /// /// Creates a new animation frame based on a texture region and a time /// /// The texture region that this frame should render /// The total amount of seconds that this frame should last for public AnimationFrame(TextureRegion region, double seconds) { this.Region = region; this.Seconds = seconds; } } }