using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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 this animation's should render.
/// If there are multiple regions, should be used instead.
///
public TextureRegion CurrentRegion => this.CurrentFrame.Region;
///
/// The texture regions that this animation's should render.
/// If there is only one region, can be used for convenience.
///
public IList CurrentRegions => this.CurrentFrame.Regions;
///
/// 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) {
if (frames.Length <= 0)
throw new ArgumentException("Cannot create a sprite animation without any 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, r => new AnimationFrame(timePerFrame, r))) {}
///
/// Creates a new sprite animation that contains the given texture region arrays as frames, where each frame represents one set of texture regions.
///
/// 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, r => new AnimationFrame(timePerFrame, r))) {}
///
/// 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, r => new TextureRegion(texture, r))) {}
///
/// 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 : GenericDataHolder {
///
/// The texture regions that this frame should render.
/// If there is only one region, can be used for convenience.
///
public readonly IList Regions;
///
/// The total amount of seconds that this frame should last for.
///
public readonly double Seconds;
///
/// The texture region that this frame should render.
/// If there are multiple regions, should be used instead.
///
/// Thrown if this animation frame has more than one region, in which case should be used instead.
public TextureRegion Region => this.Regions.Count == 1 ? this.Regions[0] : throw new InvalidOperationException("Cannot return single region for an animation frame with multiple regions");
///
/// Creates a new animation frame based on a set of texture regions and a time.
///
/// The texture regions that this frame should render
/// The total amount of seconds that this frame should last for
public AnimationFrame(double seconds, params TextureRegion[] regions) {
if (regions.Length <= 0)
throw new ArgumentException("Cannot create an animation frame without any regions");
this.Regions = new ReadOnlyCollection(regions);
this.Seconds = seconds;
}
}
}