From 482244df0e0773080450af42ef8da9b76463574c Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Aug 2019 19:07:23 +0200 Subject: [PATCH] added sprite sheet animations --- Demos/AnimationDemo.cs | 67 +++++++++++++++++++ Demos/Content/Content.mgcb | 12 ++++ Demos/Content/Textures/Anim.png | Bin 0 -> 894 bytes Demos/Program.cs | 1 + MLEM/Animations/SpriteAnimation.cs | 82 ++++++++++++++++++++++++ MLEM/Animations/SpriteAnimationGroup.cs | 47 ++++++++++++++ 6 files changed, 209 insertions(+) create mode 100644 Demos/AnimationDemo.cs create mode 100644 Demos/Content/Textures/Anim.png create mode 100644 MLEM/Animations/SpriteAnimation.cs create mode 100644 MLEM/Animations/SpriteAnimationGroup.cs 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 0000000000000000000000000000000000000000..adba8829d898d0ab16fcf121d125b18bb554c474 GIT binary patch literal 894 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>V7BmdaSW-L z^Y->eZ|Ovd;~($03UJ)(F7KFprjy{VmOR;C=@wjkk8(1n_r-F_x*XwS zb&PQl*Oa)!w2SNHrCZs8O?PfD+;{x$%wt~Bd%x|QuX_CVp7+%<`F(eO-x2l~XWF0j z`fAs?bIG2UPF^qew|qEvmaTPv==v}f&*SsiWd2JzBn3IOuCY_HAlpi>u|s%5!Z>o{_g8Bd{wqPZuQkoU#nt| z%}~kf3EY1_eEV%*v8t@BQ_GGo^?!2iVus0;mnA7?%F@#AR@~NOGg9W+l=CI~Jd0%BetPnfd;gZr zoqd0@itIbRYM@QOSAI9#>8)S4+thsj^22vi^NZ3h?%KDmKYQ|$wc`A$o@d$p_x!0) zw-sY8xGGl{a=-Ig$)^4C(){h`Cg=HoKmJyUKcMId;{->!1t}+(r^tL|Pz|4|xBq+j zrMF+J{x1KxC-5tS#x(mVeu3X_wO=zP?XP8?-kQBwZowRo>Pm)`3G5n@K<_;F^b-4Q zRoRgEyY>^q|9NN34F2x3{~K5T{H?a<;{W0WTlQvM4-;FrhCL!Wa)I0e2cz;wH!3pr z&y`bu|NHv%ck28(FQpnz&UD?quOxnw-qE>0w;O+Dcp1fDe*XSWl|S27e_}ZNE0`ac d`l$lCURfVF-F~%GHA)O*o~Nsy%Q~loCII`%ibwze literal 0 HcmV?d00001 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