using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Otter {
///
/// Class used for animations in Spritemap.
///
public class Anim {
#region Static Methods
///
/// Returns an array of numbers from min to max. Useful for passing in arguments for long animations.
///
/// The start of the animation (includes this number.)
/// The end of the animation (includes this number.)
/// The array of ints representing an animation.
public static int[] FramesRange(int min, int max) {
int[] f = new int[max - min + 1];
for (int i = min; i <= max; i++) {
f[i - min] = i;
}
return f;
}
///
/// Creates an array of frames from a string expression. The expression must be similar to the following format:
/// "0,3,7-11,2,5"
/// Whitespace is permitted, and commas are optional.
/// A string formatted as above, describing the frames to generate.
///
public static int[] ParseFrames(string input) {
// Make sure the pattern matches, and alert the user if it doesn't
var parse = SyntaxCheck.Match(input);
if (!parse.Success)
throw new Exception(string.Format("Invalid format: {0}", input));
// Get all numbers/ranges in the input string.
var frames = new List();
foreach (Match match in GetMatches.Matches(input)) {
var range = GetRange.Match(match.Value);
if (range.Success) {
int from = int.Parse(range.Groups[1].Value);
int to = int.Parse(range.Groups[2].Value);
// Support ascending and descending ranges
if (from < to) {
while (from <= to)
frames.Add(from++);
}
else {
while (from >= to)
frames.Add(from--);
}
}
else {
frames.Add(int.Parse(match.Value));
}
}
return frames.ToArray();
}
#endregion
#region Private Static Functions
// Matches strings containing only numbers and number ranges, separated by single commas and/or whitespace
static readonly Regex SyntaxCheck = new Regex(@"^(?:\d+\s*-\s*\d+|\d\s*,?\s*)*$");
// Extracts a number or number range from a string
static readonly Regex GetMatches = new Regex(@"((?:\d+\s*-\s*\d+)|(?:\d+))");
// Extracts two numbers from a string containing a range
static readonly Regex GetRange = new Regex(@"(\d+)-(\d+)");
#endregion
#region Private Fields
bool pingPong;
int loopBack = 0;
float timer;
float delay;
int direction;
int repeatsCounted = 0;
int currentFrame;
#endregion
#region Public Fields
///
/// An action to run when the animation finishes playing.
///
public Action OnComplete = delegate { };
///
/// An action that is called when the Anim switches to a new frame.
///
public Action OnNewFrame = delegate { };
///
/// Determines if the animation is active (playing.)
///
public bool Active;
#endregion
#region Public Properties
///
/// The overall playback speed of the animation.
///
public float PlaybackSpeed { get; private set; }
///
/// The repeat count of the animation.
///
public int RepeatCount { get; private set; }
///
/// The frames used in the animation.
///
public List Frames { get; private set; }
///
/// The frame delays used in the animation.
///
public List FrameDelays { get; private set; }
///
/// The total number of frames in this animation.
///
public int FrameCount {
get { return Frames.Count; }
}
///
/// The current frame of the animation.
///
public int CurrentFrame {
get { return Frames[currentFrame]; }
}
///
/// The current frame index of the animation.
///
public int CurrentFrameIndex {
get { return currentFrame; }
set { currentFrame = value; }
}
///
/// The total duration of the animation.
///
public float TotalDuration {
get {
float delay = 0;
foreach (float d in FrameDelays) {
delay += d;
}
return delay;
}
}
#endregion
#region Constructors
///
/// Creates a new Anim with an array of ints for frames, and an array of floats for frameDelays.
///
/// The frames from the sprite sheet to display.
/// The time that each frame should be displayed.
public Anim(int[] frames, float[] frameDelays = null) {
Initialize(frames, frameDelays);
}
///
/// Creates a new Anim with a string of ints for frames, and a string of floats for frameDelays.
///
/// A string of frames separated by a delim character. Example: "0,1,2-7,9,11"
/// A string of floats separated by a delim character. Example: "0.5f,1,0.5f,1"
/// The string of characters to parse the string by. Default is ","
public Anim(string frames, string frameDelays) {
string[] frameDelaysParts = Regex.Split(frameDelays.Replace(" ", ""), ",");
float[] framedelaysfloat = new float[frameDelaysParts.Length];
for (int i = 0; i < frameDelaysParts.Length; i++) {
framedelaysfloat[i] = float.Parse(frameDelaysParts[i]);
}
Initialize(ParseFrames(frames), framedelaysfloat);
}
#endregion
#region Private Methods
void Initialize(int[] frames, float[] frameDelays = null) {
Frames = new List();
FrameDelays = new List();
for (int i = 0; i < frames.Length; i++) {
Frames.Add(frames[i]);
if (frameDelays != null) {
if (i >= frameDelays.Length) {
FrameDelays.Add(frameDelays[i % frameDelays.Length]);
}
else {
FrameDelays.Add(frameDelays[i]);
}
}
else {
FrameDelays.Add(1);
}
}
RepeatCount = -1;
timer = 0;
delay = 0;
direction = 1;
PlaybackSpeed = 1;
Active = true;
}
#endregion
#region Public Methods
///
/// Determines how many times this animation loops. -1 for infinite.
///
/// How many times the animation should repeat.
/// The anim object.
public Anim Repeat(int times = -1) {
RepeatCount = times;
return this;
}
///
/// Disables repeating. Animations default to repeat on.
///
/// The anim object.
public Anim NoRepeat() {
RepeatCount = 0;
return this;
}
///
/// Determines if the animation will repeat by going back and forth between the start and end.
///
/// True for yes, false for no no no.
/// The anim object.
public Anim PingPong(bool pingpong = true) {
pingPong = pingpong;
return this;
}
///
/// Determines the playback speed of the animation. 1 = 1 frame.
///
/// The new speed.
/// The anim object.
public Anim Speed(float speed) {
PlaybackSpeed = speed;
return this;
}
///
/// Determines which frame the animation will loop back to when it repeats.
///
/// The frame to loop back to (from 0 to frame count - 1)
/// The anim object.
public Anim LoopBackTo(int frame = 0) {
loopBack = frame;
return this;
}
///
/// Stops the animation and returns it to the first frame.
///
/// The anim object.
public Anim Stop() {
Active = false;
currentFrame = 0;
return this;
}
///
/// Resets the animation back to frame 0 but does not stop it.
///
/// The anim object.
public Anim Reset() {
timer = 0;
currentFrame = 0;
return this;
}
///
/// Updates the Anim object. Handled by the Spritemap usually. If this doesn't run the animation will not play.
///
/// The time scale.
public void Update(float t = 1f) {
if (Active) {
timer += PlaybackSpeed * Game.Instance.DeltaTime * t;
}
delay = FrameDelays[currentFrame];
while (timer >= delay) {
timer -= delay;
currentFrame += direction;
if (currentFrame == Frames.Count) {
if (repeatsCounted < RepeatCount || RepeatCount < 0) {
repeatsCounted++;
if (pingPong) {
direction *= -1;
currentFrame = Frames.Count - 2;
}
else {
currentFrame = loopBack;
}
}
else {
if (pingPong) {
direction *= -1;
currentFrame = Frames.Count - 2;
}
else {
OnComplete();
Stop();
currentFrame = Frames.Count - 1;
}
}
}
if (currentFrame < loopBack) {
if (pingPong) {
if (repeatsCounted < RepeatCount || RepeatCount < 0) {
repeatsCounted++;
direction *= -1;
currentFrame = loopBack + 1;
}
else {
OnComplete();
Stop();
}
}
}
OnNewFrame();
}
}
#endregion
}
}