diff --git a/Demos/AnimationDemo.cs b/Demos/AnimationDemo.cs new file mode 100644 index 0000000..865e8c1 --- /dev/null +++ b/Demos/AnimationDemo.cs @@ -0,0 +1,67 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using MLEM.Animations; +using MLEM.Startup; +using MLEM.Textures; + +namespace Demos { + public class AnimationDemo : MlemGame { + + private SpriteAnimationGroup group; + private int facing; + + protected override void LoadContent() { + base.LoadContent(); + + var tex = LoadContent("Textures/Anim"); + + // create the four animations by supplying the time per frame, the texture and the four regions used + var downAnim = new SpriteAnimation(0.2F, tex, new Rectangle(0, 0, 8, 8), new Rectangle(0, 8, 8, 8), new Rectangle(0, 16, 8, 8), new Rectangle(0, 24, 8, 8)); + var upAnim = new SpriteAnimation(0.2F, tex, new Rectangle(8, 0, 8, 8), new Rectangle(8, 8, 8, 8), new Rectangle(8, 16, 8, 8), new Rectangle(8, 24, 8, 8)); + var leftAnim = new SpriteAnimation(0.2F, tex, new Rectangle(16, 0, 8, 8), new Rectangle(16, 8, 8, 8), new Rectangle(16, 16, 8, 8), new Rectangle(16, 24, 8, 8)); + var rightAnim = new SpriteAnimation(0.2F, tex, new Rectangle(24, 0, 8, 8), new Rectangle(24, 8, 8, 8), new Rectangle(24, 16, 8, 8), new Rectangle(24, 24, 8, 8)); + + // create a sprite animation group which manages a list of animations and figures out which one should + // be playing right now based on supplied conditions + // using a group isn't necessary, but highly recommended for things like character animations as it makes + // it very easy to have different animations play at different times + this.group = new SpriteAnimationGroup(); + // for example, the down animation should only play when we're facing down (0 in this case) + this.group.Add(downAnim, () => this.facing == 0); + this.group.Add(upAnim, () => this.facing == 1); + this.group.Add(leftAnim, () => this.facing == 2); + this.group.Add(rightAnim, () => this.facing == 3); + } + + protected override void Update(GameTime gameTime) { + base.Update(gameTime); + + if (this.InputHandler.IsKeyDown(Keys.Down)) + this.facing = 0; + else if (this.InputHandler.IsKeyDown(Keys.Up)) + this.facing = 1; + else if (this.InputHandler.IsKeyDown(Keys.Left)) + this.facing = 2; + else if (this.InputHandler.IsKeyDown(Keys.Right)) + this.facing = 3; + + // update the animation group + // if not using a group, just update the animation itself here + this.group.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) { + this.GraphicsDevice.Clear(Color.Black); + + this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(10)); + // draw the group's current region + // if not using a group, just update the animation's CurrentRegion here + this.SpriteBatch.Draw(this.group.CurrentRegion, new Vector2(10, 10), Color.White); + this.SpriteBatch.End(); + + base.Draw(gameTime); + } + + } +} \ No newline at end of file diff --git a/Demos/Content/Content.mgcb b/Demos/Content/Content.mgcb index dd14290..ba5c6f7 100644 --- a/Demos/Content/Content.mgcb +++ b/Demos/Content/Content.mgcb @@ -20,6 +20,18 @@ /processorParam:TextureFormat=Compressed /build:Fonts/TestFont.spritefont +#begin Textures/Anim.png +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:Textures/Anim.png + #begin Textures/AutoTiling.png /importer:TextureImporter /processor:TextureProcessor diff --git a/Demos/Content/Textures/Anim.png b/Demos/Content/Textures/Anim.png new file mode 100644 index 0000000..adba882 Binary files /dev/null and b/Demos/Content/Textures/Anim.png differ diff --git a/Demos/Program.cs b/Demos/Program.cs index 2eb4fb0..be70952 100644 --- a/Demos/Program.cs +++ b/Demos/Program.cs @@ -11,6 +11,7 @@ namespace Demos { static Program() { Demos.Add("Ui", () => new UiDemo()); Demos.Add("AutoTiling", () => new AutoTilingDemo()); + Demos.Add("Animation", () => new AnimationDemo()); } public static void Main(string[] args) { diff --git a/MLEM/Animations/SpriteAnimation.cs b/MLEM/Animations/SpriteAnimation.cs new file mode 100644 index 0000000..b5399e1 --- /dev/null +++ b/MLEM/Animations/SpriteAnimation.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Textures; + +namespace MLEM.Animations { + public class SpriteAnimation { + + private AnimationFrame[] frames; + public AnimationFrame this[int index] => this.frames[index]; + public AnimationFrame CurrentFrame { + get { + var accum = 0D; + foreach (var frame in this.frames) { + accum += frame.Seconds; + if (accum >= this.TimeIntoAnimation) + return frame; + } + // we might have overshot the end time by a little bit, so just return the last frame + return this.frames[this.frames.Length - 1]; + } + } + public TextureRegion CurrentRegion => this.CurrentFrame.Region; + public readonly double TotalTime; + public double TimeIntoAnimation { get; private set; } + public bool IsFinished { get; private set; } + + public bool IsLooping = true; + public Completed OnCompleted; + public bool IsPaused; + + public SpriteAnimation(params AnimationFrame[] frames) { + this.frames = frames; + foreach (var frame in frames) + this.TotalTime += frame.Seconds; + } + + public SpriteAnimation(float timePerFrame, params TextureRegion[] regions) + : this(Array.ConvertAll(regions, region => new AnimationFrame(region, timePerFrame))) { + } + + public SpriteAnimation(float timePerFrame, Texture2D texture, params Rectangle[] regions) + : this(timePerFrame, Array.ConvertAll(regions, region => new TextureRegion(texture, region))) { + } + + public void Update(GameTime time) { + if (this.IsFinished || this.IsPaused) + return; + + this.TimeIntoAnimation += time.ElapsedGameTime.TotalSeconds; + if (this.TimeIntoAnimation >= this.TotalTime) { + if (!this.IsLooping) { + this.IsFinished = true; + } else { + this.Restart(); + } + this.OnCompleted?.Invoke(this); + } + } + + public void Restart() { + this.TimeIntoAnimation = 0; + this.IsFinished = false; + this.IsPaused = false; + } + + public delegate void Completed(SpriteAnimation animation); + + } + + public class AnimationFrame { + + public readonly TextureRegion Region; + public readonly float Seconds; + + public AnimationFrame(TextureRegion region, float seconds) { + this.Region = region; + this.Seconds = seconds; + } + + } +} \ No newline at end of file diff --git a/MLEM/Animations/SpriteAnimationGroup.cs b/MLEM/Animations/SpriteAnimationGroup.cs new file mode 100644 index 0000000..1877acf --- /dev/null +++ b/MLEM/Animations/SpriteAnimationGroup.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using MLEM.Textures; + +namespace MLEM.Animations { + public class SpriteAnimationGroup { + + private readonly List animations = new List(); + private ConditionedAnimation currAnimation; + public AnimationFrame CurrentFrame => this.currAnimation != null ? this.currAnimation.Animation.CurrentFrame : null; + public TextureRegion CurrentRegion => this.currAnimation != null ? this.currAnimation.Animation.CurrentRegion : null; + + public SpriteAnimationGroup Add(SpriteAnimation anim, Func condition) { + this.animations.Add(new ConditionedAnimation(anim, condition)); + return this; + } + + public void Update(GameTime time) { + if (this.currAnimation == null || !this.currAnimation.ShouldPlay()) { + this.currAnimation = null; + foreach (var anim in this.animations) { + if (anim.ShouldPlay()) { + this.currAnimation = anim; + anim.Animation.Restart(); + break; + } + } + } + if (this.currAnimation != null) + this.currAnimation.Animation.Update(time); + } + + } + + internal class ConditionedAnimation { + + public readonly SpriteAnimation Animation; + public readonly Func ShouldPlay; + + public ConditionedAnimation(SpriteAnimation animation, Func shouldPlay) { + this.Animation = animation; + this.ShouldPlay = shouldPlay; + } + + } +} \ No newline at end of file