1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-12-24 17:29:23 +01:00

added sprite sheet animations

This commit is contained in:
Ellpeck 2019-08-14 19:07:23 +02:00
parent 75deef068a
commit 482244df0e
6 changed files with 209 additions and 0 deletions

67
Demos/AnimationDemo.cs Normal file
View file

@ -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<Texture2D>("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);
}
}
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

View file

@ -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) {

View file

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

View file

@ -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<ConditionedAnimation> animations = new List<ConditionedAnimation>();
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<bool> 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<bool> ShouldPlay;
public ConditionedAnimation(SpriteAnimation animation, Func<bool> shouldPlay) {
this.Animation = animation;
this.ShouldPlay = shouldPlay;
}
}
}