Эх сурвалжийг харах

Generic C# runtime & XNA runtime.

NathanSweet 12 жил өмнө
parent
commit
4a354f4565
37 өөрчлөгдсөн 4860 нэмэгдсэн , 0 устгасан
  1. 9 0
      .gitignore
  2. 34 0
      spine-csharp/Properties/AssemblyInfo.cs
  3. 89 0
      spine-csharp/spine-csharp.csproj
  4. 390 0
      spine-csharp/src/Animation.cs
  5. 24 0
      spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
  6. 16 0
      spine-csharp/src/Attachments/Attachment.cs
  7. 8 0
      spine-csharp/src/Attachments/AttachmentLoader.cs
  8. 6 0
      spine-csharp/src/Attachments/AttachmentType.cs
  9. 134 0
      spine-csharp/src/Attachments/RegionAttachment.cs
  10. 177 0
      spine-csharp/src/BaseAtlas.cs
  11. 83 0
      spine-csharp/src/Bone.cs
  12. 28 0
      spine-csharp/src/BoneData.cs
  13. 551 0
      spine-csharp/src/Json.cs
  14. 171 0
      spine-csharp/src/Skeleton.cs
  15. 111 0
      spine-csharp/src/SkeletonData.cs
  16. 256 0
      spine-csharp/src/SkeletonJson.cs
  17. 54 0
      spine-csharp/src/Skin.cs
  18. 61 0
      spine-csharp/src/Slot.cs
  19. 29 0
      spine-csharp/src/SlotData.cs
  20. 34 0
      spine-xna/Properties/AssemblyInfo.cs
  21. BIN
      spine-xna/example/Game.ico
  22. BIN
      spine-xna/example/GameThumbnail.png
  23. 34 0
      spine-xna/example/Properties/AssemblyInfo.cs
  24. 285 0
      spine-xna/example/data/goblins.atlas
  25. 499 0
      spine-xna/example/data/goblins.json
  26. BIN
      spine-xna/example/data/goblins.png
  27. 166 0
      spine-xna/example/data/spineboy.atlas
  28. 787 0
      spine-xna/example/data/spineboy.json
  29. BIN
      spine-xna/example/data/spineboy.png
  30. 168 0
      spine-xna/example/spine-xna-example.csproj
  31. 71 0
      spine-xna/example/src/ExampleGame.cs
  32. 13 0
      spine-xna/example/src/ExampleProgram.cs
  33. 116 0
      spine-xna/spine-xna.csproj
  34. 32 0
      spine-xna/spine-xna.sln
  35. 84 0
      spine-xna/src/Atlas.cs
  36. 100 0
      spine-xna/src/SkeletonRenderer.cs
  37. 240 0
      spine-xna/src/SpriteBatcher.cs

+ 9 - 0
.gitignore

@@ -20,3 +20,12 @@ spine-cocos2d-iphone/libs/*
 !spine-cocos2d-iphone/libs/cocos2d/Place cocos2d here.txt
 !spine-cocos2d-iphone/libs/cocos2d/Place cocos2d here.txt
 !spine-cocos2d-iphone/libs/CocosDenshion/Place CocosDenshion here.txt
 !spine-cocos2d-iphone/libs/CocosDenshion/Place CocosDenshion here.txt
 !spine-cocos2d-iphone/libs/kazmath/Place kazmath here.txt
 !spine-cocos2d-iphone/libs/kazmath/Place kazmath here.txt
+
+*.user
+forums
+spine-csharp/bin
+spine-csharp/obj
+spine-xna/bin
+spine-xna/obj
+spine-xna/example/bin
+spine-xna/example/obj

+ 34 - 0
spine-csharp/Properties/AssemblyInfo.cs

@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("spine-csharp")]
+[assembly: AssemblyProduct("spine-csharp")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type. Only Windows
+// assemblies support COM.
+[assembly: ComVisible(false)]
+
+// On Windows, the following GUID is for the ID of the typelib if this
+// project is exposed to COM. On other platforms, it unique identifies the
+// title storage container when deploying this assembly to the device.
+[assembly: Guid("3ac8567e-9ae8-4624-87b9-a84f0101c629")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]

+ 89 - 0
spine-csharp/spine-csharp.csproj

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <ProjectGuid>{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}</ProjectGuid>
+    <ProjectTypeGuids>{6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Spine</RootNamespace>
+    <AssemblyName>spine-csharp</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+    <XnaFrameworkVersion>v4.0</XnaFrameworkVersion>
+    <XnaPlatform>Windows</XnaPlatform>
+    <XnaProfile>HiDef</XnaProfile>
+    <XnaCrossPlatformGroupID>99dfd52d-8beb-4e5c-a68b-365be39e8064</XnaCrossPlatformGroupID>
+    <XnaOutputType>Library</XnaOutputType>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\x86\Debug</OutputPath>
+    <DefineConstants>DEBUG;TRACE;WINDOWS</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <NoStdLib>true</NoStdLib>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <PlatformTarget>x86</PlatformTarget>
+    <XnaCompressContent>false</XnaCompressContent>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\x86\Release</OutputPath>
+    <DefineConstants>TRACE;WINDOWS</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <NoStdLib>true</NoStdLib>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <PlatformTarget>x86</PlatformTarget>
+    <XnaCompressContent>true</XnaCompressContent>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="mscorlib">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Core">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Net">
+      <Private>False</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="src\Animation.cs" />
+    <Compile Include="src\Attachments\AttachmentLoader.cs" />
+    <Compile Include="src\Attachments\AtlasAttachmentLoader.cs" />
+    <Compile Include="src\Attachments\Attachment.cs" />
+    <Compile Include="src\Attachments\AttachmentType.cs" />
+    <Compile Include="src\Attachments\RegionAttachment.cs" />
+    <Compile Include="src\BaseAtlas.cs" />
+    <Compile Include="src\Bone.cs" />
+    <Compile Include="src\BoneData.cs" />
+    <Compile Include="src\Json.cs" />
+    <Compile Include="src\Skeleton.cs" />
+    <Compile Include="src\SkeletonData.cs" />
+    <Compile Include="src\SkeletonJson.cs" />
+    <Compile Include="src\Skin.cs" />
+    <Compile Include="src\Slot.cs" />
+    <Compile Include="src\SlotData.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\Microsoft.Xna.GameStudio.targets" />
+  <!--
+      To modify your build process, add your task inside one of the targets below and uncomment it. 
+      Other similar extension points exist, see Microsoft.Common.targets.
+      <Target Name="BeforeBuild">
+      </Target>
+      <Target Name="AfterBuild">
+      </Target>
+    -->
+</Project>

+ 390 - 0
spine-csharp/src/Animation.cs

@@ -0,0 +1,390 @@
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class Animation {
+		public String Name { get; private set; }
+		public List<Timeline> Timelines { get; set; }
+		public float Duration { get; set; }
+
+		public Animation (String name, List<Timeline> timelines, float duration) {
+			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			if (timelines == null) throw new ArgumentNullException("timelines cannot be null.");
+			Name = name;
+			Timelines = timelines;
+			Duration = duration;
+		}
+
+		/** Poses the skeleton at the specified time for this animation. */
+		public void Apply (Skeleton skeleton, float time, bool loop) {
+			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+
+			if (loop && Duration != 0) time %= Duration;
+
+			List<Timeline> timelines = Timelines;
+			for (int i = 0, n = timelines.Count; i < n; i++)
+				timelines[i].Apply(skeleton, time, 1);
+		}
+
+		/** Poses the skeleton at the specified time for this animation mixed with the current pose.
+		 * @param alpha The amount of this animation that affects the current pose. */
+		public void Mix (Skeleton skeleton, float time, bool loop, float alpha) {
+			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+
+			if (loop && Duration != 0) time %= Duration;
+
+			List<Timeline> timelines = Timelines;
+			for (int i = 0, n = timelines.Count; i < n; i++)
+				timelines[i].Apply(skeleton, time, alpha);
+		}
+
+		/** @param target After the first and before the last entry. */
+		internal static int binarySearch (float[] values, float target, int step) {
+			int low = 0;
+			int high = values.Length / step - 2;
+			if (high == 0) return step;
+			int current = (int)((uint)high >> 1);
+			while (true) {
+				if (values[(current + 1) * step] <= target)
+					low = current + 1;
+				else
+					high = current;
+				if (low == high) return (low + 1) * step;
+				current = (int)((uint)(low + high) >> 1);
+			}
+		}
+
+		internal static int linearSearch (float[] values, float target, int step) {
+			for (int i = 0, last = values.Length - step; i <= last; i += step)
+				if (values[i] > target) return i;
+			return -1;
+		}
+	}
+
+	public interface Timeline {
+		/** Sets the value(s) for the specified time. */
+		void Apply (Skeleton skeleton, float time, float alpha);
+	}
+
+	/** Base class for frames that use an interpolation bezier curve. */
+	abstract public class CurveTimeline : Timeline {
+		static protected float LINEAR = 0;
+		static protected float STEPPED = -1;
+		static protected int BEZIER_SEGMENTS = 10;
+
+		private float[] curves; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
+		public int FrameCount {
+			get {
+				return curves.Length / 6 + 1;
+			}
+		}
+
+		public CurveTimeline (int frameCount) {
+			curves = new float[(frameCount - 1) * 6];
+		}
+
+		abstract public void Apply (Skeleton skeleton, float time, float alpha);
+
+		public void SetLinear (int frameIndex) {
+			curves[frameIndex * 6] = LINEAR;
+		}
+
+		public void SetStepped (int frameIndex) {
+			curves[frameIndex * 6] = STEPPED;
+		}
+
+		/** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
+	 * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
+	 * the difference between the keyframe's values. */
+		public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
+			float subdiv_step = 1f / BEZIER_SEGMENTS;
+			float subdiv_step2 = subdiv_step * subdiv_step;
+			float subdiv_step3 = subdiv_step2 * subdiv_step;
+			float pre1 = 3 * subdiv_step;
+			float pre2 = 3 * subdiv_step2;
+			float pre4 = 6 * subdiv_step2;
+			float pre5 = 6 * subdiv_step3;
+			float tmp1x = -cx1 * 2 + cx2;
+			float tmp1y = -cy1 * 2 + cy2;
+			float tmp2x = (cx1 - cx2) * 3 + 1;
+			float tmp2y = (cy1 - cy2) * 3 + 1;
+			int i = frameIndex * 6;
+			float[] curves = this.curves;
+			curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
+			curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
+			curves[i + 2] = tmp1x * pre4 + tmp2x * pre5;
+			curves[i + 3] = tmp1y * pre4 + tmp2y * pre5;
+			curves[i + 4] = tmp2x * pre5;
+			curves[i + 5] = tmp2y * pre5;
+		}
+
+		public float GetCurvePercent (int frameIndex, float percent) {
+			int curveIndex = frameIndex * 6;
+			float[] curves = this.curves;
+			float dfx = curves[curveIndex];
+			if (dfx == LINEAR) return percent;
+			if (dfx == STEPPED) return 0;
+			float dfy = curves[curveIndex + 1];
+			float ddfx = curves[curveIndex + 2];
+			float ddfy = curves[curveIndex + 3];
+			float dddfx = curves[curveIndex + 4];
+			float dddfy = curves[curveIndex + 5];
+			float x = dfx, y = dfy;
+			int i = BEZIER_SEGMENTS - 2;
+			while (true) {
+				if (x >= percent) {
+					float lastX = x - dfx;
+					float lastY = y - dfy;
+					return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
+				}
+				if (i == 0) break;
+				i--;
+				dfx += ddfx;
+				dfy += ddfy;
+				ddfx += dddfx;
+				ddfy += dddfy;
+				x += dfx;
+				y += dfy;
+			}
+			return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
+		}
+	}
+
+	public class RotateTimeline : CurveTimeline {
+		static protected int LAST_FRAME_TIME = -2;
+		static protected int FRAME_VALUE = 1;
+
+		public int BoneIndex { get; set; }
+		public float[] Frames { get; private set; } // time, value, ...
+
+		public RotateTimeline (int frameCount)
+			: base(frameCount) {
+			Frames = new float[frameCount * 2];
+		}
+
+		/** Sets the time and value of the specified keyframe. */
+		public void SetFrame (int frameIndex, float time, float angle) {
+			frameIndex *= 2;
+			Frames[frameIndex] = time;
+			Frames[frameIndex + 1] = angle;
+		}
+
+		override public void Apply (Skeleton skeleton, float time, float alpha) {
+			float[] frames = Frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			Bone bone = skeleton.Bones[BoneIndex];
+
+			float amount;
+
+			if (time >= frames[frames.Length - 2]) { // Time is after last frame.
+				amount = bone.Data.Rotation + frames[frames.Length - 1] - bone.Rotation;
+				while (amount > 180)
+					amount -= 360;
+				while (amount < -180)
+					amount += 360;
+				bone.Rotation += amount * alpha;
+				return;
+			}
+
+			// Interpolate between the last frame and the current frame.
+			int frameIndex = Animation.binarySearch(frames, time, 2);
+			float lastFrameValue = frames[frameIndex - 1];
+			float frameTime = frames[frameIndex];
+			float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+			percent = GetCurvePercent(frameIndex / 2 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+			amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue;
+			while (amount > 180)
+				amount -= 360;
+			while (amount < -180)
+				amount += 360;
+			amount = bone.Data.Rotation + (lastFrameValue + amount * percent) - bone.Rotation;
+			while (amount > 180)
+				amount -= 360;
+			while (amount < -180)
+				amount += 360;
+			bone.Rotation += amount * alpha;
+		}
+	}
+
+	public class TranslateTimeline : CurveTimeline {
+		static protected int LAST_FRAME_TIME = -3;
+		static protected int FRAME_X = 1;
+		static protected int FRAME_Y = 2;
+
+		public int BoneIndex { get; set; }
+		public float[] Frames { get; private set; } // time, value, value, ...
+
+		public TranslateTimeline (int frameCount)
+			: base(frameCount) {
+			Frames = new float[frameCount * 3];
+		}
+
+		/** Sets the time and value of the specified keyframe. */
+		public void SetFrame (int frameIndex, float time, float x, float y) {
+			frameIndex *= 3;
+			Frames[frameIndex] = time;
+			Frames[frameIndex + 1] = x;
+			Frames[frameIndex + 2] = y;
+		}
+
+		override public void Apply (Skeleton skeleton, float time, float alpha) {
+			float[] frames = Frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			Bone bone = skeleton.Bones[BoneIndex];
+
+			if (time >= frames[frames.Length - 3]) { // Time is after last frame.
+				bone.X += (bone.Data.X + frames[frames.Length - 2] - bone.X) * alpha;
+				bone.Y += (bone.Data.Y + frames[frames.Length - 1] - bone.Y) * alpha;
+				return;
+			}
+
+			// Interpolate between the last frame and the current frame.
+			int frameIndex = Animation.binarySearch(frames, time, 3);
+			float lastFrameX = frames[frameIndex - 2];
+			float lastFrameY = frames[frameIndex - 1];
+			float frameTime = frames[frameIndex];
+			float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+			percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+			bone.X += (bone.Data.X + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.X) * alpha;
+			bone.Y += (bone.Data.Y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.Y) * alpha;
+		}
+	}
+
+	public class ScaleTimeline : TranslateTimeline {
+		public ScaleTimeline (int frameCount)
+			: base(frameCount) {
+		}
+
+		override public void Apply (Skeleton skeleton, float time, float alpha) {
+			float[] frames = Frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			Bone bone = skeleton.Bones[BoneIndex];
+			if (time >= frames[frames.Length - 3]) { // Time is after last frame.
+				bone.ScaleX += (bone.Data.ScaleX - 1 + frames[frames.Length - 2] - bone.ScaleX) * alpha;
+				bone.ScaleY += (bone.Data.ScaleY - 1 + frames[frames.Length - 1] - bone.ScaleY) * alpha;
+				return;
+			}
+
+			// Interpolate between the last frame and the current frame.
+			int frameIndex = Animation.binarySearch(frames, time, 3);
+			float lastFrameX = frames[frameIndex - 2];
+			float lastFrameY = frames[frameIndex - 1];
+			float frameTime = frames[frameIndex];
+			float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+			percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+			bone.ScaleX += (bone.Data.ScaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.ScaleX) * alpha;
+			bone.ScaleY += (bone.Data.ScaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.ScaleY) * alpha;
+		}
+	}
+
+	public class ColorTimeline : CurveTimeline {
+		static protected int LAST_FRAME_TIME = -5;
+		static protected int FRAME_R = 1;
+		static protected int FRAME_G = 2;
+		static protected int FRAME_B = 3;
+		static protected int FRAME_A = 4;
+
+		public int SlotIndex { get; set; }
+		public float[] Frames { get; private set; } // time, r, g, b, a, ...
+
+		public ColorTimeline (int frameCount)
+			: base(frameCount) {
+			Frames = new float[frameCount * 5];
+		}
+
+		/** Sets the time and value of the specified keyframe. */
+		public void setFrame (int frameIndex, float time, float r, float g, float b, float a) {
+			frameIndex *= 5;
+			Frames[frameIndex] = time;
+			Frames[frameIndex + 1] = r;
+			Frames[frameIndex + 2] = g;
+			Frames[frameIndex + 3] = b;
+			Frames[frameIndex + 4] = a;
+		}
+
+		override public void Apply (Skeleton skeleton, float time, float alpha) {
+			float[] frames = Frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			Slot slot = skeleton.Slots[SlotIndex];
+
+			if (time >= frames[frames.Length - 5]) { // Time is after last frame.
+				int i = frames.Length - 1;
+				slot.R = frames[i - 3];
+				slot.G = frames[i - 2];
+				slot.B = frames[i - 1];
+				slot.A = frames[i];
+				return;
+			}
+
+			// Interpolate between the last frame and the current frame.
+			int frameIndex = Animation.binarySearch(frames, time, 5);
+			float lastFrameR = frames[frameIndex - 4];
+			float lastFrameG = frames[frameIndex - 3];
+			float lastFrameB = frames[frameIndex - 2];
+			float lastFrameA = frames[frameIndex - 1];
+			float frameTime = frames[frameIndex];
+			float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+			percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+			float r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent;
+			float g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent;
+			float b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent;
+			float a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent;
+			if (alpha < 1) {
+				slot.R += (r - slot.R) * alpha;
+				slot.G += (g - slot.G) * alpha;
+				slot.B += (b - slot.B) * alpha;
+				slot.A += (a - slot.A) * alpha;
+			} else {
+				slot.R = r;
+				slot.G = g;
+				slot.B = b;
+				slot.A = a;
+			}
+		}
+	}
+
+	public class AttachmentTimeline : Timeline {
+		public int SlotIndex { get; set; }
+		public float[] Frames { get; private set; } // time, ...
+		public String[] AttachmentNames { get; private set; }
+		public int FrameCount {
+			get {
+				return Frames.Length;
+			}
+		}
+
+		public AttachmentTimeline (int frameCount) {
+			Frames = new float[frameCount];
+			AttachmentNames = new String[frameCount];
+		}
+
+		/** Sets the time and value of the specified keyframe. */
+		public void setFrame (int frameIndex, float time, String attachmentName) {
+			Frames[frameIndex] = time;
+			AttachmentNames[frameIndex] = attachmentName;
+		}
+
+		public void Apply (Skeleton skeleton, float time, float alpha) {
+			float[] frames = Frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			int frameIndex;
+			if (time >= frames[frames.Length - 1]) // Time is after last frame.
+				frameIndex = frames.Length - 1;
+			else
+				frameIndex = Animation.binarySearch(frames, time, 1) - 1;
+
+			String attachmentName = AttachmentNames[frameIndex];
+			skeleton.Slots[SlotIndex].Attachment =
+				 attachmentName == null ? null : skeleton.GetAttachment(SlotIndex, attachmentName);
+		}
+	}
+}

