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

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
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>();
JObject json = JObject.Parse(metadataJson);
frames = new List<Frame>();
foreach (JToken child in json.SelectToken("frames").Children()) {
Rectangle source = new Rectangle(
child.SelectToken("frame.x").Value<int>(),
child.SelectToken("frame.y").Value<int>(),
child.SelectToken("frame.w").Value<int>(),
child.SelectToken("frame.h").Value<int>());
double duration = child.SelectToken("duration").Value<double>() / 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;
JToken frameTags = json.SelectToken("meta.frameTags");
foreach (JToken child in frameTags.Children()) {
string name = child.SelectToken("name").Value<string>();
int start = child.SelectToken("from").Value<int>();
int end = child.SelectToken("to").Value<int>();
string directionString = child.SelectToken("direction").Value<string>();
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;
}
}
}