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 } }