Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: a60dd41ef13d98647b9f963089feb7b0
folderAsset: yes
timeCreated: 1456265151
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fb7099b9c6ce91740b7041dabb0752c2
timeCreated: 1456265156
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ec2f0e7143c8a174994595883f4b1e33
timeCreated: 1456265155
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,114 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
using System.Collections.Generic;
namespace Spine {
/// <summary>Stores mix (crossfade) durations to be applied when <see cref="AnimationState"/> animations are changed on the same track.</summary>
public class AnimationStateData {
internal SkeletonData skeletonData;
readonly Dictionary<AnimationPair, float> animationToMixTime = new Dictionary<AnimationPair, float>(AnimationPairComparer.Instance);
internal float defaultMix;
/// <summary>The SkeletonData to look up animations when they are specified by name.</summary>
public SkeletonData SkeletonData { get { return skeletonData; } }
/// <summary>
/// The mix duration to use when no mix duration has been specifically defined between two animations.</summary>
public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }
public AnimationStateData (SkeletonData skeletonData) {
if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData");
this.skeletonData = skeletonData;
}
/// <summary>Sets a mix duration by animation names.</summary>
public void SetMix (string fromName, string toName, float duration) {
Animation from = skeletonData.FindAnimation(fromName);
if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName");
Animation to = skeletonData.FindAnimation(toName);
if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName");
SetMix(from, to, duration);
}
/// <summary>Sets a mix duration when changing from the specified animation to the other.
/// See TrackEntry.MixDuration.</summary>
public void SetMix (Animation from, Animation to, float duration) {
if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
var key = new AnimationPair(from, to);
animationToMixTime.Remove(key);
animationToMixTime.Add(key, duration);
}
/// <summary>
/// Returns the mix duration to use when changing from the specified animation to the other on the same track,
/// or the <see cref="DefaultMix"/> if no mix duration has been set.
/// </summary>
public float GetMix (Animation from, Animation to) {
if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
var key = new AnimationPair(from, to);
float duration;
if (animationToMixTime.TryGetValue(key, out duration)) return duration;
return defaultMix;
}
public struct AnimationPair {
public readonly Animation a1;
public readonly Animation a2;
public AnimationPair (Animation a1, Animation a2) {
this.a1 = a1;
this.a2 = a2;
}
public override string ToString () {
return a1.name + "->" + a2.name;
}
}
// Avoids boxing in the dictionary.
public class AnimationPairComparer : IEqualityComparer<AnimationPair> {
public static readonly AnimationPairComparer Instance = new AnimationPairComparer();
bool IEqualityComparer<AnimationPair>.Equals (AnimationPair x, AnimationPair y) {
return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2);
}
int IEqualityComparer<AnimationPair>.GetHashCode (AnimationPair obj) {
// from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2);
int h1 = obj.a1.GetHashCode();
return (((h1 << 5) + h1) ^ obj.a2.GetHashCode());
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e03d60c517d9b974db35b9fd144a1d09
timeCreated: 1456265155
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,372 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace Spine {
public class Atlas : IEnumerable<AtlasRegion> {
readonly List<AtlasPage> pages = new List<AtlasPage>();
List<AtlasRegion> regions = new List<AtlasRegion>();
TextureLoader textureLoader;
#region IEnumerable implementation
public IEnumerator<AtlasRegion> GetEnumerator () {
return regions.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
return regions.GetEnumerator();
}
#endregion
public List<AtlasRegion> Regions { get { return regions; } }
public List<AtlasPage> Pages { get { return pages; } }
#if !(IS_UNITY)
#if WINDOWS_STOREAPP
private async Task ReadFile (string path, TextureLoader textureLoader) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
using (StreamReader reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
try {
Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader);
this.pages = atlas.pages;
this.regions = atlas.regions;
this.textureLoader = atlas.textureLoader;
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
public Atlas (string path, TextureLoader textureLoader) {
this.ReadFile(path, textureLoader).Wait();
}
#else
public Atlas (string path, TextureLoader textureLoader) {
#if WINDOWS_PHONE
Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
using (StreamReader reader = new StreamReader(stream)) {
#else
using (StreamReader reader = new StreamReader(path)) {
#endif // WINDOWS_PHONE
try {
Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader);
this.pages = atlas.pages;
this.regions = atlas.regions;
this.textureLoader = atlas.textureLoader;
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
#endif // WINDOWS_STOREAPP
#endif
public Atlas (List<AtlasPage> pages, List<AtlasRegion> regions) {
if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null.");
if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null.");
this.pages = pages;
this.regions = regions;
this.textureLoader = null;
}
public Atlas (TextReader reader, string imagesDir, TextureLoader textureLoader) {
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null.");
if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null.");
this.textureLoader = textureLoader;
string[] entry = new string[5];
AtlasPage page = null;
AtlasRegion region = null;
Dictionary<string, Action> pageFields = new Dictionary<string, Action>(5);
pageFields.Add("size", () => {
page.width = int.Parse(entry[1], CultureInfo.InvariantCulture);
page.height = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
pageFields.Add("format", () => {
page.format = (Format)Enum.Parse(typeof(Format), entry[1], false);
});
pageFields.Add("filter", () => {
page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false);
page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false);
});
pageFields.Add("repeat", () => {
if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat;
if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat;
});
pageFields.Add("pma", () => {
page.pma = entry[1] == "true";
});
Dictionary<string, Action> regionFields = new Dictionary<string, Action>(8);
regionFields.Add("xy", () => { // Deprecated, use bounds.
region.x = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.y = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("size", () => { // Deprecated, use bounds.
region.width = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.height = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("bounds", () => {
region.x = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.y = int.Parse(entry[2], CultureInfo.InvariantCulture);
region.width = int.Parse(entry[3], CultureInfo.InvariantCulture);
region.height = int.Parse(entry[4], CultureInfo.InvariantCulture);
});
regionFields.Add("offset", () => { // Deprecated, use offsets.
region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("orig", () => { // Deprecated, use offsets.
region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("offsets", () => {
region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture);
region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture);
region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture);
});
regionFields.Add("rotate", () => {
string value = entry[1];
if (value == "true")
region.degrees = 90;
else if (value != "false")
region.degrees = int.Parse(value, CultureInfo.InvariantCulture);
});
regionFields.Add("index", () => {
region.index = int.Parse(entry[1], CultureInfo.InvariantCulture);
});
string line = reader.ReadLine();
// Ignore empty lines before first entry.
while (line != null && line.Trim().Length == 0)
line = reader.ReadLine();
// Header entries.
while (true) {
if (line == null || line.Trim().Length == 0) break;
if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields.
line = reader.ReadLine();
}
// Page and region entries.
List<string> names = null;
List<int[]> values = null;
while (true) {
if (line == null) break;
if (line.Trim().Length == 0) {
page = null;
line = reader.ReadLine();
} else if (page == null) {
page = new AtlasPage();
page.name = line.Trim();
while (true) {
if (ReadEntry(entry, line = reader.ReadLine()) == 0) break;
Action field;
if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields.
}
textureLoader.Load(page, Path.Combine(imagesDir, page.name));
pages.Add(page);
} else {
region = new AtlasRegion();
region.page = page;
region.name = line;
while (true) {
int count = ReadEntry(entry, line = reader.ReadLine());
if (count == 0) break;
Action field;
if (regionFields.TryGetValue(entry[0], out field))
field();
else {
if (names == null) {
names = new List<string>(8);
values = new List<int[]>(8);
}
names.Add(entry[0]);
int[] entryValues = new int[count];
for (int i = 0; i < count; i++)
int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values.
values.Add(entryValues);
}
}
if (region.originalWidth == 0 && region.originalHeight == 0) {
region.originalWidth = region.width;
region.originalHeight = region.height;
}
if (names != null && names.Count > 0) {
region.names = names.ToArray();
region.values = values.ToArray();
names.Clear();
values.Clear();
}
region.u = region.x / (float)page.width;
region.v = region.y / (float)page.height;
if (region.degrees == 90) {
region.u2 = (region.x + region.height) / (float)page.width;
region.v2 = (region.y + region.width) / (float)page.height;
int tempSwap = region.packedWidth;
region.packedWidth = region.packedHeight;
region.packedHeight = tempSwap;
} else {
region.u2 = (region.x + region.width) / (float)page.width;
region.v2 = (region.y + region.height) / (float)page.height;
}
regions.Add(region);
}
}
}
static private int ReadEntry (string[] entry, string line) {
if (line == null) return 0;
line = line.Trim();
if (line.Length == 0) return 0;
int colon = line.IndexOf(':');
if (colon == -1) return 0;
entry[0] = line.Substring(0, colon).Trim();
for (int i = 1, lastMatch = colon + 1; ; i++) {
int comma = line.IndexOf(',', lastMatch);
if (comma == -1) {
entry[i] = line.Substring(lastMatch).Trim();
return i;
}
entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
lastMatch = comma + 1;
if (i == 4) return 4;
}
}
public void FlipV () {
for (int i = 0, n = regions.Count; i < n; i++) {
AtlasRegion region = regions[i];
region.v = 1 - region.v;
region.v2 = 1 - region.v2;
}
}
/// <summary>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.</summary>
/// <returns>The region, or null.</returns>
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 void Dispose () {
if (textureLoader == null) return;
for (int i = 0, n = pages.Count; i < n; i++)
textureLoader.Unload(pages[i].rendererObject);
}
}
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
}
public class AtlasPage {
public string name;
public int width, height;
public Format format = Format.RGBA8888;
public TextureFilter minFilter = TextureFilter.Nearest;
public TextureFilter magFilter = TextureFilter.Nearest;
public TextureWrap uWrap = TextureWrap.ClampToEdge;
public TextureWrap vWrap = TextureWrap.ClampToEdge;
public bool pma;
public object rendererObject;
public AtlasPage Clone () {
return MemberwiseClone() as AtlasPage;
}
}
public class AtlasRegion : TextureRegion {
public AtlasPage page;
public string name;
public int x, y;
public float offsetX, offsetY;
public int originalWidth, originalHeight;
public int packedWidth { get { return width; } set { width = value; } }
public int packedHeight { get { return height; } set { height = value; } }
public int degrees;
public bool rotate;
public int index;
public string[] names;
public int[][] values;
override public int OriginalWidth { get { return originalWidth; } }
override public int OriginalHeight { get { return originalHeight; } }
public AtlasRegion Clone () {
return MemberwiseClone() as AtlasRegion;
}
}
public interface TextureLoader {
void Load (AtlasPage page, string path);
void Unload (Object texture);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 60626307629cc034bafd42c53a901fff
timeCreated: 1456265154
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2afe1c6b912aac54abb5925ca4ac52c2
folderAsset: yes
timeCreated: 1456265152
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,102 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// An <see cref="AttachmentLoader"/> that configures attachments using texture regions from an <see cref="Atlas"/>.
/// See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading Skeleton Data</a> in the Spine Runtimes Guide.
/// </summary>
public class AtlasAttachmentLoader : AttachmentLoader {
private Atlas[] atlasArray;
/// <summary>If true, <see cref="FindRegion(string, string)"/> may return null. If false, an error is raised if the texture region is not
/// found. Default is false.</summary>
public bool allowMissingRegions;
public AtlasAttachmentLoader (params Atlas[] atlasArray)
: this(false, atlasArray) {
}
public AtlasAttachmentLoader (bool allowMissingRegions, params Atlas[] atlasArray) {
if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null.");
this.atlasArray = atlasArray;
this.allowMissingRegions = allowMissingRegions;
}
/// <summary>Sets each <see cref="Sequence.Regions"/> by calling <see cref="FindRegion(string, string)"/> for each texture region using
/// <see cref="Sequence.GetPath(string, int)"/>.</summary>
protected void FindRegions (string name, string basePath, Sequence sequence) {
TextureRegion[] regions = sequence.Regions;
for (int i = 0, n = regions.Length; i < n; i++) {
regions[i] = FindRegion(name, sequence.GetPath(basePath, i));
}
}
/// <summary>Looks for the region with the specified path. If not found and <see cref="allowMissingRegions"/> is false, an error is
/// raised.</summary>
protected AtlasRegion FindRegion (string name, string path) {
for (int i = 0; i < atlasArray.Length; i++) {
AtlasRegion region = atlasArray[i].FindRegion(path);
if (region != null)
return region;
}
if (!allowMissingRegions)
throw new ArgumentException(string.Format("Region not found in atlas: {0} (attachment: {1})", path, name));
return null;
}
public RegionAttachment NewRegionAttachment (Skin skin, string placeholder, string name, string path, Sequence sequence) {
FindRegions(name, path, sequence);
return new RegionAttachment(name, sequence);
}
public MeshAttachment NewMeshAttachment (Skin skin, string placeholder, string name, string path, Sequence sequence) {
FindRegions(name, path, sequence);
return new MeshAttachment(name, sequence);
}
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string placeholder, string name) {
return new BoundingBoxAttachment(name);
}
public PathAttachment NewPathAttachment (Skin skin, string placeholder, string name) {
return new PathAttachment(name);
}
public PointAttachment NewPointAttachment (Skin skin, string placeholder, string name) {
return new PointAttachment(name);
}
public ClippingAttachment NewClippingAttachment (Skin skin, string placeholder, string name) {
return new ClippingAttachment(name);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3e6ff30e27c28344bad3e67d308c94cd
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,95 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>The base class for all attachments. Multiple <see cref="Skeleton"/> instances, slots, or skins can use the same
/// attachments.</summary>
abstract public class Attachment {
static private readonly int[] Empty = new int[0];
internal Attachment timelineAttachment;
internal int[] timelineSlots = Empty;
/// <summary>The attachment's name.</summary>
public string Name { get; }
/// <summary>Timelines for the timeline attachment are also applied to this attachment.
/// May be null if no attachment-specific timelines should be applied.</summary>
public Attachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } }
/// <summary>
/// Slots that can have attachments whose <see cref="timelineAttachment"/> is this attachment.
/// </summary>
public int[] TimelineSlots { get { return timelineSlots; } set { timelineSlots = value; } }
protected Attachment (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
this.Name = name;
timelineAttachment = this;
}
/// <summary>Copy constructor.</summary>
protected Attachment (Attachment other) {
Name = other.Name;
timelineAttachment = other.timelineAttachment;
timelineSlots = other.timelineSlots;
}
/// <summary>
/// Returns true if the <c>slotIndex</c> or any <see cref="timelineSlots"/> have an attachment whose <see cref="timelineAttachment"/> is
/// this attachment.
/// </summary>
/// <param name="slots">The <see cref="Skeleton.Slots"/>.</param>
/// <param name="slotIndex">The timeline's primary slot index.</param>
public bool IsTimelineActive (Slot[] slots, int slotIndex, bool appliedPose) {
Slot slot = slots[slotIndex];
if (slot.Bone.Active) {
Attachment other = (appliedPose ? slot.AppliedPose : slot.Pose).Attachment;
if (other != null && other.timelineAttachment == this) return true;
}
for (int i = 0, n = timelineSlots.Length; i < n; i++) {
slot = slots[timelineSlots[i]];
if (!slot.Bone.Active) continue;
Attachment other = (appliedPose ? slot.AppliedPose : slot.Pose).Attachment;
if (other != null && other.timelineAttachment == this) return true;
}
return false;
}
override public string ToString () {
return Name;
}
/// <summary>Returns a copy of the attachment.</summary>
public abstract Attachment Copy ();
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 05b56321b2ddd8145a888746bc6ab917
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
namespace Spine {
public interface AttachmentLoader {
/// <return>May be null to not load any attachment.</return>
RegionAttachment NewRegionAttachment (Skin skin, string placeholder, string name, string path, Sequence sequence);
/// <return>May be null to not load any attachment.</return>
MeshAttachment NewMeshAttachment (Skin skin, string placeholder, string name, string path, Sequence sequence);
/// <return>May be null to not load any attachment.</return>
BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string placeholder, string name);
/// <returns>May be null to not load any attachment</returns>
PathAttachment NewPathAttachment (Skin skin, string placeholder, string name);
PointAttachment NewPointAttachment (Skin skin, string placeholder, string name);
ClippingAttachment NewClippingAttachment (Skin skin, string placeholder, string name);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 95466a4f5a30dca4aa69e8ee7df8ae85
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
namespace Spine {
public enum AttachmentType {
Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping, Sequence
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d6b1941960a9f6f47be3e865554d8695
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>Attachment that has a polygon for bounds checking.</summary>
public class BoundingBoxAttachment : VertexAttachment {
public BoundingBoxAttachment (string name)
: base(name) {
}
/// <summary>Copy constructor.</summary>
protected BoundingBoxAttachment (BoundingBoxAttachment other)
: base(other) {
}
public override Attachment Copy () {
return new BoundingBoxAttachment(this);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: cd8ad8fc0f5bce448ba26d096ab32e85
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,69 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
public class ClippingAttachment : VertexAttachment {
internal SlotData endSlot;
internal bool convex, inverse;
/// <summary>Clipping is performed between the clipping attachment's slot and the end slot. If null, clipping is done until the end of
/// the skeleton's rendering.</summary>
public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } }
/// <summary>
/// When true the clipping polygon is treated as convex for more efficient clipping. If the polygon deforms to concave then the
/// convex hull is used.When false the clipping polygon can be concave and if so has an additional CPU cost.Inverse clipping
/// always uses convex.
/// </summary>
public bool Convex { get { return convex; } set { convex = value; } }
/// <summary>
/// When false, everything inside the clipping polygon is visible. When true, everything outside the clipping polygon is
/// visible and clipping is convex.
/// </summary>
public bool Inverse { get { return inverse; } set { inverse = value; } }
public ClippingAttachment (string name) : base(name) {
}
/// <summary>Copy constructor.</summary>
protected ClippingAttachment (ClippingAttachment other)
: base(other) {
endSlot = other.endSlot;
convex = other.convex;
inverse = other.inverse;
}
public override Attachment Copy () {
return new ClippingAttachment(this);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3380954b107f38b4c85a4cdfeceace42
timeCreated: 1492744746
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
namespace Spine {
#if IS_UNITY
using Color32F = UnityEngine.Color;
#endif
/// <summary>Interface for an attachment that gets 1 or more texture regions from a <see cref="Sequence"/>.</summary>
public interface IHasSequence {
/// <summary>The base path for the attachment's texture region.</summary>
string Path { get; set; }
/// <summary>The color the attachment is tinted, to be combined with <see cref="SlotPose.Color"/>.</summary>
Color32F GetColor ();
void SetColor (Color32F color);
void SetColor (float r, float g, float b, float a);
/// <summary>The sequence that provides texture regions, UVs, and vertex offsets for rendering this attachment.</summary>
Sequence Sequence { get; }
void UpdateSequence ();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0e8b0a33cae75d498aa8c328787cafb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,236 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
using System;
namespace Spine {
#if IS_UNITY
using Color32F = UnityEngine.Color;
#endif
/// <summary>Attachment that displays a texture region using a mesh.</summary>
public class MeshAttachment : VertexAttachment, IHasSequence {
internal readonly Sequence sequence;
internal float[] regionUVs;
internal int[] triangles;
internal int hullLength;
internal string path;
// Color is a struct, set to protected to prevent
// Color color = slot.color; color.a = 0.5;
// modifying just a copy of the struct instead of the original
// object as in reference implementation.
protected Color32F color = new Color32F(1, 1, 1, 1);
private MeshAttachment sourceMesh;
public int HullLength { get { return hullLength; } set { hullLength = value; } }
/// <summary>The UV pair for each vertex, normalized within the texture region.</summary>
public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
/// <summary>Triplets of vertex indices which describe the mesh's triangulation.</summary>
public int[] Triangles { get { return triangles; } set { triangles = value; } }
public Color32F GetColor () {
return color;
}
public void SetColor (Color32F color) {
this.color = color;
}
public void SetColor (float r, float g, float b, float a) {
color = new Color32F(r, g, b, a);
}
public string Path { get { return path; } set { path = value; } }
public Sequence Sequence { get { return sequence; } }
/// <summary>
/// The source mesh if this is a linked mesh, else null. A linked mesh shares the
/// <see cref="VertexAttachment.Bones">Bones</see>, <see cref="VertexAttachment.Vertices">Vertices</see>,
/// <see cref="RegionUVs"/>, <see cref="Triangles"/>, <see cref="HullLength"/>, <see cref="Edges"/>,
/// <see cref="Width"/>, <see cref="Height"/> with the
/// source mesh, but may have a different <see cref="name"/> or <see cref="path"/>, and therefore a different texture region.
/// </summary>
public MeshAttachment SourceMesh {
get { return sourceMesh; }
set {
sourceMesh = value;
if (value != null) {
bones = value.bones;
vertices = value.vertices;
worldVerticesLength = value.worldVerticesLength;
regionUVs = value.regionUVs;
triangles = value.triangles;
HullLength = value.HullLength;
Edges = value.Edges;
Width = value.Width;
Height = value.Height;
}
}
}
// Nonessential.
/// <summary>
/// Vertex index pairs describing edges for controlling triangulation, or null if nonessential data was not exported. Mesh
/// triangles do not cross edges. Triangulation is not performed at runtime.
/// </summary>
public int[] Edges { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public MeshAttachment (string name, Sequence sequence)
: base(name) {
if (sequence == null) throw new ArgumentException("sequence cannot be null.", "sequence");
this.sequence = sequence;
}
/// <summary>Copy constructor. Use <see cref="NewLinkedMesh"/> if the other mesh is a linked mesh.</summary>
protected MeshAttachment (MeshAttachment other)
: base(other) {
if (sourceMesh != null) throw new ArgumentException("Use newLinkedMesh to copy a linked mesh.");
path = other.path;
color = other.color;
regionUVs = new float[other.regionUVs.Length];
Array.Copy(other.regionUVs, 0, regionUVs, 0, regionUVs.Length);
triangles = new int[other.triangles.Length];
Array.Copy(other.triangles, 0, triangles, 0, triangles.Length);
hullLength = other.hullLength;
sequence = new Sequence(other.sequence);
// Nonessential.
if (other.Edges != null) {
Edges = new int[other.Edges.Length];
Array.Copy(other.Edges, 0, Edges, 0, Edges.Length);
}
Width = other.Width;
Height = other.Height;
}
public void UpdateSequence () {
sequence.Update(this);
}
/// <summary>
/// Returns a new mesh with the <see cref="SourceMesh"/> set to this mesh's source mesh, if any, else to this mesh.
/// </summary>
public MeshAttachment NewLinkedMesh () {
var mesh = new MeshAttachment(Name, new Sequence(sequence));
mesh.timelineAttachment = timelineAttachment;
mesh.path = path;
mesh.color = color;
mesh.SourceMesh = sourceMesh != null ? sourceMesh : this;
mesh.UpdateSequence();
return mesh;
}
public override Attachment Copy () {
return sourceMesh != null ? NewLinkedMesh() : new MeshAttachment(this);
}
/// <summary>
/// Computes <see cref="Sequence.GetUVs(int)">UVs</see> for a mesh attachment.
/// </summary>
/// <param name="uvs">Output array for the computed UVs, same length as regionUVs.</param>
internal static void ComputeUVs (TextureRegion region, float[] regionUVs, float[] uvs) {
int n = uvs.Length;
float u, v, width, height;
AtlasRegion r = region as AtlasRegion;
if (r != null) {
u = r.u;
v = r.v;
float textureWidth = region.width / (region.u2 - region.u);
float textureHeight = region.height / (region.v2 - region.v);
switch (r.degrees) {
case 90: {
u -= (r.originalHeight - r.offsetY - r.packedWidth) / textureWidth;
v -= (r.originalWidth - r.offsetX - r.packedHeight) / textureHeight;
width = r.originalHeight / textureWidth;
height = r.originalWidth / textureHeight;
for (int i = 0; i < n; i += 2) {
uvs[i] = u + regionUVs[i + 1] * width;
uvs[i + 1] = v + (1 - regionUVs[i]) * height;
}
return;
}
case 180: {
u -= (r.originalWidth - r.offsetX - r.packedWidth) / textureWidth;
v -= r.offsetY / textureHeight;
width = r.originalWidth / textureWidth;
height = r.originalHeight / textureHeight;
for (int i = 0; i < n; i += 2) {
uvs[i] = u + (1 - regionUVs[i]) * width;
uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height;
}
return;
}
case 270: {
u -= r.offsetY / textureWidth;
v -= r.offsetX / textureHeight;
width = r.originalHeight / textureWidth;
height = r.originalWidth / textureHeight;
for (int i = 0; i < n; i += 2) {
uvs[i] = u + (1 - regionUVs[i + 1]) * width;
uvs[i + 1] = v + regionUVs[i] * height;
}
return;
}
default: {
u -= r.offsetX / textureWidth;
v -= (r.originalHeight - r.offsetY - r.packedHeight) / textureHeight;
width = r.originalWidth / textureWidth;
height = r.originalHeight / textureHeight;
break;
}
}
} else if (region == null) {
u = v = 0;
width = height = 1;
} else {
u = region.u;
v = region.v;
width = region.u2 - u;
height = region.v2 - v;
}
for (int i = 0; i < n; i += 2) {
uvs[i] = u + regionUVs[i] * width;
uvs[i + 1] = v + regionUVs[i + 1] * height;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b7f7514a003143844b6d01ecc93ed4d5
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
using System.Collections.Generic;
namespace Spine {
public class PathAttachment : VertexAttachment {
internal float[] lengths;
internal bool closed, constantSpeed;
/// <summary>The length in the setup pose from the start of the path to the end of each curve.</summary>
public float[] Lengths { get { return lengths; } set { lengths = value; } }
/// <summary>If true, the start and end knots are connected.</summary>
public bool Closed { get { return closed; } set { closed = value; } }
/// <summary>If true, additional calculations are performed to make computing positions along the path more accurate so movement along
/// the path has a constant speed.</summary>
public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
public PathAttachment (String name)
: base(name) {
}
/// <summary>Copy constructor.</summary>
protected PathAttachment (PathAttachment other)
: base(other) {
lengths = new float[other.lengths.Length];
Array.Copy(other.lengths, 0, lengths, 0, lengths.Length);
closed = other.closed;
constantSpeed = other.constantSpeed;
}
public override Attachment Copy () {
return new PathAttachment(this);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c77d9bf384a1e9f41966464e7e3b4870
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,78 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be
/// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a
/// skin.
/// <p>
/// See <a href="https://esotericsoftware.com/spine-points">Point Attachments</a> in the Spine User Guide.
/// </summary>
public class PointAttachment : Attachment {
internal float x, y, rotation;
/// <summary>The local x position.</summary>
public float X { get { return x; } set { x = value; } }
/// <summary>The local y position.</summary>
public float Y { get { return y; } set { y = value; } }
/// <summary>The local rotation in degrees, counter clockwise.</summary>
public float Rotation { get { return rotation; } set { rotation = value; } }
public PointAttachment (string name)
: base(name) {
}
/// <summary>Copy constructor.</summary>
protected PointAttachment (PointAttachment other)
: base(other) {
x = other.x;
y = other.y;
rotation = other.rotation;
}
/// <summary>Computes the world position from the local position.</summary>
public void ComputeWorldPosition (BonePose bone, out float ox, out float oy) {
bone.LocalToWorld(this.x, this.y, out ox, out oy);
}
/// <summary>Computes the world rotation from the local rotation.</summary>
public float ComputeWorldRotation (BonePose bone) {
float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
float x = cos * bone.a + sin * bone.b;
float y = cos * bone.c + sin * bone.d;
return MathUtils.Atan2Deg(y, x);
}
public override Attachment Copy () {
return new PointAttachment(this);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4fdde4cc4df0952468946f4f913dcb36
timeCreated: 1485603478
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,229 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
using System;
namespace Spine {
#if IS_UNITY
using Color32F = UnityEngine.Color;
#endif
/// <summary>Attachment that displays a texture region.</summary>
public class RegionAttachment : Attachment, IHasSequence {
public const int BLX = 0, BLY = 1;
public const int ULX = 2, ULY = 3;
public const int URX = 4, URY = 5;
public const int BRX = 6, BRY = 7;
internal readonly Sequence sequence;
internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height;
// Color is a struct, set to protected to prevent
// Color color = slot.color; color.a = 0.5;
// modifying just a copy of the struct instead of the original
// object as in reference implementation.
protected Color32F color = new Color32F(1, 1, 1, 1);
public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } }
/// <summary>The local rotation in degrees, counter clockwise.</summary>
public float Rotation { get { return rotation; } set { rotation = value; } }
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
public float Width { get { return width; } set { width = value; } }
public float Height { get { return height; } set { height = value; } }
public Color32F GetColor () {
return color;
}
public void SetColor (Color32F color) {
this.color = color;
}
public void SetColor (float r, float g, float b, float a) {
color = new Color32F(r, g, b, a);
}
public string Path { get; set; }
public Sequence Sequence { get { return sequence; } }
public RegionAttachment (string name, Sequence sequence)
: base(name) {
if (sequence == null) throw new ArgumentException("sequence cannot be null.", "sequence");
this.sequence = sequence;
}
/// <summary>Copy constructor.</summary>
public RegionAttachment (RegionAttachment other)
: base(other) {
Path = other.Path;
x = other.x;
y = other.y;
scaleX = other.scaleX;
scaleY = other.scaleY;
rotation = other.rotation;
width = other.width;
height = other.height;
color = other.color;
sequence = new Sequence(other.sequence);
}
/// <summary><para>
/// Transforms the attachment's four vertices to world coordinates. If the attachment has a <see cref="Sequence"/> the region may
/// be changed.</para>
/// <para>
/// See <see href='https://esotericsoftware.com/spine-runtime-skeletons#World-transforms'>World transforms</a> in the Spine
/// Runtimes Guide.</para></summary>
/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to offset + 8.</param>
/// <param name="vertexOffsets">The vertex <see cref="Sequence.GetOffsets(int)">offsets</see>.</param>
/// <param name="offset">The worldVertices index to begin writing values.</param>
/// <param name="stride">The number of worldVertices entries between the value pairs written.</param>
public void ComputeWorldVertices (Slot slot, float[] vertexOffsets, float[] worldVertices, int offset, int stride = 2) {
BonePose bone = slot.Bone.AppliedPose;
float bwx = bone.worldX, bwy = bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
// Vertex order is different from RegionAttachment.java
float offsetX = vertexOffsets[BRX]; // 0
float offsetY = vertexOffsets[BRY]; // 1
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;
offsetX = vertexOffsets[BLX]; // 2
offsetY = vertexOffsets[BLY]; // 3
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;
offsetX = vertexOffsets[ULX]; // 4
offsetY = vertexOffsets[ULY]; // 5
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;
offsetX = vertexOffsets[URX]; // 6
offsetY = vertexOffsets[URY]; // 7
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
//offset += stride;
}
/// <summary>
/// Returns the vertex <see cref="Sequence.GetOffsets(int)">offsets</see> for the specified slot pose.
/// </summary>
public float[] GetOffsets (SlotPose pose) {
return sequence.GetOffsets(sequence.ResolveIndex(pose));
}
public void UpdateSequence () {
sequence.Update(this);
}
public override Attachment Copy () {
return new RegionAttachment(this);
}
/// <summary>
/// Computes <see cref="Sequence.GetUVs(int)">UVs</see> and <see cref="Sequence.GetOffsets(int)">offsets</see> for a region attachment.
/// </summary>
/// <param name="uvs">Output array for the computed UVs, length of 8.</param>
/// <param name="offset">Output array for the computed vertex offsets, length of 8.</param>
internal static void ComputeUVs (TextureRegion region, float x, float y, float scaleX, float scaleY, float rotation, float width,
float height, float[] offset, float[] uvs) {
float localX2 = width / 2, localY2 = height / 2;
float localX = -localX2, localY = -localY2;
bool rotated = false;
AtlasRegion r = region as AtlasRegion;
if (r != null) {
localX += r.offsetX / r.originalWidth * width;
localY += r.offsetY / r.originalHeight * height;
if (r.degrees == 90) {
rotated = true;
localX2 -= (r.originalWidth - r.offsetX - r.packedHeight) / r.originalWidth * width;
localY2 -= (r.originalHeight - r.offsetY - r.packedWidth) / r.originalHeight * height;
} else {
localX2 -= (r.originalWidth - r.offsetX - r.packedWidth) / r.originalWidth * width;
localY2 -= (r.originalHeight - r.offsetY - r.packedHeight) / r.originalHeight * height;
}
}
localX *= scaleX;
localY *= scaleY;
localX2 *= scaleX;
localY2 *= scaleY;
float rot = rotation * MathUtils.DegRad, cos = (float)Math.Cos(rot), sin = (float)Math.Sin(rot);
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;
offset[BLX] = localXCos - localYSin;
offset[BLY] = localYCos + localXSin;
offset[ULX] = localXCos - localY2Sin;
offset[ULY] = localY2Cos + localXSin;
offset[URX] = localX2Cos - localY2Sin;
offset[URY] = localY2Cos + localX2Sin;
offset[BRX] = localX2Cos - localYSin;
offset[BRY] = localYCos + localX2Sin;
if (region == null) {
uvs[BLX] = 0;
uvs[BLY] = 0;
uvs[ULX] = 0;
uvs[ULY] = 1;
uvs[URX] = 1;
uvs[URY] = 1;
uvs[BRX] = 1;
uvs[BRY] = 0;
} else {
uvs[BLX] = region.u2;
uvs[ULY] = region.v2;
uvs[URX] = region.u;
uvs[BRY] = region.v;
if (rotated) {
uvs[BLY] = region.v;
uvs[ULX] = region.u2;
uvs[URY] = region.v2;
uvs[BRX] = region.u;
} else {
uvs[BLY] = region.v2;
uvs[ULX] = region.u;
uvs[URY] = region.v;
uvs[BRX] = region.u2;
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 89cefdd024734a941952a05d2b5dff71
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,173 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
using System.Text;
namespace Spine {
/// <summary>
/// Holds texture regions, UVs, and vertex offsets for rendering a region or mesh attachment. <see cref="Regions"/> must
/// be populated and <see cref="Update(IHasSequence)"/> called before use.
/// </summary>
public class Sequence {
static int nextID = 0;
static readonly Object nextIdLock = new Object();
internal readonly int id;
internal readonly TextureRegion[] regions;
internal readonly bool pathSuffix;
internal float[][] uvs, offsets;
internal int start, digits, setupIndex;
/// <summary>The starting number for the numeric <see cref="GetPath(string, int)">path</see> suffix.</summary>
public int Start { get { return start; } set { start = value; } }
/// <summary>The minimum number of digits in the numeric <see cref="GetPath(string, int)">path</see> suffix, for zero
/// padding. 0 for no zero padding.</summary>
public int Digits { get { return digits; } set { digits = value; } }
/// <summary>The index of the region to show for the setup pose.</summary>
public int SetupIndex { get { return setupIndex; } set { setupIndex = value; } }
/// <summary>The list of texture regions this sequence will display.</summary>
public TextureRegion[] Regions { get { return regions; } }
/// <summary>Returns true if the <see cref="GetPath(string, int)">path</see> has a numeric suffix.</summary>
public bool HasPathSuffix { get { return pathSuffix; } }
/// <summary>Returns a unique ID for this attachment.</summary>
public int Id { get { return id; } }
/// <param name="count">The number of texture regions this sequence will display.</param>
/// <param name="pathSuffix">If true, the <see cref="GetPath(string, int)">path</see> has a numeric suffix. If false, all
/// regions will use the same path, so <c>count</c> should be 1.</param>
public Sequence (int count, bool pathSuffix) {
lock (Sequence.nextIdLock) {
id = Sequence.nextID++;
}
regions = new TextureRegion[count];
this.pathSuffix = pathSuffix;
}
/// <summary>Copy constructor.</summary>
public Sequence (Sequence other) {
lock (Sequence.nextIdLock) {
id = Sequence.nextID++;
}
int regionCount = other.regions.Length;
regions = new TextureRegion[regionCount];
Array.Copy(other.regions, 0, regions, 0, regionCount);
start = other.start;
digits = other.digits;
setupIndex = other.setupIndex;
pathSuffix = other.pathSuffix;
if (other.uvs != null) {
int length = other.uvs[0].Length;
uvs = new float[regionCount][];
for (int i = 0; i < regionCount; i++) {
uvs[i] = new float[length];
Array.Copy(other.uvs[i], 0, uvs[i], 0, length);
}
}
if (other.offsets != null) {
offsets = new float[regionCount][];
for (int i = 0; i < regionCount; i++) {
offsets[i] = new float[8];
Array.Copy(other.offsets[i], 0, offsets[i], 0, 8);
}
}
}
public void Update (IHasSequence attachment) {
int regionCount = regions.Length;
RegionAttachment region = attachment as RegionAttachment;
if (region != null) {
uvs = new float[regionCount][];
offsets = new float[regionCount][];
for (int i = 0; i < regionCount; i++) {
uvs[i] = new float[8];
offsets[i] = new float[8];
RegionAttachment.ComputeUVs(regions[i], region.x, region.y, region.scaleX, region.scaleY, region.rotation,
region.width, region.height, offsets[i], uvs[i]);
}
} else {
MeshAttachment mesh = attachment as MeshAttachment;
if (mesh != null) {
float[] regionUVs = mesh.regionUVs;
uvs = new float[regionCount][];
offsets = null;
for (int i = 0; i < regionCount; i++) {
uvs[i] = new float[regionUVs.Length];
MeshAttachment.ComputeUVs(regions[i], regionUVs, uvs[i]);
}
}
}
}
/// <summary>Returns the <see cref="Regions"/> index for the <see cref="SlotPose.SequenceIndex"/>.</summary>
public int ResolveIndex (SlotPose pose) {
int index = pose.SequenceIndex;
if (index == -1) index = setupIndex;
if (index >= regions.Length) index = regions.Length - 1;
return index;
}
/// <summary>Returns the texture region from <see cref="Regions"/> for the specified index.</summary>
public TextureRegion GetRegion (int index) {
return regions[index];
}
/// <summary>Returns the UVs for the specified index. <see cref="Regions">Regions</see> must be populated and
/// <see cref="Update(IHasSequence)"/> called before calling this method.</summary>
public float[] GetUVs (int index) {
return uvs[index];
}
/// <summary>
/// Returns vertex offsets from the center of a <see cref="RegionAttachment"/>. Invalid to call for a <see cref="MeshAttachment"/>.
/// </summary>
public float[] GetOffsets (int index) {
return offsets[index];
}
/// <summary>Returns the specified base path with an optional numeric suffix for the specified index.</summary>
public string GetPath (string basePath, int index) {
if (!pathSuffix) return basePath;
var buffer = new StringBuilder(basePath.Length + digits);
buffer.Append(basePath);
string frame = (start + index).ToString();
for (int i = digits - frame.Length; i > 0; i--)
buffer.Append('0');
buffer.Append(frame);
return buffer.ToString();
}
}
/// <summary>Controls how <see cref="Sequence.Regions"/> are displayed over time.</summary>
public enum SequenceMode {
Hold, Once, Loop, Pingpong, OnceReverse, LoopReverse, PingpongReverse
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 522632bd4e297fe47acf78100bfd8689
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,159 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>Base class for an attachment with vertices that are transformed by one or more bones and can be deformed by
/// <see cref="SlotPose.Deform"/>.</summary>
public abstract class VertexAttachment : Attachment {
static int nextID = 0;
static readonly Object nextIdLock = new Object();
internal readonly int id;
internal int[] bones;
internal float[] vertices;
internal int worldVerticesLength;
/// <summary>Gets a unique ID for this attachment.</summary>
public int Id { get { return id; } }
/// <summary>The bones that affect the <see cref="Vertices"/>. The entries are, for each vertex, the number of bones affecting the
/// vertex followed by that many bone indices, which is <see cref="Skeleton.Bones"/> index. Null if this attachment has no
/// weights.</summary>
public int[] Bones { get { return bones; } set { bones = value; } }
/// <summary>The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are <c>x,y</c> pairs
/// for each vertex. For a weighted attachment, the values are <c>x,y,weight</c> triplets for each bone affecting each
/// vertex.</summary>
public float[] Vertices { get { return vertices; } set { vertices = value; } }
public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } }
public VertexAttachment (string name)
: base(name) {
lock (VertexAttachment.nextIdLock) {
id = VertexAttachment.nextID++;
}
}
/// <summary>Copy constructor.</summary>
public VertexAttachment (VertexAttachment other)
: base(other) {
lock (VertexAttachment.nextIdLock) {
id = VertexAttachment.nextID++;
}
timelineAttachment = other.timelineAttachment;
if (other.bones != null) {
bones = new int[other.bones.Length];
Array.Copy(other.bones, 0, bones, 0, bones.Length);
} else
bones = null;
if (other.vertices != null) {
vertices = new float[other.vertices.Length];
Array.Copy(other.vertices, 0, vertices, 0, vertices.Length);
} else
vertices = null;
worldVerticesLength = other.worldVerticesLength;
}
public void ComputeWorldVertices (Skeleton skeleton, Slot slot, float[] worldVertices) {
ComputeWorldVertices(skeleton, slot, 0, worldVerticesLength, worldVertices, 0);
}
/// <summary>
/// Transforms the attachment's local <see cref="Vertices"/> to world coordinates. If <see cref="SlotPose.Deform"/>
/// is not empty, it is used to deform the vertices.
/// <para />
/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
/// Runtimes Guide.
/// </summary>
/// <param name="start">The index of the first <see cref="Vertices"/> value to transform. Each vertex has 2 values, x and y.</param>
/// <param name="count">The number of world vertex values to output. Must be less than or equal to <see cref="WorldVerticesLength"/> - start.</param>
/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
/// <param name="stride">The number of <paramref name="worldVertices"/> entries between the value pairs written.</param>
public virtual void ComputeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
count = offset + (count >> 1) * stride;
ExposedList<float> deformArray = slot.AppliedPose.deform;
float[] vertices = this.vertices;
int[] bones = this.bones;
if (bones == null) {
if (deformArray.Count > 0) vertices = deformArray.Items;
BonePose bone = slot.bone.AppliedPose;
float x = bone.worldX, y = bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (int vv = start, w = offset; w < count; vv += 2, w += stride) {
float vx = vertices[vv], vy = vertices[vv + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
}
return;
}
int v = 0, skip = 0;
for (int i = 0; i < start; i += 2) {
int n = bones[v];
v += n + 1;
skip += n;
}
Bone[] skeletonBones = skeleton.bones.Items;
if (deformArray.Count == 0) {
for (int w = offset, b = skip * 3; w < count; w += stride) {
float wx = 0, wy = 0;
int n = bones[v++];
n += v;
for (; v < n; v++, b += 3) {
BonePose bone = skeletonBones[bones[v]].AppliedPose;
float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
} else {
float[] deform = deformArray.Items;
for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
float wx = 0, wy = 0;
int n = bones[v++];
n += v;
for (; v < n; v++, b += 3, f += 2) {
BonePose bone = skeletonBones[bones[v]].AppliedPose;
float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8b40cfb462a8b774891e1604e5360d32
timeCreated: 1466772712
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
namespace Spine {
public enum BlendMode {
Normal, Additive, Multiply, Screen
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b08ef68b8e39f40498ef24ef12cca281
timeCreated: 1456265155
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,71 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>A node in a skeleton's hierarchy with a transform that affects its children and their attachments. A bone has a
/// number of poses:
/// <list type="bullet">
/// <item><see cref="Data"/>: The setup pose.</item>
/// <item><see cref="Pose"/>: The unconstrained local pose. Set by animations and application code.</item>
/// <item><see cref="AppliedPose"/>: The local pose to use for rendering. Possibly modified by constraints.</item>
/// <item>World transform: the local pose combined with the parent world transform. Computed on a pose by
/// <see cref="BonePose.UpdateWorldTransform(Skeleton)"/> and <see cref="Skeleton.UpdateWorldTransform(Physics)"/>.</item>
/// </list>
/// </summary>
public class Bone : PosedActive<BoneData, BonePose> {
static public bool yDown;
internal Bone parent;
internal ExposedList<Bone> children = new ExposedList<Bone>(4);
internal bool sorted;
public Bone (BoneData data, Bone parent)
: base(data, new BonePose(), new BonePose()) {
this.parent = parent;
appliedPose.bone = this;
constrainedPose.bone = this;
}
/// <summary>
/// Copy constructor. Does not copy the <see cref="Children"/> bones.
/// </summary>
public Bone (Bone bone, Bone parent)
: this(bone.data, parent) {
pose.Set(bone.pose);
}
public Bone Parent { get { return parent; } }
public ExposedList<Bone> Children { get { return children; } }
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ed00e3a4b386a964fb0f1c7ffd5544e5
timeCreated: 1456265155
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,89 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// The setup pose for a bone.
/// </summary>
public class BoneData : PosedData<BonePose> {
internal int index;
internal BoneData parent;
internal float length;
/// <param name="parent">May be null.</param>
public BoneData (int index, string name, BoneData parent)
: base(name, new BonePose()) {
if (index < 0) throw new ArgumentException("index must be >= 0", "index");
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.index = index;
this.parent = parent;
}
/// <summary>Copy constructor.</summary>
/// <param name="parent">May be null.</param>
public BoneData (BoneData data, BoneData parent)
: this(data.index, data.name, parent) {
length = data.length;
setupPose.Set(data.setupPose);
}
/// <summary>The <see cref="Skeleton.Bones"/> index.</summary>
public int Index { get { return index; } }
/// <summary>May be null.</summary>
public BoneData Parent { get { return parent; } }
public float Length { get { return length; } set { length = value; } }
}
/// <summary>
/// Determines how a bone inherits world transforms from parent bones.
/// </summary>
public enum Inherit {
Normal,
OnlyTranslation,
NoRotationOrReflection,
NoScale,
NoScaleOrReflection
}
public class InheritEnum {
public static readonly Inherit[] Values = {
Inherit.Normal,
Inherit.OnlyTranslation,
Inherit.NoRotationOrReflection,
Inherit.NoScale,
Inherit.NoScaleOrReflection
};
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2cf831005966832449a5de742752e578
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,470 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>The applied local pose and world transform for a bone. This is the <see cref="Bone.Pose"/> with constraints applied and the
/// world transform computed by <see cref="Skeleton.UpdateWorldTransform(Physics)"/> and <see cref="UpdateWorldTransform(Skeleton)"/>.
/// <para>
/// If the world transform is changed, call <see cref="UpdateLocalTransform(Skeleton)"/> before using the local transform. The local
/// transform may be needed by other code (eg to apply another constraint).</para>
/// <para>
/// After changing the world transform, call <see cref="UpdateWorldTransform(Skeleton)"/> on every descendant bone. It may be more
/// convenient to modify the local transform instead, then call <see cref="Skeleton.UpdateWorldTransform(Physics)"/> to update the world
/// transforms for all bones and apply constraints.</para>
/// </summary>
public class BonePose : IPose<BonePose>, IUpdate {
public Bone bone;
internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
internal Inherit inherit;
internal float a, b, worldX;
internal float c, d, worldY;
internal int world, local;
public void Set (BonePose pose) {
if (pose == null) throw new ArgumentNullException("pose", "pose cannot be null.");
x = pose.x;
y = pose.y;
rotation = pose.rotation;
scaleX = pose.scaleX;
scaleY = pose.scaleY;
shearX = pose.shearX;
shearY = pose.shearY;
inherit = pose.inherit;
}
/// <summary>The local X translation.</summary>
public float X { get { return x; } set { x = value; } }
/// <summary>The local Y translation.</summary>
public float Y { get { return y; } set { y = value; } }
/// <summary>Sets local x and y translation.</summary>
public void SetPosition (float x, float y) {
this.x = x;
this.y = y;
}
/// <summary>The local rotation.</summary>
public float Rotation { get { return rotation; } set { rotation = value; } }
/// <summary>The local scaleX.</summary>
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
/// <summary>The local scaleY.</summary>
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
/// <summary>Sets local scaleX and scaleY.</summary>
public void SetScale (float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
}
/// <summary>Sets local scaleX and scaleY to the same value.</summary>
public void SetScale (float scale) {
scaleX = scale;
scaleY = scale;
}
/// <summary>The local shearX.</summary>
public float ShearX { get { return shearX; } set { shearX = value; } }
/// <summary>The local shearY.</summary>
public float ShearY { get { return shearY; } set { shearY = value; } }
/// <summary>Determines how parent world transforms affect this bone.</summary>
public Inherit Inherit { get { return inherit; } set { inherit = value; } }
/// <summary>
/// Called by <see cref="Skeleton.UpdateCache()"/> to compute the world transform, if needed.
/// </summary>
public void Update (Skeleton skeleton, Physics physics) {
if (world != skeleton.update) UpdateWorldTransform(skeleton);
}
/// <summary>Computes the world transform using the parent bone's world transform and this applied local pose. Child bones are not
/// updated.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
/// Runtimes Guide.</para></summary>
public void UpdateWorldTransform (Skeleton skeleton) {
if (local == skeleton.update)
UpdateLocalTransform(skeleton);
else
world = skeleton.update;
if (bone.parent == null) { // Root bone.
float sx = skeleton.scaleX, sy = skeleton.ScaleY;
float rx = (rotation + shearX) * MathUtils.DegRad;
float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
a = (float)Math.Cos(rx) * scaleX * sx;
b = (float)Math.Cos(ry) * scaleY * sx;
c = (float)Math.Sin(rx) * scaleX * sy;
d = (float)Math.Sin(ry) * scaleY * sy;
worldX = x * sx + skeleton.x;
worldY = y * sy + skeleton.y;
return;
}
BonePose parent = bone.parent.appliedPose;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
worldX = pa * x + pb * y + parent.worldX;
worldY = pc * x + pd * y + parent.worldY;
switch (inherit) {
case Inherit.Normal: {
float rx = (rotation + shearX) * MathUtils.DegRad;
float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
float la = (float)Math.Cos(rx) * scaleX;
float lb = (float)Math.Cos(ry) * scaleY;
float lc = (float)Math.Sin(rx) * scaleX;
float ld = (float)Math.Sin(ry) * scaleY;
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
break;
}
case Inherit.OnlyTranslation: {
float sx = skeleton.scaleX, sy = skeleton.ScaleY;
float rx = (rotation + shearX) * MathUtils.DegRad;
float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
a = (float)Math.Cos(rx) * scaleX * sx;
b = (float)Math.Cos(ry) * scaleY * sx;
c = (float)Math.Sin(rx) * scaleX * sy;
d = (float)Math.Sin(ry) * scaleY * sy;
break;
}
case Inherit.NoRotationOrReflection: {
float sx = skeleton.scaleX, sy = skeleton.ScaleY, sxi = 1 / sx, syi = 1 / sy;
pa *= sxi;
pc *= syi;
float s = pa * pa + pc * pc, r;
if (s > MathUtils.EpsilonSq) {
s = Math.Abs(pa * pd * syi - pb * sxi * pc) / s;
pb = pc * s;
pd = pa * s;
r = rotation - MathUtils.Atan2Deg(pc, pa);
} else {
pa = 0;
pc = 0;
r = rotation - 90 + MathUtils.Atan2Deg(pd, pb);
}
float rx = (r + shearX) * MathUtils.DegRad;
float ry = (r + shearY + 90) * MathUtils.DegRad;
float la = (float)Math.Cos(rx) * scaleX;
float lb = (float)Math.Cos(ry) * scaleY;
float lc = (float)Math.Sin(rx) * scaleX;
float ld = (float)Math.Sin(ry) * scaleY;
a = (pa * la - pb * lc) * sx;
b = (pa * lb - pb * ld) * sx;
c = (pc * la + pd * lc) * sy;
d = (pc * lb + pd * ld) * sy;
break;
}
case Inherit.NoScale:
case Inherit.NoScaleOrReflection: {
float sx = skeleton.scaleX, sy = skeleton.ScaleY, sxi = 1 / sx, syi = 1 / sy;
float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
float za = (pa * cos + pb * sin) * sxi;
float zc = (pc * cos + pd * sin) * syi;
float s = 1 / (float)Math.Sqrt(za * za + zc * zc);
za *= s;
zc *= s;
float zb = -zc, zd = za;
if (inherit == Inherit.NoScale && pa * pd - pb * pc < 0 != (sx < 0 != sy < 0)) {
zb = -zb;
zd = -zd;
}
float rx = shearX * MathUtils.DegRad;
float ry = (90 + shearY) * MathUtils.DegRad;
float la = (float)Math.Cos(rx) * scaleX;
float lb = (float)Math.Cos(ry) * scaleY;
float lc = (float)Math.Sin(rx) * scaleX;
float ld = (float)Math.Sin(ry) * scaleY;
a = (za * la + zb * lc) * sx;
b = (za * lb + zb * ld) * sx;
c = (zc * la + zd * lc) * sy;
d = (zc * lb + zd * ld) * sy;
break;
}
}
}
/// <summary>
/// Computes the local transform values from the world transform.
/// <para>
/// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The local transform after
/// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical.
/// </para></summary>
public void UpdateLocalTransform (Skeleton skeleton) {
local = 0;
world = skeleton.update;
float sx = skeleton.scaleX, sy = skeleton.ScaleY;
if (bone.parent == null) {
float sxi = 1 / sx, syi = 1 / sy;
x = (worldX - skeleton.x) * sxi;
y = (worldY - skeleton.y) * syi;
Set(a * sxi, b * sxi, c * syi, d * syi, 0);
return;
}
BonePose parent = bone.parent.appliedPose;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
float pad = pa * pd - pb * pc, pid = 1 / pad;
float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
x = dx * ia - dy * ib;
y = dy * id - dx * ic;
switch (inherit) {
case Inherit.Normal: {
Set(ia * a - ib * c, ia * b - ib * d, id * c - ic * a, id * d - ic * b, 0);
break;
}
case Inherit.OnlyTranslation: {
float sxi = 1 / sx, syi = 1 / sy;
Set(a * sxi, b * sxi, c * syi, d * syi, 0);
break;
}
case Inherit.NoRotationOrReflection: {
float sxi = 1 / sx, syi = 1 / sy;
pa *= sxi;
pc *= syi;
float wa = a * sxi, wb = b * sxi, wc = c * syi, wd = d * syi;
float s = 1 / (pa * pa + pc * pc), det = 1 / Math.Abs(pad * sxi * syi);
Set((pa * wa + pc * wc) * s, (pa * wb + pc * wd) * s, (pa * wc - pc * wa) * det, (pa * wd - pc * wb) * det,
MathUtils.Atan2Deg(pc, pa));
break;
}
case Inherit.NoScale:
case Inherit.NoScaleOrReflection: {
float sxi = 1 / sx, syi = 1 / sy;
float wa = a * sxi, wb = b * sxi, wc = c * syi, wd = d * syi;
float tx = pd * a - pb * c, ty = pa * c - pc * a;
if (pad < 0) {
tx = -tx;
ty = -ty;
}
float r = MathUtils.Atan2Deg(ty, tx);
rotation = r;
r *= MathUtils.DegRad;
float cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
float za = (pa * cos + pb * sin) * sxi;
float zc = (pc * cos + pd * sin) * syi;
float s = 1 / (float)Math.Sqrt(za * za + zc * zc);
za *= s;
zc *= s;
float si = inherit == Inherit.NoScale && pad < 0 != (sx < 0 != sy < 0) ? -1 : 1;
Set(za * wa + zc * wc, za * wb + zc * wd, (za * wc - zc * wa) * si, (za * wd - zc * wb) * si);
break;
}
}
}
private void Set (float ra, float rb, float rc, float rd) {
float x = ra * ra + rc * rc, y = rb * rb + rd * rd;
if (x > MathUtils.EpsilonSq) {
shearX = MathUtils.Atan2Deg(rc, ra);
scaleX = (float)Math.Sqrt(x);
} else {
shearX = 0;
scaleX = 0;
}
scaleY = (float)Math.Sqrt(y);
if (y > MathUtils.EpsilonSq) {
shearY = MathUtils.Atan2Deg(rd, rb);
if (ra * rd - rb * rc < 0) {
scaleY = -scaleY;
shearY += 90;
} else
shearY -= 90;
if (shearY > 180)
shearY -= 360;
else if (shearY <= -180) //
shearY += 360;
} else
shearY = 0;
}
private void Set (float ra, float rb, float rc, float rd, float ro) {
shearX = 0;
float x = ra * ra + rc * rc, y = rb * rb + rd * rd;
if (x > MathUtils.EpsilonSq) {
float r = MathUtils.Atan2Deg(rc, ra);
rotation = r + ro;
scaleX = (float)Math.Sqrt(x);
scaleY = (float)Math.Sqrt(y);
if (y > MathUtils.EpsilonSq) {
shearY = MathUtils.Atan2Deg(rd, rb);
if (ra * rd - rb * rc < 0) {
scaleY = -scaleY;
shearY += 90 - r;
} else
shearY -= 90 + r;
if (shearY > 180)
shearY -= 360;
else if (shearY <= -180) //
shearY += 360;
} else
shearY = 0;
} else {
scaleX = 0;
scaleY = (float)Math.Sqrt(y);
shearY = 0;
rotation = y > MathUtils.EpsilonSq ? MathUtils.Atan2Deg(rd, rb) - 90 + ro : ro;
}
}
/// <summary>
/// If the world transform has been modified by constraints and the local transform no longer matches,
/// <see cref="UpdateLocalTransform(Skeleton)"/> is called. Call this after <see cref="Skeleton.UpdateWorldTransform(Physics)"/> before
/// using the applied local transform.
/// </summary>
public void ValidateLocalTransform (Skeleton skeleton) {
if (local == skeleton.update) UpdateLocalTransform(skeleton);
}
internal void ModifyLocal (Skeleton skeleton) {
if (local == skeleton.update) UpdateLocalTransform(skeleton);
world = 0;
ResetWorld(skeleton.update);
}
internal void ModifyWorld (int update) {
local = update;
world = update;
ResetWorld(update);
}
private void ResetWorld (int update) {
Bone[] children = bone.children.Items;
for (int i = 0, n = bone.children.Count; i < n; i++) {
BonePose child = children[i].appliedPose;
if (child.world == update) {
child.world = 0;
child.local = 0;
child.ResetWorld(update);
}
}
}
/// <summary>The world transform <c>[a b][c d]</c> x-axis x component.</summary>
public float A { get { return a; } set { a = value; } }
/// <summary>The world transform <c>[a b][c d]</c> y-axis x component.</summary>
public float B { get { return b; } set { b = value; } }
/// <summary>The world transform <c>[a b][c d]</c> x-axis y component.</summary>
public float C { get { return c; } set { c = value; } }
/// <summary>The world transform <c>[a b][c d]</c> y-axis y component.</summary>
public float D { get { return d; } set { d = value; } }
/// <summary>The world X position.</summary>
public float WorldX { get { return worldX; } set { worldX = value; } }
/// <summary>The world Y position.</summary>
public float WorldY { get { return worldY; } set { worldY = value; } }
/// <summary>The world rotation for the X axis, calculated using <see cref="a"/> and <see cref="c"/>. This is the direction the
/// bone is pointing.</summary>
public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } }
/// <summary>The world rotation for the Y axis, calculated using <see cref="b"/> and <see cref="d"/>.</summary>
public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } }
/// <summary>Returns the magnitude (always positive) of the world scale X, calculated using <see cref="a"/> and <see cref="c"/>.</summary>
public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
/// <summary>Returns the magnitude (always positive) of the world scale Y, calculated using <see cref="b"/> and <see cref="d"/>.</summary>
public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
/// <summary>Transforms a point from world coordinates to the bone's local coordinates.</summary>
public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
float a = this.a, b = this.b, c = this.c, d = this.d;
float det = a * d - b * c;
float x = worldX - this.worldX, y = worldY - this.worldY;
localX = (x * d - y * b) / det;
localY = (y * a - x * c) / det;
}
/// <summary>Transforms a point from the bone's local coordinates to world coordinates.</summary>
public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
worldX = localX * a + localY * b + this.worldX;
worldY = localX * c + localY * d + this.worldY;
}
/// <summary>Transforms a point from world coordinates to the parent bone's local coordinates.</summary>
public void WorldToParent (float worldX, float worldY, out float parentX, out float parentY) {
if (bone.parent == null) {
parentX = worldX;
parentY = worldY;
} else {
bone.parent.appliedPose.WorldToLocal(worldX, worldY, out parentX, out parentY);
}
}
/// <summary>Transforms a point from the parent bone's coordinates to world coordinates.</summary>
public void ParentToWorld (float parentX, float parentY, out float worldX, out float worldY) {
if (bone.parent == null) {
worldX = parentX;
worldY = parentY;
} else {
bone.parent.appliedPose.LocalToWorld(parentX, parentY, out worldX, out worldY);
}
}
/// <summary>Transforms a world rotation to a local rotation.</summary>
public float WorldToLocalRotation (float worldRotation) {
worldRotation *= MathUtils.DegRad;
float sin = (float)Math.Sin(worldRotation), cos = (float)Math.Cos(worldRotation);
return MathUtils.Atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX;
}
/// <summary>Transforms a local rotation to a world rotation.</summary>
public float LocalToWorldRotation (float localRotation) {
localRotation = (localRotation - rotation - shearX) * MathUtils.DegRad;
float sin = (float)Math.Sin(localRotation), cos = (float)Math.Cos(localRotation);
return MathUtils.Atan2Deg(cos * c + sin * d, cos * a + sin * b);
}
/// <summary>Rotates the world transform the specified amount.</summary>
public void RotateWorld (float degrees) {
degrees *= MathUtils.DegRad;
float sin = (float)Math.Sin(degrees), cos = (float)Math.Cos(degrees);
float ra = a, rb = b;
a = cos * ra - sin * c;
b = cos * rb - sin * d;
c = sin * ra + cos * c;
d = sin * rb + cos * d;
}
override public string ToString () {
return bone.data.name;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 024ad131f32a77e45bc91fc1599a8e0f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,115 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if UNITY_5_3_OR_NEWER
#define IS_UNITY
#endif
#if !IS_UNITY
namespace Spine {
/// <summary>
/// 32 bit floating point color to be used with XNA/Monogame.
/// </summary>
public struct Color32F {
public float r, g, b, a;
public Color32F (float r, float g, float b, float a = 1.0f) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Color32F (Microsoft.Xna.Framework.Color xnaColor)
: this(xnaColor.R / 255f, xnaColor.G / 255f, xnaColor.B / 255f, xnaColor.A / 255f) {
}
public static implicit operator Color32F (Microsoft.Xna.Framework.Color xnaColor) {
return new Color32F(xnaColor);
}
public static implicit operator Microsoft.Xna.Framework.Color (Color32F c) {
return new Microsoft.Xna.Framework.Color(
(byte)(c.r * 255),
(byte)(c.g * 255),
(byte)(c.b * 255),
(byte)(c.a * 255)
);
}
public override string ToString () {
return string.Format("RGBA({0}, {1}, {2}, {3})", r, g, b, a);
}
public static Color32F operator + (Color32F c1, Color32F c2) {
return new Color32F(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b, c1.a + c2.a);
}
public static Color32F operator - (Color32F c1, Color32F c2) {
return new Color32F(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b, c1.a - c2.a);
}
public static Color32F operator * (Color32F c1, Color32F c2) {
return new Color32F(c1.r * c2.r, c1.g * c2.g, c1.b * c2.b, c1.a * c2.a);
}
}
static class ColorExtensions {
public static Color32F Clamp (this Color32F color) {
color.r = MathUtils.Clamp(color.r, 0, 1);
color.g = MathUtils.Clamp(color.g, 0, 1);
color.b = MathUtils.Clamp(color.b, 0, 1);
color.a = MathUtils.Clamp(color.a, 0, 1);
return color;
}
public static Color32F ClampRGB (this Color32F color) {
color.r = MathUtils.Clamp(color.r, 0, 1);
color.g = MathUtils.Clamp(color.g, 0, 1);
color.b = MathUtils.Clamp(color.b, 0, 1);
return color;
}
public static Color32F RGBA8888ToColor (this uint rgba8888) {
float r = ((rgba8888 & 0xff000000) >> 24) / 255f;
float g = ((rgba8888 & 0x00ff0000) >> 16) / 255f;
float b = ((rgba8888 & 0x0000ff00) >> 8) / 255f;
float a = ((rgba8888 & 0x000000ff)) / 255f;
return new Color32F(r, g, b, a);
}
public static Color32F XRGB888ToColor (this uint xrgb888) {
float r = ((xrgb888 & 0x00ff0000) >> 16) / 255f;
float g = ((xrgb888 & 0x0000ff00) >> 8) / 255f;
float b = ((xrgb888 & 0x000000ff)) / 255f;
return new Color32F(r, g, b);
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3eb49ca43afc71d41bdadef9155891e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,98 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if UNITY_5_3_OR_NEWER
#define IS_UNITY
#endif
#if !IS_UNITY
namespace Spine {
/// <summary>
/// 32 bit floating point color to be used with other game toolkits than Unity and XNA/Monogame.
/// </summary>
public struct Color32F {
public float r, g, b, a;
public Color32F (float r, float g, float b, float a = 1.0f) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public override string ToString () {
return string.Format("RGBA({0}, {1}, {2}, {3})", r, g, b, a);
}
public static Color32F operator + (Color32F c1, Color32F c2) {
return new Color32F(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b, c1.a + c2.a);
}
public static Color32F operator - (Color32F c1, Color32F c2) {
return new Color32F(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b, c1.a - c2.a);
}
public static Color32F operator * (Color32F c1, Color32F c2) {
return new Color32F(c1.r * c2.r, c1.g * c2.g, c1.b * c2.b, c1.a * c2.a);
}
}
static class ColorExtensions {
public static Color32F Clamp (this Color32F color) {
color.r = MathUtils.Clamp(color.r, 0, 1);
color.g = MathUtils.Clamp(color.g, 0, 1);
color.b = MathUtils.Clamp(color.b, 0, 1);
color.a = MathUtils.Clamp(color.a, 0, 1);
return color;
}
public static Color32F ClampRGB (this Color32F color) {
color.r = MathUtils.Clamp(color.r, 0, 1);
color.g = MathUtils.Clamp(color.g, 0, 1);
color.b = MathUtils.Clamp(color.b, 0, 1);
return color;
}
public static Color32F RGBA8888ToColor (this uint rgba8888) {
float r = ((rgba8888 & 0xff000000) >> 24) / 255f;
float g = ((rgba8888 & 0x00ff0000) >> 16) / 255f;
float b = ((rgba8888 & 0x0000ff00) >> 8) / 255f;
float a = ((rgba8888 & 0x000000ff)) / 255f;
return new Color32F(r, g, b, a);
}
public static Color32F XRGB888ToColor (this uint xrgb888) {
float r = ((xrgb888 & 0x00ff0000) >> 16) / 255f;
float g = ((xrgb888 & 0x0000ff00) >> 8) / 255f;
float b = ((xrgb888 & 0x000000ff)) / 255f;
return new Color32F(r, g, b);
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6c7cc15ba45b0045a609f65b360220a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,70 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if UNITY_5_3_OR_NEWER
#define IS_UNITY
#endif
#if IS_UNITY
namespace Spine {
using Color32F = UnityEngine.Color;
static class ColorExtensions {
public static Color32F Clamp (this Color32F color) {
color.r = MathUtils.Clamp(color.r, 0, 1);
color.g = MathUtils.Clamp(color.g, 0, 1);
color.b = MathUtils.Clamp(color.b, 0, 1);
color.a = MathUtils.Clamp(color.a, 0, 1);
return color;
}
public static Color32F ClampRGB (this Color32F color) {
color.r = MathUtils.Clamp(color.r, 0, 1);
color.g = MathUtils.Clamp(color.g, 0, 1);
color.b = MathUtils.Clamp(color.b, 0, 1);
return color;
}
public static Color32F RGBA8888ToColor(this uint rgba8888) {
float r = ((rgba8888 & 0xff000000) >> 24) / 255f;
float g = ((rgba8888 & 0x00ff0000) >> 16) / 255f;
float b = ((rgba8888 & 0x0000ff00) >> 8) / 255f;
float a = ((rgba8888 & 0x000000ff)) / 255f;
return new Color32F(r, g, b, a);
}
public static Color32F XRGB888ToColor (this uint xrgb888) {
float r = ((xrgb888 & 0x00ff0000) >> 16) / 255f;
float g = ((xrgb888 & 0x0000ff00) >> 8) / 255f;
float b = ((xrgb888 & 0x000000ff)) / 255f;
return new Color32F(r, g, b);
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c96d35d696c85a44ab68037d151dde5d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,63 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
public interface IConstraint : IPosedActive, IPosed {
IConstraintData IData { get; }
IConstraint Copy (Skeleton skeleton);
bool IsSourceActive { get; }
void Sort (Skeleton skeleton);
void Update (Skeleton skeleton, Physics physics);
}
/// <summary>
/// <para>
/// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
/// the last bone is as close to the target bone as possible.</para>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide.</para>
/// </summary>
public abstract class Constraint<T, D, P> : PosedActive<D, P>, IUpdate, IConstraint
where T : Constraint<T, D, P>
where D : ConstraintData<T, P>
where P : IPose<P> {
public Constraint (D data, P pose, P constrained)
: base(data, pose, constrained) {
}
public IConstraintData IData { get { return data; } }
abstract public IConstraint Copy (Skeleton skeleton);
abstract public void Sort (Skeleton skeleton);
public virtual bool IsSourceActive { get { return true; } }
abstract public void Update (Skeleton skeleton, Physics physics);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 66115e19768098240ace7f8ad9cb4d4c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,60 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
using System.Collections.Generic;
namespace Spine {
/// <summary>
/// Determines how the <see cref="BonePose.scaleY"/> changes when <see cref="BonePose.ScaleX"/> is set.
/// </summary>
public enum ScaleYMode {
/// <summary>scaleY is not changed.</summary>
None,
/// <summary>scaleY is multiplied by the scaleX factor, preserving the bone's aspect ratio.</summary>
Uniform,
/// <summary>scaleY is divided by the scaleX factor, preserving the bone's area.</summary>
Volume
}
public interface IConstraintData : IPosedData {
string Name { get; }
IConstraint Create (Skeleton skeleton);
}
public abstract class ConstraintData<T, P> : PosedData<P>, IConstraintData
where T : IConstraint
where P : IPose<P> {
public ConstraintData (string name, P setup)
: base(name, setup) {
}
abstract public IConstraint Create (Skeleton skeleton);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 510354e70dba52c49b35b2c27cd8ed3c
timeCreated: 1560867071
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,94 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
using System.Collections;
using System.Collections.Generic;
namespace Spine {
/// <summary>
/// Stores the skeleton's draw order, which is the order that each slot's attachment is rendered.
/// </summary>
public class DrawOrder {
internal readonly ExposedList<Slot> setupPose, pose, constrainedPose;
internal ExposedList<Slot> appliedPose;
public DrawOrder (ExposedList<Slot> setupPose) {
this.setupPose = setupPose;
pose = new ExposedList<Slot>(setupPose);
constrainedPose = new ExposedList<Slot>();
appliedPose = pose;
}
/// <summary>Sets the unconstrained draw order to the setup pose order.</summary>
public void SetupPose () {
pose.EnsureSize(setupPose.Count);
Array.Copy(setupPose.Items, 0, pose.Items, 0, setupPose.Count);
}
/// <summary>The unconstrained draw order, set by animations and application code.</summary>
public ExposedList<Slot> Pose {
get {
return pose;
}
}
/// <summary>
/// The constrained draw order for rendering. If no constraints modify the draw order, this is the same as <see cref="pose"/>.
/// Otherwise it is a copy of <see cref="pose"/> modified by constraints.
/// </summary>
public ExposedList<Slot> AppliedPose {
get {
return appliedPose;
}
}
/// <summary>
/// Sets the applied pose to the unconstrained pose, for when no constraints will modify the draw order.
/// </summary>
internal void Unconstrained () {
appliedPose = pose;
}
/// <summary>
/// Sets the applied pose to the constrained pose, in anticipation of the applied pose being modified by constraints.
/// </summary>
internal void Constrained () {
appliedPose = constrainedPose;
}
/// <summary>
/// Copies the unconstrained pose to the constrained pose, as a starting point for constraints to be applied.
/// </summary>
internal void ResetConstrained () { // Port: resetConstrained
constrainedPose.EnsureSize(pose.Count);
Array.Copy(pose.Items, 0, constrainedPose.Items, 0, pose.Count);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 589afc4ff646f4c4fb8d1b6084d660a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,71 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>Fired by <see cref="EventTimeline"/> when specific animation times are reached.
/// <para>See <see cref="Timeline.Apply(Skeleton, float, float, ExposedList{Event}, float, bool, bool, bool, bool)"/> and
/// <a href="https://esotericsoftware.com/spine-events">Events</a> in the Spine User Guide.</para></summary>
public class Event {
internal readonly float time;
internal readonly EventData data;
internal int intValue;
internal float floatValue;
internal string stringValue;
internal float volume, balance;
/// <summary>The event's setup pose data.</summary>
public EventData Data { get { return data; } }
/// <summary>The animation time this event was keyed, or -1 for the setup pose.</summary>
public float Time { get { return time; } }
/// <summary>The integer payload for this event.</summary>
public int Int { get { return intValue; } set { intValue = value; } }
/// <summary>The float payload for this event.</summary>
public float Float { get { return floatValue; } set { floatValue = value; } }
/// <summary>The string payload for this event.</summary>
public string String { get { return stringValue; } set { stringValue = value; } }
/// <summary>If an audio path is set, the volume for the audio.</summary>
public float Volume { get { return volume; } set { volume = value; } }
/// <summary>If an audio path is set, the left/right balance for the audio.</summary>
public float Balance { get { return balance; } set { balance = value; } }
public Event (float time, EventData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.time = time;
this.data = data;
}
override public string ToString () {
return this.data.Name;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: dec0d9d780605944eb4514125ab6350b
timeCreated: 1456265155
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>Stores the setup pose values for an Event.</summary>
public class EventData {
internal string name;
internal readonly Event setupPose;
/// <summary>The setup values that are shared by all events with this data.</summary>
public Event SetupPose { get { return setupPose; } }
/// <summary>The name of the event, unique across all events in the skeleton.
/// <para>See <see cref="SkeletonData.FindEvent(string)"/>.</para></summary>
public string Name { get { return name; } }
/// <summary>Path to an audio file relative to the audio folder as defined in Spine.</summary>
public string AudioPath { get; set; }
public EventData (string name) {
setupPose = new Event(-1, this);
this.name = name;
}
override public string ToString () {
return Name;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 37bbfb9fb268a644ba75052961a42b81
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,646 @@
//
// System.Collections.Generic.List
//
// Authors:
// Ben Maurer (bmaurer@ximian.com)
// Martin Baulig (martin@ximian.com)
// Carlos Alberto Cortez (calberto.cortez@gmail.com)
// David Waite (mass@akuma.org)
//
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
// Copyright (C) 2005 David Waite
//
// 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.Diagnostics;
namespace Spine {
[DebuggerDisplay("Count={Count}")]
public class ExposedList<T> : IEnumerable<T> {
public T[] Items;
public int Count;
private const int DefaultCapacity = 4;
private static readonly T[] EmptyArray = new T[0];
private int version;
public ExposedList () {
Items = EmptyArray;
}
public ExposedList (IEnumerable<T> collection) {
CheckCollection(collection);
// initialize to needed size (if determinable)
ICollection<T> c = collection as ICollection<T>;
if (c == null) {
Items = EmptyArray;
AddEnumerable(collection);
} else {
Items = new T[c.Count];
AddCollection(c);
}
}
public ExposedList (int capacity) {
if (capacity < 0)
throw new ArgumentOutOfRangeException("capacity");
Items = new T[capacity];
}
internal ExposedList (T[] data, int size) {
Items = data;
Count = size;
}
public void Add (T item) {
// If we check to see if we need to grow before trying to grow
// we can speed things up by 25%
if (Count == Items.Length)
GrowIfNeeded(1);
Items[Count++] = item;
version++;
}
public void GrowIfNeeded (int addedCount) {
int minimumSize = Count + addedCount;
if (minimumSize > Items.Length)
Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize);
}
public ExposedList<T> Resize (int newSize) {
int itemsLength = Items.Length;
T[] oldItems = Items;
if (newSize > itemsLength) {
Array.Resize(ref Items, newSize);
} else if (newSize < itemsLength) {
// Allow nulling of T reference type to allow GC.
for (int i = newSize; i < itemsLength; i++)
oldItems[i] = default(T);
}
Count = newSize;
return this;
}
public ExposedList<T> EnsureSize (int newSize) {
int itemsLength = Items.Length;
if (newSize > itemsLength) {
Array.Resize(ref Items, newSize);
}
Count = newSize;
return this;
}
public void EnsureCapacity (int min) {
if (Items.Length < min) {
int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2;
//if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}
private void CheckRange (int index, int count) {
if (index < 0)
throw new ArgumentOutOfRangeException("index");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if ((uint)index + (uint)count > (uint)Count)
throw new ArgumentException("index and count exceed length of list");
}
private void AddCollection (ICollection<T> collection) {
int collectionCount = collection.Count;
if (collectionCount == 0)
return;
GrowIfNeeded(collectionCount);
collection.CopyTo(Items, Count);
Count += collectionCount;
}
private void AddEnumerable (IEnumerable<T> enumerable) {
foreach (T t in enumerable) {
Add(t);
}
}
// Additional overload provided because ExposedList<T> only implements IEnumerable<T>,
// leading to sub-optimal behavior: It grows multiple times as it assumes not
// to know the final size ahead of insertion.
public void AddRange (ExposedList<T> list) {
CheckCollection(list);
int collectionCount = list.Count;
if (collectionCount == 0)
return;
GrowIfNeeded(collectionCount);
list.CopyTo(Items, Count);
Count += collectionCount;
version++;
}
public void AddRange (IEnumerable<T> collection) {
CheckCollection(collection);
ICollection<T> c = collection as ICollection<T>;
if (c != null)
AddCollection(c);
else
AddEnumerable(collection);
version++;
}
public int BinarySearch (T item) {
return Array.BinarySearch<T>(Items, 0, Count, item);
}
public int BinarySearch (T item, IComparer<T> comparer) {
return Array.BinarySearch<T>(Items, 0, Count, item, comparer);
}
public int BinarySearch (int index, int count, T item, IComparer<T> comparer) {
CheckRange(index, count);
return Array.BinarySearch<T>(Items, index, count, item, comparer);
}
public void Clear (bool clearArray = true) {
if (clearArray)
Array.Clear(Items, 0, Items.Length);
Count = 0;
version++;
}
public bool Contains (T item) {
return Array.IndexOf<T>(Items, item, 0, Count) != -1;
}
public ExposedList<TOutput> ConvertAll<TOutput> (Converter<T, TOutput> converter) {
if (converter == null)
throw new ArgumentNullException("converter");
ExposedList<TOutput> u = new ExposedList<TOutput>(Count);
u.Count = Count;
T[] items = Items;
TOutput[] uItems = u.Items;
for (int i = 0; i < Count; i++)
uItems[i] = converter(items[i]);
return u;
}
public void CopyTo (T[] array) {
Array.Copy(Items, 0, array, 0, Count);
}
public void CopyTo (T[] array, int arrayIndex) {
Array.Copy(Items, 0, array, arrayIndex, Count);
}
public void CopyTo (int index, T[] array, int arrayIndex, int count) {
CheckRange(index, count);
Array.Copy(Items, index, array, arrayIndex, count);
}
public bool Exists (Predicate<T> match) {
CheckMatch(match);
return GetIndex(0, Count, match) != -1;
}
public T Find (Predicate<T> match) {
CheckMatch(match);
int i = GetIndex(0, Count, match);
return (i != -1) ? Items[i] : default(T);
}
private static void CheckMatch (Predicate<T> match) {
if (match == null)
throw new ArgumentNullException("match");
}
public ExposedList<T> FindAll (Predicate<T> match) {
CheckMatch(match);
return FindAllList(match);
}
private ExposedList<T> FindAllList (Predicate<T> match) {
ExposedList<T> results = new ExposedList<T>();
for (int i = 0; i < Count; i++)
if (match(Items[i]))
results.Add(Items[i]);
return results;
}
public int FindIndex (Predicate<T> match) {
CheckMatch(match);
return GetIndex(0, Count, match);
}
public int FindIndex (int startIndex, Predicate<T> match) {
CheckMatch(match);
CheckIndex(startIndex);
return GetIndex(startIndex, Count - startIndex, match);
}
public int FindIndex (int startIndex, int count, Predicate<T> match) {
CheckMatch(match);
CheckRange(startIndex, count);
return GetIndex(startIndex, count, match);
}
private int GetIndex (int startIndex, int count, Predicate<T> match) {
int end = startIndex + count;
for (int i = startIndex; i < end; i++)
if (match(Items[i]))
return i;
return -1;
}
public T FindLast (Predicate<T> match) {
CheckMatch(match);
int i = GetLastIndex(0, Count, match);
return i == -1 ? default(T) : Items[i];
}
public int FindLastIndex (Predicate<T> match) {
CheckMatch(match);
return GetLastIndex(0, Count, match);
}
public int FindLastIndex (int startIndex, Predicate<T> match) {
CheckMatch(match);
CheckIndex(startIndex);
return GetLastIndex(0, startIndex + 1, match);
}
public int FindLastIndex (int startIndex, int count, Predicate<T> match) {
CheckMatch(match);
int start = startIndex - count + 1;
CheckRange(start, count);
return GetLastIndex(start, count, match);
}
private int GetLastIndex (int startIndex, int count, Predicate<T> match) {
// unlike FindLastIndex, takes regular params for search range
for (int i = startIndex + count; i != startIndex;)
if (match(Items[--i]))
return i;
return -1;
}
public void ForEach (Action<T> action) {
if (action == null)
throw new ArgumentNullException("action");
for (int i = 0; i < Count; i++)
action(Items[i]);
}
public Enumerator GetEnumerator () {
return new Enumerator(this);
}
public ExposedList<T> GetRange (int index, int count) {
CheckRange(index, count);
T[] tmpArray = new T[count];
Array.Copy(Items, index, tmpArray, 0, count);
return new ExposedList<T>(tmpArray, count);
}
public int IndexOf (T item) {
return Array.IndexOf<T>(Items, item, 0, Count);
}
public int IndexOf (T item, int index) {
CheckIndex(index);
return Array.IndexOf<T>(Items, item, index, Count - index);
}
public int IndexOf (T item, int index, int count) {
if (index < 0)
throw new ArgumentOutOfRangeException("index");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if ((uint)index + (uint)count > (uint)Count)
throw new ArgumentOutOfRangeException("index and count exceed length of list");
return Array.IndexOf<T>(Items, item, index, count);
}
private void Shift (int start, int delta) {
if (delta < 0)
start -= delta;
if (start < Count)
Array.Copy(Items, start, Items, start + delta, Count - start);
Count += delta;
if (delta < 0)
Array.Clear(Items, Count, -delta);
}
private void CheckIndex (int index) {
if (index < 0 || (uint)index > (uint)Count)
throw new ArgumentOutOfRangeException("index");
}
public void Insert (int index, T item) {
CheckIndex(index);
if (Count == Items.Length)
GrowIfNeeded(1);
Shift(index, 1);
Items[index] = item;
version++;
}
private void CheckCollection (IEnumerable<T> collection) {
if (collection == null)
throw new ArgumentNullException("collection");
}
public void InsertRange (int index, IEnumerable<T> collection) {
CheckCollection(collection);
CheckIndex(index);
if (collection == this) {
T[] buffer = new T[Count];
CopyTo(buffer, 0);
GrowIfNeeded(Count);
Shift(index, buffer.Length);
Array.Copy(buffer, 0, Items, index, buffer.Length);
} else {
ICollection<T> c = collection as ICollection<T>;
if (c != null)
InsertCollection(index, c);
else
InsertEnumeration(index, collection);
}
version++;
}
private void InsertCollection (int index, ICollection<T> collection) {
int collectionCount = collection.Count;
GrowIfNeeded(collectionCount);
Shift(index, collectionCount);
collection.CopyTo(Items, index);
}
private void InsertEnumeration (int index, IEnumerable<T> enumerable) {
foreach (T t in enumerable)
Insert(index++, t);
}
public int LastIndexOf (T item) {
return Array.LastIndexOf<T>(Items, item, Count - 1, Count);
}
public int LastIndexOf (T item, int index) {
CheckIndex(index);
return Array.LastIndexOf<T>(Items, item, index, index + 1);
}
public int LastIndexOf (T item, int index, int count) {
if (index < 0)
throw new ArgumentOutOfRangeException("index", index, "index is negative");
if (count < 0)
throw new ArgumentOutOfRangeException("count", count, "count is negative");
if (index - count + 1 < 0)
throw new ArgumentOutOfRangeException("count", count, "count is too large");
return Array.LastIndexOf<T>(Items, item, index, count);
}
public bool Remove (T item) {
int loc = IndexOf(item);
if (loc != -1)
RemoveAt(loc);
return loc != -1;
}
public int RemoveAll (Predicate<T> match) {
CheckMatch(match);
int i = 0;
int j = 0;
// Find the first item to remove
for (i = 0; i < Count; i++)
if (match(Items[i]))
break;
if (i == Count)
return 0;
version++;
// Remove any additional items
for (j = i + 1; j < Count; j++) {
if (!match(Items[j]))
Items[i++] = Items[j];
}
if (j - i > 0)
Array.Clear(Items, i, j - i);
Count = i;
return (j - i);
}
public void RemoveAt (int index) {
if (index < 0 || (uint)index >= (uint)Count)
throw new ArgumentOutOfRangeException("index");
Shift(index, -1);
Array.Clear(Items, Count, 1);
version++;
}
// Spine Added Method
// Based on Stack<T>.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs
/// <summary>Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException.</summary>
public T Pop () {
if (Count == 0)
throw new InvalidOperationException("List is empty. Nothing to pop.");
int i = Count - 1;
T item = Items[i];
Items[i] = default(T);
Count--;
version++;
return item;
}
public void RemoveRange (int index, int count) {
CheckRange(index, count);
if (count > 0) {
Shift(index, -count);
Array.Clear(Items, Count, count);
version++;
}
}
public void Reverse () {
Array.Reverse(Items, 0, Count);
version++;
}
public void Reverse (int index, int count) {
CheckRange(index, count);
Array.Reverse(Items, index, count);
version++;
}
public void Sort () {
Array.Sort<T>(Items, 0, Count, Comparer<T>.Default);
version++;
}
public void Sort (IComparer<T> comparer) {
Array.Sort<T>(Items, 0, Count, comparer);
version++;
}
public void Sort (Comparison<T> comparison) {
Array.Sort<T>(Items, comparison);
version++;
}
public void Sort (int index, int count, IComparer<T> comparer) {
CheckRange(index, count);
Array.Sort<T>(Items, index, count, comparer);
version++;
}
public T[] ToArray () {
T[] t = new T[Count];
Array.Copy(Items, t, Count);
return t;
}
public void TrimExcess () {
Capacity = Count;
}
public bool TrueForAll (Predicate<T> match) {
CheckMatch(match);
for (int i = 0; i < Count; i++)
if (!match(Items[i]))
return false;
return true;
}
public int Capacity {
get {
return Items.Length;
}
set {
if ((uint)value < (uint)Count)
throw new ArgumentOutOfRangeException();
Array.Resize(ref Items, value);
}
}
#region Interface implementations.
IEnumerator<T> IEnumerable<T>.GetEnumerator () {
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator () {
return GetEnumerator();
}
#endregion
public struct Enumerator : IEnumerator<T>, IDisposable {
private ExposedList<T> l;
private int next;
private int ver;
private T current;
internal Enumerator (ExposedList<T> l)
: this() {
this.l = l;
ver = l.version;
}
public void Dispose () {
l = null;
}
private void VerifyState () {
if (l == null)
throw new ObjectDisposedException(GetType().FullName);
if (ver != l.version)
throw new InvalidOperationException(
"Collection was modified; enumeration operation may not execute.");
}
public bool MoveNext () {
VerifyState();
if (next < 0)
return false;
if (next < l.Count) {
current = l.Items[next++];
return true;
}
next = -1;
return false;
}
public T Current {
get {
return current;
}
}
void IEnumerator.Reset () {
VerifyState();
next = 0;
}
object IEnumerator.Current {
get {
VerifyState();
if (next <= 0)
throw new InvalidOperationException();
return current;
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 89690af94a880744989712505f2957b1
timeCreated: 1456265154
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>An interface for an object representing a pose.</summary>
public interface IPose<P> {
/// <summary>Sets this pose to the specified pose.</summary>
void Set (P pose);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 114be1b77921d6645ac5974d5b33f2eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,37 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
namespace Spine {
/// <summary>The interface for items updated by <see cref="Skeleton.UpdateWorldTransform(Physics)"/>.</summary>
public interface IUpdate {
/// <param name="physics">Determines how physics and other non-deterministic updates are applied.</param>
void Update (Skeleton skeleton, Physics physics);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fba7d1e93605e0f499b6208390e798c2
timeCreated: 1560785125
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,332 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// <para>
/// Adjusts the local rotation of 1 or 2 constrained bones so the world position of the tip of the last bone is as close to the
/// target bone as possible.</para>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide.</para>
/// </summary>
public class IkConstraint : Constraint<IkConstraint, IkConstraintData, IkConstraintPose> {
internal readonly ExposedList<BonePose> bones;
internal Bone target;
public IkConstraint (IkConstraintData data, Skeleton skeleton)
: base(data, new IkConstraintPose(), new IkConstraintPose()) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
bones = new ExposedList<BonePose>(data.bones.Count);
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.bones.Items[boneData.index].constrainedPose);
target = skeleton.bones.Items[data.target.index];
}
override public IConstraint Copy (Skeleton skeleton) {
var copy = new IkConstraint(data, skeleton);
copy.pose.Set(pose);
return copy;
}
/// <summary>Applies the constraint to the constrained bones.</summary>
override public void Update (Skeleton skeleton, Physics physics) {
IkConstraintPose p = appliedPose;
if (p.mix == 0) return;
BonePose target = this.target.appliedPose;
BonePose[] bones = this.bones.Items;
switch (this.bones.Count) {
case 1:
Apply(skeleton, bones[0], target.worldX, target.worldY, p.compress, p.stretch, data.scaleY, p.mix);
break;
case 2:
Apply(skeleton, bones[0], bones[1], target.worldX, target.worldY, p.bendDirection, p.stretch, data.scaleY,
p.softness, p.mix);
break;
}
}
override public void Sort (Skeleton skeleton) {
skeleton.SortBone(target);
Bone parent = bones.Items[0].bone;
skeleton.SortBone(parent);
skeleton.updateCache.Add(this);
parent.sorted = false;
skeleton.SortReset(parent.children);
skeleton.Constrained(parent);
if (bones.Count > 1) skeleton.Constrained(bones.Items[1].bone);
}
override public bool IsSourceActive { get { return target.active; } }
/// <summary>The bones that will be modified by this IK constraint.</summary>
public ExposedList<BonePose> Bones {
get { return bones; }
}
/// <summary>The bone that is the IK target.</summary>
public Bone Target {
get { return target; }
set { target = value; }
}
/// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary>
static public void Apply (Skeleton skeleton, BonePose bone, float targetX, float targetY, bool compress, bool stretch,
ScaleYMode scaleY, float mix) {
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
bone.ModifyLocal(skeleton);
BonePose p = bone.bone.parent.appliedPose;
float pa = p.a, pb = p.b, pc = p.c, pd = p.d;
float rotationIK = -bone.shearX - bone.rotation, tx, ty;
switch (bone.inherit) {
case Inherit.OnlyTranslation:
tx = (targetX - bone.worldX) * Math.Sign(skeleton.ScaleX);
ty = (targetY - bone.worldY) * Math.Sign(skeleton.ScaleY);
break;
case Inherit.NoRotationOrReflection: {
float s = Math.Abs(pa * pd - pb * pc) / Math.Max(MathUtils.Epsilon, pa * pa + pc * pc);
float sa = pa / skeleton.scaleX;
float sc = pc / skeleton.ScaleY;
pb = -sc * s * skeleton.scaleX;
pd = sa * s * skeleton.ScaleY;
rotationIK += MathUtils.Atan2Deg(sc, sa);
goto default; // Fall through.
}
default: {
float x = targetX - p.worldX, y = targetY - p.worldY;
float d = pa * pd - pb * pc;
if (Math.Abs(d) <= MathUtils.Epsilon) {
tx = 0;
ty = 0;
} else {
tx = (x * pd - y * pb) / d - bone.x;
ty = (y * pa - x * pc) / d - bone.y;
}
break;
}
}
rotationIK += MathUtils.Atan2Deg(ty, tx);
if (bone.scaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK <= -180) //
rotationIK += 360;
bone.rotation += rotationIK * mix;
if (compress || stretch) {
switch (bone.inherit) {
case Inherit.NoScale:
case Inherit.NoScaleOrReflection:
tx = targetX - bone.worldX;
ty = targetY - bone.worldY;
break;
}
float b = bone.bone.data.length * bone.scaleX;
if (b > MathUtils.Epsilon) {
float dd = tx * tx + ty * ty;
if ((compress && dd < b * b) || (stretch && dd > b * b)) {
float s = ((float)Math.Sqrt(dd) / b - 1) * mix + 1;
bone.scaleX *= s;
switch (scaleY) {
case ScaleYMode.Uniform:
bone.scaleY *= s;
break;
case ScaleYMode.Volume:
bone.scaleY /= s < 0.7f ? 0.25f + 0.642857f * s : s;
break;
}
}
}
}
}
/// <summary>Applies 2 bone IK. The target is specified in the world coordinate system.</summary>
/// <param name="child">A direct descendant of the parent bone.</param>
static public void Apply (Skeleton skeleton, BonePose parent, BonePose child, float targetX, float targetY, int bendDir,
bool stretch, ScaleYMode scaleY, float softness, float mix) {
if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
if (child == null) throw new ArgumentNullException("child", "child cannot be null.");
if (parent.inherit != Inherit.Normal || child.inherit != Inherit.Normal) return;
parent.ModifyLocal(skeleton);
child.ModifyLocal(skeleton);
float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
int os1, os2, s2;
if (psx < 0) {
psx = -psx;
os1 = 180;
s2 = -1;
} else {
os1 = 0;
s2 = 1;
}
if (psy < 0) {
psy = -psy;
s2 = -s2;
}
if (csx < 0) {
csx = -csx;
os2 = 180;
} else
os2 = 0;
float cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
bool u = Math.Abs(psx - psy) <= MathUtils.Epsilon;
if (!u || stretch) {
child.y = 0;
cwx = a * child.x + parent.worldX;
cwy = c * child.x + parent.worldY;
} else {
cwx = a * child.x + b * child.y + parent.worldX;
cwy = c * child.x + d * child.y + parent.worldY;
}
BonePose pp = parent.bone.parent.appliedPose;
a = pp.a;
b = pp.b;
c = pp.c;
d = pp.d;
float id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY;
id = Math.Abs(id) <= MathUtils.Epsilon ? 0 : 1 / id;
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1, a2;
if (l1 < MathUtils.Epsilon) {
Apply(skeleton, parent, targetX, targetY, false, stretch, ScaleYMode.None, mix);
child.rotation = 0;
return;
}
x = targetX - pp.worldX;
y = targetY - pp.worldY;
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
float dd = tx * tx + ty * ty;
if (softness != 0) {
softness *= psx * (csx + 1) * 0.5f;
float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness;
if (sd > 0) {
float p = Math.Min(1, sd / (softness * 2)) - 1;
p = (sd - softness * (1 - p * p)) / td;
tx -= p * tx;
ty -= p * ty;
dd = tx * tx + ty * ty;
}
}
if (u) {
l2 *= psx;
float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1) {
cos = -1;
a2 = MathUtils.PI * bendDir;
} else if (cos > 1) {
cos = 1;
a2 = 0;
if (stretch) {
a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * mix + 1;
parent.scaleX *= a;
switch (scaleY) {
case ScaleYMode.Uniform:
parent.scaleY *= a;
break;
case ScaleYMode.Volume:
parent.scaleY /= a < 0.7f ? 0.25f + 0.642857f * a : a;
break;
}
}
} else
a2 = (float)Math.Acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * (float)Math.Sin(a2);
a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b);
} else {
a = psx * l2;
b = psy * l2;
float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb;
float c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
if (d >= 0) {
float q = (float)Math.Sqrt(d);
if (c1 < 0) q = -q;
q = -(c1 + q) * 0.5f;
float r0 = q / c2, r1 = c / q;
float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
r0 = dd - r * r;
if (r0 >= 0) {
y = (float)Math.Sqrt(r0) * bendDir;
a1 = ta - (float)Math.Atan2(y, r);
a2 = (float)Math.Atan2(y / psy, (r - l1) / psx);
goto break_outer; // break outer;
}
}
float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0;
float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
c = -a * l1 / (aa - bb);
if (c >= -1 && c <= 1) {
c = (float)Math.Acos(c);
x = a * (float)Math.Cos(c) + l1;
y = b * (float)Math.Sin(c);
d = x * x + y * y;
if (d < minDist) {
minAngle = c;
minDist = d;
minX = x;
minY = y;
}
if (d > maxDist) {
maxAngle = c;
maxDist = d;
maxX = x;
maxY = y;
}
}
if (dd <= (minDist + maxDist) * 0.5f) {
a1 = ta - (float)Math.Atan2(minY * bendDir, minX);
a2 = minAngle * bendDir;
} else {
a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX);
a2 = maxAngle * bendDir;
}
}
break_outer:
float os = (float)Math.Atan2(child.y, child.x) * s2;
a1 = (a1 - os) * MathUtils.RadDeg + os1 - parent.rotation;
if (a1 > 180)
a1 -= 360;
else if (a1 <= -180)
a1 += 360;
parent.rotation += a1 * mix;
a2 = ((a2 + os) * MathUtils.RadDeg - child.shearX) * s2 + os2 - child.rotation;
if (a2 > 180)
a2 -= 360;
else if (a2 <= -180)
a2 += 360;
child.rotation += a2 * mix;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 916f8e6534860cc40824adfc2916baa7
timeCreated: 1456265155
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,68 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
using System.Collections.Generic;
namespace Spine {
/// <summary>Stores the setup pose for an IkConstraint.</summary>
public class IkConstraintData : ConstraintData<IkConstraint, IkConstraintPose> {
internal ExposedList<BoneData> bones = new ExposedList<BoneData>(2);
internal BoneData target;
internal ScaleYMode scaleY = ScaleYMode.None;
public IkConstraintData (string name)
: base(name, new IkConstraintPose()) {
}
override public IConstraint Create (Skeleton skeleton) {
return new IkConstraint(this, skeleton);
}
/// <summary>The bones that are constrained by this IK Constraint.</summary>
public ExposedList<BoneData> Bones {
get { return bones; }
}
/// <summary>The bone that is the IK target.</summary>
public BoneData Target {
get { return target; }
set { target = value; }
}
/// <summary>
/// Determines how the <see cref="BonePose.scaleY"/> changes when <see cref="IkConstraintPose.Compress"/> or
/// <see cref="IkConstraintPose.Stretch"/> set <see cref="BonePose.ScaleX"/>.
/// </summary>
public ScaleYMode ScaleY {
get { return scaleY; }
set { scaleY = value; }
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 94ad1e9256073264785f806086a000ba
timeCreated: 1456265155
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,87 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>Stores a pose for an IK constraint.</summary>
public class IkConstraintPose : IPose<IkConstraintPose> {
internal int bendDirection;
internal bool compress, stretch;
internal float mix, softness;
public void Set (IkConstraintPose pose) {
mix = pose.mix;
softness = pose.softness;
bendDirection = pose.bendDirection;
compress = pose.compress;
stretch = pose.stretch;
}
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
/// <para>
/// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0.
/// </para></summary>
public float Mix {
get { return mix; }
set { mix = value; }
}
/// <summary>For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
/// will not straighten completely until the target is this far out of range.</summary>
public float Softness {
get { return softness; }
set { softness = value; }
}
/// <summary>For two bone IK, controls the bend direction of the IK bones, either 1 or -1.</summary>
public int BendDirection {
get { return bendDirection; }
set { bendDirection = value; }
}
/// <summary>For one bone IK, when true and the target is too close, the bone is scaled to reach it.</summary>
public bool Compress {
get { return compress; }
set { compress = value; }
}
/// <summary>When true and the target is out of range, the parent bone is scaled to reach it.
/// <para>
/// For two bone IK: 1) the child bone's local Y translation is set to 0,
/// 2) stretch is not applied if <see cref="Softness"/> is > 0,
/// and 3) if the parent bone has local nonuniform scale, stretch is not applied.
/// </para></summary>
public bool Stretch {
get { return stretch; }
set { stretch = value; }
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f7b8113cda0e502458c2084ec31e0cca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,457 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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:
* https://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 System;
namespace Spine {
/// <summary>
/// Takes a linear value in the range of 0-1 and outputs a (usually) non-linear, interpolated value.
/// </summary>
public abstract class Interpolation {
/// <param name="a">Alpha value between 0 and 1.</param>
abstract public float Apply (float a);
/// <param name="a">Alpha value between 0 and 1.</param>
public float Apply (float start, float end, float a) {
return start + (end - start) * Apply(a);
}
public static readonly Interpolation Linear = new LinearInterpolation();
/// <summary>Aka "smoothstep".</summary>
public static readonly Interpolation Smooth = new SmoothInterpolation();
public static readonly Interpolation Smooth2 = new Smooth2Interpolation();
/// <summary>By Ken Perlin.</summary>
public static readonly Interpolation Smoother = new SmootherInterpolation();
public static readonly Interpolation Fade = Smoother;
public static readonly PowInterpolation Pow2 = new PowInterpolation(2);
/// <summary>Slow, then fast.</summary>
public static readonly PowInInterpolation Pow2In = new PowInInterpolation(2);
public static readonly PowInInterpolation SlowFast = Pow2In;
/// <summary>Fast, then slow.</summary>
public static readonly PowOutInterpolation Pow2Out = new PowOutInterpolation(2);
public static readonly PowOutInterpolation FastSlow = Pow2Out;
public static readonly Interpolation Pow2InInverse = new Pow2InInverseInterpolation();
public static readonly Interpolation Pow2OutInverse = new Pow2OutInverseInterpolation();
public static readonly PowInterpolation Pow3 = new PowInterpolation(3);
public static readonly PowInInterpolation Pow3In = new PowInInterpolation(3);
public static readonly PowOutInterpolation Pow3Out = new PowOutInterpolation(3);
public static readonly Interpolation Pow3InInverse = new Pow3InInverseInterpolation();
public static readonly Interpolation Pow3OutInverse = new Pow3OutInverseInterpolation();
public static readonly PowInterpolation Pow4 = new PowInterpolation(4);
public static readonly PowInInterpolation Pow4In = new PowInInterpolation(4);
public static readonly PowOutInterpolation Pow4Out = new PowOutInterpolation(4);
public static readonly PowInterpolation Pow5 = new PowInterpolation(5);
public static readonly PowInInterpolation Pow5In = new PowInInterpolation(5);
public static readonly PowOutInterpolation Pow5Out = new PowOutInterpolation(5);
public static readonly Interpolation Sine = new SineInterpolation();
public static readonly Interpolation SineIn = new SineInInterpolation();
public static readonly Interpolation SineOut = new SineOutInterpolation();
public static readonly ExpInterpolation Exp10 = new ExpInterpolation(2, 10);
public static readonly ExpInInterpolation Exp10In = new ExpInInterpolation(2, 10);
public static readonly ExpOutInterpolation Exp10Out = new ExpOutInterpolation(2, 10);
public static readonly ExpInterpolation Exp5 = new ExpInterpolation(2, 5);
public static readonly ExpInInterpolation Exp5In = new ExpInInterpolation(2, 5);
public static readonly ExpOutInterpolation Exp5Out = new ExpOutInterpolation(2, 5);
public static readonly Interpolation Circle = new CircleInterpolation();
public static readonly Interpolation CircleIn = new CircleInInterpolation();
public static readonly Interpolation CircleOut = new CircleOutInterpolation();
public static readonly ElasticInterpolation Elastic = new ElasticInterpolation(2, 10, 7, 1);
public static readonly ElasticInInterpolation ElasticIn = new ElasticInInterpolation(2, 10, 6, 1);
public static readonly ElasticOutInterpolation ElasticOut = new ElasticOutInterpolation(2, 10, 7, 1);
public static readonly SwingInterpolation Swing = new SwingInterpolation(1.5f);
public static readonly SwingInInterpolation SwingIn = new SwingInInterpolation(2f);
public static readonly SwingOutInterpolation SwingOut = new SwingOutInterpolation(2f);
public static readonly BounceInterpolation Bounce = new BounceInterpolation(4);
public static readonly BounceInInterpolation BounceIn = new BounceInInterpolation(4);
public static readonly BounceOutInterpolation BounceOut = new BounceOutInterpolation(4);
#region Implementation Classes
class LinearInterpolation : Interpolation {
public override float Apply (float a) {
return a;
}
}
class SmoothInterpolation : Interpolation {
public override float Apply (float a) {
return a * a * (3 - 2 * a);
}
}
class Smooth2Interpolation : Interpolation {
public override float Apply (float a) {
a = a * a * (3 - 2 * a);
return a * a * (3 - 2 * a);
}
}
/// <summary>By Ken Perlin.</summary>
class SmootherInterpolation : Interpolation {
public override float Apply (float a) {
return a * a * a * (a * (a * 6 - 15) + 10);
}
}
public class PowInterpolation : Interpolation {
protected readonly int power;
public PowInterpolation (int power) {
this.power = power;
}
public override float Apply (float a) {
if (a <= 0.5f) return (float)Math.Pow(a * 2, power) / 2;
return (float)Math.Pow((a - 1) * 2, power) / (power % 2 == 0 ? -2 : 2) + 1;
}
}
public class PowInInterpolation : PowInterpolation {
public PowInInterpolation (int power) : base(power) {
}
public override float Apply (float a) {
return (float)Math.Pow(a, power);
}
}
public class PowOutInterpolation : PowInterpolation {
public PowOutInterpolation (int power) : base(power) {
}
public override float Apply (float a) {
return (float)Math.Pow(a - 1, power) * (power % 2 == 0 ? -1 : 1) + 1;
}
}
class Pow2InInverseInterpolation : Interpolation {
public override float Apply (float a) {
if (a < MathUtils.FloatRoundingError) return 0;
return (float)Math.Sqrt(a);
}
}
class Pow2OutInverseInterpolation : Interpolation {
public override float Apply (float a) {
if (a < MathUtils.FloatRoundingError) return 0;
if (a > 1) return 1;
return 1 - (float)Math.Sqrt(-(a - 1));
}
}
class Pow3InInverseInterpolation : Interpolation {
public override float Apply (float a) {
return MathUtils.Cbrt(a);
}
}
class Pow3OutInverseInterpolation : Interpolation {
public override float Apply (float a) {
return 1 - MathUtils.Cbrt(-(a - 1));
}
}
class SineInterpolation : Interpolation {
public override float Apply (float a) {
return (1 - MathUtils.Cos(a * MathUtils.PI)) / 2;
}
}
class SineInInterpolation : Interpolation {
public override float Apply (float a) {
return 1 - MathUtils.Cos(a * MathUtils.HalfPi);
}
}
class SineOutInterpolation : Interpolation {
public override float Apply (float a) {
return MathUtils.Sin(a * MathUtils.HalfPi);
}
}
public class ExpInterpolation : Interpolation {
protected readonly float value, power, min, scale;
public ExpInterpolation (float value, float power) {
this.value = value;
this.power = power;
min = (float)Math.Pow(value, -power);
scale = 1 / (1 - min);
}
public override float Apply (float a) {
if (a <= 0.5f) return ((float)Math.Pow(value, power * (a * 2 - 1)) - min) * scale / 2;
return (2 - ((float)Math.Pow(value, -power * (a * 2 - 1)) - min) * scale) / 2;
}
}
public class ExpInInterpolation : ExpInterpolation {
public ExpInInterpolation (float value, float power) : base(value, power) {
}
public override float Apply (float a) {
return ((float)Math.Pow(value, power * (a - 1)) - min) * scale;
}
}
public class ExpOutInterpolation : ExpInterpolation {
public ExpOutInterpolation (float value, float power) : base(value, power) {
}
public override float Apply (float a) {
return 1 - ((float)Math.Pow(value, -power * a) - min) * scale;
}
}
class CircleInterpolation : Interpolation {
public override float Apply (float a) {
if (a <= 0.5f) {
a *= 2;
return (1 - (float)Math.Sqrt(1 - a * a)) / 2;
}
a--;
a *= 2;
return ((float)Math.Sqrt(1 - a * a) + 1) / 2;
}
}
class CircleInInterpolation : Interpolation {
public override float Apply (float a) {
return 1 - (float)Math.Sqrt(1 - a * a);
}
}
class CircleOutInterpolation : Interpolation {
public override float Apply (float a) {
a--;
return (float)Math.Sqrt(1 - a * a);
}
}
public class ElasticInterpolation : Interpolation {
protected readonly float value, power, scale, bounces;
public ElasticInterpolation (float value, float power, int bounces, float scale) {
this.value = value;
this.power = power;
this.scale = scale;
this.bounces = bounces * MathUtils.PI * (bounces % 2 == 0 ? 1 : -1);
}
public override float Apply (float a) {
if (a <= 0.5f) {
a *= 2;
return (float)Math.Pow(value, power * (a - 1)) * MathUtils.Sin(a * bounces) * scale / 2;
}
a = 1 - a;
a *= 2;
return 1 - (float)Math.Pow(value, power * (a - 1)) * MathUtils.Sin(a * bounces) * scale / 2;
}
}
public class ElasticInInterpolation : ElasticInterpolation {
public ElasticInInterpolation (float value, float power, int bounces, float scale)
: base(value, power, bounces, scale) {
}
public override float Apply (float a) {
if (a >= 0.99) return 1;
return (float)Math.Pow(value, power * (a - 1)) * MathUtils.Sin(a * bounces) * scale;
}
}
public class ElasticOutInterpolation : ElasticInterpolation {
public ElasticOutInterpolation (float value, float power, int bounces, float scale)
: base(value, power, bounces, scale) {
}
public override float Apply (float a) {
if (a == 0) return 0;
a = 1 - a;
return 1 - (float)Math.Pow(value, power * (a - 1)) * MathUtils.Sin(a * bounces) * scale;
}
}
public class SwingInterpolation : Interpolation {
readonly float scale;
public SwingInterpolation (float scale) {
this.scale = scale * 2;
}
public override float Apply (float a) {
if (a <= 0.5f) {
a *= 2;
return a * a * ((scale + 1) * a - scale) / 2;
}
a--;
a *= 2;
return a * a * ((scale + 1) * a + scale) / 2 + 1;
}
}
public class SwingOutInterpolation : Interpolation {
readonly float scale;
public SwingOutInterpolation (float scale) {
this.scale = scale;
}
public override float Apply (float a) {
a--;
return a * a * ((scale + 1) * a + scale) + 1;
}
}
public class SwingInInterpolation : Interpolation {
readonly float scale;
public SwingInInterpolation (float scale) {
this.scale = scale;
}
public override float Apply (float a) {
return a * a * ((scale + 1) * a - scale);
}
}
public class BounceOutInterpolation : Interpolation {
protected readonly float[] widths, heights;
public BounceOutInterpolation (float[] widths, float[] heights) {
if (widths.Length != heights.Length)
throw new ArgumentException("Must be the same number of widths and heights.");
this.widths = widths;
this.heights = heights;
}
public BounceOutInterpolation (int bounces) {
if (bounces < 2 || bounces > 5) throw new ArgumentException("bounces cannot be < 2 or > 5: " + bounces);
widths = new float[bounces];
heights = new float[bounces];
heights[0] = 1;
switch (bounces) {
case 2:
widths[0] = 0.6f;
widths[1] = 0.4f;
heights[1] = 0.33f;
break;
case 3:
widths[0] = 0.4f;
widths[1] = 0.4f;
widths[2] = 0.2f;
heights[1] = 0.33f;
heights[2] = 0.1f;
break;
case 4:
widths[0] = 0.34f;
widths[1] = 0.34f;
widths[2] = 0.2f;
widths[3] = 0.15f;
heights[1] = 0.26f;
heights[2] = 0.11f;
heights[3] = 0.03f;
break;
case 5:
widths[0] = 0.3f;
widths[1] = 0.3f;
widths[2] = 0.2f;
widths[3] = 0.1f;
widths[4] = 0.1f;
heights[1] = 0.45f;
heights[2] = 0.3f;
heights[3] = 0.15f;
heights[4] = 0.06f;
break;
}
widths[0] *= 2;
}
public override float Apply (float a) {
if (a == 1) return 1;
a += widths[0] / 2;
float width = 0, height = 0;
for (int i = 0, n = widths.Length; i < n; i++) {
width = widths[i];
if (a <= width) {
height = heights[i];
break;
}
a -= width;
}
a /= width;
float z = 4 / width * height * a;
return 1 - (z - z * a) * width;
}
}
public class BounceInterpolation : BounceOutInterpolation {
public BounceInterpolation (float[] widths, float[] heights) : base(widths, heights) {
}
public BounceInterpolation (int bounces) : base(bounces) {
}
float Out (float a) {
float test = a + widths[0] / 2;
if (test < widths[0]) return test / (widths[0] / 2) - 1;
return base.Apply(a);
}
public override float Apply (float a) {
if (a <= 0.5f) return (1 - Out(1 - a * 2)) / 2;
return Out(a * 2 - 1) / 2 + 0.5f;
}
}
public class BounceInInterpolation : BounceOutInterpolation {
public BounceInInterpolation (float[] widths, float[] heights) : base(widths, heights) {
}
public BounceInInterpolation (int bounces) : base(bounces) {
}
public override float Apply (float a) {
return 1 - base.Apply(1 - a);
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16a8b0f8fcd20fa4a9a930b2a97f93f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,517 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace Spine {
public static class Json {
public static object Deserialize (TextReader text) {
SharpJson.JsonDecoder parser = new SharpJson.JsonDecoder();
parser.parseNumbersAsFloat = true;
return parser.Decode(text.ReadToEnd());
}
}
}
/**
* Copyright (c) 2016 Adriano Tinoco d'Oliveira Rezende
*
* Based on the JSON parser by Patrick van Bergen
* http://techblog.procurios.nl/k/news/view/14605/14863/how-do-i-write-my-own-parser-(for-json).html
*
* Changes made:
*
* - Optimized parser speed (deserialize roughly near 3x faster than original)
* - Added support to handle lexer/parser error messages with line numbers
* - Added more fine grained control over type conversions during the parsing
* - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
*
* 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.
*/
namespace SharpJson {
class Lexer {
public enum Token {
None,
Null,
True,
False,
Colon,
Comma,
String,
Number,
CurlyOpen,
CurlyClose,
SquaredOpen,
SquaredClose,
};
public bool hasError {
get {
return !success;
}
}
public int lineNumber {
get;
private set;
}
public bool parseNumbersAsFloat {
get;
set;
}
char[] json;
int index = 0;
bool success = true;
char[] stringBuffer = new char[4096];
public Lexer (string text) {
Reset();
json = text.ToCharArray();
parseNumbersAsFloat = false;
}
public void Reset () {
index = 0;
lineNumber = 1;
success = true;
}
public string ParseString () {
int idx = 0;
StringBuilder builder = null;
SkipWhiteSpaces();
// "
char c = json[index++];
bool failed = false;
bool complete = false;
while (!complete && !failed) {
if (index == json.Length)
break;
c = json[index++];
if (c == '"') {
complete = true;
break;
} else if (c == '\\') {
if (index == json.Length)
break;
c = json[index++];
switch (c) {
case '"':
stringBuffer[idx++] = '"';
break;
case '\\':
stringBuffer[idx++] = '\\';
break;
case '/':
stringBuffer[idx++] = '/';
break;
case 'b':
stringBuffer[idx++] = '\b';
break;
case 'f':
stringBuffer[idx++] = '\f';
break;
case 'n':
stringBuffer[idx++] = '\n';
break;
case 'r':
stringBuffer[idx++] = '\r';
break;
case 't':
stringBuffer[idx++] = '\t';
break;
case 'u':
int remainingLength = json.Length - index;
if (remainingLength >= 4) {
string hex = new string(json, index, 4);
// XXX: handle UTF
stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16);
// skip 4 chars
index += 4;
} else {
failed = true;
}
break;
}
} else {
stringBuffer[idx++] = c;
}
if (idx >= stringBuffer.Length) {
if (builder == null)
builder = new StringBuilder();
builder.Append(stringBuffer, 0, idx);
idx = 0;
}
}
if (!complete) {
success = false;
return null;
}
if (builder != null)
return builder.ToString();
else
return new string(stringBuffer, 0, idx);
}
string GetNumberString () {
SkipWhiteSpaces();
int lastIndex = GetLastIndexOfNumber(index);
int charLength = (lastIndex - index) + 1;
string result = new string(json, index, charLength);
index = lastIndex + 1;
return result;
}
public float ParseFloatNumber () {
float number;
string str = GetNumberString();
if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
return 0;
return number;
}
public double ParseDoubleNumber () {
double number;
string str = GetNumberString();
if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
return 0;
return number;
}
int GetLastIndexOfNumber (int index) {
int lastIndex;
for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
char ch = json[lastIndex];
if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
&& ch != '.' && ch != 'e' && ch != 'E')
break;
}
return lastIndex - 1;
}
void SkipWhiteSpaces () {
for (; index < json.Length; index++) {
char ch = json[index];
if (ch == '\n')
lineNumber++;
if (!char.IsWhiteSpace(json[index]))
break;
}
}
public Token LookAhead () {
SkipWhiteSpaces();
int savedIndex = index;
return NextToken(json, ref savedIndex);
}
public Token NextToken () {
SkipWhiteSpaces();
return NextToken(json, ref index);
}
static Token NextToken (char[] json, ref int index) {
if (index == json.Length)
return Token.None;
char c = json[index++];
switch (c) {
case '{':
return Token.CurlyOpen;
case '}':
return Token.CurlyClose;
case '[':
return Token.SquaredOpen;
case ']':
return Token.SquaredClose;
case ',':
return Token.Comma;
case '"':
return Token.String;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return Token.Number;
case ':':
return Token.Colon;
}
index--;
int remainingLength = json.Length - index;
// false
if (remainingLength >= 5) {
if (json[index] == 'f' &&
json[index + 1] == 'a' &&
json[index + 2] == 'l' &&
json[index + 3] == 's' &&
json[index + 4] == 'e') {
index += 5;
return Token.False;
}
}
// true
if (remainingLength >= 4) {
if (json[index] == 't' &&
json[index + 1] == 'r' &&
json[index + 2] == 'u' &&
json[index + 3] == 'e') {
index += 4;
return Token.True;
}
}
// null
if (remainingLength >= 4) {
if (json[index] == 'n' &&
json[index + 1] == 'u' &&
json[index + 2] == 'l' &&
json[index + 3] == 'l') {
index += 4;
return Token.Null;
}
}
return Token.None;
}
}
public class JsonDecoder {
public string errorMessage {
get;
private set;
}
public bool parseNumbersAsFloat {
get;
set;
}
Lexer lexer;
public JsonDecoder () {
errorMessage = null;
parseNumbersAsFloat = false;
}
public object Decode (string text) {
errorMessage = null;
lexer = new Lexer(text);
lexer.parseNumbersAsFloat = parseNumbersAsFloat;
return ParseValue();
}
public static object DecodeText (string text) {
JsonDecoder builder = new JsonDecoder();
return builder.Decode(text);
}
IDictionary<string, object> ParseObject () {
Dictionary<string, object> table = new Dictionary<string, object>();
// {
lexer.NextToken();
while (true) {
Lexer.Token token = lexer.LookAhead();
switch (token) {
case Lexer.Token.None:
TriggerError("Invalid token");
return null;
case Lexer.Token.Comma:
lexer.NextToken();
break;
case Lexer.Token.CurlyClose:
lexer.NextToken();
return table;
default:
// name
string name = EvalLexer(lexer.ParseString());
if (errorMessage != null)
return null;
// :
token = lexer.NextToken();
if (token != Lexer.Token.Colon) {
TriggerError("Invalid token; expected ':'");
return null;
}
// value
object value = ParseValue();
if (errorMessage != null)
return null;
table[name] = value;
break;
}
}
//return null; // Unreachable code
}
IList<object> ParseArray () {
List<object> array = new List<object>();
// [
lexer.NextToken();
while (true) {
Lexer.Token token = lexer.LookAhead();
switch (token) {
case Lexer.Token.None:
TriggerError("Invalid token");
return null;
case Lexer.Token.Comma:
lexer.NextToken();
break;
case Lexer.Token.SquaredClose:
lexer.NextToken();
return array;
default:
object value = ParseValue();
if (errorMessage != null)
return null;
array.Add(value);
break;
}
}
//return null; // Unreachable code
}
object ParseValue () {
switch (lexer.LookAhead()) {
case Lexer.Token.String:
return EvalLexer(lexer.ParseString());
case Lexer.Token.Number:
if (parseNumbersAsFloat)
return EvalLexer(lexer.ParseFloatNumber());
else
return EvalLexer(lexer.ParseDoubleNumber());
case Lexer.Token.CurlyOpen:
return ParseObject();
case Lexer.Token.SquaredOpen:
return ParseArray();
case Lexer.Token.True:
lexer.NextToken();
return true;
case Lexer.Token.False:
lexer.NextToken();
return false;
case Lexer.Token.Null:
lexer.NextToken();
return null;
case Lexer.Token.None:
break;
}
TriggerError("Unable to parse value");
return null;
}
void TriggerError (string message) {
errorMessage = string.Format("Error: '{0}' at line {1}",
message, lexer.lineNumber);
}
T EvalLexer<T> (T value) {
if (lexer.hasError)
TriggerError("Lexical error ocurred");
return value;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 371f40ecc08b2eb4cbec49585d41e2c3
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,163 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
//#define USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS
using System;
namespace Spine {
public static class MathUtils {
public const float Epsilon = 0.00001f, EpsilonSq = Epsilon * Epsilon;
public const float PI = 3.1415927f, PI2 = PI * 2, InvPI2 = 1 / PI2;
public const float RadiansToDegrees = 180f / PI, RadDeg = RadiansToDegrees;
public const float DegreesToRadians = PI / 180, DegRad = DegreesToRadians;
public const float FloatRoundingError = 0.000001f;
public const float HalfPi = PI / 2;
static Random random = new Random();
#if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS
const int SIN_BITS = 14; // 16KB. Adjust for accuracy.
const int SIN_MASK = ~(-1 << SIN_BITS);
const int SIN_COUNT = SIN_MASK + 1;
const float RadFull = PI * 2;
const float DegFull = 360;
const float RadToIndex = SIN_COUNT / RadFull;
const float DegToIndex = SIN_COUNT / DegFull;
static float[] sin = new float[SIN_COUNT];
static MathUtils () {
for (int i = 0; i < SIN_COUNT; i++)
sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull);
for (int i = 0; i < 360; i += 90)
sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad);
}
/// <summary>Returns the sine of a given angle in radians from a lookup table.</summary>
static public float Sin (float radians) {
return sin[(int)(radians * RadToIndex) & SIN_MASK];
}
/// <summary>Returns the cosine of a given angle in radians from a lookup table.</summary>
static public float Cos (float radians) {
return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK];
}
/// <summary>Returns the sine of a given angle in degrees from a lookup table.</summary>
static public float SinDeg (float degrees) {
return sin[(int)(degrees * DegToIndex) & SIN_MASK];
}
/// <summary>Returns the cosine of a given angle in degrees from a lookup table.</summary>
static public float CosDeg (float degrees) {
return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK];
}
static public float Atan2Deg (float y, float x) {
return Atan2(y, x) * RadDeg;
}
/// <summary>Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323
/// degrees), largest error of 0.00488 radians (0.2796 degrees).</summary>
static public float Atan2 (float y, float x) {
if (x == 0f) {
if (y > 0f) return PI / 2;
if (y == 0f) return 0f;
return -PI / 2;
}
float atan, z = y / x;
if (Math.Abs(z) < 1f) {
atan = z / (1f + 0.28f * z * z);
if (x < 0f) return atan + (y < 0f ? -PI : PI);
return atan;
}
atan = PI / 2 - z / (z * z + 0.28f);
return y < 0f ? atan - PI : atan;
}
#else
/// <summary>Returns the sine of a given angle in radians.</summary>
static public float Sin (float radians) {
return (float)Math.Sin(radians);
}
/// <summary>Returns the cosine of a given angle in radians.</summary>
static public float Cos (float radians) {
return (float)Math.Cos(radians);
}
/// <summary>Returns the sine of a given angle in degrees.</summary>
static public float SinDeg (float degrees) {
return (float)Math.Sin(degrees * DegRad);
}
/// <summary>Returns the cosine of a given angle in degrees.</summary>
static public float CosDeg (float degrees) {
return (float)Math.Cos(degrees * DegRad);
}
static public float Atan2Deg (float y, float x) {
return (float)Math.Atan2(y, x) * RadDeg;
}
/// <summary>Returns the atan2 using Math.Atan2.</summary>
static public float Atan2 (float y, float x) {
return (float)Math.Atan2(y, x);
}
#endif
static public float Cbrt (float x) {
return x < 0f ? -(float)Math.Pow(-x, 1.0 / 3.0) : (float)Math.Pow(x, 1.0 / 3.0);
}
static public float Clamp (float value, float min, float max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
static public float Clamp01 (float value) {
if (value < 0) return 0;
if (value > 1) return 1;
return value;
}
static public float RandomTriangle (float min, float max) {
return RandomTriangle(min, max, (min + max) * 0.5f);
}
static public float RandomTriangle (float min, float max, float mode) {
float u = (float)random.NextDouble();
float d = max - min;
if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min));
return max - (float)Math.Sqrt((1 - u) * d * (max - mode));
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 03b653e54c5403b4191f5003d64c6e18
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,518 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// <para>
/// Adjusts the rotation, translation, and scale of the constrained bones so they follow a <see cref="PathAttachment"/>.</para>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
/// </summary>
public class PathConstraint : Constraint<PathConstraint, PathConstraintData, PathConstraintPose> {
const int NONE = -1, BEFORE = -2, AFTER = -3;
internal readonly ExposedList<BonePose> bones;
internal Slot slot;
internal readonly ExposedList<float> spaces = new ExposedList<float>(), positions = new ExposedList<float>();
internal readonly ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
internal readonly float[] segments = new float[10];
public PathConstraint (PathConstraintData data, Skeleton skeleton)
: base(data, new PathConstraintPose(), new PathConstraintPose()) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
bones = new ExposedList<BonePose>(data.Bones.Count);
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.bones.Items[boneData.index].constrainedPose);
slot = skeleton.slots.Items[data.slot.index];
}
override public IConstraint Copy (Skeleton skeleton) {
var copy = new PathConstraint(data, skeleton);
copy.pose.Set(pose);
return copy;
}
public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) {
for (int i = fromIndex; i < toIndex; i++)
a[i] = val;
}
override public void Update (Skeleton skeleton, Physics physics) {
PathAttachment pathAttachment = slot.appliedPose.Attachment as PathAttachment;
if (pathAttachment == null) return;
PathConstraintPose p = appliedPose;
float mixRotate = p.mixRotate, mixX = p.mixX, mixY = p.mixY;
if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
PathConstraintData data = this.data;
bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale;
int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
BonePose[] bonesItems = this.bones.Items;
float[] spaces = this.spaces.EnsureSize(spacesCount).Items, lengths = scale ? this.lengths.EnsureSize(boneCount).Items : null;
float spacing = p.spacing;
switch (data.spacingMode) {
case SpacingMode.Percent:
if (scale) {
for (int i = 0, n = spacesCount - 1; i < n; i++) {
BonePose bone = bonesItems[i];
float setupLength = bone.bone.data.length;
float x = setupLength * bone.a, y = setupLength * bone.c;
lengths[i] = (float)Math.Sqrt(x * x + y * y);
}
}
ArraysFill(spaces, 1, spacesCount, spacing);
break;
case SpacingMode.Proportional: {
float sum = 0;
for (int i = 0, n = spacesCount - 1; i < n;) {
BonePose bone = bonesItems[i];
float setupLength = bone.bone.data.length;
if (setupLength < MathUtils.Epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
float x = setupLength * bone.a, y = setupLength * bone.c;
float length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = length;
sum += length;
}
}
if (sum > 0) {
sum = spacesCount / sum * spacing;
for (int i = 1; i < spacesCount; i++)
spaces[i] *= sum;
}
break;
}
default: {
bool lengthSpacing = data.spacingMode == SpacingMode.Length;
for (int i = 0, n = spacesCount - 1; i < n;) {
BonePose bone = bonesItems[i];
float setupLength = bone.bone.data.length;
if (setupLength < MathUtils.Epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
float x = setupLength * bone.a, y = setupLength * bone.c;
float length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength;
}
}
break;
}
}
float[] positions = ComputeWorldPositions(skeleton, pathAttachment, spacesCount, tangents);
float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
bool tip;
if (offsetRotation == 0) {
tip = data.rotateMode == RotateMode.Chain;
} else {
tip = false;
BonePose bone = slot.bone.appliedPose;
offsetRotation *= bone.a * bone.d - bone.b * bone.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
}
for (int i = 0, ip = 3, u = skeleton.update; i < boneCount; i++, ip += 3) {
BonePose bone = bonesItems[i];
bone.worldX += (boneX - bone.worldX) * mixX;
bone.worldY += (boneY - bone.worldY) * mixY;
float x = positions[ip], y = positions[ip + 1], dx = x - boneX, dy = y - boneY;
if (scale) {
float length = lengths[i];
if (length >= MathUtils.Epsilon) {
float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
bone.a *= s;
bone.c *= s;
}
}
boneX = x;
boneY = y;
if (mixRotate > 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
if (tangents)
r = positions[ip - 1];
else if (spaces[i + 1] < MathUtils.Epsilon)
r = positions[ip + 2];
else
r = MathUtils.Atan2(dy, dx);
r -= MathUtils.Atan2(c, a);
if (tip) {
cos = MathUtils.Cos(r);
sin = MathUtils.Sin(r);
float length = bone.bone.data.length;
boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
} else
r += offsetRotation;
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r *= mixRotate;
cos = MathUtils.Cos(r);
sin = MathUtils.Sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
bone.ModifyWorld(u);
}
}
float[] ComputeWorldPositions (Skeleton skeleton, PathAttachment path, int spacesCount, bool tangents) {
Slot slot = this.slot;
float position = appliedPose.position;
float[] spaces = this.spaces.Items, output = this.positions.EnsureSize(spacesCount * 3 + 2).Items, world;
bool closed = path.Closed;
int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE;
float pathLength, multiplier;
if (!path.ConstantSpeed) {
float[] lengths = path.Lengths;
curveCount -= closed ? 1 : 2;
pathLength = lengths[curveCount];
if (data.positionMode == PositionMode.Percent) position *= pathLength;
switch (data.spacingMode) {
case SpacingMode.Percent:
multiplier = pathLength;
break;
case SpacingMode.Proportional:
multiplier = pathLength / spacesCount;
break;
default:
multiplier = 1;
break;
}
world = this.world.EnsureSize(8).Items;
for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i] * multiplier;
position += space;
float p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
if (prevCurve != BEFORE) {
prevCurve = BEFORE;
path.ComputeWorldVertices(skeleton, slot, 2, 4, world, 0, 2);
}
AddBeforePosition(p, world, 0, output, o);
continue;
} else if (p > pathLength) {
if (prevCurve != AFTER) {
prevCurve = AFTER;
path.ComputeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2);
}
AddAfterPosition(p - pathLength, world, 0, output, o);
continue;
}
// Determine curve containing position.
for (; ; curve++) {
float length = lengths[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = lengths[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
if (curve != prevCurve) {
prevCurve = curve;
if (closed && curve == curveCount) {
path.ComputeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2);
path.ComputeWorldVertices(skeleton, slot, 0, 4, world, 4, 2);
} else
path.ComputeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2);
}
AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o,
tangents || (i > 0 && space < MathUtils.Epsilon));
}
return output;
}
// World vertices.
if (closed) {
verticesLength += 2;
world = this.world.EnsureSize(verticesLength).Items;
path.ComputeWorldVertices(skeleton, slot, 2, verticesLength - 4, world, 0, 2);
path.ComputeWorldVertices(skeleton, slot, 0, 2, world, verticesLength - 4, 2);
world[verticesLength - 2] = world[0];
world[verticesLength - 1] = world[1];
} else {
curveCount--;
verticesLength -= 4;
world = this.world.EnsureSize(verticesLength).Items;
path.ComputeWorldVertices(skeleton, slot, 2, verticesLength, world, 0, 2);
}
// Curve lengths.
float[] curves = this.curves.EnsureSize(curveCount).Items;
pathLength = 0;
float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
cx1 = world[w];
cy1 = world[w + 1];
cx2 = world[w + 2];
cy2 = world[w + 3];
x2 = world[w + 4];
y2 = world[w + 5];
tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
curves[i] = pathLength;
x1 = x2;
y1 = y2;
}
if (data.positionMode == PositionMode.Percent) position *= pathLength;
switch (data.spacingMode) {
case SpacingMode.Percent:
multiplier = pathLength;
break;
case SpacingMode.Proportional:
multiplier = pathLength / spacesCount;
break;
default:
multiplier = 1;
break;
}
float[] segments = this.segments;
float curveLength = 0;
for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i] * multiplier;
position += space;
float p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
segment = 0;
} else if (p < 0) {
AddBeforePosition(p, world, 0, output, o);
continue;
} else if (p > pathLength) {
AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o);
continue;
}
// Determine curve containing position.
for (; ; curve++) {
float length = curves[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = curves[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
// Curve segment lengths.
if (curve != prevCurve) {
prevCurve = curve;
int ii = curve * 6;
x1 = world[ii];
y1 = world[ii + 1];
cx1 = world[ii + 2];
cy1 = world[ii + 3];
cx2 = world[ii + 4];
cy2 = world[ii + 5];
x2 = world[ii + 6];
y2 = world[ii + 7];
tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
curveLength = (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[0] = curveLength;
for (ii = 1; ii < 8; ii++) {
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[ii] = curveLength;
}
dfx += ddfx;
dfy += ddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[8] = curveLength;
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[9] = curveLength;
segment = 0;
}
// Weight by segment length.
p *= curveLength;
for (; ; segment++) {
float length = segments[segment];
if (p > length) continue;
if (segment == 0)
p /= length;
else {
float prev = segments[segment - 1];
p = segment + (p - prev) / (length - prev);
}
break;
}
AddCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, output, o, tangents || (i > 0 && space < MathUtils.Epsilon));
}
return output;
}
static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}
static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}
static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
float[] output, int o, bool tangents) {
if (p < MathUtils.Epsilon || float.IsNaN(p)) {
output[o] = x1;
output[o + 1] = y1;
output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
return;
}
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
output[o] = x;
output[o + 1] = y;
if (tangents) {
if (p < 0.001f)
output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
else
output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
}
}
override public void Sort (Skeleton skeleton) {
int slotIndex = slot.Data.index;
Bone slotBone = slot.bone;
if (skeleton.skin != null) SortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone);
if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin)
SortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone);
SortPath(skeleton, slot.pose.attachment, slotBone);
BonePose[] bones = this.bones.Items;
int boneCount = this.bones.Count;
for (int i = 0; i < boneCount; i++) {
Bone bone = bones[i].bone;
skeleton.SortBone(bone);
skeleton.Constrained(bone);
}
skeleton.updateCache.Add(this);
for (int i = 0; i < boneCount; i++)
skeleton.SortReset(bones[i].bone.children);
for (int i = 0; i < boneCount; i++)
bones[i].bone.sorted = true;
}
private void SortPathSlot (Skeleton skeleton, Skin skin, int slotIndex, Bone slotBone) {
foreach (Skin.SkinEntry entry in skin.Attachments)
if (entry.SlotIndex == slotIndex) SortPath(skeleton, entry.Attachment, slotBone);
}
private void SortPath (Skeleton skeleton, Attachment attachment, Bone slotBone) {
if (!(attachment is PathAttachment)) return;
int[] pathBones = ((PathAttachment)attachment).bones;
if (pathBones == null)
skeleton.SortBone(slotBone);
else {
Bone[] bones = skeleton.bones.Items;
for (int i = 0, n = pathBones.Length; i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < nn)
skeleton.SortBone(bones[pathBones[i++]]);
}
}
}
override public bool IsSourceActive { get { return slot.bone.active; } }
/// <summary>The bones that will be modified by this path constraint.</summary>
public ExposedList<BonePose> Bones { get { return bones; } }
/// <summary>The slot whose path attachment will be used to constrained the bones.</summary>
public Slot Slot { get { return slot; } set { slot = value; } }
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 731d05fbc2874c74984813ce4c5bb8df
timeCreated: 1467213650
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,68 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
public class PathConstraintData : ConstraintData<PathConstraint, PathConstraintPose> {
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal SlotData slot;
internal PositionMode positionMode;
internal SpacingMode spacingMode;
internal RotateMode rotateMode;
internal float offsetRotation;
public PathConstraintData (string name)
: base(name, new PathConstraintPose()) {
}
override public IConstraint Create (Skeleton skeleton) {
return new PathConstraint(this, skeleton);
}
public ExposedList<BoneData> Bones { get { return bones; } }
public SlotData Slot { get { return slot; } set { slot = value; } }
public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }
public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
}
public enum PositionMode {
Fixed, Percent
}
public enum SpacingMode {
Length, Fixed, Percent, Proportional
}
public enum RotateMode {
Tangent, Chain, ChainScale
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9d836858269be96428428fb6764dfc3a
timeCreated: 1467213651
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,59 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// Stores a pose for a path constraint.
/// </summary>
public class PathConstraintPose : IPose<PathConstraintPose> {
internal float position, spacing, mixRotate, mixX, mixY;
public void Set (PathConstraintPose pose) {
position = pose.position;
spacing = pose.spacing;
mixRotate = pose.mixRotate;
mixX = pose.mixX;
mixY = pose.mixY;
}
/// <summary>The position along the path.</summary>
public float Position { get { return position; } set { position = value; } }
/// <summary>The spacing between bones.</summary>
public float Spacing { get { return spacing; } set { spacing = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotations.</summary>
public float MixRotate { get { return mixRotate; } set { mixRotate = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
public float MixX { get { return mixX; } set { mixX = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
public float MixY { get { return mixY; } set { mixY = value; } }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40244ea3e8f247f4fac4363b219d8418
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
namespace Spine {
/// <summary>Determines how physics and other non-deterministic updates are applied.</summary>
public enum Physics {
/// <summary>Physics are not updated or applied.</summary>
None,
/// <summary>Physics are <see cref="PhysicsConstraint.Reset(Skeleton)">reset</see>.</summary>
Reset,
/// <summary>Physics are updated and the pose from physics is applied.</summary>
Update,
/// <summary>Physics are not updated but the pose from physics is applied.</summary>
Pose
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2dab58587078e30468faff31f1ad4fa9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,317 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// Applies physics to a bone.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide.</para>
/// </summary>
public class PhysicsConstraint : Constraint<PhysicsConstraint, PhysicsConstraintData, PhysicsConstraintPose> {
internal BonePose bone;
bool reset = true;
float ux, uy, cx, cy, tx, ty;
float xOffset, xLag, xVelocity;
float yOffset, yLag, yVelocity;
float rotateOffset, rotateLag, rotateVelocity;
float scaleOffset, scaleLag, scaleVelocity;
float remaining, lastTime;
public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton)
: base(data, new PhysicsConstraintPose(), new PhysicsConstraintPose()) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
bone = skeleton.bones.Items[data.bone.index].constrainedPose;
}
override public IConstraint Copy (Skeleton skeleton) {
var copy = new PhysicsConstraint(data, skeleton);
copy.pose.Set(pose);
return copy;
}
/// <summary>Resets all physics state that was the result of previous movement. Use this after moving a bone to prevent physics
/// from reacting to the movement.</summary>
public void Reset (Skeleton skeleton) {
remaining = 0;
lastTime = skeleton.time;
reset = true;
xOffset = 0;
xLag = 0;
xVelocity = 0;
yOffset = 0;
yLag = 0;
yVelocity = 0;
rotateOffset = 0;
rotateLag = 0;
rotateVelocity = 0;
scaleOffset = 0;
scaleLag = 0;
scaleVelocity = 0;
}
/// <summary>
/// Translates the physics constraint so the next <see cref="Update(Skeleton, Physics)"/> forces are applied as if the bone moved an
/// additional amount in world space.
/// </summary>
public void Translate (float x, float y) {
ux -= x;
uy -= y;
cx -= x;
cy -= y;
}
/// <summary>
/// Rotates the physics constraint so the next <see cref="Update(Skeleton, Physics)"/> forces are applied as if the bone rotated around
/// the specified point in world space.
/// </summary>
public void Rotate (float x, float y, float degrees) {
float r = degrees * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
float dx = cx - x, dy = cy - y;
Translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy);
}
/// <summary>Applies the constraint to the constrained bones.</summary>
override public void Update (Skeleton skeleton, Physics physics) {
PhysicsConstraintPose p = appliedPose;
float mix = p.mix;
if (mix == 0) return;
bool x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0;
BonePose bone = this.bone;
float l = bone.bone.data.length, t = data.step, z = 0;
switch (physics) {
case Physics.None:
return;
case Physics.Reset:
Reset(skeleton);
goto case Physics.Update; // Fall through.
case Physics.Update:
float delta = Math.Max(skeleton.time - lastTime, 0), aa = remaining;
remaining += delta;
lastTime = skeleton.time;
float bx = bone.worldX, by = bone.worldY;
if (reset) {
reset = false;
ux = bx;
uy = by;
} else {
float a = remaining, i = p.inertia, f = skeleton.data.referenceScale, d = -1, m = 0, e = 0, qx = data.limit * delta,
qy = qx * Math.Abs(skeleton.ScaleY);
qx *= Math.Abs(skeleton.ScaleX);
if (x || y) {
if (x) {
float u = (ux - bx) * i;
xOffset += u > qx ? qx : u < -qx ? -qx : u;
ux = bx;
}
if (y) {
float u = (uy - by) * i;
yOffset += u > qy ? qy : u < -qy ? -qy : u;
uy = by;
}
if (a >= t) {
float xs = xOffset, ys = yOffset;
d = (float)Math.Pow(p.damping, 60 * t);
m = t * p.massInverse;
e = p.strength;
float w = f * p.wind, g = f * p.gravity;
float ax = (w * skeleton.windX + g * skeleton.gravityX) * skeleton.scaleX;
float ay = (w * skeleton.windY + g * skeleton.gravityY) * skeleton.ScaleY;
do {
if (x) {
xVelocity += (ax - xOffset * e) * m;
xOffset += xVelocity * t;
xVelocity *= d;
}
if (y) {
yVelocity -= (ay + yOffset * e) * m;
yOffset += yVelocity * t;
yVelocity *= d;
}
a -= t;
} while (a >= t);
xLag = xOffset - xs;
yLag = yOffset - ys;
}
z = Math.Max(0, 1 - a / t);
if (x) bone.worldX += (xOffset - xLag * z) * mix * data.x;
if (y) bone.worldY += (yOffset - yLag * z) * mix * data.y;
}
if (rotateOrShearX || scaleX) {
float ca = (float)Math.Atan2(bone.c, bone.a), c, s, mr = 0, dx = cx - bone.worldX, dy = cy - bone.worldY;
if (dx > qx)
dx = qx;
else if (dx < -qx)
dx = -qx;
if (dy > qy)
dy = qy;
else if (dy < -qy)
dy = -qy;
if (rotateOrShearX) {
mr = (data.rotate + data.shearX) * mix;
z = rotateLag * Math.Max(0, 1 - aa / t);
float r = (float)Math.Atan2(dy + ty, dx + tx) - ca - (rotateOffset - z) * mr;
rotateOffset += (r - (float)Math.Ceiling(r * MathUtils.InvPI2 - 0.5f) * MathUtils.PI2) * i;
r = (rotateOffset - z) * mr + ca;
c = (float)Math.Cos(r);
s = (float)Math.Sin(r);
if (scaleX) {
r = l * bone.WorldScaleX;
if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
}
} else {
c = (float)Math.Cos(ca);
s = (float)Math.Sin(ca);
float r = l * bone.WorldScaleX - scaleLag * Math.Max(0, 1 - aa / t);
if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
}
a = remaining;
if (a >= t) {
if (d == -1) {
d = (float)Math.Pow(p.damping, 60 * t);
m = t * p.massInverse;
e = p.strength;
}
float ax = p.wind * skeleton.windX + p.gravity * skeleton.gravityX;
float ay = p.wind * skeleton.windY + p.gravity * skeleton.gravityY;
float rs = rotateOffset, ss = scaleOffset, h = l / f;
if (Spine.Bone.yDown) ay = -ay;
while (true) {
a -= t;
if (scaleX) {
scaleVelocity += (ax * c - ay * s - scaleOffset * e) * m;
scaleOffset += scaleVelocity * t;
scaleVelocity *= d;
}
if (rotateOrShearX) {
rotateVelocity -= ((ax * s + ay * c) * h + rotateOffset * e) * m;
rotateOffset += rotateVelocity * t;
rotateVelocity *= d;
if (a < t) break;
float r = rotateOffset * mr + ca;
c = (float)Math.Cos(r);
s = (float)Math.Sin(r);
} else if (a < t) //
break;
}
rotateLag = rotateOffset - rs;
scaleLag = scaleOffset - ss;
}
z = Math.Max(0, 1 - a / t);
}
remaining = a;
}
cx = bone.worldX;
cy = bone.worldY;
break;
case Physics.Pose:
z = Math.Max(0, 1 - remaining / t);
if (x) bone.worldX += (xOffset - xLag * z) * mix * data.x;
if (y) bone.worldY += (yOffset - yLag * z) * mix * data.y;
break;
}
if (rotateOrShearX) {
float o = (rotateOffset - rotateLag * z) * mix, s, c, a;
if (data.shearX > 0) {
float r = 0;
if (data.rotate > 0) {
r = o * data.rotate;
s = (float)Math.Sin(r);
c = (float)Math.Cos(r);
a = bone.b;
bone.b = c * a - s * bone.d;
bone.d = s * a + c * bone.d;
}
r += o * data.shearX;
s = (float)Math.Sin(r);
c = (float)Math.Cos(r);
a = bone.a;
bone.a = c * a - s * bone.c;
bone.c = s * a + c * bone.c;
} else {
o *= data.rotate;
s = (float)Math.Sin(o);
c = (float)Math.Cos(o);
a = bone.a;
bone.a = c * a - s * bone.c;
bone.c = s * a + c * bone.c;
a = bone.b;
bone.b = c * a - s * bone.d;
bone.d = s * a + c * bone.d;
}
}
if (scaleX) {
float s = 1 + (scaleOffset - scaleLag * z) * mix * data.scaleX;
bone.a *= s;
bone.c *= s;
switch (data.scaleYMode) {
case ScaleYMode.Uniform: {
bone.b *= s;
bone.d *= s;
break;
}
case ScaleYMode.Volume: {
s = Math.Abs(s);
s = s >= 0.7f ? 1 / s : 4 - 3.67347f * s;
bone.b *= s;
bone.d *= s;
break;
}
}
}
if (physics != Physics.Pose) {
tx = l * bone.a;
ty = l * bone.c;
}
bone.ModifyWorld(skeleton.update);
}
override public void Sort (Skeleton skeleton) {
Bone bone = this.bone.bone;
skeleton.SortBone(bone);
skeleton.updateCache.Add(this);
skeleton.SortReset(bone.children);
skeleton.Constrained(bone);
}
override public bool IsSourceActive { get { return bone.bone.active; } }
/// <summary>The bone constrained by this physics constraint.</summary>
public BonePose Bone { get { return bone; } set { bone = value; } }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2816491d178b3b4986920107586ce55
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,92 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
namespace Spine {
/// <summary>
/// Stores the setup pose for a <see cref="PhysicsConstraint"/>.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide.</para>
/// </summary>
public class PhysicsConstraintData : ConstraintData<PhysicsConstraint, PhysicsConstraintPose> {
internal BoneData bone;
internal float x, y, rotate, scaleX, shearX, limit, step;
internal bool inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal;
internal ScaleYMode scaleYMode = ScaleYMode.None;
public PhysicsConstraintData (string name)
: base(name, new PhysicsConstraintPose()) {
}
override public IConstraint Create (Skeleton skeleton) {
return new PhysicsConstraint(this, skeleton);
}
/// <summary>The bone constrained by this physics constraint.</summary>
public BoneData Bone { get { return bone; } }
/// <summary>The time in milliseconds required to advance the physics simulation one step.</summary>
public float Step { get { return step; } set { step = value; } }
/// <summary>Physics influence on x translation, 0-1.</summary>
public float X { get { return x; } set { x = value; } }
/// <summary>Physics influence on y translation, 0-1.</summary>
public float Y { get { return y; } set { y = value; } }
/// <summary>Physics influence on rotation, 0-1.</summary>
public float Rotate { get { return rotate; } set { rotate = value; } }
/// <summary>Physics influence on scaleX, 0-1.</summary>
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
/// <summary>Physics influence on shearX, 0-1.</summary>
public float ShearX { get { return shearX; } set { shearX = value; } }
/// <summary>Movement greater than the limit will not have a greater effect on physics.</summary>
public float Limit { get { return limit; } set { limit = value; } }
/// <summary>
/// Determines how the <see cref="BonePose.scaleY"/> changes when <see cref="PhysicsConstraintData.scaleX"/> sets
/// <see cref="BonePose.scaleX"/>
/// </summary>
public ScaleYMode ScaleYMode {
get { return scaleYMode; }
set { scaleYMode = value; }
}
/// <summary>True when this constraint's inertia is controlled by global slider timelines.</summary>
public bool InertiaGlobal { get { return inertiaGlobal; } set { inertiaGlobal = value; } }
/// <summary>True when this constraint's strength is controlled by global slider timelines.</summary>
public bool StrengthGlobal { get { return strengthGlobal; } set { strengthGlobal = value; } }
/// <summary>True when this constraint's damping is controlled by global slider timelines.</summary>
public bool DampingGlobal { get { return dampingGlobal; } set { dampingGlobal = value; } }
/// <summary>True when this constraint's mass is controlled by global slider timelines.</summary>
public bool MassGlobal { get { return massGlobal; } set { massGlobal = value; } }
/// <summary>True when this constraint's wind is controlled by global slider timelines.</summary>
public bool WindGlobal { get { return windGlobal; } set { windGlobal = value; } }
/// <summary>True when this constraint's gravity is controlled by global slider timelines.</summary>
public bool GravityGlobal { get { return gravityGlobal; } set { gravityGlobal = value; } }
/// <summary>True when this constraint's mix is controlled by global slider timelines.</summary>
public bool MixGlobal { get { return mixGlobal; } set { mixGlobal = value; } }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 438688f6194e6dc40953a23d05d48e1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
/// <summary>
/// Stores a pose for a physics constraint.
/// </summary>
public class PhysicsConstraintPose : IPose<PhysicsConstraintPose> {
internal float inertia, strength, damping, massInverse, wind, gravity, mix;
public void Set (PhysicsConstraintPose pose) {
inertia = pose.inertia;
strength = pose.strength;
damping = pose.damping;
massInverse = pose.massInverse;
wind = pose.wind;
gravity = pose.gravity;
mix = pose.mix;
}
/// <summary>Controls how much bone movement is converted into physics movement.</summary>
public float Inertia { get { return inertia; } set { inertia = value; } }
/// <summary>The amount of force used to return properties to the unconstrained value.</summary>
public float Strength { get { return strength; } set { strength = value; } }
/// <summary>Reduces the speed of physics movements, with more of a reduction at higher speeds.</summary>
public float Damping { get { return damping; } set { damping = value; } }
/// <summary>Determines susceptibility to acceleration.</summary>
public float MassInverse { get { return massInverse; } set { massInverse = value; } }
/// <summary>Applies a constant force along the <see cref="Skeleton.WindX"/>, <see cref="Skeleton.WindY"/> vector.</summary>
public float Wind { get { return wind; } set { wind = value; } }
/// <summary>Applies a constant force along the <see cref="Skeleton.GravityX"/>, <see cref="Skeleton.GravityY"/> vector.</summary>
public float Gravity { get { return gravity; } set { gravity = value; } }
/// <summary>A percentage (0+) that controls the mix between the constrained and unconstrained poses.</summary>
public float Mix { get { return mix; } set { mix = value; } }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6974fd8e5e17807499e8fed214659cf9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,106 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
internal interface IPosedInternal {
// replaces "object.pose == object.appliedPose" of reference implementation.
bool PoseEqualsApplied { get; }
// replaces "object.appliedPose = object.pose" of reference implementation.
void Unconstrained ();
// replaces "object.appliedPose = object.constrainedPose" of reference implementation.
void Constrained ();
// replaces "object.appliedPose.Set(object.pose)" of reference implementation.
void ResetConstrained ();
}
public interface IPosed {
void SetupPose ();
}
/// <summary>The base class for an object with a number of poses:
/// <list type="bullet">
/// <item><see cref="Data"/>: The setup pose.</item>
/// <item><see cref="Pose"/>: The unconstrained pose. Set by animations and application code.</item>
/// <item><see cref="AppliedPose"/>: The pose to use for rendering. Possibly modified by constraints.</item>
/// </list>
/// </summary>
public class Posed<D, P> : IPosed, IPosedInternal
where D : PosedData<P>
where P : IPose<P> {
internal readonly D data;
internal readonly P pose, constrainedPose;
internal P appliedPose;
protected Posed (D data, P pose, P constrainedPose) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data;
this.pose = pose;
this.constrainedPose = constrainedPose;
appliedPose = pose;
}
/// <summary>Sets the unconstrained pose to the setup pose.</summary>
public virtual void SetupPose () {
pose.Set(data.setupPose);
}
bool IPosedInternal.PoseEqualsApplied {
get { return (object)pose == (object)appliedPose; }
}
void IPosedInternal.Unconstrained () {
appliedPose = pose;
}
void IPosedInternal.Constrained () {
appliedPose = constrainedPose;
}
void IPosedInternal.ResetConstrained () {
appliedPose.Set(pose);
}
/// <summary>The setup pose data. May be shared with multiple instances.</summary>
public D Data { get { return data; } }
/// <summary>The unconstrained pose for this object, set by animations and application code.</summary>
public P Pose { get { return pose; } }
/// <summary>The pose to use for rendering. If no constraints modify this pose, this is the same as <see cref="Pose"/>. Otherwise it is a
/// copy of <see cref="Pose"/> modified by constraints.</summary>
public P AppliedPose { get { return appliedPose; } }
override public string ToString () {
return data.name;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 208c5c9065683a84e8166c4ce75c8951
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,55 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
public interface IPosedActive {
bool Active { get; set; }
}
/// <summary>A posed object that may be active or inactive.</summary>
public class PosedActive<D, P> : Posed<D, P>, IPosedActive
where D : PosedData<P>
where P : IPose<P> {
internal bool active;
protected PosedActive (D data, P pose, P constrained)
: base(data, pose, constrained) {
SetupPose();
}
/// <summary>Returns false when this constraint won't be updated by
/// <see cref="Skeleton.UpdateWorldTransform(Physics)"/> because a skin is required and the
/// <see cref="Skeleton.Skin">active skin</see> does not contain this item. See <see cref="Skin.Bones"/>,
/// <see cref="Skin.Constraints"/>, <see cref="PosedData{P}.SkinRequired"/>, and <see cref="Skeleton.UpdateCache()"/>.</summary>
public bool Active { get { return active; } set { active = value; } }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3e0989017e310f44900496191b7d268
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,72 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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 System;
namespace Spine {
public interface IPosedData {
bool SkinRequired { get; }
}
/// <summary>The base class for storing setup data for a posed object. May be shared with multiple instances.</summary>
public class PosedData<P> : IPosedData
where P : IPose<P> {
internal readonly string name;
internal readonly P setupPose;
internal bool skinRequired;
protected PosedData (string name, P setupPose) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
this.setupPose = setupPose;
}
public string Name { get { return name; } }
/// <summary>The setup pose that most animations are relative to.</summary>
public P GetSetupPose () {
return setupPose;
}
/// <summary>
/// When true, <see cref="Skeleton.UpdateWorldTransform(Physics)"/> only updates this constraint if the <see cref="Skeleton.Skin"/>
/// contains this constraint.
/// </summary>
/// <seealso cref="Skin.Constraints"/>
public bool SkinRequired {
get { return skinRequired; }
set { skinRequired = value; }
}
override public string ToString () {
return name;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 984c45a4e280725489d63d5bf5cf526e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,605 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2026, 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.
*****************************************************************************/
#if UNITY_5_3_OR_NEWER
#define IS_UNITY
#endif
using System;
namespace Spine {
#if IS_UNITY
using Color32F = UnityEngine.Color;
#endif
/// <summary>Stores bones and slots to be posed by animations and application code. Multiple skeleton instances can share the same
/// <see cref="SkeletonData"/>, including animations, attachments, and skins.
/// <para>After posing, call <see cref="UpdateWorldTransform(Physics)"/> to apply constraints and compute world transforms for
/// rendering.</para>
/// <para>See <see href="https://esotericsoftware.com/spine-runtime-architecture#Instance-objects">Instance objects</see> in the
/// Spine Runtimes Guide.</para></summary>
public class Skeleton {
static private readonly int[] quadTriangles = { 0, 1, 2, 2, 3, 0 };
internal SkeletonData data;
internal ExposedList<Bone> bones;
internal ExposedList<Slot> slots;
internal readonly DrawOrder drawOrder;
internal ExposedList<IConstraint> constraints;
internal ExposedList<PhysicsConstraint> physics;
internal ExposedList<object> updateCache = new ExposedList<object>();
internal ExposedList<IPosedInternal> resetCache = new ExposedList<IPosedInternal>(16);
internal Skin skin;
// Color is a struct, set to protected to prevent
// Color32F color = slot.color; color.a = 0.5;
// modifying just a copy of the struct instead of the original
// object as in reference implementation.
protected Color32F color;
internal float x, y, scaleX = 1, time, windX = 1, windY = 0, gravityX = 0, gravityY = 1;
/// <summary>Private to enforce usage of ScaleY getter taking Bone.yDown into account.</summary>
private float scaleY = 1;
internal int update;
public Skeleton (SkeletonData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data;
bones = new ExposedList<Bone>(data.bones.Count);
Bone[] bonesItems = this.bones.Items;
foreach (BoneData boneData in data.bones) {
Bone bone;
if (boneData.parent == null) {
bone = new Bone(boneData, null);
} else {
Bone parent = bonesItems[boneData.parent.index];
bone = new Bone(boneData, parent);
parent.children.Add(bone);
}
this.bones.Add(bone);
}
slots = new ExposedList<Slot>(data.slots.Count);
foreach (SlotData slotData in data.slots)
slots.Add(new Slot(slotData, this));
drawOrder = new DrawOrder(slots);
physics = new ExposedList<PhysicsConstraint>(8);
constraints = new ExposedList<IConstraint>(data.constraints.Count);
foreach (IConstraintData constraintData in data.constraints) {
IConstraint constraint = constraintData.Create(this);
PhysicsConstraint physicsConstraint = constraint as PhysicsConstraint;
if (physicsConstraint != null) physics.Add(physicsConstraint);
constraints.Add(constraint);
}
physics.TrimExcess();
color = new Color32F(1, 1, 1, 1);
UpdateCache();
}
/// <summary>Copy constructor.</summary>
public Skeleton (Skeleton skeleton) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
data = skeleton.data;
bones = new ExposedList<Bone>(skeleton.bones.Count);
foreach (Bone bone in skeleton.bones) {
Bone newBone;
if (bone.parent == null)
newBone = new Bone(bone, null);
else {
Bone parent = bones.Items[bone.parent.data.index];
newBone = new Bone(bone, parent);
parent.children.Add(newBone);
}
bones.Add(newBone);
}
slots = new ExposedList<Slot>(skeleton.slots.Count);
Bone[] bonesItems = bones.Items;
foreach (Slot slot in skeleton.slots)
slots.Add(new Slot(slot, bonesItems[slot.bone.data.index], this));
drawOrder = new DrawOrder(slots);
drawOrder.pose.Clear();
foreach (Slot slot in skeleton.drawOrder.pose)
drawOrder.pose.Add(slots.Items[slot.data.index]);
physics = new ExposedList<PhysicsConstraint>(skeleton.physics.Count);
constraints = new ExposedList<IConstraint>(skeleton.constraints.Count);
foreach (IConstraint other in skeleton.constraints) {
IConstraint constraint = other.Copy(this);
PhysicsConstraint physicsConstraint = constraint as PhysicsConstraint;
if (physicsConstraint != null) physics.Add(physicsConstraint);
constraints.Add(constraint);
}
skin = skeleton.skin;
color = skeleton.color;
x = skeleton.x;
y = skeleton.y;
scaleX = skeleton.scaleX;
scaleY = skeleton.scaleY;
time = skeleton.time;
UpdateCache();
}
/// <summary>Caches information about bones and constraints. Must be called if the <see cref="Skin"/> is modified or if bones, constraints, or
/// constraints, or weighted path attachments are added or removed.</summary>
public void UpdateCache () {
updateCache.Clear();
resetCache.Clear();
drawOrder.Unconstrained();
Slot[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++) {
((IPosedInternal)slots[i]).Unconstrained();
}
int boneCount = this.bones.Count;
Bone[] bones = this.bones.Items;
for (int i = 0; i < boneCount; i++) {
Bone bone = bones[i];
bone.sorted = bone.data.skinRequired;
bone.active = !bone.sorted;
((IPosedInternal)bone).Unconstrained();
}
if (skin != null) {
BoneData[] skinBones = skin.bones.Items;
for (int i = 0, n = skin.bones.Count; i < n; i++) {
Bone bone = bones[skinBones[i].index];
do {
bone.sorted = false;
bone.active = true;
bone = bone.parent;
} while (bone != null);
}
}
IConstraint[] constraints = this.constraints.Items;
{ // scope added to prevent compile error of n already being declared in enclosing scope
int n = this.constraints.Count;
for (int i = 0; i < n; i++) {
((IPosedInternal)constraints[i]).Unconstrained();
}
for (int i = 0; i < n; i++) {
IConstraint constraint = constraints[i];
constraint.Active = constraint.IsSourceActive
&& (!constraint.IData.SkinRequired || (skin != null && skin.constraints.Contains(constraint.IData)));
if (constraint.Active) constraint.Sort(this);
}
for (int i = 0; i < boneCount; i++)
SortBone(bones[i]);
object[] updateCache = this.updateCache.Items;
n = this.updateCache.Count;
for (int i = 0; i < n; i++) {
Bone bone = updateCache[i] as Bone;
if (bone != null) updateCache[i] = bone.appliedPose;
}
}
}
internal void Constrained (IPosedInternal obj) {
if (obj.PoseEqualsApplied) { // if (obj.pose == obj.appliedPose) {
obj.Constrained();
resetCache.Add(obj);
}
}
internal void SortBone (Bone bone) {
if (bone.sorted || !bone.active) return;
Bone parent = bone.parent;
if (parent != null) SortBone(parent);
bone.sorted = true;
updateCache.Add(bone);
}
internal void SortReset (ExposedList<Bone> bones) {
Bone[] items = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = items[i];
if (bone.active) {
if (bone.sorted) SortReset(bone.children);
bone.sorted = false;
}
}
}
/// <summary>
/// Updates the world transform for each bone and applies all constraints.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
/// Runtimes Guide.</para>
/// </summary>
public void UpdateWorldTransform (Physics physics) {
update++;
if (drawOrder.appliedPose == drawOrder.constrainedPose) drawOrder.ResetConstrained();
IPosedInternal[] resetCache = this.resetCache.Items;
for (int i = 0, n = this.resetCache.Count; i < n; i++) {
resetCache[i].ResetConstrained();
}
object[] updateCache = this.updateCache.Items;
for (int i = 0, n = this.updateCache.Count; i < n; i++)
((IUpdate)updateCache[i]).Update(this, physics);
}
/// <summary>Sets the bones, constraints, slots, and draw order to their setup pose values.</summary>
public void SetupPose () {
SetupPoseBones();
SetupPoseSlots();
}
/// <summary>Sets the bones and constraints to their setup pose values.</summary>
public void SetupPoseBones () {
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++)
bones[i].SetupPose();
IConstraint[] constraints = this.constraints.Items;
for (int i = 0, n = this.constraints.Count; i < n; i++)
constraints[i].SetupPose();
}
/// <summary>Sets the slots and draw order to their setup pose values.</summary>
public void SetupPoseSlots () {
drawOrder.SetupPose();
Slot[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++)
slots[i].SetupPose();
}
/// <summary>The skeleton's setup pose data.</summary>
public SkeletonData Data { get { return data; } }
/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
public ExposedList<Bone> Bones { get { return bones; } }
/// <summary>
/// The list of bones and constraints, sorted in the order they should be updated, as computed by <see cref="UpdateCache()"/>.
/// </summary>
public ExposedList<object> UpdateCacheList { get { return updateCache; } }
/// <summary>Returns the root bone, or null if the skeleton has no bones.</summary>
public Bone RootBone {
get { return bones.Count == 0 ? null : bones.Items[0]; }
}
/// <summary>Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
/// repeatedly.</summary>
/// <returns>May be null.</returns>
public Bone FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
if (bone.data.name == boneName) return bone;
}
return null;
}
/// <summary>The skeleton's slots. To add a slot, also add it to <see cref="DrawOrder.Pose"/>.</summary>
public ExposedList<Slot> Slots { get { return slots; } }
/// <summary>Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
/// repeatedly.</summary>
/// <returns>May be null.</returns>
public Slot FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
Slot[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++) {
Slot slot = slots[i];
if (slot.data.name == slotName) return slot;
}
return null;
}
/// <summary>
/// The skeleton's draw order. Use <see cref="DrawOrder.AppliedPose"/> for rendering and
/// <see cref="DrawOrder.Pose"/> for changing the draw order.
/// </summary>
public DrawOrder DrawOrder {
get { return drawOrder; }
}
/// <summary>The skeleton's current skin. May be null. See <see cref="SetSkin(Spine.Skin)"/></summary>
public Skin Skin {
/// <summary>The skeleton's current skin. May be null.</summary>
get { return skin; }
/// <summary>Sets a skin, <see cref="SetSkin(Skin)"/>.</summary>
set { SetSkin(value); }
}
/// <summary>Sets a skin by name (see <see cref="SetSkin(Spine.Skin)"/>).</summary>
public void SetSkin (string skinName) {
Skin foundSkin = data.FindSkin(skinName);
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
SetSkin(foundSkin);
}
/// <summary>
/// <para>Sets the skin used to look up attachments before looking in <see cref="SkeletonData.DefaultSkin"/>. If the skin is
/// changed, <see cref="UpdateCache()"/> is called.
/// </para>
/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was no
/// old skin, each slot's setup mode attachment is attached from the new skin.
/// </para>
/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling
/// <see cref="Skeleton.SetupPoseSlots()"/>. Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the skeleton is
/// rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.</para>
/// </summary>
/// <param name="newSkin">May be null.</param>
public void SetSkin (Skin newSkin) {
if (newSkin == skin) return;
if (newSkin != null) {
if (skin != null)
newSkin.AttachAll(this, skin);
else {
Slot[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++) {
Slot slot = slots[i];
string name = slot.data.attachmentName;
if (name != null) {
Attachment attachment = newSkin.GetAttachment(i, name);
if (attachment != null) slot.pose.Attachment = attachment;
}
}
}
}
skin = newSkin;
UpdateCache();
}
/// <summary>Finds an attachment by looking in the <see cref="Skeleton.Skin"/> and <see cref="SkeletonData.DefaultSkin"/> using the slot name and attachment
/// name.</summary>
/// <returns>May be null.</returns>
/// <seealso cref="GetAttachment(int, string)"/>
public Attachment GetAttachment (string slotName, string placeholder) {
SlotData slot = data.FindSlot(slotName);
if (slot == null) throw new ArgumentException("Slot not found: " + slotName, "slotName");
return GetAttachment(slot.index, placeholder);
}
/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and skin
/// placeholder name. First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
/// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, string placeholder) {
if (placeholder == null) throw new ArgumentNullException("placeholder", "placeholder cannot be null.");
if (skin != null) {
Attachment attachment = skin.GetAttachment(slotIndex, placeholder);
if (attachment != null) return attachment;
}
if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, placeholder);
return null;
}
/// <summary>A convenience method to set an attachment by finding the slot with <see cref="FindSlot(string)"/>, finding the attachment with
/// <see cref="GetAttachment(int, string)"/>, then setting the slot's <see cref="SlotPose.Attachment"/>.</summary>
/// <param name="placeholder">May be null to clear the slot's attachment.</param>
public void SetAttachment (string slotName, string placeholder) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
Slot slot = FindSlot(slotName);
if (slot == null) throw new ArgumentException("Slot not found: " + slotName, "slotName");
Attachment attachment = null;
if (placeholder != null) {
attachment = GetAttachment(slot.data.index, placeholder);
if (attachment == null)
throw new ArgumentException("Attachment not found: " + placeholder + ", for slot: " + slotName, "placeholder");
}
slot.pose.Attachment = attachment;
}
/// <summary>The skeleton's constraints.</summary>
public ExposedList<IConstraint> Constraints { get { return constraints; } }
/// <summary>The skeleton's physics constraints.</summary>
public ExposedList<PhysicsConstraint> PhysicsConstraints { get { return physics; } }
/// <summary>Finds a constraint of the specified type by comparing each constraint's name. It is more efficient to cache the
/// results of this method than to call it multiple times.</summary>
/// <returns>May be null.</returns>
public T FindConstraint<T> (string constraintName) where T : class, IConstraint {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
IConstraint[] constraints = this.constraints.Items;
for (int i = 0, n = this.constraints.Count; i < n; i++) {
IConstraint constraint = constraints[i];
if (constraint is T && constraint.IData.Name == constraintName) return (T)constraint;
}
return null;
}
/// <summary>Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the applied pose.</summary>
/// <param name="x">The horizontal distance between the skeleton origin and the left side of the AABB.</param>
/// <param name="y">The vertical distance between the skeleton origin and the bottom side of the AABB.</param>
/// <param name="width">The width of the AABB</param>
/// <param name="height">The height of the AABB.</param>
/// <param name="vertexBuffer">Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed.</param>
public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer,
SkeletonClipping clipper = null) {
float[] temp = vertexBuffer;
temp = temp ?? new float[8];
ExposedList<Slot> drawOrder = this.drawOrder.appliedPose;
Slot[] slots = drawOrder.Items;
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
for (int i = 0, n = drawOrder.Count; i < n; i++) {
Slot slot = slots[i];
if (!slot.bone.active) continue;
int verticesLength = 0;
float[] vertices = null;
int[] triangles = null;
Attachment attachment = slot.appliedPose.attachment;
RegionAttachment region = attachment as RegionAttachment;
if (region != null) {
verticesLength = 8;
vertices = temp;
if (vertices.Length < 8) vertices = temp = new float[8];
region.ComputeWorldVertices(slot, region.GetOffsets(slot.appliedPose), vertices, 0, 2);
triangles = quadTriangles;
} else {
MeshAttachment mesh = attachment as MeshAttachment;
if (mesh != null) {
verticesLength = mesh.WorldVerticesLength;
vertices = temp;
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
mesh.ComputeWorldVertices(this, slot, 0, verticesLength, temp, 0, 2);
triangles = mesh.Triangles;
} else if (clipper != null) {
ClippingAttachment clip = attachment as ClippingAttachment;
if (clip != null) {
clipper.ClipEnd(slot);
clipper.ClipStart(this, slot, clip);
continue;
}
}
}
if (vertices != null) {
if (clipper != null && clipper.IsClipping && clipper.ClipTriangles(vertices, triangles, triangles.Length)) {
vertices = clipper.ClippedVertices.Items;
verticesLength = clipper.ClippedVertices.Count;
}
for (int ii = 0; ii < verticesLength; ii += 2) {
float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy);
maxX = Math.Max(maxX, vx);
maxY = Math.Max(maxY, vy);
}
}
if (clipper != null) clipper.ClipEnd(slot);
}
if (clipper != null) clipper.ClipEnd();
x = minX;
y = minY;
width = maxX - minX;
height = maxY - minY;
vertexBuffer = temp;
}
/// <returns>A copy of the color to tint all the skeleton's attachments.</returns>
public Color32F GetColor () {
return color;
}
/// <summary>Sets the color to tint all the skeleton's attachments.</summary>
public void SetColor (Color32F color) {
this.color = color;
}
/// <summary>
/// A convenience method for setting the skeleton color. The color can also be set by
/// <see cref="SetColor(Color32F)"/>
/// </summary>
public void SetColor (float r, float g, float b, float a) {
color = new Color32F(r, g, b, a);
}
/// <summary><para> Scales the entire skeleton on the X axis.</para>
/// <para>
/// Bones that do not inherit scale are still affected by this property.</para></summary>
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
/// <summary><para> Scales the entire skeleton on the Y axis.</para>
/// <para>
/// Bones that do not inherit scale are still affected by this property.</para></summary>
public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } }
/// <summary>
/// Scales the entire skeleton on the X and Y axes.
/// <para>
/// Bones that do not inherit scale are still affected by this property.
/// </para></summary>
public void SetScale (float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
}
/// <summary><para>The skeleton X position, which is added to the root bone worldX position.</para>
/// <para>
/// Bones that do not inherit translation are still affected by this property.</para></summary>
public float X { get { return x; } set { x = value; } }
/// <summary><para>The skeleton Y position, which is added to the root bone worldY position.</para>
/// <para>
/// Bones that do not inherit translation are still affected by this property.</para></summary>
public float Y { get { return y; } set { y = value; } }
/// <summary>
/// Sets the skeleton X and Y position, which is added to the root bone worldX and worldY position.
/// <para>
/// Bones that do not inherit translation are still affected by this property.</para></summary>
public void SetPosition (float x, float y) {
this.x = x;
this.y = y;
}
/// <summary>The x component of a vector that defines the direction <see cref="PhysicsConstraintPose.Wind"/> is applied.</summary>
public float WindX { get { return windX; } set { windX = value; } }
/// <summary>The y component of a vector that defines the direction <see cref="PhysicsConstraintPose.Wind"/> is applied.</summary>
public float WindY { get { return windY; } set { windY = value; } }
/// <summary>The x component of a vector that defines the direction <see cref="PhysicsConstraintPose.Gravity"/> is applied.</summary>
public float GravityX { get { return gravityX; } set { gravityX = value; } }
/// <summary>The y component of a vector that defines the direction <see cref="PhysicsConstraintPose.Gravity"/> is applied.</summary>
public float GravityY { get { return gravityY; } set { gravityY = value; } }
/// <summary>
/// Calls <see cref="PhysicsConstraint.Translate(float, float)"/> for each physics constraint.
/// </summary>
public void PhysicsTranslate (float x, float y) {
PhysicsConstraint[] physicsConstraints = this.physics.Items;
for (int i = 0, n = this.physics.Count; i < n; i++)
physicsConstraints[i].Translate(x, y);
}
/// <summary>
/// Calls <see cref="PhysicsConstraint.Rotate(float, float, float)"/> for each physics constraint.
/// </summary>
public void PhysicsRotate (float x, float y, float degrees) {
PhysicsConstraint[] physicsConstraints = this.physics.Items;
for (int i = 0, n = this.physics.Count; i < n; i++)
physicsConstraints[i].Rotate(x, y, degrees);
}
/// <summary>Returns the skeleton's time, used for time-based manipulations, such as <see cref="PhysicsConstraint"/>.</summary>
/// <seealso cref="Update(float)"/>
public float Time { get { return time; } set { time = value; } }
/// <summary>Increments the skeleton's <see cref="time"/>.</summary>
public void Update (float delta) {
time += delta;
}
override public string ToString () {
return data.name;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 12ac3c1c7546be24fb9625d3c850619d
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More