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.

116 lines
3.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. public 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. enum AnimationDirection {
  17. Forward,
  18. PingPong
  19. }
  20. struct SpriteAnimation {
  21. public readonly int Start;
  22. public readonly int End;
  23. public readonly double Duration;
  24. public readonly AnimationDirection Direction;
  25. public SpriteAnimation(int start, int end, double duration, AnimationDirection direction) {
  26. Start = start;
  27. End = end;
  28. Duration = duration;
  29. Direction = direction;
  30. }
  31. }
  32. struct Frame {
  33. public readonly Rectangle Source;
  34. public readonly double Duration;
  35. public Frame(Rectangle source, double duration) {
  36. Source = source;
  37. Duration = duration;
  38. }
  39. }
  40. public class Sprite {
  41. public readonly TextureRef Texture;
  42. private readonly Dictionary<string, SpriteAnimation> animations;
  43. private readonly List<Frame> frames;
  44. public Sprite(TextureRef texture, string metadataJson) {
  45. Texture = texture;
  46. animations = new Dictionary<string, SpriteAnimation>();
  47. JObject json = JObject.Parse(metadataJson);
  48. frames = new List<Frame>();
  49. foreach (JToken child in json.SelectToken("frames").Children()) {
  50. Rectangle source = new Rectangle(
  51. child.SelectToken("frame.x").Value<int>(),
  52. child.SelectToken("frame.y").Value<int>(),
  53. child.SelectToken("frame.w").Value<int>(),
  54. child.SelectToken("frame.h").Value<int>());
  55. double duration = child.SelectToken("duration").Value<double>() / 1000;
  56. frames.Add(new Frame(source, duration));
  57. }
  58. JToken frameTags = json.SelectToken("meta.frameTags");
  59. foreach (JToken child in frameTags.Children()) {
  60. string name = child.SelectToken("name").Value<string>();
  61. int start = child.SelectToken("from").Value<int>();
  62. int end = child.SelectToken("to").Value<int>();
  63. string directionString = child.SelectToken("direction").Value<string>();
  64. AnimationDirection direction = directionString == "pingpong" ?
  65. AnimationDirection.PingPong : AnimationDirection.Forward;
  66. double duration = 0;
  67. for (int i = start; i <= end; i++) {
  68. duration += frames[i].Duration;
  69. }
  70. // A PingPong animation repeats every frame but the first and last.
  71. // Therefore its duration is 2x, minus the duration of the first and last frames.
  72. if (direction == AnimationDirection.PingPong) {
  73. duration = duration * 2 - frames[start].Duration - frames[end].Duration;
  74. }
  75. animations[name] = new SpriteAnimation(start, end, duration, direction);
  76. }
  77. }
  78. public Rectangle GetTextureSource(string animationName, double time) {
  79. SpriteAnimation animation = animations[animationName];
  80. time %= animation.Duration;
  81. for (int i = animation.Start; i <= animation.End; i++) {
  82. double frameTime = frames[i].Duration;
  83. if (time < frameTime) {
  84. return frames[i].Source;
  85. }
  86. time -= frameTime;
  87. }
  88. if (animation.Direction == AnimationDirection.PingPong) {
  89. for (int i = animation.End - 1; i > animation.Start; i--) {
  90. double frameTime = frames[i].Duration;
  91. if (time < frameTime) {
  92. return frames[i].Source;
  93. }
  94. time -= frameTime;
  95. }
  96. }
  97. // We shouldn't get here, but if we did, the last frame is a fine thing to return.
  98. return frames[animation.End].Source;
  99. }
  100. }
  101. }