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.

117 lines
4.0 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. enum AnimationDirection {
  17. Forward,
  18. PingPong
  19. }
  20. struct SpriteAnimation {
  21. public readonly int Start;
  22. public readonly int End;
  23. public readonly int DurationMs;
  24. public readonly AnimationDirection Direction;
  25. public SpriteAnimation(int start, int end, int durationMs, AnimationDirection direction) {
  26. Start = start;
  27. End = end;
  28. DurationMs = durationMs;
  29. Direction = direction;
  30. }
  31. }
  32. struct Frame {
  33. public readonly Rectangle Source;
  34. public readonly int DurationMs;
  35. public Frame(Rectangle source, int durationMs) {
  36. Source = source;
  37. DurationMs = durationMs;
  38. }
  39. }
  40. 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. // TODO: convert all durations to floats.
  56. int durationMs = child.SelectToken("duration").Value<int>();
  57. frames.Add(new Frame(source, durationMs));
  58. }
  59. JToken frameTags = json.SelectToken("meta.frameTags");
  60. foreach (JToken child in frameTags.Children()) {
  61. string name = child.SelectToken("name").Value<string>();
  62. int start = child.SelectToken("from").Value<int>();
  63. int end = child.SelectToken("to").Value<int>();
  64. string directionString = child.SelectToken("direction").Value<string>();
  65. AnimationDirection direction = directionString == "pingpong" ?
  66. AnimationDirection.PingPong : AnimationDirection.Forward;
  67. int durationMs = 0;
  68. for (int i = start; i <= end; i++) {
  69. durationMs += frames[i].DurationMs;
  70. }
  71. // A PingPong animation repeats every frame but the first and last.
  72. // Therefore its duration is 2x, minus the duration of the first and last frames.
  73. if (direction == AnimationDirection.PingPong) {
  74. durationMs = durationMs * 2 - frames[start].DurationMs - frames[end].DurationMs;
  75. }
  76. animations[name] = new SpriteAnimation(start, end, durationMs, direction);
  77. }
  78. }
  79. public Rectangle GetTextureSource(string animationName, int timeMs) {
  80. SpriteAnimation animation = animations[animationName];
  81. int time = timeMs % animation.DurationMs;
  82. for (int i = animation.Start; i <= animation.End; i++) {
  83. int frameTime = frames[i].DurationMs;
  84. if (time < frameTime) {
  85. return frames[i].Source;
  86. }
  87. time -= frameTime;
  88. }
  89. if (animation.Direction == AnimationDirection.PingPong) {
  90. for (int i = animation.End - 1; i > animation.Start; i--) {
  91. int frameTime = frames[i].DurationMs;
  92. if (time < frameTime) {
  93. return frames[i].Source;
  94. }
  95. time -= frameTime;
  96. }
  97. }
  98. // We shouldn't get here, but if we did, the last frame is a fine thing to return.
  99. return frames[animation.End].Source;
  100. }
  101. }
  102. }