diff --git a/spine-unity/Assets/spine-unity/Components/SkeletonAnimator.cs b/spine-unity/Assets/spine-unity/Components/SkeletonAnimator.cs
index f6179d654..d9d5db4d4 100644
--- a/spine-unity/Assets/spine-unity/Components/SkeletonAnimator.cs
+++ b/spine-unity/Assets/spine-unity/Components/SkeletonAnimator.cs
@@ -1,296 +1,463 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
- * All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * or other intellectual property or proprietary rights notices on or in the
- * Software, including any copy thereof. Redistributions in binary or source
- * form must include this license and terms.
- *
- * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using UnityEngine;
-using System.Collections.Generic;
-
-namespace Spine.Unity {
- [RequireComponent(typeof(Animator))]
- public class SkeletonAnimator : SkeletonRenderer, ISkeletonAnimation {
-
- [SerializeField] protected MecanimTranslator translator;
- public MecanimTranslator Translator { get { return translator; } }
-
- #region Bone Callbacks (ISkeletonAnimation)
- protected event UpdateBonesDelegate _UpdateLocal;
- protected event UpdateBonesDelegate _UpdateWorld;
- protected event UpdateBonesDelegate _UpdateComplete;
-
- ///
- /// Occurs after the animations are applied and before world space values are resolved.
- /// Use this callback when you want to set bone local values.
- public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
-
- ///
- /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
- /// Using this callback will cause the world space values to be solved an extra time.
- /// Use this callback if want to use bone world space values, and also set bone local values.
- public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
-
- ///
- /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
- /// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
- /// This callback can also be used when setting world position and the bone matrix.
- public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
- #endregion
-
- public override void Initialize (bool overwrite) {
- if (valid && !overwrite) return;
- base.Initialize(overwrite);
- if (!valid) return;
-
- if (translator == null) translator = new MecanimTranslator();
- translator.Initialize(GetComponent(), this.skeletonDataAsset);
- }
-
- public void Update () {
- if (!valid) return;
-
- #if UNITY_EDITOR
- if (Application.isPlaying) {
- translator.Apply(skeleton);
- } else {
- var translatorAnimator = translator.Animator;
- if (translatorAnimator != null && translatorAnimator.isInitialized)
- translator.Apply(skeleton);
- }
- #else
- translator.Apply(skeleton);
- #endif
-
- // UpdateWorldTransform and Bone Callbacks
- {
- if (_UpdateLocal != null)
- _UpdateLocal(this);
-
- skeleton.UpdateWorldTransform();
-
- if (_UpdateWorld != null) {
- _UpdateWorld(this);
- skeleton.UpdateWorldTransform();
- }
-
- if (_UpdateComplete != null)
- _UpdateComplete(this);
- }
- }
-
- [System.Serializable]
- public class MecanimTranslator {
- #region Inspector
- public bool autoReset = true;
- public MixMode[] layerMixModes = new MixMode[0];
- #endregion
-
- public enum MixMode { AlwaysMix, MixNext, SpineStyle }
-
- readonly Dictionary animationTable = new Dictionary(IntEqualityComparer.Instance);
- readonly Dictionary clipNameHashCodeTable = new Dictionary(AnimationClipEqualityComparer.Instance);
- readonly List previousAnimations = new List();
- readonly List clipInfoCache = new List();
- readonly List nextClipInfoCache = new List();
-
- Animator animator;
- public Animator Animator { get { return this.animator; } }
-
- public void Initialize (Animator animator, SkeletonDataAsset skeletonDataAsset) {
- this.animator = animator;
-
- previousAnimations.Clear();
-
- animationTable.Clear();
- var data = skeletonDataAsset.GetSkeletonData(true);
- foreach (var a in data.Animations)
- animationTable.Add(a.Name.GetHashCode(), a);
-
- clipNameHashCodeTable.Clear();
- clipInfoCache.Clear();
- nextClipInfoCache.Clear();
- }
-
- public void Apply (Skeleton skeleton) {
- if (layerMixModes.Length < animator.layerCount)
- System.Array.Resize(ref layerMixModes, animator.layerCount);
-
- //skeleton.Update(Time.deltaTime); // Doesn't actually do anything, currently. (Spine 3.6).
-
- // Clear Previous
- if (autoReset) {
- var previousAnimations = this.previousAnimations;
- for (int i = 0, n = previousAnimations.Count; i < n; i++)
- previousAnimations[i].SetKeyedItemsToSetupPose(skeleton);
-
- previousAnimations.Clear();
- for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
- float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
- if (layerWeight <= 0) continue;
-
- AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
-
- bool hasNext = nextStateInfo.fullPathHash != 0;
-
- int clipInfoCount, nextClipInfoCount;
- IList clipInfo, nextClipInfo;
- GetAnimatorClipInfos(layer, out clipInfoCount, out nextClipInfoCount, out clipInfo, out nextClipInfo);
-
- for (int c = 0; c < clipInfoCount; c++) {
- var info = clipInfo[c];
- float weight = info.weight * layerWeight; if (weight == 0) continue;
- previousAnimations.Add(GetAnimation(info.clip));
- }
-
- if (hasNext) {
- for (int c = 0; c < nextClipInfoCount; c++) {
- var info = nextClipInfo[c];
- float weight = info.weight * layerWeight; if (weight == 0) continue;
- previousAnimations.Add(GetAnimation(info.clip));
- }
- }
- }
- }
-
- // Apply
- for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
- float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
- AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
- AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
-
- bool hasNext = nextStateInfo.fullPathHash != 0;
-
- int clipInfoCount, nextClipInfoCount;
- IList clipInfo, nextClipInfo;
- GetAnimatorClipInfos(layer, out clipInfoCount, out nextClipInfoCount, out clipInfo, out nextClipInfo);
-
- MixMode mode = layerMixModes[layer];
- if (mode == MixMode.AlwaysMix) {
- // Always use Mix instead of Applying the first non-zero weighted clip.
- for (int c = 0; c < clipInfoCount; c++) {
- var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
- GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
- }
- if (hasNext) {
- for (int c = 0; c < nextClipInfoCount; c++) {
- var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
- GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
- }
- }
- } else { // case MixNext || SpineStyle
- // Apply first non-zero weighted clip
- int c = 0;
- for (; c < clipInfoCount; c++) {
- var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
- GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, 1f, MixPose.Current, MixDirection.In);
- break;
- }
- // Mix the rest
- for (; c < clipInfoCount; c++) {
- var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
- GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
- }
-
- c = 0;
- if (hasNext) {
- // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
- if (mode == MixMode.SpineStyle) {
- for (; c < nextClipInfoCount; c++) {
- var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
- GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, 1f, MixPose.Current, MixDirection.In);
- break;
- }
- }
- // Mix the rest
- for (; c < nextClipInfoCount; c++) {
- var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
- GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
- }
- }
- }
- }
- }
-
- static float AnimationTime (float normalizedTime, float clipLength, bool loop, bool reversed) {
- if (reversed)
- normalizedTime = (1-normalizedTime + (int)normalizedTime) + (int)normalizedTime;
- float time = normalizedTime * clipLength;
- if (loop) return time;
- const float EndSnapEpsilon = 1f/30f; // Workaround for end-duration keys not being applied.
- return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength;
- }
-
- static float AnimationTime (float normalizedTime, float clipLength, bool reversed) {
- if (reversed)
- normalizedTime = (1-normalizedTime + (int)normalizedTime) + (int)normalizedTime;
-
- return normalizedTime * clipLength;
- }
-
- void GetAnimatorClipInfos (
- int layer,
- out int clipInfoCount,
- out int nextClipInfoCount,
- out IList clipInfo,
- out IList nextClipInfo) {
- clipInfoCount = animator.GetCurrentAnimatorClipInfoCount(layer);
- nextClipInfoCount = animator.GetNextAnimatorClipInfoCount(layer);
- if (clipInfoCache.Capacity < clipInfoCount) clipInfoCache.Capacity = clipInfoCount;
- if (nextClipInfoCache.Capacity < nextClipInfoCount) nextClipInfoCache.Capacity = nextClipInfoCount;
- animator.GetCurrentAnimatorClipInfo(layer, clipInfoCache);
- animator.GetNextAnimatorClipInfo(layer, nextClipInfoCache);
-
- clipInfo = clipInfoCache;
- nextClipInfo = nextClipInfoCache;
- }
-
- Spine.Animation GetAnimation (AnimationClip clip) {
- int clipNameHashCode;
- if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
- clipNameHashCode = clip.name.GetHashCode();
- clipNameHashCodeTable.Add(clip, clipNameHashCode);
- }
- Spine.Animation animation;
- animationTable.TryGetValue(clipNameHashCode, out animation);
- return animation;
- }
-
- class AnimationClipEqualityComparer : IEqualityComparer {
- internal static readonly IEqualityComparer Instance = new AnimationClipEqualityComparer();
- public bool Equals (AnimationClip x, AnimationClip y) { return x.GetInstanceID() == y.GetInstanceID(); }
- public int GetHashCode (AnimationClip o) { return o.GetInstanceID(); }
- }
-
- class IntEqualityComparer : IEqualityComparer {
- internal static readonly IEqualityComparer Instance = new IntEqualityComparer();
- public bool Equals (int x, int y) { return x == y; }
- public int GetHashCode(int o) { return o; }
- }
- }
-
- }
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace Spine.Unity {
+ [RequireComponent(typeof(Animator))]
+ public class SkeletonAnimator : SkeletonRenderer, ISkeletonAnimation {
+
+ [SerializeField] protected MecanimTranslator translator;
+ public MecanimTranslator Translator { get { return translator; } }
+
+ #region Bone Callbacks (ISkeletonAnimation)
+ protected event UpdateBonesDelegate _UpdateLocal;
+ protected event UpdateBonesDelegate _UpdateWorld;
+ protected event UpdateBonesDelegate _UpdateComplete;
+
+ ///
+ /// Occurs after the animations are applied and before world space values are resolved.
+ /// Use this callback when you want to set bone local values.
+ public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
+
+ ///
+ /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
+ /// Using this callback will cause the world space values to be solved an extra time.
+ /// Use this callback if want to use bone world space values, and also set bone local values.
+ public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
+
+ ///
+ /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
+ /// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
+ /// This callback can also be used when setting world position and the bone matrix.
+ public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
+ #endregion
+
+ public override void Initialize (bool overwrite) {
+ if (valid && !overwrite) return;
+ base.Initialize(overwrite);
+ if (!valid) return;
+
+ if (translator == null) translator = new MecanimTranslator();
+ translator.Initialize(GetComponent(), this.skeletonDataAsset);
+ }
+
+ public void Update () {
+ if (!valid) return;
+
+ #if UNITY_EDITOR
+ if (Application.isPlaying) {
+ translator.Apply(skeleton);
+ } else {
+ var translatorAnimator = translator.Animator;
+ if (translatorAnimator != null && translatorAnimator.isInitialized)
+ translator.Apply(skeleton);
+ }
+ #else
+ translator.Apply(skeleton);
+ #endif
+
+ // UpdateWorldTransform and Bone Callbacks
+ {
+ if (_UpdateLocal != null)
+ _UpdateLocal(this);
+
+ skeleton.UpdateWorldTransform();
+
+ if (_UpdateWorld != null) {
+ _UpdateWorld(this);
+ skeleton.UpdateWorldTransform();
+ }
+
+ if (_UpdateComplete != null)
+ _UpdateComplete(this);
+ }
+ }
+
+ [System.Serializable]
+ public class MecanimTranslator {
+ #region Inspector
+ public bool autoReset = true;
+ public MixMode[] layerMixModes = new MixMode[0];
+ #endregion
+
+ public enum MixMode { AlwaysMix, MixNext, SpineStyle }
+
+ readonly Dictionary animationTable = new Dictionary(IntEqualityComparer.Instance);
+ readonly Dictionary clipNameHashCodeTable = new Dictionary(AnimationClipEqualityComparer.Instance);
+ readonly List previousAnimations = new List();
+
+ protected class ClipInfos {
+ public bool isInterruptionActive = false;
+ public bool isLastFrameOfInterruption = false;
+
+ public int clipInfoCount = 0;
+ public int nextClipInfoCount = 0;
+ public int interruptingClipInfoCount = 0;
+ public readonly List clipInfos = new List();
+ public readonly List nextClipInfos = new List();
+ public readonly List interruptingClipInfos = new List();
+
+ public AnimatorStateInfo stateInfo;
+ public AnimatorStateInfo nextStateInfo;
+ public AnimatorStateInfo interruptingStateInfo;
+
+ public float interruptingClipTimeAddition = 0;
+ }
+ protected ClipInfos[] layerClipInfos = new ClipInfos[0];
+
+ Animator animator;
+ public Animator Animator { get { return this.animator; } }
+
+ public void Initialize(Animator animator, SkeletonDataAsset skeletonDataAsset) {
+ this.animator = animator;
+
+ previousAnimations.Clear();
+
+ animationTable.Clear();
+ var data = skeletonDataAsset.GetSkeletonData(true);
+ foreach (var a in data.Animations)
+ animationTable.Add(a.Name.GetHashCode(), a);
+
+ clipNameHashCodeTable.Clear();
+ ClearClipInfosForLayers();
+ }
+
+ public void Apply(Skeleton skeleton) {
+ if (layerMixModes.Length < animator.layerCount)
+ System.Array.Resize(ref layerMixModes, animator.layerCount);
+
+ InitClipInfosForLayers();
+ for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
+ GetStateUpdatesFromAnimator(layer);
+ }
+
+ //skeleton.Update(Time.deltaTime); // Doesn't actually do anything, currently. (Spine 3.6).
+
+ // Clear Previous
+ if (autoReset) {
+ var previousAnimations = this.previousAnimations;
+ for (int i = 0, n = previousAnimations.Count; i < n; i++)
+ previousAnimations[i].SetKeyedItemsToSetupPose(skeleton);
+
+ previousAnimations.Clear();
+ for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
+ float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
+ if (layerWeight <= 0) continue;
+
+ AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
+
+ bool hasNext = nextStateInfo.fullPathHash != 0;
+
+ int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
+ IList clipInfo, nextClipInfo, interruptingClipInfo;
+ bool isInterruptionActive, shallInterpolateWeightTo1;
+ GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount,
+ out clipInfo, out nextClipInfo, out interruptingClipInfo, out shallInterpolateWeightTo1);
+
+ for (int c = 0; c < clipInfoCount; c++) {
+ var info = clipInfo[c];
+ float weight = info.weight * layerWeight; if (weight == 0) continue;
+ previousAnimations.Add(GetAnimation(info.clip));
+ }
+
+ if (hasNext) {
+ for (int c = 0; c < nextClipInfoCount; c++) {
+ var info = nextClipInfo[c];
+ float weight = info.weight * layerWeight; if (weight == 0) continue;
+ previousAnimations.Add(GetAnimation(info.clip));
+ }
+ }
+
+ if (isInterruptionActive) {
+ for (int c = 0; c < interruptingClipInfoCount; c++)
+ {
+ var info = interruptingClipInfo[c];
+ float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
+ float weight = clipWeight * layerWeight; if (weight == 0) continue;
+ previousAnimations.Add(GetAnimation(info.clip));
+ }
+ }
+ }
+ }
+
+ // Apply
+ for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
+ float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
+
+ bool isInterruptionActive;
+ AnimatorStateInfo stateInfo;
+ AnimatorStateInfo nextStateInfo;
+ AnimatorStateInfo interruptingStateInfo;
+ float interruptingClipTimeAddition;
+ GetAnimatorStateInfos(layer, out isInterruptionActive, out stateInfo, out nextStateInfo, out interruptingStateInfo, out interruptingClipTimeAddition);
+
+ bool hasNext = nextStateInfo.fullPathHash != 0;
+
+ int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
+ IList clipInfo, nextClipInfo, interruptingClipInfo;
+ bool shallInterpolateWeightTo1;
+ GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount,
+ out clipInfo, out nextClipInfo, out interruptingClipInfo, out shallInterpolateWeightTo1);
+
+ MixMode mode = layerMixModes[layer];
+ if (mode == MixMode.AlwaysMix) {
+ // Always use Mix instead of Applying the first non-zero weighted clip.
+ for (int c = 0; c < clipInfoCount; c++) {
+ var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
+ }
+ if (hasNext) {
+ for (int c = 0; c < nextClipInfoCount; c++) {
+ var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
+ }
+ }
+ if (isInterruptionActive) {
+ for (int c = 0; c < interruptingClipInfoCount; c++)
+ {
+ var info = interruptingClipInfo[c];
+ float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
+ float weight = clipWeight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0),
+ interruptingStateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
+ }
+ }
+ } else { // case MixNext || SpineStyle
+ // Apply first non-zero weighted clip
+ int c = 0;
+ for (; c < clipInfoCount; c++) {
+ var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, 1f, MixPose.Current, MixDirection.In);
+ break;
+ }
+ // Mix the rest
+ for (; c < clipInfoCount; c++) {
+ var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
+ }
+
+ c = 0;
+ if (hasNext) {
+ // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
+ if (mode == MixMode.SpineStyle) {
+ for (; c < nextClipInfoCount; c++) {
+ var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, 1f, MixPose.Current, MixDirection.In);
+ break;
+ }
+ }
+ // Mix the rest
+ for (; c < nextClipInfoCount; c++) {
+ var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
+ }
+ }
+
+ c = 0;
+ if (isInterruptionActive) {
+ // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
+ if (mode == MixMode.SpineStyle) {
+ for (; c < interruptingClipInfoCount; c++) {
+ var info = interruptingClipInfo[c]; float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
+ float weight = clipWeight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), interruptingStateInfo.loop, null, 1f, MixPose.Current, MixDirection.In);
+ break;
+ }
+ }
+ // Mix the rest
+ for (; c < interruptingClipInfoCount; c++) {
+ var info = interruptingClipInfo[c]; float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
+ float weight = clipWeight * layerWeight; if (weight == 0) continue;
+ GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), interruptingStateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
+ }
+ }
+ }
+ }
+ }
+
+ static float AnimationTime(float normalizedTime, float clipLength, bool loop, bool reversed) {
+ if (reversed)
+ normalizedTime = (1 - normalizedTime + (int)normalizedTime) + (int)normalizedTime;
+ float time = normalizedTime * clipLength;
+ if (loop) return time;
+ const float EndSnapEpsilon = 1f / 30f; // Workaround for end-duration keys not being applied.
+ return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength;
+ }
+
+ static float AnimationTime(float normalizedTime, float clipLength, bool reversed) {
+ if (reversed)
+ normalizedTime = (1 - normalizedTime + (int)normalizedTime) + (int)normalizedTime;
+
+ return normalizedTime * clipLength;
+ }
+
+ void InitClipInfosForLayers() {
+ if (layerClipInfos.Length < animator.layerCount) {
+ System.Array.Resize(ref layerClipInfos, animator.layerCount);
+ for (int layer = 0, n = animator.layerCount; layer < n; ++layer) {
+ if (layerClipInfos[layer] == null)
+ layerClipInfos[layer] = new ClipInfos();
+ }
+ }
+ }
+
+ void ClearClipInfosForLayers() {
+ for (int layer = 0, n = layerClipInfos.Length; layer < n; ++layer) {
+ if (layerClipInfos[layer] == null)
+ layerClipInfos[layer] = new ClipInfos();
+ else {
+ layerClipInfos[layer].isInterruptionActive = false;
+ layerClipInfos[layer].isLastFrameOfInterruption = false;
+ layerClipInfos[layer].clipInfos.Clear();
+ layerClipInfos[layer].nextClipInfos.Clear();
+ layerClipInfos[layer].interruptingClipInfos.Clear();
+ }
+ }
+ }
+
+ void GetStateUpdatesFromAnimator(int layer) {
+
+ var layerInfos = layerClipInfos[layer];
+ int clipInfoCount = animator.GetCurrentAnimatorClipInfoCount(layer);
+ int nextClipInfoCount = animator.GetNextAnimatorClipInfoCount(layer);
+
+ var clipInfos = layerInfos.clipInfos;
+ var nextClipInfos = layerInfos.nextClipInfos;
+ var interruptingClipInfos = layerInfos.interruptingClipInfos;
+
+ layerInfos.isInterruptionActive = (clipInfoCount == 0 && nextClipInfoCount == 0);
+
+ // Note: during interruption, GetCurrentAnimatorClipInfoCount and GetNextAnimatorClipInfoCount
+ // are returning 0 in calls above. Therefore we keep previous clipInfos and nextClipInfos
+ // until the interruption is over.
+ if (layerInfos.isInterruptionActive) {
+
+ // Note: The last frame of a transition interruption
+ // will have fullPathHash set to 0, therefore we have to use previous
+ // frame's infos about interruption clips and correct some values
+ // accordingly (normalizedTime and weight).
+ var interruptingStateInfo = animator.GetNextAnimatorStateInfo(layer);
+ layerInfos.isLastFrameOfInterruption = interruptingStateInfo.fullPathHash == 0;
+ if (!layerInfos.isLastFrameOfInterruption) {
+ layerInfos.interruptingClipInfoCount = interruptingClipInfos.Count;
+
+ animator.GetNextAnimatorClipInfo(layer, interruptingClipInfos);
+ float oldTime = layerInfos.interruptingStateInfo.normalizedTime;
+ float newTime = interruptingStateInfo.normalizedTime;
+ layerInfos.interruptingClipTimeAddition = newTime - oldTime;
+ layerInfos.interruptingStateInfo = interruptingStateInfo;
+ }
+ }
+ else {
+ layerInfos.clipInfoCount = clipInfoCount;
+ layerInfos.nextClipInfoCount = nextClipInfoCount;
+ layerInfos.interruptingClipInfoCount = 0;
+ layerInfos.isLastFrameOfInterruption = false;
+
+ if (clipInfos.Capacity < clipInfoCount) clipInfos.Capacity = clipInfoCount;
+ if (nextClipInfos.Capacity < nextClipInfoCount) nextClipInfos.Capacity = nextClipInfoCount;
+
+ animator.GetCurrentAnimatorClipInfo(layer, clipInfos);
+ animator.GetNextAnimatorClipInfo(layer, nextClipInfos);
+
+ layerInfos.stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
+ layerInfos.nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
+ }
+ }
+
+ void GetAnimatorClipInfos (
+ int layer,
+ out bool isInterruptionActive,
+ out int clipInfoCount,
+ out int nextClipInfoCount,
+ out int interruptingClipInfoCount,
+ out IList clipInfo,
+ out IList nextClipInfo,
+ out IList interruptingClipInfo,
+ out bool shallInterpolateWeightTo1) {
+
+ var layerInfos = layerClipInfos[layer];
+ isInterruptionActive = layerInfos.isInterruptionActive;
+
+ clipInfoCount = layerInfos.clipInfoCount;
+ nextClipInfoCount = layerInfos.nextClipInfoCount;
+ interruptingClipInfoCount = layerInfos.interruptingClipInfoCount;
+
+ clipInfo = layerInfos.clipInfos;
+ nextClipInfo = layerInfos.nextClipInfos;
+ interruptingClipInfo = isInterruptionActive ? layerInfos.interruptingClipInfos : null;
+ shallInterpolateWeightTo1 = layerInfos.isLastFrameOfInterruption;
+ }
+
+ void GetAnimatorStateInfos(
+ int layer,
+ out bool isInterruptionActive,
+ out AnimatorStateInfo stateInfo,
+ out AnimatorStateInfo nextStateInfo,
+ out AnimatorStateInfo interruptingStateInfo,
+ out float interruptingClipTimeAddition) {
+
+ var layerInfos = layerClipInfos[layer];
+ isInterruptionActive = layerInfos.isInterruptionActive;
+
+ stateInfo = layerInfos.stateInfo;
+ nextStateInfo = layerInfos.nextStateInfo;
+ interruptingStateInfo = layerInfos.interruptingStateInfo;
+ interruptingClipTimeAddition = layerInfos.isLastFrameOfInterruption ? layerInfos.interruptingClipTimeAddition : 0;
+ }
+
+ Spine.Animation GetAnimation (AnimationClip clip) {
+ int clipNameHashCode;
+ if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
+ clipNameHashCode = clip.name.GetHashCode();
+ clipNameHashCodeTable.Add(clip, clipNameHashCode);
+ }
+ Spine.Animation animation;
+ animationTable.TryGetValue(clipNameHashCode, out animation);
+ return animation;
+ }
+
+ class AnimationClipEqualityComparer : IEqualityComparer {
+ internal static readonly IEqualityComparer Instance = new AnimationClipEqualityComparer();
+ public bool Equals (AnimationClip x, AnimationClip y) { return x.GetInstanceID() == y.GetInstanceID(); }
+ public int GetHashCode (AnimationClip o) { return o.GetInstanceID(); }
+ }
+
+ class IntEqualityComparer : IEqualityComparer {
+ internal static readonly IEqualityComparer Instance = new IntEqualityComparer();
+ public bool Equals (int x, int y) { return x == y; }
+ public int GetHashCode(int o) { return o; }
+ }
+ }
+
+ }
+}