diff --git a/spine-csharp/spine-csharp.csproj b/spine-csharp/spine-csharp.csproj
index 50792c8fa..40f697f02 100644
--- a/spine-csharp/spine-csharp.csproj
+++ b/spine-csharp/spine-csharp.csproj
@@ -71,11 +71,11 @@
+
-
diff --git a/spine-monogame/example/ExampleGame.cs b/spine-monogame/example/ExampleGame.cs
index 1aa562018..637940bc6 100644
--- a/spine-monogame/example/ExampleGame.cs
+++ b/spine-monogame/example/ExampleGame.cs
@@ -33,15 +33,252 @@ using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Spine {
+
+ public abstract class Screen {
+ protected Example game;
+ protected SkeletonRenderer skeletonRenderer;
+ private MouseState lastMouseState;
+ protected Boolean mouseClicked = false;
+
+ public Screen(Example game) {
+ this.game = game;
+ skeletonRenderer = new SkeletonRenderer(game.GraphicsDevice);
+ skeletonRenderer.PremultipliedAlpha = false;
+ }
+
+ public void UpdateInput() {
+ MouseState state = Mouse.GetState();
+ mouseClicked = lastMouseState.LeftButton == ButtonState.Pressed && state.LeftButton == ButtonState.Released;
+ lastMouseState = state;
+ }
+
+ public abstract void Render(float deltaTime);
+ }
+
+ ///
+ /// The raptor screen shows basic loading and rendering of a Spine skeleton.
+ ///
+ internal class RaptorScreen : Screen {
+ Atlas atlas;
+ Skeleton skeleton;
+ AnimationState state;
+
+ public RaptorScreen(Example game) : base (game) {
+ // Load the texture atlas
+ atlas = new Atlas("data/raptor.atlas", new XnaTextureLoader(game.GraphicsDevice));
+
+ // Load the .json file using a scale of 0.5
+ SkeletonJson json = new SkeletonJson(atlas);
+ json.Scale = 0.5f;
+ SkeletonData skeletonData = json.ReadSkeletonData("data/raptor-pro.json");
+
+ // Create the skeleton and animation state
+ skeleton = new Skeleton(skeletonData);
+ AnimationStateData stateData = new AnimationStateData(skeleton.Data);
+ state = new AnimationState(stateData);
+
+ // Flip the skeleton on the y-axis and center it within the viewport
+ skeleton.ScaleY = -1;
+ skeleton.X = game.GraphicsDevice.Viewport.Width / 2;
+ skeleton.Y = game.GraphicsDevice.Viewport.Height;
+
+ // Set the "walk" animation on track one and let it loop forever
+ state.SetAnimation(0, "walk", true);
+ }
+
+ public override void Render(float deltaTime) {
+ // Update the animation state and apply the animations
+ // to the skeleton
+ state.Update(deltaTime);
+ state.Apply(skeleton);
+
+ // Update the transformations of bones and other parts of the skeleton
+ skeleton.UpdateWorldTransform();
+
+ // Clear the screen and setup the projection matrix of the skeleton renderer
+ game.GraphicsDevice.Clear(Color.Black);
+ ((BasicEffect)skeletonRenderer.Effect).Projection = Matrix.CreateOrthographicOffCenter(0, game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height, 0, 1, 0);
+
+ // Draw the skeletons
+ skeletonRenderer.Begin();
+ skeletonRenderer.Draw(skeleton);
+ skeletonRenderer.End();
+
+ // Check if the mouse button was clicked and switch scene
+ if (mouseClicked) game.currentScreen = new TankScreen(game);
+ }
+ }
+
+ ///
+ /// The tank screen shows how to enable two color tinting.
+ ///
+ internal class TankScreen : Screen {
+ Atlas atlas;
+ Skeleton skeleton;
+ AnimationState state;
+
+ public TankScreen(Example game) : base(game) {
+ // Instantiate and configure the two color tinting effect and
+ // assign it to the skeleton renderer
+ var twoColorTintEffect = game.Content.Load("Content\\SpineEffect");
+ twoColorTintEffect.Parameters["World"].SetValue(Matrix.Identity);
+ twoColorTintEffect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up));
+ skeletonRenderer.Effect = twoColorTintEffect;
+
+ // The remaining code loads the atlas and skeleton data as in the raptor screen
+ atlas = new Atlas("data/tank.atlas", new XnaTextureLoader(game.GraphicsDevice));
+ SkeletonJson json = new SkeletonJson(atlas);
+ json.Scale = 0.25f;
+ SkeletonData skeletonData = json.ReadSkeletonData("data/tank-pro.json");
+
+ skeleton = new Skeleton(skeletonData);
+ AnimationStateData stateData = new AnimationStateData(skeleton.Data);
+ state = new AnimationState(stateData);
+
+ skeleton.ScaleY = -1;
+ skeleton.X = game.GraphicsDevice.Viewport.Width / 2 + 200;
+ skeleton.Y = game.GraphicsDevice.Viewport.Height;
+
+ state.SetAnimation(0, "shoot", true);
+ }
+
+ public override void Render(float deltaTime) {
+ state.Update(deltaTime);
+ state.Apply(skeleton);
+
+ skeleton.UpdateWorldTransform();
+
+ // Clear the screen and setup the projection matrix of the custom effect through the
+ // "Projection" parameter.
+ game.GraphicsDevice.Clear(Color.Black);
+ skeletonRenderer.Effect.Parameters["Projection"].SetValue(Matrix.CreateOrthographicOffCenter(0, game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height, 0, 1, 0));
+
+ skeletonRenderer.Begin();
+ skeletonRenderer.Draw(skeleton);
+ skeletonRenderer.End();
+
+ if (mouseClicked) game.currentScreen = new SpineboyScreen(game);
+ }
+ }
+
+ ///
+ /// The Spineboy screen shows how to queue up multiple animations via animation state,
+ /// set the default mix time to smoothly transition between animations, and load a
+ /// skeleton from a binary .skel file.
+ ///
+ internal class SpineboyScreen : Screen {
+ Atlas atlas;
+ Skeleton skeleton;
+ AnimationState state;
+
+ public SpineboyScreen(Example game) : base(game) {
+ atlas = new Atlas("data/spineboy.atlas", new XnaTextureLoader(game.GraphicsDevice));
+
+ SkeletonBinary binary = new SkeletonBinary(atlas);
+ binary.Scale = 0.5f;
+ SkeletonData skeletonData = binary.ReadSkeletonData("data/spineboy-pro.skel");
+
+ skeleton = new Skeleton(skeletonData);
+ AnimationStateData stateData = new AnimationStateData(skeleton.Data);
+ state = new AnimationState(stateData);
+
+ skeleton.ScaleY = -1;
+ skeleton.X = game.GraphicsDevice.Viewport.Width / 2;
+ skeleton.Y = game.GraphicsDevice.Viewport.Height;
+
+ // We want 0.2 seconds of mixing time when transitioning from
+ // any animation to any other animation.
+ stateData.DefaultMix = 0.2f;
+
+ // Set the "walk" animation on track one and let it loop forever
+ state.SetAnimation(0, "walk", true);
+
+ // Queue another animation after 2 seconds to let Spineboy jump
+ state.AddAnimation(0, "jump", false, 2);
+
+ // After the jump is complete, let Spineboy walk
+ state.AddAnimation(0, "run", true, 0);
+ }
+
+ public override void Render(float deltaTime) {
+ state.Update(deltaTime);
+ state.Apply(skeleton);
+ skeleton.UpdateWorldTransform();
+
+ game.GraphicsDevice.Clear(Color.Black);
+ ((BasicEffect)skeletonRenderer.Effect).Projection = Matrix.CreateOrthographicOffCenter(0, game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height, 0, 1, 0);
+
+ skeletonRenderer.Begin();
+ skeletonRenderer.Draw(skeleton);
+ skeletonRenderer.End();
+
+ if (mouseClicked) game.currentScreen = new MixAndMatchScreen(game);
+ }
+ }
+
+ ///
+ /// The mix-and-match screen demonstrates how to create and apply a skin
+ /// composed of other skins. This method can be used to create customizable
+ /// avatar systems.
+ ///
+ internal class MixAndMatchScreen : Screen {
+ Atlas atlas;
+ Skeleton skeleton;
+ AnimationState state;
+
+ public MixAndMatchScreen(Example game) : base(game) {
+ atlas = new Atlas("data/mix-and-match.atlas", new XnaTextureLoader(game.GraphicsDevice));
+
+ SkeletonJson json = new SkeletonJson(atlas);
+ json.Scale = 0.5f;
+ SkeletonData skeletonData = json.ReadSkeletonData("data/mix-and-match-pro.json");
+
+ skeleton = new Skeleton(skeletonData);
+ AnimationStateData stateData = new AnimationStateData(skeleton.Data);
+ state = new AnimationState(stateData);
+
+ skeleton.ScaleY = -1;
+ skeleton.X = game.GraphicsDevice.Viewport.Width / 2;
+ skeleton.Y = game.GraphicsDevice.Viewport.Height;
+
+ state.SetAnimation(0, "dance", true);
+
+ // Create a new skin, by mixing and matching other skins
+ // that fit together. Items making up the girl are individual
+ // skins. Using the skin API, a new skin is created which is
+ // a combination of all these individual item skins.
+ var mixAndMatchSkin = new Spine.Skin("custom-girl");
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("skin-base"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("nose/short"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("eyes/eyelids-girly"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("eyes/violet"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("hair/brown"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("clothes/hoodie-orange"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("legs/pants-jeans"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("accessories/bag"));
+ mixAndMatchSkin.AddSkin(skeletonData.FindSkin("accessories/hat-red-yellow"));
+ skeleton.SetSkin(mixAndMatchSkin);
+ }
+
+ public override void Render(float deltaTime) {
+ state.Update(deltaTime);
+ state.Apply(skeleton);
+ skeleton.UpdateWorldTransform();
+
+ game.GraphicsDevice.Clear(Color.Black);
+ ((BasicEffect)skeletonRenderer.Effect).Projection = Matrix.CreateOrthographicOffCenter(0, game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height, 0, 1, 0);
+
+ skeletonRenderer.Begin();
+ skeletonRenderer.Draw(skeleton);
+ skeletonRenderer.End();
+
+ if (mouseClicked) game.currentScreen = new RaptorScreen(game);
+ }
+ }
+
public class Example : Microsoft.Xna.Framework.Game {
GraphicsDeviceManager graphics;
- SkeletonRenderer skeletonRenderer;
- Skeleton skeleton;
- Slot headSlot;
- AnimationState state;
- SkeletonBounds bounds = new SkeletonBounds();
-
- private string assetsFolder = "data/";
+ public Screen currentScreen;
public Example() {
IsMouseVisible = true;
@@ -53,139 +290,15 @@ namespace Spine {
}
protected override void LoadContent() {
- // Two color tint effect, comment line 80 to disable
- var spineEffect = Content.Load("Content\\SpineEffect");
- spineEffect.Parameters["World"].SetValue(Matrix.Identity);
- spineEffect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up));
-
- skeletonRenderer = new SkeletonRenderer(GraphicsDevice);
- skeletonRenderer.PremultipliedAlpha = false;
- skeletonRenderer.Effect = spineEffect;
-
- // String name = "spineboy-ess";
- // String name = "goblins-pro";
- String name = "raptor-pro";
- // String name = "tank-pro";
- // String name = "coin-pro";
- String atlasName = name.Replace("-pro", "").Replace("-ess", "");
- bool binaryData = false;
-
- Atlas atlas = new Atlas(assetsFolder + atlasName + ".atlas", new XnaTextureLoader(GraphicsDevice));
-
- float scale = 1;
- if (name == "spineboy-ess") scale = 0.6f;
- if (name == "raptor-pro") scale = 0.5f;
- if (name == "tank-pro") scale = 0.3f;
- if (name == "coin-pro") scale = 1;
-
- SkeletonData skeletonData;
- if (binaryData) {
- SkeletonBinary binary = new SkeletonBinary(atlas);
- binary.Scale = scale;
- skeletonData = binary.ReadSkeletonData(assetsFolder + name + ".skel");
- }
- else {
- SkeletonJson json = new SkeletonJson(atlas);
- json.Scale = scale;
- skeletonData = json.ReadSkeletonData(assetsFolder + name + ".json");
- }
- skeleton = new Skeleton(skeletonData);
- if (name == "goblins-pro") skeleton.SetSkin("goblin");
-
- // Define mixing between animations.
- AnimationStateData stateData = new AnimationStateData(skeleton.Data);
- state = new AnimationState(stateData);
-
- if (name == "spineboy-ess") {
- skeleton.SetAttachment("head-bb", "head"); // Activate the head BoundingBoxAttachment.
-
- stateData.SetMix("run", "jump", 0.2f);
- stateData.SetMix("jump", "run", 0.4f);
-
- // Event handling for all animations.
- state.Start += Start;
- state.End += End;
- state.Complete += Complete;
- state.Event += Event;
-
- state.SetAnimation(0, "run", false);
- TrackEntry entry = state.AddAnimation(0, "jump", false, 0);
- entry.End += End; // Event handling for queued animations.
- state.AddAnimation(0, "run", true, 0);
- }
- else if (name == "raptor-pro") {
- state.SetAnimation(0, "walk", true);
- state.AddAnimation(1, "gun-grab", false, 2);
- }
- else if (name == "coin-pro") {
- state.SetAnimation(0, "animation", true);
- }
- else if (name == "tank-pro") {
- state.SetAnimation(0, "drive", true);
- }
- else {
- state.SetAnimation(0, "walk", true);
- }
-
- skeleton.X = 400 + (name == "tank-pro" ? 300 : 0);
- skeleton.Y = GraphicsDevice.Viewport.Height;
- skeleton.ScaleY = -1;
- skeleton.UpdateWorldTransform();
-
- headSlot = skeleton.FindSlot("head");
+ currentScreen = new MixAndMatchScreen(this);
}
protected override void Update(GameTime gameTime) {
- base.Update(gameTime);
+ currentScreen.UpdateInput();
}
protected override void Draw(GameTime gameTime) {
- GraphicsDevice.Clear(Color.Black);
-
- state.Update(gameTime.ElapsedGameTime.Milliseconds / 1000f);
- state.Apply(skeleton);
- skeleton.UpdateWorldTransform();
- if (skeletonRenderer.Effect is BasicEffect) {
- ((BasicEffect)skeletonRenderer.Effect).Projection = Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 1, 0);
- }
- else {
- skeletonRenderer.Effect.Parameters["Projection"].SetValue(Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 1, 0));
- }
- skeletonRenderer.Begin();
- skeletonRenderer.Draw(skeleton);
- skeletonRenderer.End();
-
- bounds.Update(skeleton, true);
- MouseState mouse = Mouse.GetState();
- if (headSlot != null) {
- headSlot.G = 1;
- headSlot.B = 1;
- if (bounds.AabbContainsPoint(mouse.X, mouse.Y)) {
- BoundingBoxAttachment hit = bounds.ContainsPoint(mouse.X, mouse.Y);
- if (hit != null) {
- headSlot.G = 0;
- headSlot.B = 0;
- }
- }
- }
-
- base.Draw(gameTime);
- }
-
- public void Start(TrackEntry entry) {
- Console.WriteLine(entry + ": start");
- }
-
- public void End(TrackEntry entry) {
- Console.WriteLine(entry + ": end");
- }
-
- public void Complete(TrackEntry entry) {
- Console.WriteLine(entry + ": complete ");
- }
-
- public void Event(TrackEntry entry, Event e) {
- Console.WriteLine(entry + ": event " + e);
- }
+ currentScreen.Render(gameTime.ElapsedGameTime.Milliseconds / 1000.0f);
+ }
}
}