+ 24 - 0
spine-csharp/src/Attachments/AtlasAttachmentLoader.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace Spine {
+	public class AtlasAttachmentLoader : AttachmentLoader {
+		private BaseAtlas atlas;
+
+		public AtlasAttachmentLoader (BaseAtlas atlas) {
+			if (atlas == null) throw new ArgumentNullException("atlas cannot be null.");
+			this.atlas = atlas;
+		}
+
+		public Attachment NewAttachment (AttachmentType type, String name) {
+			switch (type) {
+			case AttachmentType.region:
+				AtlasRegion region = atlas.FindRegion(name);
+				if (region == null) throw new Exception("Region not found in atlas: " + name + " (" + type + ")");
+				RegionAttachment attachment = new RegionAttachment(name);
+				attachment.Region = region;
+				return attachment;
+			}
+			throw new Exception("Unknown attachment type: " + type);
+		}
+	}
+}

+ 16 - 0
spine-csharp/src/Attachments/Attachment.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace Spine {
+	abstract public class Attachment {
+		public String Name { get; private set; }
+
+		public Attachment (String name) {
+			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			Name = name;
+		}
+
+		override public String ToString () {
+			return Name;
+		}
+	}
+}

+ 8 - 0
spine-csharp/src/Attachments/AttachmentLoader.cs

@@ -0,0 +1,8 @@
+using System;
+
+namespace Spine {
+	public interface AttachmentLoader {
+		/** @return May be null to not load any attachment. */
+		Attachment NewAttachment (AttachmentType type, String name);
+	}
+}

+ 6 - 0
spine-csharp/src/Attachments/AttachmentType.cs

@@ -0,0 +1,6 @@
+
+namespace Spine {
+	public enum AttachmentType {
+		region, regionSequence
+	}
+}

+ 134 - 0
spine-csharp/src/Attachments/RegionAttachment.cs

@@ -0,0 +1,134 @@
+using System;
+
+namespace Spine {
+	/** Attachment that displays a texture region. */
+	public class RegionAttachment : Attachment {
+		public const int X1 = 0;
+		public const int Y1 = 1;
+		public const int X2 = 2;
+		public const int Y2 = 3;
+		public const int X3 = 4;
+		public const int Y3 = 5;
+		public const int X4 = 6;
+		public const int Y4 = 7;
+
+		public float X { get; set; }
+		public float Y { get; set; }
+		public float ScaleX { get; set; }
+		public float ScaleY { get; set; }
+		public float Rotation { get; set; }
+		public float Width { get; set; }
+		public float Height { get; set; }
+
+		public float[] Offset { get; private set; }
+		public float[] Vertices { get; private set; }
+		public float[] UVs { get; private set; }
+
+		private AtlasRegion region;
+		public AtlasRegion Region {
+			get {
+				return region;
+			}
+			set {
+				region = value;
+				float[] uvs = UVs;
+				if (value.Rotate) {
+					uvs[X2] = value.U;
+					uvs[Y2] = value.V2;
+					uvs[X3] = value.U;
+					uvs[Y3] = value.V;
+					uvs[X4] = value.U2;
+					uvs[Y4] = value.V;
+					uvs[X1] = value.U2;
+					uvs[Y1] = value.V2;
+				} else {
+					uvs[X1] = value.U;
+					uvs[Y1] = value.V2;
+					uvs[X2] = value.U;
+					uvs[Y2] = value.V;
+					uvs[X3] = value.U2;
+					uvs[Y3] = value.V;
+					uvs[X4] = value.U2;
+					uvs[Y4] = value.V2;
+				}
+			}
+		}
+
+		public RegionAttachment (string name)
+			: base(name) {
+			Offset = new float[8];
+			Vertices = new float[8];
+			UVs = new float[8];
+			ScaleX = 1;
+			ScaleY = 1;
+		}
+
+		public void UpdateOffset () {
+			float width = Width;
+			float height = Height;
+			float localX2 = width / 2;
+			float localY2 = height / 2;
+			float localX = -localX2;
+			float localY = -localY2;
+			AtlasRegion region = Region;
+			if (region.Rotate) {
+				localX += region.OffsetX / region.OriginalWidth * height;
+				localY += region.OffsetY / region.OriginalHeight * width;
+				localX2 -= (region.OriginalWidth - region.OffsetX - region.Height) / region.OriginalWidth * width;
+				localY2 -= (region.OriginalHeight - region.OffsetY - region.Width) / region.OriginalHeight * height;
+			} else {
+				localX += region.OffsetX / region.OriginalWidth * width;
+				localY += region.OffsetY / region.OriginalHeight * height;
+				localX2 -= (region.OriginalWidth - region.OffsetX - region.Width) / region.OriginalWidth * width;
+				localY2 -= (region.OriginalHeight - region.OffsetY - region.Height) / region.OriginalHeight * height;
+			}
+			float scaleX = ScaleX;
+			float scaleY = ScaleY;
+			localX *= scaleX;
+			localY *= scaleY;
+			localX2 *= scaleX;
+			localY2 *= scaleY;
+			float radians = Rotation * (float)Math.PI / 180;
+			float cos = (float)Math.Cos(radians);
+			float sin = (float)Math.Sin(radians);
+			float x = X;
+			float y = Y;
+			float localXCos = localX * cos + x;
+			float localXSin = localX * sin;
+			float localYCos = localY * cos + y;
+			float localYSin = localY * sin;
+			float localX2Cos = localX2 * cos + x;
+			float localX2Sin = localX2 * sin;
+			float localY2Cos = localY2 * cos + y;
+			float localY2Sin = localY2 * sin;
+			float[] offset = Offset;
+			offset[X1] = localXCos - localYSin;
+			offset[Y1] = localYCos + localXSin;
+			offset[X2] = localXCos - localY2Sin;
+			offset[Y2] = localY2Cos + localXSin;
+			offset[X3] = localX2Cos - localY2Sin;
+			offset[Y3] = localY2Cos + localX2Sin;
+			offset[X4] = localX2Cos - localYSin;
+			offset[Y4] = localYCos + localX2Sin;
+		}
+
+		public void UpdateVertices (Bone bone) {
+			float x = bone.WorldX;
+			float y = bone.WorldY;
+			float m00 = bone.M00;
+			float m01 = bone.M01;
+			float m10 = bone.M10;
+			float m11 = bone.M11;
+			float[] vertices = Vertices;
+			float[] offset = Offset;
+			vertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x;
+			vertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y;
+			vertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x;
+			vertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y;
+			vertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x;
+			vertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y;
+			vertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x;
+			vertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y;
+		}
+	}
+}

+ 177 - 0
spine-csharp/src/BaseAtlas.cs

@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Spine {
+	abstract public class BaseAtlas {
+		List<AtlasPage> pages = new List<AtlasPage>();
+		List<AtlasRegion> regions = new List<AtlasRegion>();
+
+		abstract protected AtlasPage NewAtlasPage (String path);
+
+		public void load (StreamReader reader, String imagesDir) {
+			String[] tuple = new String[4];
+			AtlasPage page = null;
+			while (true) {
+				String line = reader.ReadLine();
+				if (line == null) break;
+				if (line.Trim().Length == 0)
+					page = null;
+				else if (page == null) {
+					page = NewAtlasPage(Path.Combine(imagesDir, line));
+
+					page.Format = (Format)Enum.Parse(typeof(Format), readValue(reader), false);
+
+					readTuple(reader, tuple);
+					page.MinFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0]);
+					page.MagFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1]);
+
+					String direction = readValue(reader);
+					page.UWrap = TextureWrap.ClampToEdge;
+					page.VWrap = TextureWrap.ClampToEdge;
+					if (direction == "x")
+						page.UWrap = TextureWrap.Repeat;
+					else if (direction == "y")
+						page.VWrap = TextureWrap.Repeat;
+					else if (direction == "xy")
+						page.UWrap = page.VWrap = TextureWrap.Repeat;
+
+					pages.Add(page);
+
+				} else {
+					AtlasRegion region = new AtlasRegion();
+					region.Name = line;
+					region.Page = page;
+
+					region.Rotate = Boolean.Parse(readValue(reader));
+
+					readTuple(reader, tuple);
+					int x = int.Parse(tuple[0]);
+					int y = int.Parse(tuple[1]);
+
+					readTuple(reader, tuple);
+					int width = int.Parse(tuple[0]);
+					int height = int.Parse(tuple[1]);
+
+					float invTexWidth = 1f / page.GetTextureWidth();
+					float invTexHeight = 1f / page.GetTextureHeight();
+					region.U = x * invTexWidth;
+					region.V = y * invTexHeight;
+					region.U2 = (x + width) * invTexWidth;
+					region.V2 = (y + height) * invTexHeight;
+					region.Width = Math.Abs(width);
+					region.Height = Math.Abs(height);
+
+					if (readTuple(reader, tuple) == 4) { // split is optional
+						region.Splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
+								int.Parse(tuple[2]), int.Parse(tuple[3])};
+
+						if (readTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
+							region.Pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
+									int.Parse(tuple[2]), int.Parse(tuple[3])};
+
+							readTuple(reader, tuple);
+						}
+					}
+
+					region.OriginalWidth = int.Parse(tuple[0]);
+					region.OriginalHeight = int.Parse(tuple[1]);
+
+					readTuple(reader, tuple);
+					region.OffsetX = int.Parse(tuple[0]);
+					region.OffsetY = int.Parse(tuple[1]);
+
+					region.Index = int.Parse(readValue(reader));
+
+					regions.Add(region);
+				}
+			}
+		}
+
+		static String readValue (StreamReader reader) {
+			String line = reader.ReadLine();
+			int colon = line.IndexOf(':');
+			if (colon == -1) throw new Exception("Invalid line: " + line);
+			return line.Substring(colon + 1).Trim();
+		}
+
+		/** Returns the number of tuple values read (2 or 4). */
+		static int readTuple (StreamReader reader, String[] tuple) {
+			String line = reader.ReadLine();
+			int colon = line.IndexOf(':');
+			if (colon == -1) throw new Exception("Invalid line: " + line);
+			int i = 0, lastMatch = colon + 1;
+			for (i = 0; i < 3; i++) {
+				int comma = line.IndexOf(',', lastMatch);
+				if (comma == -1) {
+					if (i == 0) throw new Exception("Invalid line: " + line);
+					break;
+				}
+				tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
+				lastMatch = comma + 1;
+			}
+			tuple[i] = line.Substring(lastMatch).Trim();
+			return i + 1;
+		}
+
+		/** Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
+		 * should be cached rather than calling this method multiple times.
+		 * @return The region, or null. */
+		public AtlasRegion FindRegion (String name) {
+			for (int i = 0, n = regions.Count; i < n; i++)
+				if (regions[i].Name == name) return regions[i];
+			return null;
+		}
+	}
+
+	public enum Format {
+		Alpha,
+		Intensity,
+		LuminanceAlpha,
+		RGB565,
+		RGBA4444,
+		RGB888,
+		RGBA8888
+	}
+
+	public enum TextureFilter {
+		Nearest,
+		Linear,
+		MipMap,
+		MipMapNearestNearest,
+		MipMapLinearNearest,
+		MipMapNearestLinear,
+		MipMapLinearLinear
+	}
+
+	public enum TextureWrap {
+		MirroredRepeat,
+		ClampToEdge,
+		Repeat
+	}
+
+	abstract public class AtlasPage {
+		public Format Format;
+		public TextureFilter MinFilter;
+		public TextureFilter MagFilter;
+		public TextureWrap UWrap;
+		public TextureWrap VWrap;
+
+		abstract public int GetTextureWidth ();
+		abstract public int GetTextureHeight ();
+	}
+
+	public class AtlasRegion {
+		public AtlasPage Page;
+		public float U, V;
+		public float U2, V2;
+		public int Width, Height;
+		public int Index;
+		public String Name;
+		public float OffsetX, OffsetY;
+		public int OriginalWidth, OriginalHeight;
+		public bool Rotate;
+		public int[] Splits;
+		public int[] Pads;
+	}
+}

+ 83 - 0
spine-csharp/src/Bone.cs

@@ -0,0 +1,83 @@
+using System;
+
+namespace Spine {
+	public class Bone {
+		static public bool yDown;
+
+		public BoneData Data { get; private set; }
+		public Bone Parent { get; private set; }
+		public float X { get; set; }
+		public float Y { get; set; }
+		public float Rotation { get; set; }
+		public float ScaleX { get; set; }
+		public float ScaleY { get; set; }
+
+		public float M00 { get; private set; }
+		public float M01 { get; private set; }
+		public float M10 { get; private set; }
+		public float M11 { get; private set; }
+		public float WorldX { get; private set; }
+		public float WorldY { get; private set; }
+		public float WorldRotation { get; private set; }
+		public float WorldScaleX { get; private set; }
+		public float WorldScaleY { get; private set; }
+
+		/** @param parent May be null. */
+		public Bone (BoneData data, Bone parent) {
+			if (data == null) throw new ArgumentNullException("data cannot be null.");
+			Data = data;
+			Parent = parent;
+			SetToBindPose();
+		}
+
+		/** Computes the world SRT using the parent bone and the local SRT. */
+		public void UpdateWorldTransform (bool flipX, bool flipY) {
+			Bone parent = Parent;
+			if (parent != null) {
+				WorldX = X * parent.M00 + Y * parent.M01 + parent.WorldX;
+				WorldY = X * parent.M10 + Y * parent.M11 + parent.WorldY;
+				WorldScaleX = parent.WorldScaleX * ScaleX;
+				WorldScaleY = parent.WorldScaleY * ScaleY;
+				WorldRotation = parent.WorldRotation + Rotation;
+			} else {
+				WorldX = X;
+				WorldY = Y;
+				WorldScaleX = ScaleX;
+				WorldScaleY = ScaleY;
+				WorldRotation = Rotation;
+			}
+			float radians = WorldRotation * (float)Math.PI / 180;
+			float cos = (float)Math.Cos(radians);
+			float sin = (float)Math.Sin(radians);
+			M00 = cos * WorldScaleX;
+			M10 = sin * WorldScaleX;
+			M01 = -sin * WorldScaleY;
+			M11 = cos * WorldScaleY;
+			if (flipX) {
+				M00 = -M00;
+				M01 = -M01;
+			}
+			if (flipY) {
+				M10 = -M10;
+				M11 = -M11;
+			}
+			if (yDown) {
+				M10 = -M10;
+				M11 = -M11;
+			}
+		}
+
+		public void SetToBindPose () {
+			BoneData data = Data;
+			X = data.X;
+			Y = data.Y;
+			Rotation = data.Rotation;
+			ScaleX = data.ScaleX;
+			ScaleY = data.ScaleY;
+		}
+
+		override public String ToString () {
+			return Data.Name;
+		}
+	}
+}

+ 28 - 0
spine-csharp/src/BoneData.cs

@@ -0,0 +1,28 @@
+using System;
+
+namespace Spine {
+	public class BoneData {
+		/** May be null. */
+		public BoneData Parent { get; private set; }
+		public String Name { get; private set; }
+		public float Length { get; set; }
+		public float X { get; set; }
+		public float Y { get; set; }
+		public float Rotation { get; set; }
+		public float ScaleX { get; set; }
+		public float ScaleY { get; set; }
+
+		/** @param parent May be null. */
+		public BoneData (String name, BoneData parent) {
+			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			Name = name;
+			Parent = parent;
+			ScaleX = 1;
+			ScaleY = 1;
+		}
+
+		override public String ToString () {
+			return Name;
+		}
+	}
+}

+ 551 - 0
spine-csharp/src/Json.cs

