diff --git a/Shared/NPC.cs b/Shared/NPC.cs index d7af36f..bbf6d66 100644 --- a/Shared/NPC.cs +++ b/Shared/NPC.cs @@ -25,14 +25,10 @@ namespace SemiColinGames { position.X += (int) (120 * Facing * modelTime); } - private int SpriteIndex() { - int frameNum = (int) Clock.ModelTime.TotalMilliseconds / 125 % 4; - return 35 + frameNum; - } - public void Draw(SpriteBatch spriteBatch) { - int index = SpriteIndex(); - Rectangle textureSource = new Rectangle(index * spriteWidth, 0, spriteWidth, spriteHeight); + Rectangle textureSource = Sprites.Executioner.GetTextureSource( + "run", (int) Clock.ModelTime.TotalMilliseconds); + // TODO: move this into Sprite metadata. Vector2 spriteCenter = new Vector2(spriteWidth / 2, spriteHeight / 2 + spriteCenterYOffset); SpriteEffects effect = Facing == 1 ? SpriteEffects.None : SpriteEffects.FlipHorizontally; diff --git a/Shared/Shared.projitems b/Shared/Shared.projitems index a7c8d9b..fae96a1 100644 --- a/Shared/Shared.projitems +++ b/Shared/Shared.projitems @@ -13,6 +13,7 @@ + diff --git a/Shared/SneakGame.cs b/Shared/SneakGame.cs index 52f9314..90d942e 100644 --- a/Shared/SneakGame.cs +++ b/Shared/SneakGame.cs @@ -61,6 +61,8 @@ namespace SemiColinGames { base.LoadContent(); SoundEffects.Load(Content); Textures.Load(Content); + Sprites.Load(Content); + // TODO: move this into World. linesOfSight = new LinesOfSight(GraphicsDevice); LoadLevel(); } diff --git a/Shared/Sprites.cs b/Shared/Sprites.cs new file mode 100644 index 0000000..2662ec4 --- /dev/null +++ b/Shared/Sprites.cs @@ -0,0 +1,89 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + +namespace SemiColinGames { + struct SpriteAnimation { + public readonly int Start; + public readonly int End; + public readonly int DurationMs; + + public SpriteAnimation(int start, int end, int durationMs) { + Start = start; + End = end; + DurationMs = durationMs; + } + } + + struct Frame { + public readonly Rectangle Source; + public readonly int DurationMs; + + public Frame(Rectangle source, int durationMs) { + Source = source; + DurationMs = durationMs; + } + } + + class Sprite { + public readonly TextureRef Texture; + + private readonly Dictionary animations; + private readonly List frames; + + public Sprite(TextureRef texture, string metadataJson) { + Texture = texture; + animations = new Dictionary(); + + JObject json = JObject.Parse(metadataJson); + + frames = new List(); + foreach (JToken child in json.SelectToken("frames").Children()) { + Rectangle source = new Rectangle( + child.SelectToken("frame.x").Value(), + child.SelectToken("frame.y").Value(), + child.SelectToken("frame.w").Value(), + child.SelectToken("frame.h").Value()); + int durationMs = child.SelectToken("duration").Value(); + frames.Add(new Frame(source, durationMs)); + } + + // TODO: handle ping-pong animations. + JToken frameTags = json.SelectToken("meta.frameTags"); + foreach (JToken child in frameTags.Children()) { + string name = child.SelectToken("name").Value(); + int start = child.SelectToken("from").Value(); + int end = child.SelectToken("to").Value(); + int durationMs = 0; + for (int i = start; i <= end; i++) { + durationMs += frames[i].DurationMs; + } + animations[name] = new SpriteAnimation(start, end, durationMs); + } + } + + public Rectangle GetTextureSource(string animationName, int timeMs) { + SpriteAnimation animation = animations[animationName]; + int time = timeMs % animation.DurationMs; + for (int i = animation.Start; i <= animation.End; i++) { + int frameTime = frames[i].DurationMs; + if (time < frameTime) { + return frames[i].Source; + } + time -= frameTime; + } + // We shouldn't get here, but if we did, the last frame is a fine thing to return. + return frames[animation.End].Source; + } + } + + static class Sprites { + public static Sprite Executioner; + + public static void Load(ContentManager content) { + Executioner = new Sprite( + Textures.Executioner, content.Load("sprites/ccg/executioner_female_json")); + } + } +}