123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- /******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated April 5, 2025. Replaces all prior versions.
- *
- * Copyright (c) 2013-2025, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Graphics;
- using Microsoft.Xna.Framework.Input;
- using System;
- namespace Spine {
- public abstract class Screen {
- protected Example game;
- protected SkeletonRenderer skeletonRenderer;
- private MouseState lastMouseState;
- protected Boolean mouseClicked = false;
- protected Point mousePos;
- 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;
- mousePos = lastMouseState.Position;
- }
- public abstract void Render (float deltaTime);
- }
- /// <summary>
- /// The raptor screen shows basic loading and rendering of a Spine skeleton.
- /// </summary>
- 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);
- // Center within the viewport
- 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);
- skeleton.Update(deltaTime);
- state.Apply(skeleton);
- // Update the transformations of bones and other parts of the skeleton
- skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
- // 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);
- }
- }
- /// <summary>
- /// The tank screen shows how to enable two color tinting.
- /// </summary>
- 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<Effect>("Content\\shaders\\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.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);
- skeleton.Update(deltaTime);
- state.Apply(skeleton);
- skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
- // 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);
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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.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);
- skeleton.Update(deltaTime);
- state.Apply(skeleton);
- skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
- 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 PhysicsScreenCelestial(game);
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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.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("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);
- skeleton.Update(deltaTime);
- state.Apply(skeleton);
- skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
- 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);
- }
- }
- /// <summary>
- /// The physics screen Cloud Pot demonstrates Physics Constraints introduced in Spine 4.2
- /// using the cloud-pot skeleton.
- /// </summary>
- internal class PhysicsScreenCloudPot : Screen {
- Atlas atlas;
- Skeleton skeleton;
- AnimationState state;
- public PhysicsScreenCloudPot (Example game) : base(game) {
- atlas = new Atlas("data/cloud-pot.atlas", new XnaTextureLoader(game.GraphicsDevice));
- SkeletonBinary binary = new SkeletonBinary(atlas);
- binary.Scale = 0.15f;
- SkeletonData skeletonData = binary.ReadSkeletonData("data/cloud-pot.skel");
- skeleton = new Skeleton(skeletonData);
- AnimationStateData stateData = new AnimationStateData(skeleton.Data);
- state = new AnimationState(stateData);
- skeleton.X = game.GraphicsDevice.Viewport.Width / 2;
- skeleton.Y = game.GraphicsDevice.Viewport.Height * 2f / 3f;
- state.SetAnimation(0, "playing-in-the-rain", true);
- }
- public override void Render (float deltaTime) {
- Vector2 position = mousePos.ToVector2();
- skeleton.X = position.X;
- skeleton.Y = position.Y;
- state.Update(deltaTime);
- skeleton.Update(deltaTime);
- // Note: if you are not directly modifying skeleton.X or .Y, you can apply external
- // movement to physics via the following code:
- // Vector2 lastPosition; // add as a member variable
- // ..
- // Vector2 currentPosition = <current world position>;
- // Vector2 externalPositionDelta = currentPosition - lastPosition;
- // skeleton.PhysicsTranslate(externalPositionDelta.x, externalPositionDelta.y);
- // lastPosition = currentPosition;
- state.Apply(skeleton);
- skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
- 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);
- }
- }
- /// <summary>
- /// The physics screen Celestial demonstrates Physics Constraints introduced in Spine 4.2
- /// using the celestial-circus skeleton.
- /// </summary>
- internal class PhysicsScreenCelestial : Screen {
- Atlas atlas;
- Skeleton skeleton;
- AnimationState state;
- public PhysicsScreenCelestial (Example game) : base(game) {
- atlas = new Atlas("data/celestial-circus.atlas", new XnaTextureLoader(game.GraphicsDevice));
- SkeletonJson json = new SkeletonJson(atlas);
- json.Scale = 0.15f;
- SkeletonData skeletonData = json.ReadSkeletonData("data/celestial-circus-pro.json");
- skeleton = new Skeleton(skeletonData);
- AnimationStateData stateData = new AnimationStateData(skeleton.Data);
- state = new AnimationState(stateData);
- skeleton.X = game.GraphicsDevice.Viewport.Width / 2;
- skeleton.Y = game.GraphicsDevice.Viewport.Height * 2f / 3f;
- state.SetAnimation(0, "swing", true);
- state.SetAnimation(1, "eyeblink", true);
- }
- public override void Render (float deltaTime) {
- Vector2 position = mousePos.ToVector2();
- skeleton.X = position.X;
- skeleton.Y = position.Y;
- state.Update(deltaTime);
- skeleton.Update(deltaTime);
- // Note: if you are not directly modifying skeleton.X or .Y, you can apply external
- // movement to physics via the following code:
- // Vector2 lastPosition; // add as a member variable
- // ..
- // Vector2 currentPosition = <current world position>;
- // Vector2 externalPositionDelta = currentPosition - lastPosition;
- // skeleton.PhysicsTranslate(externalPositionDelta.x, externalPositionDelta.y);
- // lastPosition = currentPosition;
- state.Apply(skeleton);
- skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
- 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 PhysicsScreenCloudPot(game);
- }
- }
- public class Example : Microsoft.Xna.Framework.Game {
- GraphicsDeviceManager graphics;
- public Screen currentScreen;
- public Example () {
- IsMouseVisible = true;
- graphics = new GraphicsDeviceManager(this);
- graphics.IsFullScreen = false;
- graphics.PreferredBackBufferWidth = 800;
- graphics.PreferredBackBufferHeight = 600;
- }
- protected override void LoadContent () {
- currentScreen = new PhysicsScreenCelestial(this);
- }
- protected override void Update (GameTime gameTime) {
- currentScreen.UpdateInput();
- }
- protected override void Draw (GameTime gameTime) {
- currentScreen.Render((float)(gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0));
- }
- }
- }
|