@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2012 Calvin Rien
+ *
+ * Based on the JSON parser by Patrick van Bergen
+ * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
+ *
+ * Simplified it so that it doesn't throw exceptions
+ * and can be used in Unity iPhone with maximum code stripping.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+ 
+namespace Spine
+{
+    // Example usage:
+    //
+    //  using UnityEngine;
+    //  using System.Collections;
+    //  using System.Collections.Generic;
+    //  using MiniJSON;
+    //
+    //  public class MiniJSONTest : MonoBehaviour {
+    //      void Start () {
+    //          var jsonString = "{ \"array\": [1.44,2,3], " +
+    //                          "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
+    //                          "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
+    //                          "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " +
+    //                          "\"int\": 65536, " +
+    //                          "\"float\": 3.1415926, " +
+    //                          "\"bool\": true, " +
+    //                          "\"null\": null }";
+    //
+    //          var dict = Json.Deserialize(jsonString) as Dictionary<string,object>;
+    //
+    //          Debug.Log("deserialized: " + dict.GetType());
+    //          Debug.Log("dict['array'][0]: " + ((List<object>) dict["array"])[0]);
+    //          Debug.Log("dict['string']: " + (string) dict["string"]);
+	 //          Debug.Log("dict['float']: " + (float) dict["float"]);
+    //          Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs
+    //          Debug.Log("dict['unicode']: " + (string) dict["unicode"]);
+    //
+    //          var str = Json.Serialize(dict);
+    //
+    //          Debug.Log("serialized: " + str);
+    //      }
+    //  }
+ 
+    /// <summary>
+    /// This class encodes and decodes JSON strings.
+    /// Spec. details, see http://www.json.org/
+    ///
+    /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary.
+    /// All numbers are parsed to floats.
+    /// </summary>
+    public static class Json {
+        /// <summary>
+        /// Parses the string json into a value
+        /// </summary>
+        /// <param name="json">A JSON string.</param>
+		 /// <returns>An List&lt;object&gt;, a Dictionary&lt;string, object&gt;, a float, an integer,a string, null, true, or false</returns>
+        public static object Deserialize(string json) {
+            // save the string for debug information
+            if (json == null) {
+                return null;
+            }
+ 
+            return Parser.Parse(json);
+        }
+ 
+        sealed class Parser : IDisposable {
+            const string WHITE_SPACE = " \t\n\r";
+            const string WORD_BREAK = " \t\n\r{}[],:\"";
+ 
+            enum TOKEN {
+                NONE,
+                CURLY_OPEN,
+                CURLY_CLOSE,
+                SQUARED_OPEN,
+                SQUARED_CLOSE,
+                COLON,
+                COMMA,
+                STRING,
+                NUMBER,
+                TRUE,
+                FALSE,
+                NULL
+            };
+ 
+            StringReader json;
+ 
+            Parser(string jsonString) {
+                json = new StringReader(jsonString);
+            }
+ 
+            public static object Parse(string jsonString) {
+                using (var instance = new Parser(jsonString)) {
+                    return instance.ParseValue();
+                }
+            }
+ 
+            public void Dispose() {
+                json.Dispose();
+                json = null;
+            }
+ 
+            Dictionary<string, object> ParseObject() {
+                Dictionary<string, object> table = new Dictionary<string, object>();
+ 
+                // ditch opening brace
+                json.Read();
+ 
+                // {
+                while (true) {
+                    switch (NextToken) {
+                    case TOKEN.NONE:
+                        return null;
+                    case TOKEN.COMMA:
+                        continue;
+                    case TOKEN.CURLY_CLOSE:
+                        return table;
+                    default:
+                        // name
+                        string name = ParseString();
+                        if (name == null) {
+                            return null;
+                        }
+ 
+                        // :
+                        if (NextToken != TOKEN.COLON) {
+                            return null;
+                        }
+                        // ditch the colon
+                        json.Read();
+ 
+                        // value
+                        table[name] = ParseValue();
+                        break;
+                    }
+                }
+            }
+ 
+            List<object> ParseArray() {
+                List<object> array = new List<object>();
+ 
+                // ditch opening bracket
+                json.Read();
+ 
+                // [
+                var parsing = true;
+                while (parsing) {
+                    TOKEN nextToken = NextToken;
+ 
+                    switch (nextToken) {
+                    case TOKEN.NONE:
+                        return null;
+                    case TOKEN.COMMA:
+                        continue;
+                    case TOKEN.SQUARED_CLOSE:
+                        parsing = false;
+                        break;
+                    default:
+                        object value = ParseByToken(nextToken);
+ 
+                        array.Add(value);
+                        break;
+                    }
+                }
+ 
+                return array;
+            }
+ 
+            object ParseValue() {
+                TOKEN nextToken = NextToken;
+                return ParseByToken(nextToken);
+            }
+ 
+            object ParseByToken(TOKEN token) {
+                switch (token) {
+                case TOKEN.STRING:
+                    return ParseString();
+                case TOKEN.NUMBER:
+                    return ParseNumber();
+                case TOKEN.CURLY_OPEN:
+                    return ParseObject();
+                case TOKEN.SQUARED_OPEN:
+                    return ParseArray();
+                case TOKEN.TRUE:
+                    return true;
+                case TOKEN.FALSE:
+                    return false;
+                case TOKEN.NULL:
+                    return null;
+                default:
+                    return null;
+                }
+            }
+ 
+            string ParseString() {
+                StringBuilder s = new StringBuilder();
+                char c;
+ 
+                // ditch opening quote
+                json.Read();
+ 
+                bool parsing = true;
+                while (parsing) {
+ 
+                    if (json.Peek() == -1) {
+                        parsing = false;
+                        break;
+                    }
+ 
+                    c = NextChar;
+                    switch (c) {
+                    case '"':
+                        parsing = false;
+                        break;
+                    case '\\':
+                        if (json.Peek() == -1) {
+                            parsing = false;
+                            break;
+                        }
+ 
+                        c = NextChar;
+                        switch (c) {
+                        case '"':
+                        case '\\':
+                        case '/':
+                            s.Append(c);
+                            break;
+                        case 'b':
+                            s.Append('\b');
+                            break;
+                        case 'f':
+                            s.Append('\f');
+                            break;
+                        case 'n':
+                            s.Append('\n');
+                            break;
+                        case 'r':
+                            s.Append('\r');
+                            break;
+                        case 't':
+                            s.Append('\t');
+                            break;
+                        case 'u':
+                            var hex = new StringBuilder();
+ 
+                            for (int i=0; i< 4; i++) {
+                                hex.Append(NextChar);
+                            }
+ 
+                            s.Append((char) Convert.ToInt32(hex.ToString(), 16));
+                            break;
+                        }
+                        break;
+                    default:
+                        s.Append(c);
+                        break;
+                    }
+                }
+ 
+                return s.ToString();
+            }
+ 
+            object ParseNumber() {
+                string number = NextWord;
+
+					 //NO!! always serialize to a float
+                //if (number.IndexOf('.') == -1) {
+                //    long parsedInt;
+                //    Int64.TryParse(number, out parsedInt);
+                //    return parsedInt;
+                //}
+
+					 float parsedFloat;
+					 float.TryParse(number, out parsedFloat);
+					 return parsedFloat;
+            }
+ 
+            void EatWhitespace() {
+                while (WHITE_SPACE.IndexOf(PeekChar) != -1) {
+                    json.Read();
+ 
+                    if (json.Peek() == -1) {
+                        break;
+                    }
+                }
+            }
+ 
+            char PeekChar {
+                get {
+                    return Convert.ToChar(json.Peek());
+                }
+            }
+ 
+            char NextChar {
+                get {
+                    return Convert.ToChar(json.Read());
+                }
+            }
+ 
+            string NextWord {
+                get {
+                    StringBuilder word = new StringBuilder();
+ 
+                    while (WORD_BREAK.IndexOf(PeekChar) == -1) {
+                        word.Append(NextChar);
+ 
+                        if (json.Peek() == -1) {
+                            break;
+                        }
+                    }
+ 
+                    return word.ToString();
+                }
+            }
+ 
+            TOKEN NextToken {
+                get {
+                    EatWhitespace();
+ 
+                    if (json.Peek() == -1) {
+                        return TOKEN.NONE;
+                    }
+ 
+                    char c = PeekChar;
+                    switch (c) {
+                    case '{':
+                        return TOKEN.CURLY_OPEN;
+                    case '}':
+                        json.Read();
+                        return TOKEN.CURLY_CLOSE;
+                    case '[':
+                        return TOKEN.SQUARED_OPEN;
+                    case ']':
+                        json.Read();
+                        return TOKEN.SQUARED_CLOSE;
+                    case ',':
+                        json.Read();
+                        return TOKEN.COMMA;
+                    case '"':
+                        return TOKEN.STRING;
+                    case ':':
+                        return TOKEN.COLON;
+                    case '0':
+                    case '1':
+                    case '2':
+                    case '3':
+                    case '4':
+                    case '5':
+                    case '6':
+                    case '7':
+                    case '8':
+                    case '9':
+                    case '-':
+                        return TOKEN.NUMBER;
+                    }
+ 
+                    string word = NextWord;
+ 
+                    switch (word) {
+                    case "false":
+                        return TOKEN.FALSE;
+                    case "true":
+                        return TOKEN.TRUE;
+                    case "null":
+                        return TOKEN.NULL;
+                    }
+ 
+                    return TOKEN.NONE;
+                }
+            }
+        }
+ 
+        /// <summary>
+        /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
+        /// </summary>
+        /// <param name="json">A Dictionary&lt;string, object&gt; / List&lt;object&gt;</param>
+        /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
+        public static string Serialize(object obj) {
+            return Serializer.Serialize(obj);
+        }
+ 
+        sealed class Serializer {
+            StringBuilder builder;
+ 
+            Serializer() {
+                builder = new StringBuilder();
+            }
+ 
+            public static string Serialize(object obj) {
+                var instance = new Serializer();
+ 
+                instance.SerializeValue(obj);
+ 
+                return instance.builder.ToString();
+            }
+ 
+            void SerializeValue(object value) {
+                IList asList;
+                IDictionary asDict;
+                string asStr;
+ 
+                if (value == null) {
+                    builder.Append("null");
+                }
+                else if ((asStr = value as string) != null) {
+                    SerializeString(asStr);
+                }
+                else if (value is bool) {
+                    builder.Append(value.ToString().ToLower());
+                }
+                else if ((asList = value as IList) != null) {
+                    SerializeArray(asList);
+                }
+                else if ((asDict = value as IDictionary) != null) {
+                    SerializeObject(asDict);
+                }
+                else if (value is char) {
+                    SerializeString(value.ToString());
+                }
+                else {
+                    SerializeOther(value);
+                }
+            }
+ 
+            void SerializeObject(IDictionary obj) {
+                bool first = true;
+ 
+                builder.Append('{');
+ 
+                foreach (object e in obj.Keys) {
+                    if (!first) {
+                        builder.Append(',');
+                    }
+ 
+                    SerializeString(e.ToString());
+                    builder.Append(':');
+ 
+                    SerializeValue(obj[e]);
+ 
+                    first = false;
+                }
+ 
+                builder.Append('}');
+            }
+ 
+            void SerializeArray(IList anArray) {
+                builder.Append('[');
+ 
+                bool first = true;
+ 
+                foreach (object obj in anArray) {
+                    if (!first) {
+                        builder.Append(',');
+                    }
+ 
+                    SerializeValue(obj);
+ 
+                    first = false;
+                }
+ 
+                builder.Append(']');
+            }
+ 
+            void SerializeString(string str) {
+                builder.Append('\"');
+ 
+                char[] charArray = str.ToCharArray();
+                foreach (var c in charArray) {
+                    switch (c) {
+                    case '"':
+                        builder.Append("\\\"");
+                        break;
+                    case '\\':
+                        builder.Append("\\\\");
+                        break;
+                    case '\b':
+                        builder.Append("\\b");
+                        break;
+                    case '\f':
+                        builder.Append("\\f");
+                        break;
+                    case '\n':
+                        builder.Append("\\n");
+                        break;
+                    case '\r':
+                        builder.Append("\\r");
+                        break;
+                    case '\t':
+                        builder.Append("\\t");
+                        break;
+                    default:
+                        int codepoint = Convert.ToInt32(c);
+                        if ((codepoint >= 32) && (codepoint <= 126)) {
+                            builder.Append(c);
+                        }
+                        else {
+                            builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0'));
+                        }
+                        break;
+                    }
+                }
+ 
+                builder.Append('\"');
+            }
+ 
+            void SerializeOther(object value) {
+                if (value is float
+                    || value is int
+                    || value is uint
+                    || value is long
+						  || value is float
+                    || value is sbyte
+                    || value is byte
+                    || value is short
+                    || value is ushort
+                    || value is ulong
+                    || value is decimal) {
+                    builder.Append(value.ToString());
+                }
+                else {
+                    SerializeString(value.ToString());
+                }
+            }
+        }
+    }
+}

+ 171 - 0
spine-csharp/src/Skeleton.cs

@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class Skeleton {
+		public SkeletonData Data { get; private set; }
+		public List<Bone> Bones { get; private set; }
+		public List<Slot> Slots { get; private set; }
+		public List<Slot> DrawOrder { get; private set; }
+		public Skin Skin { get; set; }
+		public float R { get; set; }
+		public float G { get; set; }
+		public float B { get; set; }
+		public float A { get; set; }
+		public float Time { get; set; }
+		public bool FlipX { get; set; }
+		public bool FlipY { get; set; }
+		public Bone RootBone {
+			get {
+				return Bones.Count == 0 ? null : Bones[0];
+			}
+		}
+
+		public Skeleton (SkeletonData data) {
+			if (data == null) throw new ArgumentNullException("data cannot be null.");
+			Data = data;
+
+			Bones = new List<Bone>(Data.Bones.Count);
+			foreach (BoneData boneData in Data.Bones) {
+				Bone parent = boneData.Parent == null ? null : Bones[Data.Bones.IndexOf(boneData.Parent)];
+				Bones.Add(new Bone(boneData, parent));
+			}
+
+			Slots = new List<Slot>(Data.Slots.Count);
+			DrawOrder = new List<Slot>(Data.Slots.Count);
+			foreach (SlotData slotData in Data.Slots) {
+				Bone bone = Bones[Data.Bones.IndexOf(slotData.BoneData)];
+				Slot slot = new Slot(slotData, this, bone);
+				Slots.Add(slot);
+				DrawOrder.Add(slot);
+			}
+
+			R = 1;
+			G = 1;
+			B = 1;
+			A = 1;
+		}
+
+		/** Updates the world transform for each bone. */
+		public void UpdateWorldTransform () {
+			bool flipX = FlipX;
+			bool flipY = FlipY;
+			List<Bone> bones = Bones;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				bones[i].UpdateWorldTransform(flipX, flipY);
+		}
+
+		/** Sets the bones and slots to their bind pose values. */
+		public void SetToBindPose () {
+			SetBonesToBindPose();
+			SetSlotsToBindPose();
+		}
+
+		public void SetBonesToBindPose () {
+			List<Bone> bones = this.Bones;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				bones[i].SetToBindPose();
+		}
+
+		public void SetSlotsToBindPose () {
+			List<Slot> slots = this.Slots;
+			for (int i = 0, n = slots.Count; i < n; i++)
+				slots[i].SetToBindPose(i);
+		}
+
+		/** @return May be null. */
+		public Bone FindBone (String boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
+			List<Bone> bones = this.Bones;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bones[i];
+				if (bone.Data.Name == boneName) return bone;
+			}
+			return null;
+		}
+
+		/** @return -1 if the bone was not found. */
+		public int FindBoneIndex (String boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
+			List<Bone> bones = this.Bones;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				if (bones[i].Data.Name == boneName) return i;
+			return -1;
+		}
+
+		/** @return May be null. */
+		public Slot FindSlot (String slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			List<Slot> slots = this.Slots;
+			for (int i = 0, n = slots.Count; i < n; i++) {
+				Slot slot = slots[i];
+				if (slot.Data.Name == slotName) return slot;
+			}
+			return null;
+		}
+
+		/** @return -1 if the bone was not found. */
+		public int FindSlotIndex (String slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			List<Slot> slots = this.Slots;
+			for (int i = 0, n = slots.Count; i < n; i++)
+				if (slots[i].Data.Name.Equals(slotName)) return i;
+			return -1;
+		}
+
+		/** Sets a skin by name.
+		 * @see #setSkin(Skin) */
+		public void SetSkin (String skinName) {
+			Skin skin = Data.FindSkin(skinName);
+			if (skin == null) throw new ArgumentException("Skin not found: " + skinName);
+			SetSkin(skin);
+		}
+
+		/** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments
+	 * from the new skin are attached if the corresponding attachment from the old skin was attached.
+	 * @param newSkin May be null. */
+		public void SetSkin (Skin newSkin) {
+			if (Skin != null && newSkin != null) newSkin.AttachAll(this, Skin);
+			Skin = newSkin;
+		}
+
+		/** @return May be null. */
+		public Attachment GetAttachment (String slotName, String attachmentName) {
+			return GetAttachment(Data.FindSlotIndex(slotName), attachmentName);
+		}
+
+		/** @return May be null. */
+		public Attachment GetAttachment (int slotIndex, String attachmentName) {
+			if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null.");
+			if (Skin != null) {
+				Attachment attachment = Skin.GetAttachment(slotIndex, attachmentName);
+				if (attachment != null) return attachment;
+			}
+			if (Data.DefaultSkin != null) return Data.DefaultSkin.GetAttachment(slotIndex, attachmentName);
+			return null;
+		}
+
+		/** @param attachmentName May be null. */
+		public void SetAttachment (String slotName, String attachmentName) {
+			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			List<Slot> slots = Slots;
+			for (int i = 0, n = slots.Count; i < n; i++) {
+				Slot slot = slots[i];
+				if (slot.Data.Name == slotName) {
+					Attachment attachment = null;
+					if (attachmentName != null) {
+						attachment = GetAttachment(i, attachmentName);
+						if (attachment == null) throw new ArgumentNullException("Attachment not found: " + attachmentName + ", for slot: " + slotName);
+					}
+					slot.Attachment = attachment;
+					return;
+				}
+			}
+			throw new Exception("Slot not found: " + slotName);
+		}
+
+		public void Update (float delta) {
+			Time += delta;
+		}
+	}
+}

+ 111 - 0
spine-csharp/src/SkeletonData.cs

@@ -0,0 +1,111 @@
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class SkeletonData {
+		public String Name { get; set; }
+		public List<BoneData> Bones { get; private set; } // Ordered parents first.
+		public List<SlotData> Slots { get; private set; } // Bind pose draw order.
+		public List<Skin> Skins { get; private set; }
+		/** May be null. */
+		public Skin DefaultSkin;
+		public List<Animation> Animations { get; private set; }
+
+		public SkeletonData () {
+			Bones = new List<BoneData>();
+			Slots = new List<SlotData>();
+			Skins = new List<Skin>();
+			Animations = new List<Animation>();
+		}
+
+		// --- Bones.
+
+		public void AddBone (BoneData bone) {
+			if (bone == null) throw new ArgumentNullException("bone cannot be null.");
+			Bones.Add(bone);
+		}
+
+
+		/** @return May be null. */
+		public BoneData FindBone (String boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
+			for (int i = 0, n = Bones.Count; i < n; i++) {
+				BoneData bone = Bones[i];
+				if (bone.Name == boneName) return bone;
+			}
+			return null;
+		}
+
+		/** @return -1 if the bone was not found. */
+		public int FindBoneIndex (String boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
+			for (int i = 0, n = Bones.Count; i < n; i++)
+				if (Bones[i].Name == boneName) return i;
+			return -1;
+		}
+
+		// --- Slots.
+
+		public void AddSlot (SlotData slot) {
+			if (slot == null) throw new ArgumentNullException("slot cannot be null.");
+			Slots.Add(slot);
+		}
+
+		/** @return May be null. */
+		public SlotData FindSlot (String slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			for (int i = 0, n = Slots.Count; i < n; i++) {
+				SlotData slot = Slots[i];
+				if (slot.Name == slotName) return slot;
+			}
+			return null;
+		}
+
+		/** @return -1 if the bone was not found. */
+		public int FindSlotIndex (String slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			for (int i = 0, n = Slots.Count; i < n; i++)
+				if (Slots[i].Name == slotName) return i;
+			return -1;
+		}
+
+		// --- Skins.
+
+		public void AddSkin (Skin skin) {
+			if (skin == null) throw new ArgumentNullException("skin cannot be null.");
+			Skins.Add(skin);
+		}
+
+		/** @return May be null. */
+		public Skin FindSkin (String skinName) {
+			if (skinName == null) throw new ArgumentNullException("skinName cannot be null.");
+			foreach (Skin skin in Skins)
+				if (skin.Name == skinName) return skin;
+			return null;
+		}
+
+		// --- Animations.
+
+		public void AddAnimation (Animation animation) {
+			if (animation == null) throw new ArgumentNullException("animation cannot be null.");
+			Animations.Add(animation);
+		}
+
+		/** @return May be null. */
+		public Animation FindAnimation (String animationName) {
+			if (animationName == null) throw new ArgumentNullException("animationName cannot be null.");
+			for (int i = 0, n = Animations.Count; i < n; i++) {
+				Animation animation = Animations[i];
+				if (animation.Name == animationName) return animation;
+			}
+			return null;
+		}
+
+		// ---
+
+		override public String ToString () {
+			return Name != null ? Name : base.ToString();
+		}
+	}
+}

+ 256 - 0
spine-csharp/src/SkeletonJson.cs

