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"));
+ }
+ }
+}