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.

126 lines
4.4 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 int Width;
  42. public readonly int Height;
  43. // Measures the empty pixels between the ground (where the sprite's feet rest in idle pose)
  44. // and the bottom of the sprite's image.
  45. public readonly int GroundPadding = 7;
  46. public readonly TextureRef Texture;
  47. private readonly Dictionary<string, SpriteAnimation> animations;
  48. private readonly List<Frame> frames;
  49. public Sprite(TextureRef texture, string metadataJson) {
  50. Texture = texture;
  51. animations = new Dictionary<string, SpriteAnimation>();
  52. JObject json = JObject.Parse(metadataJson);
  53. frames = new List<Frame>();
  54. foreach (JToken child in json.SelectToken("frames").Children()) {
  55. Rectangle source = new Rectangle(
  56. child.SelectToken("frame.x").Value<int>(),
  57. child.SelectToken("frame.y").Value<int>(),
  58. child.SelectToken("frame.w").Value<int>(),
  59. child.SelectToken("frame.h").Value<int>());
  60. double duration = child.SelectToken("duration").Value<double>() / 1000;
  61. frames.Add(new Frame(source, duration));
  62. }
  63. // We assume that all frames are the same size (which right now is assured by the
  64. // Aseprite-based spritesheet export process).
  65. Width = frames[0].Source.Width;
  66. Height = frames[0].Source.Height;
  67. JToken frameTags = json.SelectToken("meta.frameTags");
  68. foreach (JToken child in frameTags.Children()) {
  69. string name = child.SelectToken("name").Value<string>();
  70. int start = child.SelectToken("from").Value<int>();
  71. int end = child.SelectToken("to").Value<int>();
  72. string directionString = child.SelectToken("direction").Value<string>();
  73. AnimationDirection direction = directionString == "pingpong" ?
  74. AnimationDirection.PingPong : AnimationDirection.Forward;
  75. double duration = 0;
  76. for (int i = start; i <= end; i++) {
  77. duration += frames[i].Duration;
  78. }
  79. // A PingPong animation repeats every frame but the first and last.
  80. // Therefore its duration is 2x, minus the duration of the first and last frames.
  81. if (direction == AnimationDirection.PingPong) {
  82. duration = duration * 2 - frames[start].Duration - frames[end].Duration;
  83. }
  84. animations[name] = new SpriteAnimation(start, end, duration, direction);
  85. }
  86. }
  87. public Rectangle GetTextureSource(string animationName, double time) {
  88. SpriteAnimation animation = animations[animationName];
  89. time %= animation.Duration;
  90. for (int i = animation.Start; i <= animation.End; i++) {
  91. double frameTime = frames[i].Duration;
  92. if (time < frameTime) {
  93. return frames[i].Source;
  94. }
  95. time -= frameTime;
  96. }
  97. if (animation.Direction == AnimationDirection.PingPong) {
  98. for (int i = animation.End - 1; i > animation.Start; i--) {
  99. double frameTime = frames[i].Duration;
  100. if (time < frameTime) {
  101. return frames[i].Source;
  102. }
  103. time -= frameTime;
  104. }
  105. }
  106. // We shouldn't get here, but if we did, the last frame is a fine thing to return.
  107. return frames[animation.End].Source;
  108. }
  109. }
  110. }