@@ -0,0 +1,256 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class SkeletonJson {
+		static public String TIMELINE_SCALE = "scale";
+		static public String TIMELINE_ROTATE = "rotate";
+		static public String TIMELINE_TRANSLATE = "translate";
+		static public String TIMELINE_ATTACHMENT = "attachment";
+		static public String TIMELINE_COLOR = "color";
+
+		static public String ATTACHMENT_REGION = "region";
+		static public String ATTACHMENT_REGION_SEQUENCE = "regionSequence";
+
+		private AttachmentLoader attachmentLoader;
+		public float Scale { get; set; }
+
+		public SkeletonJson (BaseAtlas atlas) {
+			this.attachmentLoader = new AtlasAttachmentLoader(atlas);
+			Scale = 1;
+		}
+
+		public SkeletonJson (AttachmentLoader attachmentLoader) {
+			this.attachmentLoader = attachmentLoader;
+			Scale = 1;
+		}
+
+		public SkeletonData readSkeletonData (String name, String json) {
+			if (json == null) throw new ArgumentNullException("json cannot be null.");
+
+			SkeletonData skeletonData = new SkeletonData();
+			skeletonData.Name = name;
+
+			var root = Json.Deserialize(json) as Dictionary<String, Object>;
+
+			// Bones.
+			foreach (Dictionary<String, Object> boneMap in (List<Object>)root["bones"]) {
+				BoneData parent = null;
+				if (boneMap.ContainsKey("parent")) {
+					parent = skeletonData.FindBone((String)boneMap["parent"]);
+					if (parent == null) throw new Exception("Parent bone not found: " + boneMap["parent"]);
+				}
+				BoneData boneData = new BoneData((String)boneMap["name"], parent);
+				boneData.Length = getFloat(boneMap, "length", 0) * Scale;
+				boneData.X = getFloat(boneMap, "x", 0) * Scale;
+				boneData.Y = getFloat(boneMap, "y", 0) * Scale;
+				boneData.Rotation = getFloat(boneMap, "rotation", 0);
+				boneData.ScaleX = getFloat(boneMap, "scaleX", 1);
+				boneData.ScaleY = getFloat(boneMap, "scaleY", 1);
+				skeletonData.AddBone(boneData);
+			}
+
+			// Slots.
+			if (root.ContainsKey("slots")) {
+				var slots = (List<Object>)root["slots"];
+				foreach (Dictionary<String, Object> slotMap in (List<Object>)slots) {
+					String slotName = (String)slotMap["name"];
+					String boneName = (String)slotMap["bone"];
+					BoneData boneData = skeletonData.FindBone(boneName);
+					if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
+					SlotData slotData = new SlotData(slotName, boneData);
+
+					if (slotMap.ContainsKey("color")) {
+						String color = (String)slotMap["color"];
+						slotData.R = toColor(color, 0);
+						slotData.G = toColor(color, 1);
+						slotData.B = toColor(color, 2);
+						slotData.A = toColor(color, 3);
+					}
+
+					slotData.AttachmentName = (String)slotMap["attachment"];
+
+					skeletonData.AddSlot(slotData);
+				}
+			}
+
+			// Skins.
+			if (root.ContainsKey("skins")) {
+				Dictionary<String, Object> skinMap = (Dictionary<String, Object>)root["skins"];
+				foreach (KeyValuePair<String, Object> entry in skinMap) {
+					Skin skin = new Skin(entry.Key);
+					foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)entry.Value) {
+						int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
+						foreach (KeyValuePair<String, Object> attachmentEntry in ((Dictionary<String, Object>)slotEntry.Value)) {
+							Attachment attachment = readAttachment(attachmentEntry.Key, (Dictionary<String, Object>)attachmentEntry.Value);
+							skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment);
+						}
+					}
+					skeletonData.AddSkin(skin);
+					if (skin.Name == "default") skeletonData.DefaultSkin = skin;
+				}
+			}
+
+
+			// Animations.
+			if (root.ContainsKey("animations")) {
+				Dictionary<String, Object> animationMap = (Dictionary<String, Object>)root["animations"];
+				foreach (KeyValuePair<String, Object> entry in animationMap)
+					readAnimation(entry.Key, (Dictionary<String, Object>)entry.Value, skeletonData);
+			}
+
+			skeletonData.Bones.TrimExcess();
+			skeletonData.Slots.TrimExcess();
+			skeletonData.Skins.TrimExcess();
+			skeletonData.Animations.TrimExcess();
+			return skeletonData;
+		}
+
+		private Attachment readAttachment (String name, Dictionary<String, Object> map) {
+			if (map.ContainsKey("name")) name = (String)map["name"];
+
+			AttachmentType type = AttachmentType.region;
+			if (map.ContainsKey("type")) type = (AttachmentType)Enum.Parse(typeof(AttachmentType), (String)map["type"], false);
+			Attachment attachment = attachmentLoader.NewAttachment(type, name);
+
+			if (attachment is RegionAttachment) {
+				RegionAttachment regionAttachment = (RegionAttachment)attachment;
+				regionAttachment.X = getFloat(map, "x", 0) * Scale;
+				regionAttachment.Y = getFloat(map, "y", 0) * Scale;
+				regionAttachment.ScaleX = getFloat(map, "scaleX", 1);
+				regionAttachment.ScaleY = getFloat(map, "scaleY", 1);
+				regionAttachment.Rotation = getFloat(map, "rotation", 0);
+				regionAttachment.Width = getFloat(map, "width", 32) * Scale;
+				regionAttachment.Height = getFloat(map, "height", 32) * Scale;
+				regionAttachment.UpdateOffset();
+			}
+
+			return attachment;
+		}
+
+		private float getFloat (Dictionary<String, Object> map, String name, float defaultValue) {
+			if (!map.ContainsKey(name)) return (float)defaultValue;
+			return (float)map[name];
+		}
+
+		public static float toColor (String hexString, int colorIndex) {
+			if (hexString.Length != 8) throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString);
+			return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
+		}
+
+		private void readAnimation (String name, Dictionary<String, Object> map, SkeletonData skeletonData) {
+			var timelines = new List<Timeline>();
+			float duration = 0;
+
+			var bonesMap = (Dictionary<String, Object>)map["bones"];
+			foreach (KeyValuePair<String, Object> entry in bonesMap) {
+				String boneName = entry.Key;
+				int boneIndex = skeletonData.FindBoneIndex(boneName);
+				if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
+
+				Dictionary<String, Object> timelineMap = (Dictionary<String, Object>)entry.Value;
+				foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
+					List<Object> values = (List<Object>)timelineEntry.Value;
+					String timelineName = (String)timelineEntry.Key;
+					if (timelineName.Equals(TIMELINE_ROTATE)) {
+						RotateTimeline timeline = new RotateTimeline(values.Count);
+						timeline.BoneIndex = boneIndex;
+
+						int frameIndex = 0;
+						foreach (Dictionary<String, Object> valueMap in values) {
+							float time = (float)valueMap["time"];
+							timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]);
+							readCurve(timeline, frameIndex, valueMap);
+							frameIndex++;
+						}
+						timelines.Add(timeline);
+						duration = Math.Max(duration, timeline.Frames[timeline.FrameCount * 2 - 2]);
+
+					} else if (timelineName.Equals(TIMELINE_TRANSLATE) || timelineName.Equals(TIMELINE_SCALE)) {
+						TranslateTimeline timeline;
+						float timelineScale = 1;
+						if (timelineName.Equals(TIMELINE_SCALE))
+							timeline = new ScaleTimeline(values.Count);
+						else {
+							timeline = new TranslateTimeline(values.Count);
+							timelineScale = Scale;
+						}
+						timeline.BoneIndex = boneIndex;
+
+						int frameIndex = 0;
+						foreach (Dictionary<String, Object> valueMap in values) {
+							float time = (float)valueMap["time"];
+							float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0;
+							float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0;
+							timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale);
+							readCurve(timeline, frameIndex, valueMap);
+							frameIndex++;
+						}
+						timelines.Add(timeline);
+						duration = Math.Max(duration, timeline.Frames[timeline.FrameCount * 3 - 3]);
+
+					} else
+						throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
+				}
+			}
+
+			if (map.ContainsKey("slots")) {
+				Dictionary<String, Object> slotsMap = (Dictionary<String, Object>)map["slots"];
+				foreach (KeyValuePair<String, Object> entry in slotsMap) {
+					String slotName = entry.Key;
+					int slotIndex = skeletonData.FindSlotIndex(slotName);
+					Dictionary<String, Object> timelineMap = (Dictionary<String, Object>)entry.Value;
+
+					foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
+						List<Dictionary<String, Object>> values = (List<Dictionary<String, Object>>)timelineEntry.Value;
+						String timelineName = (String)timelineEntry.Key;
+						if (timelineName.Equals(TIMELINE_COLOR)) {
+							ColorTimeline timeline = new ColorTimeline(values.Count);
+							timeline.SlotIndex = slotIndex;
+
+							int frameIndex = 0;
+							foreach (Dictionary<String, Object> valueMap in values) {
+								float time = (float)valueMap["time"];
+								String c = (String)valueMap["color"];
+								timeline.setFrame(frameIndex, time, toColor(c, 0), toColor(c, 1), toColor(c, 2), toColor(c, 3));
+								readCurve(timeline, frameIndex, valueMap);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.Frames[timeline.FrameCount * 5 - 5]);
+
+						} else if (timelineName.Equals(TIMELINE_ATTACHMENT)) {
+							AttachmentTimeline timeline = new AttachmentTimeline(values.Count);
+							timeline.SlotIndex = slotIndex;
+
+							int frameIndex = 0;
+							foreach (Dictionary<String, Object> valueMap in values) {
+								float time = (float)valueMap["time"];
+								timeline.setFrame(frameIndex++, time, (String)valueMap["name"]);
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.Frames[timeline.FrameCount - 1]);
+
+						} else
+							throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
+					}
+				}
+			}
+
+			timelines.TrimExcess();
+			skeletonData.AddAnimation(new Animation(name, timelines, duration));
+		}
+
+		private void readCurve (CurveTimeline timeline, int frameIndex, Dictionary<String, Object> valueMap) {
+			if (!valueMap.ContainsKey("curve")) return;
+			Object curveObject = valueMap["curve"];
+			if (curveObject.Equals("stepped"))
+				timeline.SetStepped(frameIndex);
+			else if (curveObject.GetType() == typeof(List<float>)) {
+				List<float> curve = (List<float>)curveObject;
+				timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
+			}
+		}
+	}
+}

+ 54 - 0
spine-csharp/src/Skin.cs

@@ -0,0 +1,54 @@
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	/** Stores attachments by slot index and attachment name. */
+	public class Skin {
+		public String Name { get; private set; }
+		private Dictionary<Tuple<int, String>, Attachment> attachments = new Dictionary<Tuple<int, String>, Attachment>();
+
+		public Skin (String name) {
+			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			Name = name;
+		}
+
+		public void AddAttachment (int slotIndex, String name, Attachment attachment) {
+			if (attachment == null) throw new ArgumentNullException("attachment cannot be null.");
+			attachments.Add(Tuple.Create<int, String>(slotIndex, name), attachment);
+		}
+
+		/** @return May be null. */
+		public Attachment GetAttachment (int slotIndex, String name) {
+			return attachments[Tuple.Create<int, String>(slotIndex, name)];
+		}
+
+		public void FindNamesForSlot (int slotIndex, List<String> names) {
+			if (names == null) throw new ArgumentNullException("names cannot be null.");
+			foreach (Tuple<int, String> key in attachments.Keys)
+				if (key.Item1 == slotIndex) names.Add(key.Item2);
+		}
+
+		public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
+			if (attachments == null) throw new ArgumentNullException("attachments cannot be null.");
+			foreach (KeyValuePair<Tuple<int, String>, Attachment> entry in this.attachments)
+				if (entry.Key.Item1 == slotIndex) attachments.Add(entry.Value);
+		}
+
+		override public String ToString () {
+			return Name;
+		}
+
+		/** Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. */
+		internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
+			foreach (KeyValuePair<Tuple<int, String>, Attachment> entry in oldSkin.attachments) {
+				int slotIndex = entry.Key.Item1;
+				Slot slot = skeleton.Slots[slotIndex];
+				if (slot.Attachment == entry.Value) {
+					Attachment attachment = GetAttachment(slotIndex, entry.Key.Item2);
+					if (attachment != null) slot.Attachment = attachment;
+				}
+			}
+		}
+	}
+}

+ 61 - 0
spine-csharp/src/Slot.cs

@@ -0,0 +1,61 @@
+using System;
+
+namespace Spine {
+	public class Slot {
+		public SlotData Data { get; private set; }
+		public Bone Bone { get; private set; }
+		public Skeleton Skeleton { get; private set; }
+		public float R { get; set; }
+		public float G { get; set; }
+		public float B { get; set; }
+		public float A { get; set; }
+
+		/** May be null. */
+		private Attachment attachment;
+		public Attachment Attachment {
+			get {
+				return attachment;
+			}
+			set {
+				attachment = value;
+				attachmentTime = Skeleton.Time;
+			}
+		}
+
+		private float attachmentTime;
+		public float AttachmentTime {
+			get {
+				return Skeleton.Time - attachmentTime;
+			}
+			set {
+				attachmentTime = Skeleton.Time - value;
+			}
+		}
+
+		public Slot (SlotData data, Skeleton skeleton, Bone bone) {
+			if (data == null) throw new ArgumentNullException("data cannot be null.");
+			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+			if (bone == null) throw new ArgumentNullException("bone cannot be null.");
+			Data = data;
+			Skeleton = skeleton;
+			Bone = bone;
+			SetToBindPose();
+		}
+
+		internal void SetToBindPose (int slotIndex) {
+			R = Data.R;
+			G = Data.G;
+			B = Data.B;
+			A = Data.A;
+			Attachment = Data.AttachmentName == null ? null : Skeleton.GetAttachment(slotIndex, Data.AttachmentName);
+		}
+
+		public void SetToBindPose () {
+			SetToBindPose(Skeleton.Data.Slots.IndexOf(Data));
+		}
+
+		override public String ToString () {
+			return Data.Name;
+		}
+	}
+}

+ 29 - 0
spine-csharp/src/SlotData.cs

@@ -0,0 +1,29 @@
+using System;
+
+namespace Spine {
+	public class SlotData {
+		public String Name { get; private set; }
+		public BoneData BoneData { get; private set; }
+		public float R { get; set; }
+		public float G { get; set; }
+		public float B { get; set; }
+		public float A { get; set; }
+		/** @param attachmentName May be null. */
+		public String AttachmentName { get; set; }
+
+		public SlotData (String name, BoneData boneData) {
+			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			if (boneData == null) throw new ArgumentNullException("boneData cannot be null.");
+			Name = name;
+			BoneData = boneData;
+			R = 1;
+			G = 1;
+			B = 1;
+			A = 1;
+		}
+
+		override public String ToString () {
+			return Name;
+		}
+	}
+}

+ 34 - 0
spine-xna/Properties/AssemblyInfo.cs

