130 lines
4.4 KiB
C#
130 lines
4.4 KiB
C#
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Content;
|
|
using System.Collections.Generic;
|
|
using System.Text.Json;
|
|
|
|
namespace SemiColinGames {
|
|
public static class Sprites {
|
|
public static Sprite Executioner;
|
|
public static Sprite Ninja;
|
|
|
|
public static void Load(ContentManager content) {
|
|
Executioner = new Sprite(
|
|
Textures.Executioner, content.LoadString("sprites/ccg/executioner_female.json"));
|
|
Ninja = new Sprite(
|
|
Textures.Ninja, content.LoadString("sprites/ccg/ninja_female.json"));
|
|
}
|
|
}
|
|
|
|
enum AnimationDirection {
|
|
Forward,
|
|
PingPong
|
|
}
|
|
|
|
struct SpriteAnimation {
|
|
public readonly int Start;
|
|
public readonly int End;
|
|
public readonly double Duration;
|
|
public readonly AnimationDirection Direction;
|
|
|
|
public SpriteAnimation(int start, int end, double duration, AnimationDirection direction) {
|
|
Start = start;
|
|
End = end;
|
|
Duration = duration;
|
|
Direction = direction;
|
|
}
|
|
}
|
|
|
|
struct Frame {
|
|
public readonly Rectangle Source;
|
|
public readonly double Duration;
|
|
|
|
public Frame(Rectangle source, double duration) {
|
|
Source = source;
|
|
Duration = duration;
|
|
}
|
|
}
|
|
|
|
public class Sprite {
|
|
public readonly int Width;
|
|
public readonly int Height;
|
|
// Measures the empty pixels between the ground (where the sprite's feet rest in idle pose)
|
|
// and the bottom of the sprite's image.
|
|
public readonly int GroundPadding = 7;
|
|
|
|
public readonly TextureRef Texture;
|
|
|
|
private readonly Dictionary<string, SpriteAnimation> animations;
|
|
private readonly List<Frame> frames;
|
|
|
|
public Sprite(TextureRef texture, string metadataJson) {
|
|
Texture = texture;
|
|
animations = new Dictionary<string, SpriteAnimation>();
|
|
|
|
JsonElement jsonRoot = JsonDocument.Parse(metadataJson).RootElement;
|
|
|
|
frames = new List<Frame>();
|
|
foreach (JsonElement child in jsonRoot.GetProperty("frames").EnumerateArray()) {
|
|
JsonElement frame = child.GetProperty("frame");
|
|
Rectangle source = new Rectangle(
|
|
frame.GetProperty("x").GetInt32(),
|
|
frame.GetProperty("y").GetInt32(),
|
|
frame.GetProperty("w").GetInt32(),
|
|
frame.GetProperty("h").GetInt32());
|
|
|
|
double duration = child.GetProperty("duration").GetDouble() / 1000;
|
|
frames.Add(new Frame(source, duration));
|
|
}
|
|
|
|
// We assume that all frames are the same size (which right now is assured by the
|
|
// Aseprite-based spritesheet export process).
|
|
Width = frames[0].Source.Width;
|
|
Height = frames[0].Source.Height;
|
|
|
|
JsonElement frameTags = jsonRoot.GetProperty("meta").GetProperty("frameTags");
|
|
foreach (JsonElement child in frameTags.EnumerateArray()) {
|
|
string name = child.GetProperty("name").GetString();
|
|
int start = child.GetProperty("from").GetInt32();
|
|
int end = child.GetProperty("to").GetInt32();
|
|
string directionString = child.GetProperty("direction").GetString();
|
|
AnimationDirection direction = directionString == "pingpong" ?
|
|
AnimationDirection.PingPong : AnimationDirection.Forward;
|
|
double duration = 0;
|
|
for (int i = start; i <= end; i++) {
|
|
duration += frames[i].Duration;
|
|
}
|
|
// A PingPong animation repeats every frame but the first and last.
|
|
// Therefore its duration is 2x, minus the duration of the first and last frames.
|
|
if (direction == AnimationDirection.PingPong) {
|
|
duration = duration * 2 - frames[start].Duration - frames[end].Duration;
|
|
}
|
|
|
|
animations[name] = new SpriteAnimation(start, end, duration, direction);
|
|
}
|
|
}
|
|
|
|
public Rectangle GetTextureSource(string animationName, double time) {
|
|
SpriteAnimation animation = animations[animationName];
|
|
time %= animation.Duration;
|
|
for (int i = animation.Start; i <= animation.End; i++) {
|
|
double frameTime = frames[i].Duration;
|
|
if (time < frameTime) {
|
|
return frames[i].Source;
|
|
}
|
|
time -= frameTime;
|
|
}
|
|
if (animation.Direction == AnimationDirection.PingPong) {
|
|
for (int i = animation.End - 1; i > animation.Start; i--) {
|
|
double frameTime = frames[i].Duration;
|
|
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;
|
|
}
|
|
}
|
|
}
|