2024-10-23 17:55:55 +08:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Spine Runtimes License Agreement
2024-11-21 09:35:48 +08:00
* Last updated July 28 , 2023. Replaces all prior versions .
2024-10-23 17:55:55 +08:00
*
2024-11-21 09:35:48 +08:00
* Copyright ( c ) 2013 - 2023 , Esoteric Software LLC
2024-10-23 17:55:55 +08:00
*
* 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
*
2024-11-21 09:35:48 +08:00
* Otherwise , it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes ( collectively ,
2024-10-23 17:55:55 +08:00
* "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
2024-11-21 09:35:48 +08:00
* ( 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 .
2024-10-23 17:55:55 +08:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
using System.Collections.Generic ;
2024-11-21 09:35:48 +08:00
using UnityEngine ;
#if UNITY_EDITOR
using UnityEditor.Animations ;
#endif
2024-10-23 17:55:55 +08:00
namespace Spine.Unity {
[RequireComponent(typeof(Animator))]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonMecanim-Component")]
public class SkeletonMecanim : SkeletonRenderer , ISkeletonAnimation {
[SerializeField] protected MecanimTranslator translator ;
public MecanimTranslator Translator { get { return translator ; } }
private bool wasUpdatedAfterInit = true ;
2024-11-21 09:35:48 +08:00
#region Bone and Initialization Callbacks ISkeletonAnimation
protected event ISkeletonAnimationDelegate _OnAnimationRebuild ;
protected event UpdateBonesDelegate _BeforeApply ;
2024-10-23 17:55:55 +08:00
protected event UpdateBonesDelegate _UpdateLocal ;
protected event UpdateBonesDelegate _UpdateWorld ;
protected event UpdateBonesDelegate _UpdateComplete ;
2024-11-21 09:35:48 +08:00
/// <summary>OnAnimationRebuild is raised after the SkeletonAnimation component is successfully initialized.</summary>
public event ISkeletonAnimationDelegate OnAnimationRebuild { add { _OnAnimationRebuild + = value ; } remove { _OnAnimationRebuild - = value ; } }
/// <summary>
/// Occurs before the animations are applied.
/// Use this callback when you want to change the skeleton state before animations are applied on top.
/// </summary>
public event UpdateBonesDelegate BeforeApply { add { _BeforeApply + = value ; } remove { _BeforeApply - = value ; } }
2024-10-23 17:55:55 +08:00
/// <summary>
/// Occurs after the animations are applied and before world space values are resolved.
/// Use this callback when you want to set bone local values.</summary>
public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal + = value ; } remove { _UpdateLocal - = value ; } }
/// <summary>
/// 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.
2024-11-21 09:35:48 +08:00
/// Use this callback if want to use bone world space values, and also set bone local values.
/// </summary>
2024-10-23 17:55:55 +08:00
public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld + = value ; } remove { _UpdateWorld - = value ; } }
/// <summary>
/// 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.</summary>
public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete + = value ; } remove { _UpdateComplete - = value ; } }
2024-11-21 09:35:48 +08:00
[SerializeField] protected UpdateTiming updateTiming = UpdateTiming . InUpdate ;
public UpdateTiming UpdateTiming { get { return updateTiming ; } set { updateTiming = value ; } }
2024-10-23 17:55:55 +08:00
#endregion
2024-11-21 09:35:48 +08:00
public override void Initialize ( bool overwrite , bool quiet = false ) {
2024-10-23 17:55:55 +08:00
if ( valid & & ! overwrite )
return ;
2024-11-21 09:35:48 +08:00
#if UNITY_EDITOR
if ( BuildUtilities . IsInSkeletonAssetBuildPreProcessing )
return ;
#endif
base . Initialize ( overwrite , quiet ) ;
2024-10-23 17:55:55 +08:00
if ( ! valid )
return ;
if ( translator = = null ) translator = new MecanimTranslator ( ) ;
translator . Initialize ( GetComponent < Animator > ( ) , this . skeletonDataAsset ) ;
wasUpdatedAfterInit = false ;
2024-11-21 09:35:48 +08:00
if ( _OnAnimationRebuild ! = null )
_OnAnimationRebuild ( this ) ;
}
public virtual void Update ( ) {
if ( ! valid | | updateTiming ! = UpdateTiming . InUpdate ) return ;
UpdateAnimation ( Time . deltaTime ) ;
}
public virtual void FixedUpdate ( ) {
if ( ! valid | | updateTiming ! = UpdateTiming . InFixedUpdate ) return ;
UpdateAnimation ( Time . deltaTime ) ;
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
/// <summary>Manual animation update. Required when <c>updateTiming</c> is set to <c>ManualUpdate</c>.</summary>
/// <param name="deltaTime">Ignored parameter.</param>
public virtual void Update ( float deltaTime ) {
2024-10-23 17:55:55 +08:00
if ( ! valid ) return ;
2024-11-21 09:35:48 +08:00
UpdateAnimation ( deltaTime ) ;
}
2024-10-23 17:55:55 +08:00
2024-11-21 09:35:48 +08:00
protected void UpdateAnimation ( float deltaTime ) {
2024-10-23 17:55:55 +08:00
wasUpdatedAfterInit = true ;
2024-11-21 09:35:48 +08:00
2024-10-23 17:55:55 +08:00
// animation status is kept by Mecanim Animator component
if ( updateMode < = UpdateMode . OnlyAnimationStatus )
return ;
2024-11-21 09:35:48 +08:00
skeleton . Update ( deltaTime ) ;
ApplyTransformMovementToPhysics ( ) ;
2024-10-23 17:55:55 +08:00
ApplyAnimation ( ) ;
}
2024-11-21 09:35:48 +08:00
public virtual void ApplyAnimation ( ) {
if ( _BeforeApply ! = null )
_BeforeApply ( this ) ;
#if UNITY_EDITOR
Animator translatorAnimator = translator . Animator ;
2024-10-23 17:55:55 +08:00
if ( translatorAnimator ! = null & & ! translatorAnimator . isInitialized )
translatorAnimator . Rebind ( ) ;
if ( Application . isPlaying ) {
translator . Apply ( skeleton ) ;
2024-11-21 09:35:48 +08:00
} else {
2024-10-23 17:55:55 +08:00
if ( translatorAnimator ! = null & & translatorAnimator . isInitialized & &
translatorAnimator . isActiveAndEnabled & & translatorAnimator . runtimeAnimatorController ! = null ) {
// Note: Rebind is required to prevent warning "Animator is not playing an AnimatorController" with prefabs
translatorAnimator . Rebind ( ) ;
translator . Apply ( skeleton ) ;
}
}
2024-11-21 09:35:48 +08:00
#else
2024-10-23 17:55:55 +08:00
translator . Apply ( skeleton ) ;
2024-11-21 09:35:48 +08:00
#endif
AfterAnimationApplied ( ) ;
}
2024-10-23 17:55:55 +08:00
2024-11-21 09:35:48 +08:00
public virtual void AfterAnimationApplied ( ) {
if ( _UpdateLocal ! = null )
_UpdateLocal ( this ) ;
2024-10-23 17:55:55 +08:00
2024-11-21 09:35:48 +08:00
if ( _UpdateWorld = = null ) {
UpdateWorldTransform ( Skeleton . Physics . Update ) ;
} else {
UpdateWorldTransform ( Skeleton . Physics . Pose ) ;
_UpdateWorld ( this ) ;
UpdateWorldTransform ( Skeleton . Physics . Update ) ;
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
if ( _UpdateComplete ! = null )
_UpdateComplete ( this ) ;
2024-10-23 17:55:55 +08:00
}
public override void LateUpdate ( ) {
2024-11-21 09:35:48 +08:00
if ( updateTiming = = UpdateTiming . InLateUpdate & & valid & & translator ! = null & & translator . Animator ! = null )
UpdateAnimation ( Time . deltaTime ) ;
2024-10-23 17:55:55 +08:00
// instantiation can happen from Update() after this component, leading to a missing Update() call.
if ( ! wasUpdatedAfterInit ) Update ( ) ;
base . LateUpdate ( ) ;
}
2024-11-21 09:35:48 +08:00
public override void OnBecameVisible ( ) {
UpdateMode previousUpdateMode = updateMode ;
updateMode = UpdateMode . FullUpdate ;
// OnBecameVisible is called after LateUpdate()
if ( previousUpdateMode ! = UpdateMode . FullUpdate & &
previousUpdateMode ! = UpdateMode . EverythingExceptMesh )
Update ( ) ;
if ( previousUpdateMode ! = UpdateMode . FullUpdate )
LateUpdate ( ) ;
}
2024-10-23 17:55:55 +08:00
[System.Serializable]
public class MecanimTranslator {
2024-11-21 09:35:48 +08:00
const float WeightEpsilon = 0.0001f ;
2024-10-23 17:55:55 +08:00
#region Inspector
public bool autoReset = true ;
public bool useCustomMixMode = true ;
public MixMode [ ] layerMixModes = new MixMode [ 0 ] ;
public MixBlend [ ] layerBlendModes = new MixBlend [ 0 ] ;
#endregion
public delegate void OnClipAppliedDelegate ( Spine . Animation clip , int layerIndex , float weight ,
float time , float lastTime , bool playsBackward ) ;
protected event OnClipAppliedDelegate _OnClipApplied ;
public event OnClipAppliedDelegate OnClipApplied { add { _OnClipApplied + = value ; } remove { _OnClipApplied - = value ; } }
2024-11-21 09:35:48 +08:00
public enum MixMode { AlwaysMix , MixNext , Hard , Match }
2024-10-23 17:55:55 +08:00
readonly Dictionary < int , Spine . Animation > animationTable = new Dictionary < int , Spine . Animation > ( IntEqualityComparer . Instance ) ;
readonly Dictionary < AnimationClip , int > clipNameHashCodeTable = new Dictionary < AnimationClip , int > ( AnimationClipEqualityComparer . Instance ) ;
readonly List < Animation > previousAnimations = new List < Animation > ( ) ;
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 < AnimatorClipInfo > clipInfos = new List < AnimatorClipInfo > ( ) ;
public readonly List < AnimatorClipInfo > nextClipInfos = new List < AnimatorClipInfo > ( ) ;
public readonly List < AnimatorClipInfo > interruptingClipInfos = new List < AnimatorClipInfo > ( ) ;
2024-11-21 09:35:48 +08:00
public float [ ] clipResolvedWeights = new float [ 0 ] ;
public float [ ] nextClipResolvedWeights = new float [ 0 ] ;
public float [ ] interruptingClipResolvedWeights = new float [ 0 ] ;
2024-10-23 17:55:55 +08:00
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 int MecanimLayerCount {
get {
if ( ! animator )
return 0 ;
return animator . layerCount ;
}
}
public string [ ] MecanimLayerNames {
get {
if ( ! animator )
return new string [ 0 ] ;
string [ ] layerNames = new string [ animator . layerCount ] ;
for ( int i = 0 ; i < animator . layerCount ; + + i ) {
layerNames [ i ] = animator . GetLayerName ( i ) ;
}
return layerNames ;
}
}
2024-11-21 09:35:48 +08:00
public void Initialize ( Animator animator , SkeletonDataAsset skeletonDataAsset ) {
2024-10-23 17:55:55 +08:00
this . animator = animator ;
previousAnimations . Clear ( ) ;
animationTable . Clear ( ) ;
2024-11-21 09:35:48 +08:00
SkeletonData data = skeletonDataAsset . GetSkeletonData ( true ) ;
foreach ( Animation a in data . Animations )
2024-10-23 17:55:55 +08:00
animationTable . Add ( a . Name . GetHashCode ( ) , a ) ;
clipNameHashCodeTable . Clear ( ) ;
ClearClipInfosForLayers ( ) ;
}
private bool ApplyAnimation ( Skeleton skeleton , AnimatorClipInfo info , AnimatorStateInfo stateInfo ,
2024-11-21 09:35:48 +08:00
int layerIndex , float layerWeight , MixBlend layerBlendMode ,
bool useCustomClipWeight = false , float customClipWeight = 1.0f ) {
2024-10-23 17:55:55 +08:00
float weight = info . weight * layerWeight ;
2024-11-21 09:35:48 +08:00
if ( weight < WeightEpsilon )
2024-10-23 17:55:55 +08:00
return false ;
2024-11-21 09:35:48 +08:00
Animation clip = GetAnimation ( info . clip ) ;
2024-10-23 17:55:55 +08:00
if ( clip = = null )
return false ;
2024-11-21 09:35:48 +08:00
float time = AnimationTime ( stateInfo . normalizedTime , info . clip . length ,
2024-10-23 17:55:55 +08:00
info . clip . isLooping , stateInfo . speed < 0 ) ;
2024-11-21 09:35:48 +08:00
weight = useCustomClipWeight ? layerWeight * customClipWeight : weight ;
2024-10-23 17:55:55 +08:00
clip . Apply ( skeleton , 0 , time , info . clip . isLooping , null ,
weight , layerBlendMode , MixDirection . In ) ;
if ( _OnClipApplied ! = null )
OnClipAppliedCallback ( clip , stateInfo , layerIndex , time , info . clip . isLooping , weight ) ;
return true ;
}
private bool ApplyInterruptionAnimation ( Skeleton skeleton ,
bool interpolateWeightTo1 , AnimatorClipInfo info , AnimatorStateInfo stateInfo ,
int layerIndex , float layerWeight , MixBlend layerBlendMode , float interruptingClipTimeAddition ,
2024-11-21 09:35:48 +08:00
bool useCustomClipWeight = false , float customClipWeight = 1.0f ) {
2024-10-23 17:55:55 +08:00
float clipWeight = interpolateWeightTo1 ? ( info . weight + 1.0f ) * 0.5f : info . weight ;
float weight = clipWeight * layerWeight ;
2024-11-21 09:35:48 +08:00
if ( weight < WeightEpsilon )
2024-10-23 17:55:55 +08:00
return false ;
2024-11-21 09:35:48 +08:00
Animation clip = GetAnimation ( info . clip ) ;
2024-10-23 17:55:55 +08:00
if ( clip = = null )
return false ;
2024-11-21 09:35:48 +08:00
float time = AnimationTime ( stateInfo . normalizedTime + interruptingClipTimeAddition ,
info . clip . length , info . clip . isLooping , stateInfo . speed < 0 ) ;
weight = useCustomClipWeight ? layerWeight * customClipWeight : weight ;
2024-10-23 17:55:55 +08:00
clip . Apply ( skeleton , 0 , time , info . clip . isLooping , null ,
weight , layerBlendMode , MixDirection . In ) ;
if ( _OnClipApplied ! = null ) {
OnClipAppliedCallback ( clip , stateInfo , layerIndex , time , info . clip . isLooping , weight ) ;
}
return true ;
}
private void OnClipAppliedCallback ( Spine . Animation clip , AnimatorStateInfo stateInfo ,
int layerIndex , float time , bool isLooping , float weight ) {
float speedFactor = stateInfo . speedMultiplier * stateInfo . speed ;
float lastTime = time - ( Time . deltaTime * speedFactor ) ;
2024-11-21 09:35:48 +08:00
float clipDuration = clip . Duration ;
if ( isLooping & & clipDuration ! = 0 ) {
time % = clipDuration ;
lastTime % = clipDuration ;
2024-10-23 17:55:55 +08:00
}
_OnClipApplied ( clip , layerIndex , weight , time , lastTime , speedFactor < 0 ) ;
}
public void Apply ( Skeleton skeleton ) {
2024-11-21 09:35:48 +08:00
#if UNITY_EDITOR
2024-10-23 17:55:55 +08:00
if ( ! Application . isPlaying ) {
GetLayerBlendModes ( ) ;
}
2024-11-21 09:35:48 +08:00
#endif
2024-10-23 17:55:55 +08:00
if ( layerMixModes . Length < animator . layerCount ) {
int oldSize = layerMixModes . Length ;
System . Array . Resize < MixMode > ( ref layerMixModes , animator . layerCount ) ;
for ( int layer = oldSize ; layer < animator . layerCount ; + + layer ) {
bool isAdditiveLayer = false ;
if ( layer < layerBlendModes . Length )
isAdditiveLayer = layerBlendModes [ layer ] = = MixBlend . Add ;
layerMixModes [ layer ] = isAdditiveLayer ? MixMode . AlwaysMix : MixMode . MixNext ;
}
}
InitClipInfosForLayers ( ) ;
for ( int layer = 0 , n = animator . layerCount ; layer < n ; layer + + ) {
GetStateUpdatesFromAnimator ( layer ) ;
}
// Clear Previous
if ( autoReset ) {
2024-11-21 09:35:48 +08:00
List < Animation > previousAnimations = this . previousAnimations ;
2024-10-23 17:55:55 +08:00
for ( int i = 0 , n = previousAnimations . Count ; i < n ; i + + )
2024-11-21 09:35:48 +08:00
previousAnimations [ i ] . Apply ( skeleton , 0 , 0 , false , null , 0 , MixBlend . Setup , MixDirection . Out ) ; // SetKeyedItemsToSetupPose
2024-10-23 17:55:55 +08:00
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 < AnimatorClipInfo > 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 + + ) {
2024-11-21 09:35:48 +08:00
AnimatorClipInfo info = clipInfo [ c ] ;
float weight = info . weight * layerWeight ; if ( weight < WeightEpsilon ) continue ;
Spine . Animation clip = GetAnimation ( info . clip ) ;
2024-10-23 17:55:55 +08:00
if ( clip ! = null )
previousAnimations . Add ( clip ) ;
}
if ( hasNext ) {
for ( int c = 0 ; c < nextClipInfoCount ; c + + ) {
2024-11-21 09:35:48 +08:00
AnimatorClipInfo info = nextClipInfo [ c ] ;
float weight = info . weight * layerWeight ; if ( weight < WeightEpsilon ) continue ;
Spine . Animation clip = GetAnimation ( info . clip ) ;
2024-10-23 17:55:55 +08:00
if ( clip ! = null )
previousAnimations . Add ( clip ) ;
}
}
if ( isInterruptionActive ) {
2024-11-21 09:35:48 +08:00
for ( int c = 0 ; c < interruptingClipInfoCount ; c + + ) {
AnimatorClipInfo info = interruptingClipInfo [ c ] ;
2024-10-23 17:55:55 +08:00
float clipWeight = shallInterpolateWeightTo1 ? ( info . weight + 1.0f ) * 0.5f : info . weight ;
2024-11-21 09:35:48 +08:00
float weight = clipWeight * layerWeight ; if ( weight < WeightEpsilon ) continue ;
Spine . Animation clip = GetAnimation ( info . clip ) ;
2024-10-23 17:55:55 +08:00
if ( clip ! = null )
previousAnimations . Add ( 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 < AnimatorClipInfo > clipInfo , nextClipInfo , interruptingClipInfo ;
bool interpolateWeightTo1 ;
GetAnimatorClipInfos ( layer , out isInterruptionActive , out clipInfoCount , out nextClipInfoCount , out interruptingClipInfoCount ,
out clipInfo , out nextClipInfo , out interruptingClipInfo , out interpolateWeightTo1 ) ;
MixBlend layerBlendMode = ( layer < layerBlendModes . Length ) ? layerBlendModes [ layer ] : MixBlend . Replace ;
MixMode mode = GetMixMode ( layer , layerBlendMode ) ;
if ( mode = = MixMode . AlwaysMix ) {
// Always use Mix instead of Applying the first non-zero weighted clip.
for ( int c = 0 ; c < clipInfoCount ; c + + ) {
ApplyAnimation ( skeleton , clipInfo [ c ] , stateInfo , layer , layerWeight , layerBlendMode ) ;
}
if ( hasNext ) {
for ( int c = 0 ; c < nextClipInfoCount ; c + + ) {
ApplyAnimation ( skeleton , nextClipInfo [ c ] , nextStateInfo , layer , layerWeight , layerBlendMode ) ;
}
}
if ( isInterruptionActive ) {
2024-11-21 09:35:48 +08:00
for ( int c = 0 ; c < interruptingClipInfoCount ; c + + ) {
2024-10-23 17:55:55 +08:00
ApplyInterruptionAnimation ( skeleton , interpolateWeightTo1 ,
interruptingClipInfo [ c ] , interruptingStateInfo ,
layer , layerWeight , layerBlendMode , interruptingClipTimeAddition ) ;
}
}
2024-11-21 09:35:48 +08:00
} else if ( mode = = MixMode . Match ) {
// Calculate matching Spine lerp(lerp(A, B, w2), C, w3) weights
// from Unity's absolute weights A*W1 + B*W2 + C*W3.
MatchWeights ( layerClipInfos [ layer ] , hasNext , isInterruptionActive , clipInfoCount , nextClipInfoCount , interruptingClipInfoCount ,
clipInfo , nextClipInfo , interruptingClipInfo ) ;
float [ ] customWeights = layerClipInfos [ layer ] . clipResolvedWeights ;
for ( int c = 0 ; c < clipInfoCount ; c + + ) {
ApplyAnimation ( skeleton , clipInfo [ c ] , stateInfo , layer , layerWeight , layerBlendMode ,
true , customWeights [ c ] ) ;
}
if ( hasNext ) {
customWeights = layerClipInfos [ layer ] . nextClipResolvedWeights ;
for ( int c = 0 ; c < nextClipInfoCount ; c + + ) {
ApplyAnimation ( skeleton , nextClipInfo [ c ] , nextStateInfo , layer , layerWeight , layerBlendMode ,
true , customWeights [ c ] ) ;
}
}
if ( isInterruptionActive ) {
customWeights = layerClipInfos [ layer ] . interruptingClipResolvedWeights ;
for ( int c = 0 ; c < interruptingClipInfoCount ; c + + ) {
ApplyInterruptionAnimation ( skeleton , interpolateWeightTo1 ,
interruptingClipInfo [ c ] , interruptingStateInfo ,
layer , layerWeight , layerBlendMode , interruptingClipTimeAddition ,
true , customWeights [ c ] ) ;
}
}
2024-10-23 17:55:55 +08:00
} else { // case MixNext || Hard
2024-11-21 09:35:48 +08:00
// Apply first non-zero weighted clip
2024-10-23 17:55:55 +08:00
int c = 0 ;
for ( ; c < clipInfoCount ; c + + ) {
2024-11-21 09:35:48 +08:00
if ( ! ApplyAnimation ( skeleton , clipInfo [ c ] , stateInfo , layer , layerWeight , layerBlendMode ,
true , 1.0f ) )
2024-10-23 17:55:55 +08:00
continue ;
+ + c ; break ;
}
// Mix the rest
for ( ; c < clipInfoCount ; c + + ) {
ApplyAnimation ( skeleton , clipInfo [ c ] , stateInfo , layer , layerWeight , layerBlendMode ) ;
}
c = 0 ;
if ( hasNext ) {
// Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
if ( mode = = MixMode . Hard ) {
for ( ; c < nextClipInfoCount ; c + + ) {
2024-11-21 09:35:48 +08:00
if ( ! ApplyAnimation ( skeleton , nextClipInfo [ c ] , nextStateInfo , layer , layerWeight , layerBlendMode ,
true , 1.0f ) )
2024-10-23 17:55:55 +08:00
continue ;
+ + c ; break ;
}
}
// Mix the rest
for ( ; c < nextClipInfoCount ; c + + ) {
if ( ! ApplyAnimation ( skeleton , nextClipInfo [ c ] , nextStateInfo , layer , layerWeight , layerBlendMode ) )
continue ;
}
}
c = 0 ;
if ( isInterruptionActive ) {
// Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
if ( mode = = MixMode . Hard ) {
for ( ; c < interruptingClipInfoCount ; c + + ) {
if ( ApplyInterruptionAnimation ( skeleton , interpolateWeightTo1 ,
interruptingClipInfo [ c ] , interruptingStateInfo ,
2024-11-21 09:35:48 +08:00
layer , layerWeight , layerBlendMode , interruptingClipTimeAddition , true , 1.0f ) ) {
2024-10-23 17:55:55 +08:00
+ + c ; break ;
}
}
}
// Mix the rest
for ( ; c < interruptingClipInfoCount ; c + + ) {
ApplyInterruptionAnimation ( skeleton , interpolateWeightTo1 ,
interruptingClipInfo [ c ] , interruptingStateInfo ,
layer , layerWeight , layerBlendMode , interruptingClipTimeAddition ) ;
}
}
}
}
}
2024-11-21 09:35:48 +08:00
/// <summary>
/// Resolve matching weights from Unity's absolute weights A*w1 + B*w2 + C*w3 to
/// Spine's lerp(lerp(A, B, x), C, y) weights, in reverse order of clips.
/// </summary>
protected void MatchWeights ( ClipInfos clipInfos , bool hasNext , bool isInterruptionActive ,
int clipInfoCount , int nextClipInfoCount , int interruptingClipInfoCount ,
IList < AnimatorClipInfo > clipInfo , IList < AnimatorClipInfo > nextClipInfo , IList < AnimatorClipInfo > interruptingClipInfo ) {
if ( clipInfos . clipResolvedWeights . Length < clipInfoCount ) {
System . Array . Resize < float > ( ref clipInfos . clipResolvedWeights , clipInfoCount ) ;
}
if ( hasNext & & clipInfos . nextClipResolvedWeights . Length < nextClipInfoCount ) {
System . Array . Resize < float > ( ref clipInfos . nextClipResolvedWeights , nextClipInfoCount ) ;
}
if ( isInterruptionActive & & clipInfos . interruptingClipResolvedWeights . Length < interruptingClipInfoCount ) {
System . Array . Resize < float > ( ref clipInfos . interruptingClipResolvedWeights , interruptingClipInfoCount ) ;
}
float inverseWeight = 1.0f ;
if ( isInterruptionActive ) {
for ( int c = interruptingClipInfoCount - 1 ; c > = 0 ; c - - ) {
float unityWeight = interruptingClipInfo [ c ] . weight ;
clipInfos . interruptingClipResolvedWeights [ c ] = interruptingClipInfo [ c ] . weight * inverseWeight ;
inverseWeight / = ( 1.0f - unityWeight ) ;
}
}
if ( hasNext ) {
for ( int c = nextClipInfoCount - 1 ; c > = 0 ; c - - ) {
float unityWeight = nextClipInfo [ c ] . weight ;
clipInfos . nextClipResolvedWeights [ c ] = nextClipInfo [ c ] . weight * inverseWeight ;
inverseWeight / = ( 1.0f - unityWeight ) ;
}
}
for ( int c = clipInfoCount - 1 ; c > = 0 ; c - - ) {
float unityWeight = clipInfo [ c ] . weight ;
clipInfos . clipResolvedWeights [ c ] = ( c = = 0 ) ? 1f : clipInfo [ c ] . weight * inverseWeight ;
inverseWeight / = ( 1.0f - unityWeight ) ;
}
}
2024-10-23 17:55:55 +08:00
public KeyValuePair < Spine . Animation , float > GetActiveAnimationAndTime ( int layer ) {
if ( layer > = layerClipInfos . Length )
return new KeyValuePair < Spine . Animation , float > ( null , 0 ) ;
2024-11-21 09:35:48 +08:00
ClipInfos layerInfos = layerClipInfos [ layer ] ;
2024-10-23 17:55:55 +08:00
bool isInterruptionActive = layerInfos . isInterruptionActive ;
AnimationClip clip = null ;
Spine . Animation animation = null ;
AnimatorStateInfo stateInfo ;
if ( isInterruptionActive & & layerInfos . interruptingClipInfoCount > 0 ) {
clip = layerInfos . interruptingClipInfos [ 0 ] . clip ;
stateInfo = layerInfos . interruptingStateInfo ;
2024-11-21 09:35:48 +08:00
} else {
2024-10-23 17:55:55 +08:00
clip = layerInfos . clipInfos [ 0 ] . clip ;
stateInfo = layerInfos . stateInfo ;
}
animation = GetAnimation ( clip ) ;
float time = AnimationTime ( stateInfo . normalizedTime , clip . length ,
clip . isLooping , stateInfo . speed < 0 ) ;
return new KeyValuePair < Animation , float > ( animation , time ) ;
}
static float AnimationTime ( float normalizedTime , float clipLength , bool loop , bool reversed ) {
2024-11-21 09:35:48 +08:00
float time = ToSpineAnimationTime ( normalizedTime , clipLength , loop , reversed ) ;
2024-10-23 17:55:55 +08:00
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;
}
2024-11-21 09:35:48 +08:00
static float ToSpineAnimationTime ( float normalizedTime , float clipLength , bool loop , bool reversed ) {
2024-10-23 17:55:55 +08:00
if ( reversed )
2024-11-21 09:35:48 +08:00
normalizedTime = ( 1 - normalizedTime ) ;
if ( normalizedTime < 0.0f )
normalizedTime = loop ? ( normalizedTime % 1.0f ) + 1.0f : 0.0f ;
2024-10-23 17:55:55 +08:00
return normalizedTime * clipLength ;
}
void InitClipInfosForLayers ( ) {
if ( layerClipInfos . Length < animator . layerCount ) {
System . Array . Resize < ClipInfos > ( 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 ( ) ;
}
}
}
private MixMode GetMixMode ( int layer , MixBlend layerBlendMode ) {
if ( useCustomMixMode ) {
MixMode mode = layerMixModes [ layer ] ;
// Note: at additive blending it makes no sense to use constant weight 1 at a fadeout anim add1 as
// with override layers, so we use AlwaysMix instead to use the proper weights.
// AlwaysMix leads to the expected result = lower_layer + lerp(add1, add2, transition_weight).
if ( layerBlendMode = = MixBlend . Add & & mode = = MixMode . MixNext ) {
mode = MixMode . AlwaysMix ;
layerMixModes [ layer ] = mode ;
}
return mode ;
2024-11-21 09:35:48 +08:00
} else {
2024-10-23 17:55:55 +08:00
return layerBlendMode = = MixBlend . Add ? MixMode . AlwaysMix : MixMode . MixNext ;
}
}
#if UNITY_EDITOR
2024-11-21 09:35:48 +08:00
void GetLayerBlendModes ( ) {
2024-10-23 17:55:55 +08:00
if ( layerBlendModes . Length < animator . layerCount ) {
System . Array . Resize < MixBlend > ( ref layerBlendModes , animator . layerCount ) ;
}
for ( int layer = 0 , n = animator . layerCount ; layer < n ; + + layer ) {
2024-11-21 09:35:48 +08:00
AnimatorController controller = animator . runtimeAnimatorController as UnityEditor . Animations . AnimatorController ;
2024-10-23 17:55:55 +08:00
if ( controller ! = null ) {
layerBlendModes [ layer ] = MixBlend . First ;
if ( layer > 0 ) {
layerBlendModes [ layer ] = controller . layers [ layer ] . blendingMode = = UnityEditor . Animations . AnimatorLayerBlendingMode . Additive ?
MixBlend . Add : MixBlend . Replace ;
}
}
}
}
2024-11-21 09:35:48 +08:00
#endif
2024-10-23 17:55:55 +08:00
void GetStateUpdatesFromAnimator ( int layer ) {
2024-11-21 09:35:48 +08:00
ClipInfos layerInfos = layerClipInfos [ layer ] ;
2024-10-23 17:55:55 +08:00
int clipInfoCount = animator . GetCurrentAnimatorClipInfoCount ( layer ) ;
int nextClipInfoCount = animator . GetNextAnimatorClipInfoCount ( layer ) ;
2024-11-21 09:35:48 +08:00
List < AnimatorClipInfo > clipInfos = layerInfos . clipInfos ;
List < AnimatorClipInfo > nextClipInfos = layerInfos . nextClipInfos ;
List < AnimatorClipInfo > interruptingClipInfos = layerInfos . interruptingClipInfos ;
2024-10-23 17:55:55 +08:00
layerInfos . isInterruptionActive = ( clipInfoCount = = 0 & & clipInfos . Count ! = 0 & &
nextClipInfoCount = = 0 & & nextClipInfos . Count ! = 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).
2024-11-21 09:35:48 +08:00
AnimatorStateInfo interruptingStateInfo = animator . GetNextAnimatorStateInfo ( layer ) ;
2024-10-23 17:55:55 +08:00
layerInfos . isLastFrameOfInterruption = interruptingStateInfo . fullPathHash = = 0 ;
if ( ! layerInfos . isLastFrameOfInterruption ) {
animator . GetNextAnimatorClipInfo ( layer , interruptingClipInfos ) ;
layerInfos . interruptingClipInfoCount = interruptingClipInfos . Count ;
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 < AnimatorClipInfo > clipInfo ,
out IList < AnimatorClipInfo > nextClipInfo ,
out IList < AnimatorClipInfo > interruptingClipInfo ,
out bool shallInterpolateWeightTo1 ) {
2024-11-21 09:35:48 +08:00
ClipInfos layerInfos = layerClipInfos [ layer ] ;
2024-10-23 17:55:55 +08:00
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 ) {
2024-11-21 09:35:48 +08:00
ClipInfos layerInfos = layerClipInfos [ layer ] ;
2024-10-23 17:55:55 +08:00
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 < AnimationClip > {
internal static readonly IEqualityComparer < AnimationClip > 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 < int > {
internal static readonly IEqualityComparer < int > Instance = new IntEqualityComparer ( ) ;
public bool Equals ( int x , int y ) { return x = = y ; }
2024-11-21 09:35:48 +08:00
public int GetHashCode ( int o ) { return o ; }
2024-10-23 17:55:55 +08:00
}
}
}
}