@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("spine-xna")]
+[assembly: AssemblyProduct("spine-xna")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type. Only Windows
+// assemblies support COM.
+[assembly: ComVisible(false)]
+
+// On Windows, the following GUID is for the ID of the typelib if this
+// project is exposed to COM. On other platforms, it unique identifies the
+// title storage container when deploying this assembly to the device.
+[assembly: Guid("bce68f54-1e09-449a-90a2-b7ca28f491a5")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]

BIN
spine-xna/example/Game.ico


BIN
spine-xna/example/GameThumbnail.png


+ 34 - 0
spine-xna/example/Properties/AssemblyInfo.cs

@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("spine-xna-example")]
+[assembly: AssemblyProduct("spine-xna-example")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type. Only Windows
+// assemblies support COM.
+[assembly: ComVisible(false)]
+
+// On Windows, the following GUID is for the ID of the typelib if this
+// project is exposed to COM. On other platforms, it unique identifies the
+// title storage container when deploying this assembly to the device.
+[assembly: Guid("078eb4ac-3a70-4ab4-b103-a048c6a15898")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]

+ 285 - 0
spine-xna/example/data/goblins.atlas

@@ -0,0 +1,285 @@
+
+goblins.png
+format: RGBA8888
+filter: Linear,Linear
+repeat: none
+spear
+  rotate: false
+  xy: 2, 142
+  size: 22, 368
+  orig: 22, 368
+  offset: 0, 0
+  index: -1
+goblingirl/head
+  rotate: false
+  xy: 26, 429
+  size: 103, 81
+  orig: 103, 81
+  offset: 0, 0
+  index: -1
+goblin/head
+  rotate: false
+  xy: 26, 361
+  size: 103, 66
+  orig: 103, 66
+  offset: 0, 0
+  index: -1
+goblin/torso
+  rotate: false
+  xy: 131, 414
+  size: 68, 96
+  orig: 68, 96
+  offset: 0, 0
+  index: -1
+goblingirl/torso
+  rotate: false
+  xy: 26, 263
+  size: 68, 96
+  orig: 68, 96
+  offset: 0, 0
+  index: -1
+dagger
+  rotate: false
+  xy: 26, 153
+  size: 26, 108
+  orig: 26, 108
+  offset: 0, 0
+  index: -1
+goblin/right-lower-leg
+  rotate: false
+  xy: 201, 434
+  size: 36, 76
+  orig: 36, 76
+  offset: 0, 0
+  index: -1
+goblingirl/right-lower-leg
+  rotate: false
+  xy: 54, 185
+  size: 36, 76
+  orig: 36, 76
+  offset: 0, 0
+  index: -1
+goblin/left-upper-leg
+  rotate: false
+  xy: 96, 286
+  size: 33, 73
+  orig: 33, 73
+  offset: 0, 0
+  index: -1
+goblin/pelvis
+  rotate: false
+  xy: 131, 369
+  size: 62, 43
+  orig: 62, 43
+  offset: 0, 0
+  index: -1
+goblingirl/pelvis
+  rotate: false
+  xy: 131, 324
+  size: 62, 43
+  orig: 62, 43
+  offset: 0, 0
+  index: -1
+goblin/right-foot
+  rotate: false
+  xy: 131, 289
+  size: 63, 33
+  orig: 63, 33
+  offset: 0, 0
+  index: -1
+goblin/left-lower-leg
+  rotate: false
+  xy: 2, 70
+  size: 33, 70
+  orig: 33, 70
+  offset: 0, 0
+  index: -1
+goblin/right-upper-leg
+  rotate: false
+  xy: 2, 5
+  size: 34, 63
+  orig: 34, 63
+  offset: 0, 0
+  index: -1
+goblingirl/left-lower-leg
+  rotate: false
+  xy: 195, 342
+  size: 33, 70
+  orig: 33, 70
+  offset: 0, 0
+  index: -1
+goblingirl/left-upper-leg
+  rotate: false
+  xy: 37, 81
+  size: 33, 70
+  orig: 33, 70
+  offset: 0, 0
+  index: -1
+goblingirl/right-upper-leg
+  rotate: false
+  xy: 38, 16
+  size: 34, 63
+  orig: 34, 63
+  offset: 0, 0
+  index: -1
+goblin/eyes-closed
+  rotate: false
+  xy: 38, 2
+  size: 34, 12
+  orig: 34, 12
+  offset: 0, 0
+  index: -1
+goblin/undies
+  rotate: false
+  xy: 54, 154
+  size: 36, 29
+  orig: 36, 29
+  offset: 0, 0
+  index: -1
+goblin/right-arm
+  rotate: false
+  xy: 72, 102
+  size: 23, 50
+  orig: 23, 50
+  offset: 0, 0
+  index: -1
+goblin/left-foot
+  rotate: false
+  xy: 131, 256
+  size: 65, 31
+  orig: 65, 31
+  offset: 0, 0
+  index: -1
+goblingirl/right-arm
+  rotate: false
+  xy: 196, 290
+  size: 28, 50
+  orig: 28, 50
+  offset: 0, 0
+  index: -1
+goblingirl/left-shoulder
+  rotate: false
+  xy: 226, 294
+  size: 28, 46
+  orig: 28, 46
+  offset: 0, 0
+  index: -1
+goblin/left-arm
+  rotate: false
+  xy: 198, 253
+  size: 37, 35
+  orig: 37, 35
+  offset: 0, 0
+  index: -1
+goblingirl/left-foot
+  rotate: false
+  xy: 92, 223
+  size: 65, 31
+  orig: 65, 31
+  offset: 0, 0
+  index: -1
+goblingirl/right-foot
+  rotate: false
+  xy: 92, 188
+  size: 63, 33
+  orig: 63, 33
+  offset: 0, 0
+  index: -1
+goblin/undie-straps
+  rotate: false
+  xy: 92, 167
+  size: 55, 19
+  orig: 55, 19
+  offset: 0, 0
+  index: -1
+goblingirl/left-arm
+  rotate: false
+  xy: 159, 219
+  size: 37, 35
+  orig: 37, 35
+  offset: 0, 0
+  index: -1
+goblin/right-shoulder
+  rotate: false
+  xy: 97, 120
+  size: 39, 45
+  orig: 39, 45
+  offset: 0, 0
+  index: -1
+goblingirl/right-shoulder
+  rotate: false
+  xy: 198, 206
+  size: 39, 45
+  orig: 39, 45
+  offset: 0, 0
+  index: -1
+goblin/left-hand
+  rotate: false
+  xy: 157, 176
+  size: 36, 41
+  orig: 36, 41
+  offset: 0, 0
+  index: -1
+goblin/neck
+  rotate: false
+  xy: 195, 163
+  size: 36, 41
+  orig: 36, 41
+  offset: 0, 0
+  index: -1
+goblingirl/undie-straps
+  rotate: false
+  xy: 97, 99
+  size: 55, 19
+  orig: 55, 19
+  offset: 0, 0
+  index: -1
+goblingirl/neck
+  rotate: false
+  xy: 138, 120
+  size: 35, 41
+  orig: 35, 41
+  offset: 0, 0
+  index: -1
+goblingirl/left-hand
+  rotate: false
+  xy: 175, 121
+  size: 35, 40
+  orig: 35, 40
+  offset: 0, 0
+  index: -1
+goblin/left-shoulder
+  rotate: false
+  xy: 212, 117
+  size: 29, 44
+  orig: 29, 44
+  offset: 0, 0
+  index: -1
+goblingirl/eyes-closed
+  rotate: false
+  xy: 154, 97
+  size: 37, 21
+  orig: 37, 21
+  offset: 0, 0
+  index: -1
+goblin/right-hand
+  rotate: false
+  xy: 193, 78
+  size: 36, 37
+  orig: 36, 37
+  offset: 0, 0
+  index: -1
+goblingirl/right-hand
+  rotate: false
+  xy: 74, 39
+  size: 36, 37
+  orig: 36, 37
+  offset: 0, 0
+  index: -1
+goblingirl/undies
+  rotate: false
+  xy: 74, 8
+  size: 36, 29
+  orig: 36, 29
+  offset: 0, 0
+  index: -1

+ 499 - 0
spine-xna/example/data/goblins.json

@@ -0,0 +1,499 @@
+{
+"bones": [
+	{ "name": "root" },
+	{ "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 },
+	{ "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 },
+	{ "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "x": 56.34, "y": 0.98, "rotation": -16.65 },
+	{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 58.94, "y": -7.61, "rotation": 102.43 },
+	{ "name": "right upper leg", "parent": "hip", "length": 42.45, "x": -20.07, "y": -6.83, "rotation": -97.49 },
+	{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 42.99, "y": -0.61, "rotation": -14.34 },
+	{ "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 },
+	{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 93.92 },
+	{ "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -1.51 },
+	{ "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59, "rotation": -13.92 },
+	{ "name": "right shoulder", "parent": "torso", "length": 37.24, "x": 76.02, "y": 18.14, "rotation": 133.88 },
+	{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 37.6, "y": 0.31, "rotation": 36.32 },
+	{ "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 },
+	{ "name": "left shoulder", "parent": "torso", "length": 35.43, "x": 74.04, "y": -20.38, "rotation": -156.96 },
+	{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 37.85, "y": -2.34, "rotation": 28.16 },
+	{ "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 },
+	{ "name": "pelvis", "parent": "hip", "x": 1.41, "y": -6.57 }
+],
+"slots": [
+	{ "name": "left shoulder", "bone": "left shoulder", "attachment": "left shoulder" },
+	{ "name": "left arm", "bone": "left arm", "attachment": "left arm" },
+	{ "name": "left hand item", "bone": "left hand", "attachment": "spear" },
+	{ "name": "left hand", "bone": "left hand", "attachment": "left hand" },
+	{ "name": "left foot", "bone": "left foot", "attachment": "left foot" },
+	{ "name": "left lower leg", "bone": "left lower leg", "attachment": "left lower leg" },
+	{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left upper leg" },
+	{ "name": "neck", "bone": "neck", "attachment": "neck" },
+	{ "name": "torso", "bone": "torso", "attachment": "torso" },
+	{ "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" },
+	{ "name": "right foot", "bone": "right foot", "attachment": "right foot" },
+	{ "name": "right lower leg", "bone": "right lower leg", "attachment": "right lower leg" },
+	{ "name": "undie straps", "bone": "pelvis", "attachment": "undie straps" },
+	{ "name": "undies", "bone": "pelvis", "attachment": "undies" },
+	{ "name": "right upper leg", "bone": "right upper leg", "attachment": "right upper leg" },
+	{ "name": "head", "bone": "head", "attachment": "head" },
+	{ "name": "eyes", "bone": "head" },
+	{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" },
+	{ "name": "right arm", "bone": "right arm", "attachment": "right arm" },
+	{ "name": "right hand item", "bone": "right hand", "attachment": "dagger" },
+	{ "name": "right hand", "bone": "right hand", "attachment": "right hand" }
+],
+"skins": {
+	"default": {
+		"left hand item": {
+			"dagger": { "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 },
+			"spear": { "x": -4.55, "y": 39.2, "rotation": 13.04, "width": 22, "height": 368 }
+		},
+		"right hand item": {
+			"dagger": { "x": 6.51, "y": -24.15, "rotation": -8.06, "width": 26, "height": 108 }
+		}
+	},
+	"goblin": {
+		"neck": {
+			"neck": { "name": "goblin/neck", "x": 10.1, "y": 0.42, "rotation": -93.69, "width": 36, "height": 41 }
+		},
+		"undies": {
+			"undies": { "name": "goblin/undies", "x": 6.3, "y": 0.12, "rotation": 0.91, "width": 36, "height": 29 }
+		},
+		"right hand": {
+			"right hand": { "name": "goblin/right-hand", "x": 7.88, "y": 2.78, "rotation": 91.96, "width": 36, "height": 37 }
+		},
+		"right arm": {
+			"right arm": { "name": "goblin/right-arm", "x": 16.44, "y": -1.04, "rotation": 94.32, "width": 23, "height": 50 }
+		},
+		"head": {
+			"head": { "name": "goblin/head", "x": 25.73, "y": 2.33, "rotation": -92.29, "width": 103, "height": 66 }
+		},
+		"left shoulder": {
+			"left shoulder": { "name": "goblin/left-shoulder", "x": 15.56, "y": -2.26, "rotation": 62.01, "width": 29, "height": 44 }
+		},
+		"left arm": {
+			"left arm": {
+				"name": "goblin/left-arm",
+				"x": 16.7,
+				"y": -1.69,
+				"scaleX": 1.057,
+				"scaleY": 1.057,
+				"rotation": 33.84,
+				"width": 37,
+				"height": 35
+			}
+		},
+		"left hand": {
+			"left hand": {
+				"name": "goblin/left-hand",
+				"x": 3.47,
+				"y": 3.41,
+				"scaleX": 0.892,
+				"scaleY": 0.892,
+				"rotation": 31.14,
+				"width": 36,
+				"height": 41
+			}
+		},
+		"right lower leg": {
+			"right lower leg": { "name": "goblin/right-lower-leg", "x": 25.68, "y": -3.15, "rotation": 111.83, "width": 36, "height": 76 }
+		},
+		"right upper leg": {
+			"right upper leg": { "name": "goblin/right-upper-leg", "x": 20.35, "y": 1.47, "rotation": 97.49, "width": 34, "height": 63 }
+		},
+		"pelvis": {
+			"pelvis": { "name": "goblin/pelvis", "x": -5.61, "y": 0.76, "width": 62, "height": 43 }
+		},
+		"left lower leg": {
+			"left lower leg": { "name": "goblin/left-lower-leg", "x": 23.58, "y": -2.06, "rotation": 105.75, "width": 33, "height": 70 }
+		},
+		"left upper leg": {
+			"left upper leg": { "name": "goblin/left-upper-leg", "x": 29.68, "y": -3.87, "rotation": 89.09, "width": 33, "height": 73 }
+		},
+		"torso": {
+			"torso": { "name": "goblin/torso", "x": 38.09, "y": -3.87, "rotation": -94.95, "width": 68, "height": 96 }
+		},
+		"right shoulder": {
+			"right shoulder": { "name": "goblin/right-shoulder", "x": 15.68, "y": -1.03, "rotation": 130.65, "width": 39, "height": 45 }
+		},
+		"right foot": {
+			"right foot": { "name": "goblin/right-foot", "x": 23.56, "y": 9.8, "rotation": 1.52, "width": 63, "height": 33 }
+		},
+		"left foot": {
+			"left foot": { "name": "goblin/left-foot", "x": 24.85, "y": 8.74, "rotation": 3.32, "width": 65, "height": 31 }
+		},
+		"undie straps": {
+			"undie straps": { "name": "goblin/undie-straps", "x": -3.87, "y": 13.1, "scaleX": 1.089, "width": 55, "height": 19 }
+		},
+		"eyes": {
+			"eyes closed": { "name": "goblin/eyes-closed", "x": 32.21, "y": -21.27, "rotation": -88.92, "width": 34, "height": 12 }
+		}
+	},
+	"goblingirl": {
+		"left upper leg": {
+			"left upper leg": { "name": "goblingirl/left-upper-leg", "x": 30.21, "y": -2.95, "rotation": 89.09, "width": 33, "height": 70 }
+		},
+		"left lower leg": {
+			"left lower leg": { "name": "goblingirl/left-lower-leg", "x": 25.02, "y": -0.6, "rotation": 105.75, "width": 33, "height": 70 }
+		},
+		"left foot": {
+			"left foot": { "name": "goblingirl/left-foot", "x": 25.17, "y": 7.92, "rotation": 3.32, "width": 65, "height": 31 }
+		},
+		"right upper leg": {
+			"right upper leg": { "name": "goblingirl/right-upper-leg", "x": 19.69, "y": 2.13, "rotation": 97.49, "width": 34, "height": 63 }
+		},
+		"right lower leg": {
+			"right lower leg": { "name": "goblingirl/right-lower-leg", "x": 26.15, "y": -3.27, "rotation": 111.83, "width": 36, "height": 76 }
+		},
+		"right foot": {
+			"right foot": { "name": "goblingirl/right-foot", "x": 23.46, "y": 9.66, "rotation": 1.52, "width": 63, "height": 33 }
+		},
+		"torso": {
+			"torso": { "name": "goblingirl/torso", "x": 36.28, "y": -5.14, "rotation": -95.74, "width": 68, "height": 96 }
+		},
+		"left shoulder": {
+			"left shoulder": { "name": "goblingirl/left-shoulder", "x": 19.8, "y": -0.42, "rotation": 61.21, "width": 28, "height": 46 }
+		},
+		"left arm": {
+			"left arm": { "name": "goblingirl/left-arm", "x": 19.64, "y": -2.42, "rotation": 33.05, "width": 37, "height": 35 }
+		},
+		"left hand": {
+			"left hand": {
+				"name": "goblingirl/left-hand",
+				"x": 4.34,
+				"y": 2.39,
+				"scaleX": 0.896,
+				"scaleY": 0.896,
+				"rotation": 30.34,
+				"width": 35,
+				"height": 40
+			}
+		},
+		"neck": {
+			"neck": { "name": "goblingirl/neck", "x": 6.16, "y": -3.14, "rotation": -98.86, "width": 35, "height": 41 }
+		},
+		"head": {
+			"head": { "name": "goblingirl/head", "x": 27.71, "y": -4.32, "rotation": -85.58, "width": 103, "height": 81 }
+		},
+		"right shoulder": {
+			"right shoulder": { "name": "goblingirl/right-shoulder", "x": 14.46, "y": 0.45, "rotation": 129.85, "width": 39, "height": 45 }
+		},
+		"right arm": {
+			"right arm": { "name": "goblingirl/right-arm", "x": 16.85, "y": -0.66, "rotation": 93.52, "width": 28, "height": 50 }
+		},
+		"right hand": {
+			"right hand": { "name": "goblingirl/right-hand", "x": 7.21, "y": 3.43, "rotation": 91.16, "width": 36, "height": 37 }
+		},
+		"pelvis": {
+			"pelvis": { "name": "goblingirl/pelvis", "x": -3.87, "y": 3.18, "width": 62, "height": 43 }
+		},
+		"undie straps": {
+			"undie straps": { "name": "goblingirl/undie-straps", "x": -1.51, "y": 14.18, "width": 55, "height": 19 }
+		},
+		"undies": {
+			"undies": { "name": "goblingirl/undies", "x": 5.4, "y": 1.7, "width": 36, "height": 29 }
+		},
+		"eyes": {
+			"eyes closed": { "name": "goblingirl/eyes-closed", "x": 28, "y": -25.54, "rotation": -87.04, "width": 37, "height": 21 }
+		}
+	}
+},
+"animations": {
+	"walk": {
+		"bones": {
+			"left upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": -26.55 },
+					{ "time": 0.1333, "angle": -8.78 },
+					{ "time": 0.2333, "angle": 9.51 },
+					{ "time": 0.3666, "angle": 30.74 },
+					{ "time": 0.5, "angle": 25.33 },
+					{ "time": 0.6333, "angle": 26.11 },
+					{ "time": 0.7333, "angle": -7.7 },
+					{ "time": 0.8666, "angle": -21.19 },
+					{ "time": 1, "angle": -26.55 }
+				],
+				"translate": [
+					{ "time": 0, "x": -1.32, "y": 1.7 },
+					{ "time": 0.3666, "x": -0.06, "y": 2.42 },
+					{ "time": 1, "x": -1.32, "y": 1.7 }
+				]
+			},
+			"right upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": 42.45 },
+					{ "time": 0.1333, "angle": 52.1 },
+					{ "time": 0.2333, "angle": 8.53 },
+					{ "time": 0.5, "angle": -16.93 },
+					{ "time": 0.6333, "angle": 1.89 },
+					{
+						"time": 0.7333,
+						"angle": 28.06,
+						"curve": [ 0.462, 0.11, 1, 1 ]
+					},
+					{
+						"time": 0.8666,
+						"angle": 58.68,
+						"curve": [ 0.5, 0.02, 1, 1 ]
+					},
+					{ "time": 1, "angle": 42.45 }
+				],
+				"translate": [
+					{ "time": 0, "x": 6.23, "y": 0 },
+					{ "time": 0.2333, "x": 2.14, "y": 2.4 },
+					{ "time": 0.5, "x": 2.44, "y": 4.8 },
+					{ "time": 1, "x": 6.23, "y": 0 }
+				]
+			},
+			"left lower leg": {
+				"rotate": [
+					{ "time": 0, "angle": -22.98 },
+					{ "time": 0.1333, "angle": -63.5 },
+					{ "time": 0.2333, "angle": -73.76 },
+					{ "time": 0.5, "angle": 5.11 },
+					{ "time": 0.6333, "angle": -28.29 },
+					{ "time": 0.7333, "angle": 4.08 },
+					{ "time": 0.8666, "angle": 3.53 },
+					{ "time": 1, "angle": -22.98 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.2333, "x": 2.55, "y": -0.47 },
+					{ "time": 0.5, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			},
+			"left foot": {
+				"rotate": [
+					{ "time": 0, "angle": -3.69 },
+					{ "time": 0.1333, "angle": -10.42 },
+					{ "time": 0.2333, "angle": -5.01 },
+					{ "time": 0.3666, "angle": 3.87 },
+					{ "time": 0.5, "angle": -3.87 },
+					{ "time": 0.6333, "angle": 2.78 },
+					{ "time": 0.7333, "angle": 1.68 },
+					{ "time": 0.8666, "angle": -8.54 },
+					{ "time": 1, "angle": -3.69 }
+				]
+			},
+			"right shoulder": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 5.29,
+						"curve": [ 0.264, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6333, "angle": 6.65 },
+					{ "time": 1, "angle": 5.29 }
+				]
+			},
+			"right arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -4.02,
+						"curve": [ 0.267, 0, 0.804, 0.99 ]
+					},
+					{
+						"time": 0.6333,
+						"angle": 19.78,
+						"curve": [ 0.307, 0, 0.787, 0.99 ]
+					},
+					{ "time": 1, "angle": -4.02 }
+				]
+			},
+			"right hand": {
+				"rotate": [
+					{ "time": 0, "angle": 8.98 },
+					{ "time": 0.6333, "angle": 0.51 },
+					{ "time": 1, "angle": 8.98 }
+				]
+			},
+			"left shoulder": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 6.25,
+						"curve": [ 0.339, 0, 0.683, 1 ]
+					},
+					{
+						"time": 0.5,
+						"angle": -11.78,
+						"curve": [ 0.281, 0, 0.686, 0.99 ]
+					},
+					{ "time": 1, "angle": 6.25 }
+				],
+				"translate": [
+					{ "time": 0, "x": 1.15, "y": 0.23 }
+				]
+			},
+			"left hand": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -21.23,
+						"curve": [ 0.295, 0, 0.755, 0.98 ]
+					},
+					{
+						"time": 0.5,
+						"angle": -27.28,
+						"curve": [ 0.241, 0, 0.75, 0.97 ]
+					},
+					{ "time": 1, "angle": -21.23 }
+				]
+			},
+			"left arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 28.37,
+						"curve": [ 0.339, 0, 0.683, 1 ]
+					},
+					{
+						"time": 0.5,
+						"angle": 60.09,
+						"curve": [ 0.281, 0, 0.686, 0.99 ]
+					},
+					{ "time": 1, "angle": 28.37 }
+				]
+			},
+			"torso": {
+				"rotate": [
+					{ "time": 0, "angle": -10.28 },
+					{
+						"time": 0.1333,
+						"angle": -15.38,
+						"curve": [ 0.545, 0, 0.818, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"angle": -9.78,
+						"curve": [ 0.58, 0.17, 0.669, 0.99 ]
+					},
+					{
+						"time": 0.6333,
+						"angle": -15.75,
+						"curve": [ 0.235, 0.01, 0.795, 1 ]
+					},
+					{
+						"time": 0.8666,
+						"angle": -7.06,
+						"curve": [ 0.209, 0, 0.816, 0.98 ]
+					},
+					{ "time": 1, "angle": -10.28 }
+				],
+				"translate": [
+					{ "time": 0, "x": -1.29, "y": 1.68 }
+				]
+			},
+			"right foot": {
+				"rotate": [
+					{ "time": 0, "angle": -5.25 },
+					{ "time": 0.2333, "angle": -1.91 },
+					{ "time": 0.3666, "angle": -6.45 },
+					{ "time": 0.5, "angle": -5.39 },
+					{ "time": 0.7333, "angle": -11.68 },
+					{ "time": 0.8666, "angle": 0.46 },
+					{ "time": 1, "angle": -5.25 }
+				]
+			},
+			"right lower leg": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -3.39,
+						"curve": [ 0.316, 0.01, 0.741, 0.98 ]
+					},
+					{
+						"time": 0.1333,
+						"angle": -45.53,
+						"curve": [ 0.229, 0, 0.738, 0.97 ]
+					},
+					{ "time": 0.2333, "angle": -4.83 },
+					{ "time": 0.5, "angle": -19.53 },
+					{ "time": 0.6333, "angle": -64.8 },
+					{
+						"time": 0.7333,
+						"angle": -82.56,
+						"curve": [ 0.557, 0.18, 1, 1 ]
+					},
+					{ "time": 1, "angle": -3.39 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.5, "x": 0, "y": 0 },
+					{ "time": 0.6333, "x": 2.18, "y": 0.21 },
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			},
+			"hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": -4.16 },
+					{
+						"time": 0.1333,
+						"x": 0,
+						"y": -7.05,
+						"curve": [ 0.359, 0.47, 0.646, 0.74 ]
+					},
+					{ "time": 0.3666, "x": 0, "y": 6.78 },
+					{ "time": 0.5, "x": 0, "y": -6.13 },
+					{
+						"time": 0.6333,
+						"x": 0,
+						"y": -7.05,
+						"curve": [ 0.359, 0.47, 0.646, 0.74 ]
+					},
+					{ "time": 0.8666, "x": 0, "y": 6.78 },
+					{ "time": 1, "x": 0, "y": -4.16 }
+				]
+			},
+			"neck": {
+				"rotate": [
+					{ "time": 0, "angle": 3.6 },
+					{ "time": 0.1333, "angle": 17.49 },
+					{ "time": 0.2333, "angle": 6.1 },
+					{ "time": 0.3666, "angle": 3.45 },
+					{ "time": 0.5, "angle": 5.17 },
+					{ "time": 0.6333, "angle": 18.36 },
+					{ "time": 0.7333, "angle": 6.09 },
+					{ "time": 0.8666, "angle": 2.28 },
+					{ "time": 1, "angle": 3.6 }
+				]
+			},
+			"head": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 3.6,
+						"curve": [ 0, 0, 0.704, 1.17 ]
+					},
+					{ "time": 0.1333, "angle": -0.2 },
+					{ "time": 0.2333, "angle": 6.1 },
+					{ "time": 0.3666, "angle": 3.45 },
+					{
+						"time": 0.5,
+						"angle": 5.17,
+						"curve": [ 0, 0, 0.704, 1.61 ]
+					},
+					{ "time": 0.6666, "angle": 1.1 },
+					{ "time": 0.7333, "angle": 6.09 },
+					{ "time": 0.8666, "angle": 2.28 },
+					{ "time": 1, "angle": 3.6 }
+				]
+			}
+		},
+		"slots": {
+			"eyes": {
+				"attachment": [
+					{ "time": 0.7, "name": "eyes closed" },
+					{ "time": 0.8, "name": null }
+				]
+			}
+		}
+	}
+}
+}

