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