A stealth-based 2D platformer where you don't have to kill anyone unless you want to. https://www.semicolin.games
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

92 lines
2.9 KiB

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Content;
  3. using Newtonsoft.Json.Linq;
  4. using System.Collections.Generic;
  5. namespace SemiColinGames {
  6. static class Sprites {
  7. public static Sprite Executioner;
  8. public static Sprite Ninja;
  9. public static void Load(ContentManager content) {
  10. Executioner = new Sprite(
  11. Textures.Executioner, content.LoadString("sprites/ccg/executioner_female.json"));
  12. Ninja = new Sprite(
  13. Textures.Ninja, content.LoadString("sprites/ccg/ninja_female.json"));
  14. }
  15. }
  16. struct SpriteAnimation {
  17. public readonly int Start;
  18. public readonly int End;
  19. public readonly int DurationMs;
  20. public SpriteAnimation(int start, int end, int durationMs) {
  21. Start = start;
  22. End = end;
  23. DurationMs = durationMs;
  24. }
  25. }
  26. struct Frame {
  27. public readonly Rectangle Source;
  28. public readonly int DurationMs;
  29. public Frame(Rectangle source, int durationMs) {
  30. Source = source;
  31. DurationMs = durationMs;
  32. }
  33. }
  34. class Sprite {
  35. public readonly TextureRef Texture;
  36. private readonly Dictionary<string, SpriteAnimation> animations;
  37. private readonly List<Frame> frames;
  38. public Sprite(TextureRef texture, string metadataJson) {
  39. Texture = texture;
  40. animations = new Dictionary<string, SpriteAnimation>();
  41. JObject json = JObject.Parse(metadataJson);
  42. frames = new List<Frame>();
  43. foreach (JToken child in json.SelectToken("frames").Children()) {
  44. Rectangle source = new Rectangle(
  45. child.SelectToken("frame.x").Value<int>(),
  46. child.SelectToken("frame.y").Value<int>(),
  47. child.SelectToken("frame.w").Value<int>(),
  48. child.SelectToken("frame.h").Value<int>());
  49. int durationMs = child.SelectToken("duration").Value<int>();
  50. frames.Add(new Frame(source, durationMs));
  51. }
  52. // TODO: handle ping-pong animations.
  53. JToken frameTags = json.SelectToken("meta.frameTags");
  54. foreach (JToken child in frameTags.Children()) {
  55. string name = child.SelectToken("name").Value<string>();
  56. int start = child.SelectToken("from").Value<int>();
  57. int end = child.SelectToken("to").Value<int>();
  58. int durationMs = 0;
  59. for (int i = start; i <= end; i++) {
  60. durationMs += frames[i].DurationMs;
  61. }
  62. animations[name] = new SpriteAnimation(start, end, durationMs);
  63. }
  64. }
  65. public Rectangle GetTextureSource(string animationName, int timeMs) {
  66. SpriteAnimation animation = animations[animationName];
  67. int time = timeMs % animation.DurationMs;
  68. for (int i = animation.Start; i <= animation.End; i++) {
  69. int frameTime = frames[i].DurationMs;
  70. if (time < frameTime) {
  71. return frames[i].Source;
  72. }
  73. time -= frameTime;
  74. }
  75. // We shouldn't get here, but if we did, the last frame is a fine thing to return.
  76. return frames[animation.End].Source;
  77. }
  78. }
  79. }