BIN
spine-xna/example/data/goblins.png


+ 166 - 0
spine-xna/example/data/spineboy.atlas

@@ -0,0 +1,166 @@
+
+spineboy.png
+format: RGBA8888
+filter: Linear,Linear
+repeat: none
+head
+  rotate: false
+  xy: 1, 122
+  size: 121, 132
+  orig: 121, 132
+  offset: 0, 0
+  index: -1
+torso
+  rotate: false
+  xy: 1, 28
+  size: 68, 92
+  orig: 68, 92
+  offset: 0, 0
+  index: -1
+left-pant-bottom
+  rotate: false
+  xy: 1, 4
+  size: 44, 22
+  orig: 44, 22
+  offset: 0, 0
+  index: -1
+right-pant-bottom
+  rotate: false
+  xy: 47, 8
+  size: 46, 18
+  orig: 46, 18
+  offset: 0, 0
+  index: -1
+right-upper-leg
+  rotate: false
+  xy: 71, 50
+  size: 44, 70
+  orig: 44, 70
+  offset: 0, 0
+  index: -1
+pelvis
+  rotate: false
+  xy: 95, 1
+  size: 63, 47
+  orig: 63, 47
+  offset: 0, 0
+  index: -1
+left-upper-leg
+  rotate: false
+  xy: 117, 53
+  size: 33, 67
+  orig: 33, 67
+  offset: 0, 0
+  index: -1
+right-foot
+  rotate: false
+  xy: 160, 224
+  size: 67, 30
+  orig: 67, 30
+  offset: 0, 0
+  index: -1
+left-shoulder
+  rotate: false
+  xy: 124, 201
+  size: 34, 53
+  orig: 34, 53
+  offset: 0, 0
+  index: -1
+left-ankle
+  rotate: false
+  xy: 229, 222
+  size: 25, 32
+  orig: 25, 32
+  offset: 0, 0
+  index: -1
+left-foot
+  rotate: false
+  xy: 160, 192
+  size: 65, 30
+  orig: 65, 30
+  offset: 0, 0
+  index: -1
+neck
+  rotate: false
+  xy: 124, 171
+  size: 34, 28
+  orig: 34, 28
+  offset: 0, 0
+  index: -1
+right-arm
+  rotate: false
+  xy: 124, 124
+  size: 21, 45
+  orig: 21, 45
+  offset: 0, 0
+  index: -1
+right-ankle
+  rotate: false
+  xy: 227, 190
+  size: 25, 30
+  orig: 25, 30
+  offset: 0, 0
+  index: -1
+left-hand
+  rotate: false
+  xy: 147, 131
+  size: 35, 38
+  orig: 35, 38
+  offset: 0, 0
+  index: -1
+left-arm
+  rotate: false
+  xy: 184, 161
+  size: 35, 29
+  orig: 35, 29
+  offset: 0, 0
+  index: -1
+eyes-closed
+  rotate: false
+  xy: 221, 161
+  size: 34, 27
+  orig: 34, 27
+  offset: 0, 0
+  index: -1
+right-lower-leg
+  rotate: false
+  xy: 152, 65
+  size: 51, 64
+  orig: 51, 64
+  offset: 0, 0
+  index: -1
+right-foot-idle
+  rotate: false
+  xy: 184, 131
+  size: 53, 28
+  orig: 53, 28
+  offset: 0, 0
+  index: -1
+left-lower-leg
+  rotate: false
+  xy: 205, 65
+  size: 49, 64
+  orig: 49, 64
+  offset: 0, 0
+  index: -1
+right-shoulder
+  rotate: false
+  xy: 160, 12
+  size: 52, 51
+  orig: 52, 51
+  offset: 0, 0
+  index: -1
+eyes
+  rotate: false
+  xy: 214, 36
+  size: 34, 27
+  orig: 34, 27
+  offset: 0, 0
+  index: -1
+right-hand
+  rotate: false
+  xy: 214, 2
+  size: 32, 32
+  orig: 32, 32
+  offset: 0, 0
+  index: -1

+ 787 - 0
spine-xna/example/data/spineboy.json

@@ -0,0 +1,787 @@
+{
+"bones": [
+	{ "name": "root" },
+	{ "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 },
+	{ "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 },
+	{ "name": "left lower leg", "parent": "left upper leg", "length": 56.45, "x": 51.78, "y": 3.46, "rotation": -16.65 },
+	{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 64.02, "y": -8.67, "rotation": 102.43 },
+	{ "name": "right upper leg", "parent": "hip", "length": 45.76, "x": -18.27, "rotation": -101.13 },
+	{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 50.21, "y": 0.6, "rotation": -10.7 },
+	{ "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 },
+	{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 94.95 },
+	{ "name": "neck", "parent": "torso", "length": 18.38, "x": 83.64, "y": -1.78, "rotation": 0.9 },
+	{ "name": "head", "parent": "neck", "length": 68.28, "x": 19.09, "y": 6.97, "rotation": -8.94 },
+	{ "name": "right shoulder", "parent": "torso", "length": 49.95, "x": 81.9, "y": 6.79, "rotation": 130.6 },
+	{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 49.95, "y": -0.12, "rotation": 40.12 },
+	{ "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 },
+	{ "name": "left shoulder", "parent": "torso", "length": 44.19, "x": 78.96, "y": -15.75, "rotation": -156.96 },
+	{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 44.19, "y": -0.01, "rotation": 28.16 },
+	{ "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 },
+	{ "name": "pelvis", "parent": "hip", "x": 1.41, "y": -6.57 }
+],
+"slots": [
+	{ "name": "left shoulder", "bone": "left shoulder", "attachment": "left-shoulder" },
+	{ "name": "left arm", "bone": "left arm", "attachment": "left-arm" },
+	{ "name": "left hand", "bone": "left hand", "attachment": "left-hand" },
+	{ "name": "left foot", "bone": "left foot", "attachment": "left-foot" },
+	{ "name": "left lower leg", "bone": "left lower leg", "attachment": "left-lower-leg" },
+	{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left-upper-leg" },
+	{ "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" },
+	{ "name": "right foot", "bone": "right foot", "attachment": "right-foot" },
+	{ "name": "right lower leg", "bone": "right lower leg", "attachment": "right-lower-leg" },
+	{ "name": "right upper leg", "bone": "right upper leg", "attachment": "right-upper-leg" },
+	{ "name": "torso", "bone": "torso", "attachment": "torso" },
+	{ "name": "neck", "bone": "neck", "attachment": "neck" },
+	{ "name": "head", "bone": "head", "attachment": "head" },
+	{ "name": "eyes", "bone": "head", "attachment": "eyes" },
+	{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right-shoulder" },
+	{ "name": "right arm", "bone": "right arm", "attachment": "right-arm" },
+	{ "name": "right hand", "bone": "right hand", "attachment": "right-hand" }
+],
+"skins": {
+	"default": {
+		"left shoulder": {
+			"left-shoulder": { "x": 23.74, "y": 0.11, "rotation": 62.01, "width": 34, "height": 53 }
+		},
+		"left arm": {
+			"left-arm": { "x": 15.11, "y": -0.44, "rotation": 33.84, "width": 35, "height": 29 }
+		},
+		"left hand": {
+			"left-hand": { "x": 0.75, "y": 1.86, "rotation": 31.14, "width": 35, "height": 38 }
+		},
+		"left foot": {
+			"left-foot": { "x": 24.35, "y": 8.88, "rotation": 3.32, "width": 65, "height": 30 }
+		},
+		"left lower leg": {
+			"left-lower-leg": { "x": 24.55, "y": -1.92, "rotation": 105.75, "width": 49, "height": 64 }
+		},
+		"left upper leg": {
+			"left-upper-leg": { "x": 26.12, "y": -1.85, "rotation": 89.09, "width": 33, "height": 67 }
+		},
+		"pelvis": {
+			"pelvis": { "x": -4.83, "y": 10.62, "width": 63, "height": 47 }
+		},
+		"right foot": {
+			"right-foot": { "x": 19.02, "y": 8.47, "rotation": 1.52, "width": 67, "height": 30 }
+		},
+		"right lower leg": {
+			"right-lower-leg": { "x": 23.28, "y": -2.59, "rotation": 111.83, "width": 51, "height": 64 }
+		},
+		"right upper leg": {
+			"right-upper-leg": { "x": 23.03, "y": 0.25, "rotation": 101.13, "width": 44, "height": 70 }
+		},
+		"torso": {
+			"torso": { "x": 44.57, "y": -7.08, "rotation": -94.95, "width": 68, "height": 92 }
+		},
+		"neck": {
+			"neck": { "x": 9.42, "y": -3.66, "rotation": -100.15, "width": 34, "height": 28 }
+		},
+		"head": {
+			"head": { "x": 53.94, "y": -5.75, "rotation": -86.9, "width": 121, "height": 132 }
+		},
+		"eyes": {
+			"eyes": { "x": 28.94, "y": -32.92, "rotation": -86.9, "width": 34, "height": 27 },
+			"eyes-closed": { "x": 28.77, "y": -32.86, "rotation": -86.9, "width": 34, "height": 27 }
+		},
+		"right shoulder": {
+			"right-shoulder": { "x": 25.86, "y": 0.03, "rotation": 134.44, "width": 52, "height": 51 }
+		},
+		"right arm": {
+			"right-arm": { "x": 18.34, "y": -2.64, "rotation": 94.32, "width": 21, "height": 45 }
+		},
+		"right hand": {
+			"right-hand": { "x": 6.82, "y": 1.25, "rotation": 91.96, "width": 32, "height": 32 }
+		}
+	}
+},
+"animations": {
+	"walk": {
+		"bones": {
+			"left upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": -26.55 },
+					{ "time": 0.1333, "angle": -8.78 },
+					{ "time": 0.2666, "angle": 9.51 },
+					{ "time": 0.4, "angle": 30.74 },
+					{ "time": 0.5333, "angle": 25.33 },
+					{ "time": 0.6666, "angle": 26.11 },
+					{ "time": 0.8, "angle": -7.7 },
+					{ "time": 0.9333, "angle": -21.19 },
+					{ "time": 1.0666, "angle": -26.55 }
+				],
+				"translate": [
+					{ "time": 0, "x": -3, "y": -2.25 },
+					{ "time": 0.4, "x": -2.18, "y": -2.25 },
+					{ "time": 1.0666, "x": -3, "y": -2.25 }
+				]
+			},
+			"right upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": 42.45 },
+					{ "time": 0.1333, "angle": 52.1 },
+					{ "time": 0.2666, "angle": 5.96 },
+					{ "time": 0.5333, "angle": -16.93 },
+					{ "time": 0.6666, "angle": 1.89 },
+					{
+						"time": 0.8,
+						"angle": 28.06,
+						"curve": [ 0.462, 0.11, 1, 1 ]
+					},
+					{
+						"time": 0.9333,
+						"angle": 58.68,
+						"curve": [ 0.5, 0.02, 1, 1 ]
+					},
+					{ "time": 1.0666, "angle": 42.45 }
+				],
+				"translate": [
+					{ "time": 0, "x": 8.11, "y": -2.36 },
+					{ "time": 0.1333, "x": 10.03, "y": -2.56 },
+					{ "time": 0.4, "x": 2.76, "y": -2.97 },
+					{ "time": 0.5333, "x": 2.76, "y": -2.81 },
+					{ "time": 0.9333, "x": 8.67, "y": -2.54 },
+					{ "time": 1.0666, "x": 8.11, "y": -2.36 }
+				]
+			},
+			"left lower leg": {
+				"rotate": [
+					{ "time": 0, "angle": -10.21 },
+					{ "time": 0.1333, "angle": -55.64 },
+					{ "time": 0.2666, "angle": -68.12 },
+					{ "time": 0.5333, "angle": 5.11 },
+					{ "time": 0.6666, "angle": -28.29 },
+					{ "time": 0.8, "angle": 4.08 },
+					{ "time": 0.9333, "angle": 3.53 },
+					{ "time": 1.0666, "angle": -10.21 }
+				]
+			},
+			"left foot": {
+				"rotate": [
+					{ "time": 0, "angle": -3.69 },
+					{ "time": 0.1333, "angle": -10.42 },
+					{ "time": 0.2666, "angle": -17.14 },
+					{ "time": 0.4, "angle": -2.83 },
+					{ "time": 0.5333, "angle": -3.87 },
+					{ "time": 0.6666, "angle": 2.78 },
+					{ "time": 0.8, "angle": 1.68 },
+					{ "time": 0.9333, "angle": -8.54 },
+					{ "time": 1.0666, "angle": -3.69 }
+				]
+			},
+			"right shoulder": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 20.89,
+						"curve": [ 0.264, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.1333,
+						"angle": 3.72,
+						"curve": [ 0.272, 0, 0.841, 1 ]
+					},
+					{ "time": 0.6666, "angle": -278.28 },
+					{ "time": 1.0666, "angle": 20.89 }
+				],
+				"translate": [
+					{ "time": 0, "x": -7.84, "y": 7.19 },
+					{ "time": 0.1333, "x": -6.36, "y": 6.42 },
+					{ "time": 0.6666, "x": -11.07, "y": 5.25 },
+					{ "time": 1.0666, "x": -7.84, "y": 7.19 }
+				]
+			},
+			"right arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -4.02,
+						"curve": [ 0.267, 0, 0.804, 0.99 ]
+					},
+					{
+						"time": 0.1333,
+						"angle": -13.99,
+						"curve": [ 0.341, 0, 1, 1 ]
+					},
+					{
+						"time": 0.6666,
+						"angle": 36.54,
+						"curve": [ 0.307, 0, 0.787, 0.99 ]
+					},
+					{ "time": 1.0666, "angle": -4.02 }
+				]
+			},
+			"right hand": {
+				"rotate": [
+					{ "time": 0, "angle": 22.92 },
+					{ "time": 0.4, "angle": -8.97 },
+					{ "time": 0.6666, "angle": 0.51 },
+					{ "time": 1.0666, "angle": 22.92 }
+				]
+			},
+			"left shoulder": {
+				"rotate": [
+					{ "time": 0, "angle": -1.47 },
+					{ "time": 0.1333, "angle": 13.6 },
+					{ "time": 0.6666, "angle": 280.74 },
+					{ "time": 1.0666, "angle": -1.47 }
+				],
+				"translate": [
+					{ "time": 0, "x": -1.76, "y": 0.56 },
+					{ "time": 0.6666, "x": -2.47, "y": 8.14 },
+					{ "time": 1.0666, "x": -1.76, "y": 0.56 }
+				]
+			},
+			"left hand": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 11.58,
+						"curve": [ 0.169, 0.37, 0.632, 1.55 ]
+					},
+					{
+						"time": 0.1333,
+						"angle": 28.13,
+						"curve": [ 0.692, 0, 0.692, 0.99 ]
+					},
+					{
+						"time": 0.6666,
+						"angle": -27.42,
+						"curve": [ 0.117, 0.41, 0.738, 1.76 ]
+					},
+					{ "time": 0.8, "angle": -36.32 },
+					{ "time": 1.0666, "angle": 11.58 }
+				]
+			},
+			"left arm": {
+				"rotate": [
+					{ "time": 0, "angle": -8.27 },
+					{ "time": 0.1333, "angle": 18.43 },
+					{ "time": 0.6666, "angle": 0.88 },
+					{ "time": 1.0666, "angle": -8.27 }
+				]
+			},
+			"torso": {
+				"rotate": [
+					{ "time": 0, "angle": -10.28 },
+					{
+						"time": 0.1333,
+						"angle": -15.38,
+						"curve": [ 0.545, 0, 1, 1 ]
+					},
+					{
+						"time": 0.4,
+						"angle": -9.78,
+						"curve": [ 0.58, 0.17, 1, 1 ]
+					},
+					{ "time": 0.6666, "angle": -15.75 },
+					{ "time": 0.9333, "angle": -7.06 },
+					{ "time": 1.0666, "angle": -10.28 }
+				],
+				"translate": [
+					{ "time": 0, "x": -3.67, "y": 1.68 },
+					{ "time": 0.1333, "x": -3.67, "y": 0.68 },
+					{ "time": 0.4, "x": -3.67, "y": 1.97 },
+					{ "time": 0.6666, "x": -3.67, "y": -0.14 },
+					{ "time": 1.0666, "x": -3.67, "y": 1.68 }
+				]
+			},
+			"right foot": {
+				"rotate": [
+					{ "time": 0, "angle": -5.25 },
+					{ "time": 0.2666, "angle": -4.08 },
+					{ "time": 0.4, "angle": -6.45 },
+					{ "time": 0.5333, "angle": -5.39 },
+					{ "time": 0.8, "angle": -11.68 },
+					{ "time": 0.9333, "angle": 0.46 },
+					{ "time": 1.0666, "angle": -5.25 }
+				]
+			},
+			"right lower leg": {
+				"rotate": [
+					{ "time": 0, "angle": -3.39 },
+					{ "time": 0.1333, "angle": -45.53 },
+					{ "time": 0.2666, "angle": -2.59 },
+					{ "time": 0.5333, "angle": -19.53 },
+					{ "time": 0.6666, "angle": -64.8 },
+					{
+						"time": 0.8,
+						"angle": -82.56,
+						"curve": [ 0.557, 0.18, 1, 1 ]
+					},
+					{ "time": 1.0666, "angle": -3.39 }
+				]
+			},
+			"hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1.0666, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{
+						"time": 0.1333,
+						"x": 0,
+						"y": -7.61,
+						"curve": [ 0.272, 0.86, 1, 1 ]
+					},
+					{ "time": 0.4, "x": 0, "y": 8.7 },
+					{ "time": 0.5333, "x": 0, "y": -0.41 },
+					{
+						"time": 0.6666,
+						"x": 0,
+						"y": -7.05,
+						"curve": [ 0.235, 0.89, 1, 1 ]
+					},
+					{ "time": 0.8, "x": 0, "y": 2.92 },
+					{ "time": 0.9333, "x": 0, "y": 6.78 },
+					{ "time": 1.0666, "x": 0, "y": 0 }
+				]
+			},
+			"neck": {
+				"rotate": [
+					{ "time": 0, "angle": 3.6 },
+					{ "time": 0.1333, "angle": 17.49 },
+					{ "time": 0.2666, "angle": 6.1 },
+					{ "time": 0.4, "angle": 3.45 },
+					{ "time": 0.5333, "angle": 5.17 },
+					{ "time": 0.6666, "angle": 18.36 },
+					{ "time": 0.8, "angle": 6.09 },
+					{ "time": 0.9333, "angle": 2.28 },
+					{ "time": 1.0666, "angle": 3.6 }
+				]
+			},
+			"head": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 3.6,
+						"curve": [ 0, 0, 0.704, 1.61 ]
+					},
+					{ "time": 0.1666, "angle": -0.2 },
+					{ "time": 0.2666, "angle": 6.1 },
+					{ "time": 0.4, "angle": 3.45 },
+					{
+						"time": 0.5333,
+						"angle": 5.17,
+						"curve": [ 0, 0, 0.704, 1.61 ]
+					},
+					{ "time": 0.7, "angle": 1.1 },
+					{ "time": 0.8, "angle": 6.09 },
+					{ "time": 0.9333, "angle": 2.28 },
+					{ "time": 1.0666, "angle": 3.6 }
+				]
+			}
+		}
+	},
+	"jump": {
+		"bones": {
+			"hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.9333, "angle": 0, "curve": "stepped" },
+					{ "time": 1.3666, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": -11.57, "y": -3 },
+					{ "time": 0.2333, "x": -16.2, "y": -19.43 },
+					{
+						"time": 0.3333,
+						"x": 7.66,
+						"y": -8.48,
+						"curve": [ 0.057, 0.06, 0.712, 1 ]
+					},
+					{ "time": 0.3666, "x": 15.38, "y": 5.01 },
+					{ "time": 0.4666, "x": -7.84, "y": 57.22 },
+					{
+						"time": 0.6,
+						"x": -10.81,
+						"y": 96.34,
+						"curve": [ 0.241, 0, 1, 1 ]
+					},
+					{ "time": 0.7333, "x": -7.01, "y": 54.7 },
+					{ "time": 0.8, "x": -10.58, "y": 32.2 },
+					{ "time": 0.9333, "x": -31.99, "y": 0.45 },
+					{ "time": 1.0666, "x": -12.48, "y": -29.47 },
+					{ "time": 1.3666, "x": -11.57, "y": -3 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"left upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": 17.13 },
+					{ "time": 0.2333, "angle": 44.35 },
+					{ "time": 0.3333, "angle": 16.46 },
+					{ "time": 0.4, "angle": -9.88 },
+					{ "time": 0.4666, "angle": -11.42 },
+					{ "time": 0.5666, "angle": 23.46 },
+					{ "time": 0.7666, "angle": 71.82 },
+					{ "time": 0.9333, "angle": 65.53 },
+					{ "time": 1.0666, "angle": 51.01 },
+					{ "time": 1.3666, "angle": 17.13 }
+				],
+				"translate": [
+					{ "time": 0, "x": -3, "y": -2.25, "curve": "stepped" },
+					{ "time": 0.9333, "x": -3, "y": -2.25, "curve": "stepped" },
+					{ "time": 1.3666, "x": -3, "y": -2.25 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"left lower leg": {
+				"rotate": [
+					{ "time": 0, "angle": -16.25 },
+					{ "time": 0.2333, "angle": -52.21 },
+					{ "time": 0.4, "angle": 15.04 },
+					{ "time": 0.4666, "angle": -8.95 },
+					{ "time": 0.5666, "angle": -39.53 },
+					{ "time": 0.7666, "angle": -27.27 },
+					{ "time": 0.9333, "angle": -3.52 },
+					{ "time": 1.0666, "angle": -61.92 },
+					{ "time": 1.3666, "angle": -16.25 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"left foot": {
+				"rotate": [
+					{ "time": 0, "angle": 0.33 },
+					{ "time": 0.2333, "angle": 6.2 },
+					{ "time": 0.3333, "angle": 14.73 },
+					{ "time": 0.4, "angle": -15.54 },
+					{ "time": 0.4333, "angle": -21.2 },
+					{ "time": 0.5666, "angle": -7.55 },
+					{ "time": 0.7666, "angle": -0.67 },
+					{ "time": 0.9333, "angle": -0.58 },
+					{ "time": 1.0666, "angle": 14.64 },
+					{ "time": 1.3666, "angle": 0.33 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"right upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": 25.97 },
+					{ "time": 0.2333, "angle": 46.43 },
+					{ "time": 0.3333, "angle": 22.61 },
+					{ "time": 0.4, "angle": 2.13 },
+					{
+						"time": 0.4666,
+						"angle": 0.04,
+						"curve": [ 0, 0, 0.637, 0.98 ]
+					},
+					{ "time": 0.6, "angle": 65.55 },
+					{ "time": 0.7666, "angle": 64.93 },
+					{ "time": 0.9333, "angle": 41.08 },
+					{ "time": 1.0666, "angle": 66.25 },
+					{ "time": 1.3666, "angle": 25.97 }
+				],
+				"translate": [
+					{ "time": 0, "x": 5.74, "y": 0.61 },
+					{ "time": 0.2333, "x": 4.79, "y": 1.79 },
+					{ "time": 0.3333, "x": 6.05, "y": -4.55 },
+					{ "time": 0.9333, "x": 4.79, "y": 1.79, "curve": "stepped" },
+					{ "time": 1.0666, "x": 4.79, "y": 1.79 },
+					{ "time": 1.3666, "x": 5.74, "y": 0.61 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"right lower leg": {
+				"rotate": [
+					{ "time": 0, "angle": -27.46 },
+					{ "time": 0.2333, "angle": -64.03 },
+					{ "time": 0.4, "angle": -48.36 },
+					{ "time": 0.5666, "angle": -76.86 },
+					{ "time": 0.7666, "angle": -26.89 },
+					{ "time": 0.9, "angle": -18.97 },
+					{ "time": 0.9333, "angle": -14.18 },
+					{ "time": 1.0666, "angle": -80.45 },
+					{ "time": 1.3666, "angle": -27.46 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"right foot": {
+				"rotate": [
+					{ "time": 0, "angle": 1.08 },
+					{ "time": 0.2333, "angle": 16.02 },
+					{ "time": 0.3, "angle": 12.94 },
+					{ "time": 0.3333, "angle": 15.16 },
+					{ "time": 0.4, "angle": -14.7 },
+					{ "time": 0.4333, "angle": -12.85 },
+					{ "time": 0.4666, "angle": -19.18 },
+					{ "time": 0.5666, "angle": -15.82 },
+					{ "time": 0.6, "angle": -3.59 },
+					{ "time": 0.7666, "angle": -3.56 },
+					{ "time": 0.9333, "angle": 1.86 },
+					{ "time": 1.0666, "angle": 16.02 },
+					{ "time": 1.3666, "angle": 1.08 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"torso": {
+				"rotate": [
+					{ "time": 0, "angle": -13.35 },
+					{ "time": 0.2333, "angle": -48.95 },
+					{ "time": 0.4333, "angle": -35.77 },
+					{ "time": 0.6, "angle": -4.59 },
+					{ "time": 0.7666, "angle": 14.61 },
+					{ "time": 0.9333, "angle": 15.74 },
+					{ "time": 1.0666, "angle": -32.44 },
+					{ "time": 1.3666, "angle": -13.35 }
+				],
+				"translate": [
+					{ "time": 0, "x": -3.67, "y": 1.68, "curve": "stepped" },
+					{ "time": 0.9333, "x": -3.67, "y": 1.68, "curve": "stepped" },
+					{ "time": 1.3666, "x": -3.67, "y": 1.68 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"neck": {
+				"rotate": [
+					{ "time": 0, "angle": 12.78 },
+					{ "time": 0.2333, "angle": 16.46 },
+					{ "time": 0.4, "angle": 26.49 },
+					{ "time": 0.6, "angle": 15.51 },
+					{ "time": 0.7666, "angle": 1.34 },
+					{ "time": 0.9333, "angle": 2.35 },
+					{ "time": 1.0666, "angle": 6.08 },
+					{ "time": 1.3, "angle": 21.23 },
+					{ "time": 1.3666, "angle": 12.78 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"head": {
+				"rotate": [
+					{ "time": 0, "angle": 5.19 },
+					{ "time": 0.2333, "angle": 20.27 },
+					{ "time": 0.4, "angle": 15.27 },
+					{ "time": 0.6, "angle": -24.69 },
+					{ "time": 0.7666, "angle": -11.02 },
+					{ "time": 0.9333, "angle": -24.38 },
+					{ "time": 1.0666, "angle": 11.99 },
+					{ "time": 1.3, "angle": 4.86 },
+					{ "time": 1.3666, "angle": 5.19 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"left shoulder": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0.05,
+						"curve": [ 0, 0, 0.62, 1 ]
+					},
+					{
+						"time": 0.2333,
+						"angle": 279.66,
+						"curve": [ 0.218, 0.67, 0.66, 0.99 ]
+					},
+					{
+						"time": 0.5,
+						"angle": 62.27,
+						"curve": [ 0.462, 0, 0.764, 0.58 ]
+					},
+					{ "time": 0.9333, "angle": 28.91 },
+					{ "time": 1.0666, "angle": -8.62 },
+					{ "time": 1.1666, "angle": -18.43 },
+					{ "time": 1.3666, "angle": 0.05 }
+				],
+				"translate": [
+					{ "time": 0, "x": -1.76, "y": 0.56, "curve": "stepped" },
+					{ "time": 0.9333, "x": -1.76, "y": 0.56, "curve": "stepped" },
+					{ "time": 1.3666, "x": -1.76, "y": 0.56 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"left hand": {
+				"rotate": [
+					{ "time": 0, "angle": 11.58, "curve": "stepped" },
+					{ "time": 0.9333, "angle": 11.58, "curve": "stepped" },
+					{ "time": 1.3666, "angle": 11.58 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"left arm": {
+				"rotate": [
+					{ "time": 0, "angle": 0.51 },
+					{ "time": 0.4333, "angle": 12.82 },
+					{ "time": 0.6, "angle": 47.55 },
+					{ "time": 0.9333, "angle": 12.82 },
+					{ "time": 1.1666, "angle": -6.5 },
+					{ "time": 1.3666, "angle": 0.51 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"right shoulder": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 43.82,
+						"curve": [ 0, 0, 0.62, 1 ]
+					},
+					{
+						"time": 0.2333,
+						"angle": -8.74,
+						"curve": [ 0.304, 0.58, 0.709, 0.97 ]
+					},
+					{
+						"time": 0.5333,
+						"angle": -208.02,
+						"curve": [ 0.462, 0, 0.764, 0.58 ]
+					},
+					{ "time": 0.9333, "angle": -246.72 },
+					{ "time": 1.0666, "angle": -307.13 },
+					{ "time": 1.1666, "angle": 37.15 },
+					{ "time": 1.3666, "angle": 43.82 }
+				],
+				"translate": [
+					{ "time": 0, "x": -7.84, "y": 7.19, "curve": "stepped" },
+					{ "time": 0.9333, "x": -7.84, "y": 7.19, "curve": "stepped" },
+					{ "time": 1.3666, "x": -7.84, "y": 7.19 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"right arm": {
+				"rotate": [
+					{ "time": 0, "angle": -4.02 },
+					{ "time": 0.6, "angle": 17.5 },
+					{ "time": 0.9333, "angle": -4.02 },
+					{ "time": 1.1666, "angle": -16.72 },
+					{ "time": 1.3666, "angle": -4.02 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"right hand": {
+				"rotate": [
+					{ "time": 0, "angle": 22.92, "curve": "stepped" },
+					{ "time": 0.9333, "angle": 22.92, "curve": "stepped" },
+					{ "time": 1.3666, "angle": 22.92 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1.3666, "x": 0, "y": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			},
+			"root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.4333, "angle": -14.52 },
+					{ "time": 0.8, "angle": 9.86 },
+					{ "time": 1.3666, "angle": 0 }
+				],
+				"scale": [
+					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.3666, "x": 1, "y": 1 }
+				]
+			}
+		}
+	}
+}
+}

BIN
spine-xna/example/data/spineboy.png


+ 168 - 0
spine-xna/example/spine-xna-example.csproj

@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <ProjectGuid>{29CC4385-294A-4885-A3E8-FD4825E0CFDD}</ProjectGuid>
+    <ProjectTypeGuids>{6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Spine</RootNamespace>
+    <AssemblyName>spine-xna-example</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+    <XnaFrameworkVersion>v4.0</XnaFrameworkVersion>
+    <XnaPlatform>Windows</XnaPlatform>
+    <XnaProfile>HiDef</XnaProfile>
+    <XnaCrossPlatformGroupID>bf3b738e-f348-48d3-b35b-94bc118edb90</XnaCrossPlatformGroupID>
+    <XnaOutputType>Game</XnaOutputType>
+    <ApplicationIcon>Game.ico</ApplicationIcon>
+    <Thumbnail>GameThumbnail.png</Thumbnail>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\x86\Debug</OutputPath>
+    <DefineConstants>DEBUG;TRACE;WINDOWS</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <NoStdLib>true</NoStdLib>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <PlatformTarget>x86</PlatformTarget>
+    <XnaCompressContent>True</XnaCompressContent>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\x86\Release</OutputPath>
+    <DefineConstants>TRACE;WINDOWS</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <NoStdLib>true</NoStdLib>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <PlatformTarget>x86</PlatformTarget>
+    <XnaCompressContent>True</XnaCompressContent>
+  </PropertyGroup>
+  <PropertyGroup>
+    <StartupObject>Spine.ExampleProgram</StartupObject>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.GamerServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Video, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Avatar, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Net, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Storage, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="mscorlib">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Xml">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Core">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Xml.Linq">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Net">
+      <Private>False</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="src\ExampleGame.cs" />
+    <Compile Include="src\ExampleProgram.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Game.ico" />
+    <Content Include="GameThumbnail.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\spine-csharp\spine-csharp.csproj">
+      <Project>{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}</Project>
+      <Name>spine-csharp</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\spine-xna.csproj">
+      <Project>{7F8F2327-C016-49C8-BB4D-F3F77971961E}</Project>
+      <Name>spine-xna</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4 Client Profile %28x86 and x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+      <Visible>False</Visible>
+      <ProductName>Windows Installer 3.1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Xna.Framework.4.0">
+      <Visible>False</Visible>
+      <ProductName>Microsoft XNA Framework Redistributable 4.0</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\Microsoft.Xna.GameStudio.targets" />
+  <!--
+      To modify your build process, add your task inside one of the targets below and uncomment it. 
+      Other similar extension points exist, see Microsoft.Common.targets.
+      <Target Name="BeforeBuild">
+      </Target>
+      <Target Name="AfterBuild">
+      </Target>
+    -->
+</Project>

+ 71 - 0
spine-xna/example/src/ExampleGame.cs

@@ -0,0 +1,71 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.GamerServices;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Media;
+using Spine;
+
+namespace Spine {
+	public class Example : Microsoft.Xna.Framework.Game {
+		GraphicsDeviceManager graphics;
+		SkeletonRenderer skeletonRenderer;
+		Skeleton skeleton;
+		Animation animation;
+		float time;
+
+		public Example () {
+			graphics = new GraphicsDeviceManager(this);
+			graphics.IsFullScreen = false;
+			graphics.PreferredBackBufferWidth = 640;
+			graphics.PreferredBackBufferHeight = 480;
+		}
+
+		protected override void Initialize () {
+			// TODO: Add your initialization logic here
+
+			base.Initialize();
+		}
+
+		protected override void LoadContent () {
+			skeletonRenderer = new SkeletonRenderer(GraphicsDevice);
+			Atlas atlas = new Atlas(GraphicsDevice, "data/spineboy.atlas");
+			SkeletonJson json = new SkeletonJson(atlas);
+			skeleton = new Skeleton(json.readSkeletonData("spineboy", File.ReadAllText("data/spineboy.json")));
+			animation = skeleton.Data.FindAnimation("walk");
+
+			skeleton.RootBone.X = 320;
+			skeleton.RootBone.Y = 440;
+			skeleton.UpdateWorldTransform();
+		}
+
+		protected override void UnloadContent () {
+			// TODO: Unload any non ContentManager content here
+		}
+
+		protected override void Update (GameTime gameTime) {
+			// Allows the game to exit
+			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
+				this.Exit();
+
+			// TODO: Add your update logic here
+
+			base.Update(gameTime);
+		}
+
+		protected override void Draw (GameTime gameTime) {
+			GraphicsDevice.Clear(Color.Black);
+
+			time += gameTime.ElapsedGameTime.Milliseconds / 1000f;
+			animation.Apply(skeleton, time, true);
+			skeleton.UpdateWorldTransform();
+			skeletonRenderer.Draw(skeleton);
+
+			base.Draw(gameTime);
+		}
+	}
+}

+ 13 - 0
spine-xna/example/src/ExampleProgram.cs

@@ -0,0 +1,13 @@
+using System;
+
+namespace Spine {
+#if WINDOWS || XBOX
+    static class ExampleProgram {
+        static void Main (string[] args) {
+            using (Example game = new Example()) {
+                game.Run();
+            }
+        }
+    }
+#endif
+}

+ 116 - 0
spine-xna/spine-xna.csproj

@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <ProjectGuid>{7F8F2327-C016-49C8-BB4D-F3F77971961E}</ProjectGuid>
+    <ProjectTypeGuids>{6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Spine</RootNamespace>
+    <AssemblyName>spine-xna</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+    <XnaFrameworkVersion>v4.0</XnaFrameworkVersion>
+    <XnaPlatform>Windows</XnaPlatform>
+    <XnaProfile>HiDef</XnaProfile>
+    <XnaCrossPlatformGroupID>f1fc4580-2d86-4a03-bd33-44b1703f36a1</XnaCrossPlatformGroupID>
+    <XnaOutputType>Library</XnaOutputType>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\x86\Debug</OutputPath>
+    <DefineConstants>DEBUG;TRACE;WINDOWS</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <NoStdLib>true</NoStdLib>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <PlatformTarget>x86</PlatformTarget>
+    <XnaCompressContent>false</XnaCompressContent>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\x86\Release</OutputPath>
+    <DefineConstants>TRACE;WINDOWS</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <NoStdLib>true</NoStdLib>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <PlatformTarget>x86</PlatformTarget>
+    <XnaCompressContent>true</XnaCompressContent>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.GamerServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Video, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Avatar, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Net, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.Xna.Framework.Storage, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="mscorlib">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Xml">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Core">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Xml.Linq">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Net">
+      <Private>False</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="src\Atlas.cs" />
+    <Compile Include="src\SkeletonRenderer.cs" />
+    <Compile Include="src\SpriteBatcher.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\spine-csharp\spine-csharp.csproj">
+      <Project>{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}</Project>
+      <Name>spine-csharp</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\Microsoft.Xna.GameStudio.targets" />
+  <!--
+      To modify your build process, add your task inside one of the targets below and uncomment it. 
+      Other similar extension points exist, see Microsoft.Common.targets.
+      <Target Name="BeforeBuild">
+      </Target>
+      <Target Name="AfterBuild">
+      </Target>
+    -->
+</Project>

+ 32 - 0
spine-xna/spine-xna.sln

@@ -0,0 +1,32 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C# Express 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "spine-xna", "spine-xna.csproj", "{7F8F2327-C016-49C8-BB4D-F3F77971961E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "spine-xna-example", "example\spine-xna-example.csproj", "{29CC4385-294A-4885-A3E8-FD4825E0CFDD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "spine-csharp", "..\spine-csharp\spine-csharp.csproj", "{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|x86.ActiveCfg = Debug|x86
+		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|x86.Build.0 = Debug|x86
+		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Release|x86.ActiveCfg = Release|x86
+		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Release|x86.Build.0 = Release|x86
+		{29CC4385-294A-4885-A3E8-FD4825E0CFDD}.Debug|x86.ActiveCfg = Debug|x86
+		{29CC4385-294A-4885-A3E8-FD4825E0CFDD}.Debug|x86.Build.0 = Debug|x86
+		{29CC4385-294A-4885-A3E8-FD4825E0CFDD}.Release|x86.ActiveCfg = Release|x86
+		{29CC4385-294A-4885-A3E8-FD4825E0CFDD}.Release|x86.Build.0 = Release|x86
+		{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}.Debug|x86.ActiveCfg = Debug|x86
+		{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}.Debug|x86.Build.0 = Debug|x86
+		{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}.Release|x86.ActiveCfg = Release|x86
+		{94144E22-2431-4A8F-AC04-DEC22F7EDD8F}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

+ 84 - 0
spine-xna/src/Atlas.cs

@@ -0,0 +1,84 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.GamerServices;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Media;
+
+namespace Spine {
+	public class Atlas : BaseAtlas {
+		private GraphicsDevice device;
+
+		public Atlas (GraphicsDevice device, String atlasFile) {
+			this.device = device;
+			using (StreamReader reader = new StreamReader(atlasFile)) {
+				load(reader, Path.GetDirectoryName(atlasFile));
+			}
+		}
+
+		override protected AtlasPage NewAtlasPage (String path) {
+			XnaAtlasPage page = new XnaAtlasPage();
+			page.Texture = loadTexture(path);
+			return page;
+		}
+
+		private Texture2D loadTexture (string path) {
+			Texture2D file;
+			using (Stream fileStream = new FileStream(path, FileMode.Open)) {
+				file = Texture2D.FromStream(device, fileStream);
+			}
+
+			// Setup a render target to hold our final texture which will have premulitplied alpha values
+			RenderTarget2D result = new RenderTarget2D(device, file.Width, file.Height);
+			device.SetRenderTarget(result);
+			device.Clear(Color.Black);
+
+			// Multiply each color by the source alpha, and write in just the color values into the final texture
+			BlendState blendColor = new BlendState();
+			blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;
+			blendColor.AlphaDestinationBlend = Blend.Zero;
+			blendColor.ColorDestinationBlend = Blend.Zero;
+			blendColor.AlphaSourceBlend = Blend.SourceAlpha;
+			blendColor.ColorSourceBlend = Blend.SourceAlpha;
+
+			SpriteBatch spriteBatch = new SpriteBatch(device);
+			spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
+			spriteBatch.Draw(file, file.Bounds, Color.White);
+			spriteBatch.End();
+
+			// Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
+			BlendState blendAlpha = new BlendState();
+			blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;
+			blendAlpha.AlphaDestinationBlend = Blend.Zero;
+			blendAlpha.ColorDestinationBlend = Blend.Zero;
+			blendAlpha.AlphaSourceBlend = Blend.One;
+			blendAlpha.ColorSourceBlend = Blend.One;
+
+			spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
+			spriteBatch.Draw(file, file.Bounds, Color.White);
+			spriteBatch.End();
+
+			// Release the GPU back to drawing to the screen
+			device.SetRenderTarget(null);
+
+			return result as Texture2D;
+		}
+	}
+
+	public class XnaAtlasPage : AtlasPage {
+		public Texture2D Texture { get; set; }
+
+		override public int GetTextureWidth () {
+			return Texture.Width;
+		}
+
+		override public int GetTextureHeight () {
+			return Texture.Height;
+		}
+	}
+}

+ 100 - 0
spine-xna/src/SkeletonRenderer.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework;
+
+namespace Spine {
+	public class SkeletonRenderer {
+		GraphicsDevice device;
+		SpriteBatcher batcher;
+		BasicEffect effect;
+		RasterizerState rasterizerState;
+
+		public SkeletonRenderer (GraphicsDevice device) {
+			this.device = device;
+
+			batcher = new SpriteBatcher();
+
+			effect = new BasicEffect(device);
+			effect.World = Matrix.Identity;
+			effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
+			effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0);
+			effect.TextureEnabled = true;
+			effect.VertexColorEnabled = true;
+
+			rasterizerState = new RasterizerState();
+			rasterizerState.CullMode = CullMode.None;
+
+			Bone.yDown = true;
+		}
+
+		public void Draw (Skeleton skeleton) {
+			List<Slot> drawOrder = skeleton.DrawOrder;
+			for (int i = 0, n = drawOrder.Count; i < n; i++) {
+				Slot slot = drawOrder[i];
+				Attachment attachment = slot.Attachment;
+				if (attachment == null) continue;
+				if (attachment is RegionAttachment) {
+					RegionAttachment regionAttachment = (RegionAttachment)attachment;
+
+					SpriteBatchItem item = batcher.CreateBatchItem();
+					item.Texture = ((XnaAtlasPage)regionAttachment.Region.Page).Texture;
+
+					byte r = (byte)(slot.R * 255);
+					byte g = (byte)(slot.G * 255);
+					byte b = (byte)(slot.B * 255);
+					byte a = (byte)(slot.A * 255);
+					item.vertexTL.Color.R = r;
+					item.vertexTL.Color.G = g;
+					item.vertexTL.Color.B = b;
+					item.vertexTL.Color.A = a;
+					item.vertexBL.Color.R = r;
+					item.vertexBL.Color.G = g;
+					item.vertexBL.Color.B = b;
+					item.vertexBL.Color.A = a;
+					item.vertexBR.Color.R = r;
+					item.vertexBR.Color.G = g;
+					item.vertexBR.Color.B = b;
+					item.vertexBR.Color.A = a;
+					item.vertexTR.Color.R = r;
+					item.vertexTR.Color.G = g;
+					item.vertexTR.Color.B = b;
+					item.vertexTR.Color.A = a;
+
+					regionAttachment.UpdateVertices(slot.Bone);
+					float[] vertices = regionAttachment.Vertices;
+					item.vertexTL.Position.X = vertices[RegionAttachment.X1];
+					item.vertexTL.Position.Y = vertices[RegionAttachment.Y1];
+					item.vertexTL.Position.Z = 0;
+					item.vertexBL.Position.X = vertices[RegionAttachment.X2];
+					item.vertexBL.Position.Y = vertices[RegionAttachment.Y2];
+					item.vertexBL.Position.Z = 0;
+					item.vertexBR.Position.X = vertices[RegionAttachment.X3];
+					item.vertexBR.Position.Y = vertices[RegionAttachment.Y3];
+					item.vertexBR.Position.Z = 0;
+					item.vertexTR.Position.X = vertices[RegionAttachment.X4];
+					item.vertexTR.Position.Y = vertices[RegionAttachment.Y4];
+					item.vertexTR.Position.Z = 0;
+
+					float[] uvs = regionAttachment.UVs;
+					item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1];
+					item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1];
+					item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2];
+					item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2];
+					item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3];
+					item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3];
+					item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4];
+					item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4];
+				}
+			}
+
+			device.RasterizerState = rasterizerState;
+			device.BlendState = BlendState.AlphaBlend;
+
+			foreach (EffectPass pass in effect.CurrentTechnique.Passes) {
+				pass.Apply();
+				batcher.Draw(device);
+			}
+		}
+	}
+}

+ 240 - 0
spine-xna/src/SpriteBatcher.cs

@@ -0,0 +1,240 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework;
+
+namespace Spine {
+	// #region License
+	// /*
+	// Microsoft Public License (Ms-PL)
+	// MonoGame - Copyright © 2009 The MonoGame Team
+	// 
+	// All rights reserved.
+	// 
+	// This license governs use of the accompanying software. If you use the software, you accept this license. If you do not
+	// accept the license, do not use the software.
+	// 
+	// 1. Definitions
+	// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under 
+	// U.S. copyright law.
+	// 
+	// A "contribution" is the original software, or any additions or changes to the software.
+	// A "contributor" is any person that distributes its contribution under this license.
+	// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
+	// 
+	// 2. Grant of Rights
+	// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, 
+	// each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
+	// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, 
+	// each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
+	// 
+	// 3. Conditions and Limitations
+	// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
+	// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, 
+	// your patent license from such contributor to the software ends automatically.
+	// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution 
+	// notices that are present in the software.
+	// (D) If you distribute any portion of the software in source code form, you may do so only under this license by including 
+	// a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object 
+	// code form, you may only do so under a license that complies with this license.
+	// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees
+	// or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent
+	// permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular
+	// purpose and non-infringement.
+	// */
+	// #endregion License
+	// 
+
+	/// <summary>
+	/// This class handles the queueing of batch items into the GPU by creating the triangle tesselations
+	/// that are used to draw the sprite textures. This class supports int.MaxValue number of sprites to be
+	/// batched and will process them into short.MaxValue groups (strided by 6 for the number of vertices
+	/// sent to the GPU). 
+	/// </summary>
+	public class SpriteBatcher {
+		/*
+		 * Note that this class is fundamental to high performance for SpriteBatch games. Please exercise
+		 * caution when making changes to this class.
+		 */
+
+		/// <summary>
+		/// Initialization size for the batch item list and queue.
+		/// </summary>
+		private const int InitialBatchSize = 256;
+		/// <summary>
+		/// The maximum number of batch items that can be processed per iteration
+		/// </summary>
+		private const int MaxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad
+		/// <summary>
+		/// Initialization size for the vertex array, in batch units.
+		/// </summary>
+		private const int InitialVertexArraySize = 256;
+
+		/// <summary>
+		/// The list of batch items to process.
+		/// </summary>
+		private readonly List<SpriteBatchItem> _batchItemList;
+
+		/// <summary>
+		/// The available SpriteBatchItem queue so that we reuse these objects when we can.
+		/// </summary>
+		private readonly Queue<SpriteBatchItem> _freeBatchItemQueue;
+
+		/// <summary>
+		/// Vertex index array. The values in this array never change.
+		/// </summary>
+		private short[] _index;
+
+		private VertexPositionColorTexture[] _vertexArray;
+
+		public SpriteBatcher () {
+			_batchItemList = new List<SpriteBatchItem>(InitialBatchSize);
+			_freeBatchItemQueue = new Queue<SpriteBatchItem>(InitialBatchSize);
+
+			EnsureArrayCapacity(InitialBatchSize);
+		}
+
+		/// <summary>
+		/// Create an instance of SpriteBatchItem if there is none available in the free item queue. Otherwise,
+		/// a previously allocated SpriteBatchItem is reused.
+		/// </summary>
+		/// <returns></returns>
+		public SpriteBatchItem CreateBatchItem () {
+			SpriteBatchItem item;
+			if (_freeBatchItemQueue.Count > 0)
+				item = _freeBatchItemQueue.Dequeue();
+			else
+				item = new SpriteBatchItem();
+			_batchItemList.Add(item);
+			return item;
+		}
+
+		/// <summary>
+		/// Resize and recreate the missing indices for the index and vertex position color buffers.
+		/// </summary>
+		/// <param name="numBatchItems"></param>
+		private void EnsureArrayCapacity (int numBatchItems) {
+			int neededCapacity = 6 * numBatchItems;
+			if (_index != null && neededCapacity <= _index.Length) {
+				// Short circuit out of here because we have enough capacity.
+				return;
+			}
+			short[] newIndex = new short[6 * numBatchItems];
+			int start = 0;
+			if (_index != null) {
+				_index.CopyTo(newIndex, 0);
+				start = _index.Length / 6;
+			}
+			for (var i = start; i < numBatchItems; i++) {
+				/*
+				 *  TL    TR
+				 *   0----1 0,1,2,3 = index offsets for vertex indices
+				 *   |   /| TL,TR,BL,BR are vertex references in SpriteBatchItem.
+				 *   |  / |
+				 *   | /  |
+				 *   |/   |
+				 *   2----3
+				 *  BL    BR
+				 */
+				// Triangle 1
+				newIndex[i * 6 + 0] = (short)(i * 4);
+				newIndex[i * 6 + 1] = (short)(i * 4 + 1);
+				newIndex[i * 6 + 2] = (short)(i * 4 + 2);
+				// Triangle 2
+				newIndex[i * 6 + 3] = (short)(i * 4 + 1);
+				newIndex[i * 6 + 4] = (short)(i * 4 + 3);
+				newIndex[i * 6 + 5] = (short)(i * 4 + 2);
+			}
+			_index = newIndex;
+
+			_vertexArray = new VertexPositionColorTexture[4 * numBatchItems];
+		}
+
+		/// <summary>
+		/// Sorts the batch items and then groups batch drawing into maximal allowed batch sets that do not
+		/// overflow the 16 bit array indices for vertices.
+		/// </summary>
+		/// <param name="sortMode">The type of depth sorting desired for the rendering.</param>
+		public void Draw (GraphicsDevice device) {
+			// nothing to do
+			if (_batchItemList.Count == 0)
+				return;
+
+			// Determine how many iterations through the drawing code we need to make
+			int batchIndex = 0;
+			int batchCount = _batchItemList.Count;
+			// Iterate through the batches, doing short.MaxValue sets of vertices only.
+			while (batchCount > 0) {
+				// setup the vertexArray array
+				var startIndex = 0;
+				var index = 0;
+				Texture2D tex = null;
+
+				int numBatchesToProcess = batchCount;
+				if (numBatchesToProcess > MaxBatchSize) {
+					numBatchesToProcess = MaxBatchSize;
+				}
+				EnsureArrayCapacity(numBatchesToProcess);
+				// Draw the batches
+				for (int i = 0; i < numBatchesToProcess; i++, batchIndex++) {
+					SpriteBatchItem item = _batchItemList[batchIndex];
+					// if the texture changed, we need to flush and bind the new texture
+					var shouldFlush = !ReferenceEquals(item.Texture, tex);
+					if (shouldFlush) {
+						FlushVertexArray(device, startIndex, index);
+
+						tex = item.Texture;
+						startIndex = index = 0;
+						device.Textures[0] = tex;
+					}
+
+					// store the SpriteBatchItem data in our vertexArray
+					_vertexArray[index++] = item.vertexTL;
+					_vertexArray[index++] = item.vertexTR;
+					_vertexArray[index++] = item.vertexBL;
+					_vertexArray[index++] = item.vertexBR;
+
+					// Release the texture and return the item to the queue.
+					item.Texture = null;
+					_freeBatchItemQueue.Enqueue(item);
+				}
+				// flush the remaining vertexArray data
+				FlushVertexArray(device, startIndex, index);
+				// Update our batch count to continue the process of culling down
+				// large batches
+				batchCount -= numBatchesToProcess;
+			}
+			_batchItemList.Clear();
+		}
+
+		/// <summary>
+		/// Sends the triangle list to the graphics device. Here is where the actual drawing starts.
+		/// </summary>
+		/// <param name="start">Start index of vertices to draw. Not used except to compute the count of vertices to draw.</param>
+		/// <param name="end">End index of vertices to draw. Not used except to compute the count of vertices to draw.</param>
+		private void FlushVertexArray (GraphicsDevice device, int start, int end) {
+			if (start == end)
+				return;
+
+			var vertexCount = end - start;
+
+			device.DrawUserIndexedPrimitives(
+				 PrimitiveType.TriangleList,
+				 _vertexArray,
+				 0,
+				 vertexCount,
+				 _index,
+				 0,
+				 (vertexCount / 4) * 2,
+				 VertexPositionColorTexture.VertexDeclaration);
+		}
+	}
+
+	public class SpriteBatchItem {
+		public Texture2D Texture;
+		public VertexPositionColorTexture vertexTL;
+		public VertexPositionColorTexture vertexTR;
+		public VertexPositionColorTexture vertexBL;
+		public VertexPositionColorTexture vertexBR;
+	}
+}