Parcourir la source

[unity] Added multiple features to Timeline SpineAnimationStateClip such as playback speed, blending by overlap and synchronisation of MixDuration with blend duration. Adds preferences parameter "Use Blend Duration" which can be disabled to enable old behaviour. Fixes #1262.

Harald Csaszar il y a 6 ans
Parent
commit
f0b25c8a86

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs

@@ -138,6 +138,9 @@ namespace Spine.Unity.Editor {
 			const string MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY = "SPINE_MECANIM_EVENT_INCLUDE_FOLDERNAME";
 			public static bool mecanimEventIncludeFolderName = SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME;
 
+			const string TIMELINE_USE_BLEND_DURATION_KEY = "SPINE_TIMELINE_USE_BLEND_DURATION_KEY";
+			public static bool timelineUseBlendDuration = SpinePreferences.DEFAULT_TIMELINE_USE_BLEND_DURATION;
+
 			static bool preferencesLoaded = false;
 
 			public static void Load () {
@@ -154,6 +157,7 @@ namespace Spine.Unity.Editor {
 				mecanimEventIncludeFolderName = EditorPrefs.GetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME);
 				atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING);
 				textureImporterWarning = EditorPrefs.GetBool(TEXTUREIMPORTER_WARNING_KEY, SpinePreferences.DEFAULT_TEXTUREIMPORTER_WARNING);
+				timelineUseBlendDuration = EditorPrefs.GetBool(TIMELINE_USE_BLEND_DURATION_KEY, SpinePreferences.DEFAULT_TIMELINE_USE_BLEND_DURATION);
 
 				SpineHandles.handleScale = EditorPrefs.GetFloat(SCENE_ICONS_SCALE_KEY, DEFAULT_SCENE_ICONS_SCALE);
 				preferencesLoaded = true;
@@ -171,6 +175,7 @@ namespace Spine.Unity.Editor {
 				newPreferences.mecanimEventIncludeFolderName = EditorPrefs.GetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME);
 				newPreferences.atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING);
 				newPreferences.textureImporterWarning = EditorPrefs.GetBool(TEXTUREIMPORTER_WARNING_KEY, SpinePreferences.DEFAULT_TEXTUREIMPORTER_WARNING);
+				newPreferences.timelineUseBlendDuration = EditorPrefs.GetBool(TIMELINE_USE_BLEND_DURATION_KEY, SpinePreferences.DEFAULT_TIMELINE_USE_BLEND_DURATION);
 			}
 
 			public static void SaveToEditorPrefs(SpinePreferences preferences) {
@@ -184,6 +189,7 @@ namespace Spine.Unity.Editor {
 				EditorPrefs.SetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, preferences.mecanimEventIncludeFolderName);
 				EditorPrefs.SetBool(ATLASTXT_WARNING_KEY, preferences.atlasTxtImportWarning);
 				EditorPrefs.SetBool(TEXTUREIMPORTER_WARNING_KEY, preferences.textureImporterWarning);
+				EditorPrefs.SetBool(TIMELINE_USE_BLEND_DURATION_KEY, preferences.timelineUseBlendDuration);
 			}
 #endif
 
@@ -265,6 +271,12 @@ namespace Spine.Unity.Editor {
 					if (GUILayout.Button("Disable", GUILayout.Width(64)))
 						SpineTK2DEditorUtility.DisableTK2D();
 				}
+
+				GUILayout.Space(20);
+				EditorGUILayout.LabelField("Timeline Extension", EditorStyles.boldLabel);
+				{
+					SpineEditorUtilities.BoolPrefsField(ref timelineUseBlendDuration, TIMELINE_USE_BLEND_DURATION_KEY, new GUIContent("Use Blend Duration", "When enabled, MixDuration will be synced with timeline clip transition duration 'Ease In Duration'."));
+				}
 			}
 		#endif // !NEW_PREFERENCES_SETTINGS_PROVIDER
 		}

+ 10 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs

@@ -92,6 +92,10 @@ namespace Spine.Unity.Editor {
 		public const bool DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME = true;
 		public bool mecanimEventIncludeFolderName = DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME;
 
+		// Timeline extension module
+		public const bool DEFAULT_TIMELINE_USE_BLEND_DURATION = true;
+		public bool timelineUseBlendDuration = DEFAULT_TIMELINE_USE_BLEND_DURATION;
+
 #if NEW_PREFERENCES_SETTINGS_PROVIDER
 		public static void Load () {
 			SpineHandles.handleScale = EditorPrefs.GetFloat(SCENE_ICONS_SCALE_KEY, DEFAULT_SCENE_ICONS_SCALE);
@@ -185,6 +189,12 @@ namespace Spine.Unity.Editor {
 					if (GUILayout.Button("Disable", GUILayout.Width(64)))
 						SpineEditorUtilities.SpineTK2DEditorUtility.DisableTK2D();
 				}
+
+				GUILayout.Space(20);
+				EditorGUILayout.LabelField("Timeline Extension", EditorStyles.boldLabel);
+				{
+					EditorGUILayout.PropertyField(settings.FindProperty("timelineUseBlendDuration"), new GUIContent("Use Blend Duration", "When enabled, MixDuration will be synced with timeline clip transition duration 'Ease In Duration'."));
+				}
 			}
 			EditorGUIUtility.labelWidth = prevLabelWidth;
 		}

+ 116 - 0
spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs

@@ -0,0 +1,116 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, 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.
+ *
+ * THIS SOFTWARE IS 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 THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEditor;
+using Spine.Unity.Playables;
+using UnityEngine.Timeline;
+
+namespace Spine.Unity.Editor {
+
+	[CustomEditor(typeof(SpineAnimationStateClip))]
+	[CanEditMultipleObjects]
+	public class SpineAnimationStateClipInspector : UnityEditor.Editor {
+
+		protected SerializedProperty templateProp = null;
+
+		protected class ClipInfo {
+			public TimelineClip timelineClip;
+			public float previousBlendInDuration = -1.0f;
+			public float unblendedMixDuration = 0.2f;
+		}
+
+		protected ClipInfo[] clipInfo = null;
+		
+		public void OnEnable () {
+			templateProp = serializedObject.FindProperty("template");
+			System.Array.Resize(ref clipInfo, targets.Length);
+			for (int i = 0; i < targets.Length; ++i) {
+				var clip = (SpineAnimationStateClip)targets[i];
+				clipInfo[i] = new ClipInfo();
+				clipInfo[i].timelineClip = FindTimelineClip(clip);
+			}
+		}
+
+		public override void OnInspectorGUI () {
+			serializedObject.Update();
+			EditorGUILayout.PropertyField(templateProp);
+
+			for (int i = 0; i < targets.Length; ++i) {
+				var targetClip = (SpineAnimationStateClip)targets[i];
+				if (targetClip.template.useBlendDuration)
+					AdjustMixDuration(targetClip, clipInfo[i]);
+			}
+			
+			serializedObject.ApplyModifiedProperties();
+		}
+
+		protected void AdjustMixDuration(SpineAnimationStateClip targetClip, ClipInfo timelineClipInfo) {
+
+			if (timelineClipInfo == null)
+				return;
+
+			var timelineClip = timelineClipInfo.timelineClip;
+			if (timelineClip == null)
+				return;
+
+			float blendInDur = (float)timelineClip.blendInDuration;
+			bool isBlendingNow = blendInDur > 0;
+			bool wasBlendingBefore = timelineClipInfo.previousBlendInDuration > 0;
+
+			if (isBlendingNow) {
+				if (!wasBlendingBefore) {
+					timelineClipInfo.unblendedMixDuration = targetClip.template.mixDuration;
+				}
+				targetClip.template.mixDuration = blendInDur;
+				EditorUtility.SetDirty(targetClip);
+			}
+			else if (wasBlendingBefore) {
+				targetClip.template.mixDuration = timelineClipInfo.unblendedMixDuration;
+				EditorUtility.SetDirty(targetClip);
+			}
+			timelineClipInfo.previousBlendInDuration = blendInDur;
+		}
+
+		protected TimelineClip FindTimelineClip(SpineAnimationStateClip targetClip) {
+			string[] guids = AssetDatabase.FindAssets("t:TimelineAsset");
+			foreach (string guid in guids) {
+				TimelineAsset timeline = (TimelineAsset)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid), typeof(TimelineAsset));
+				foreach (var track in timeline.GetOutputTracks()) {
+					foreach (var clip in track.GetClips()) {
+						if (clip.asset.GetType() == typeof(SpineAnimationStateClip) && object.ReferenceEquals(clip.asset, targetClip)) {
+							return clip;
+						}
+					}
+				}
+			}
+			return null;
+		}
+
+	}
+}

+ 12 - 0
spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 95642d062fbcfda4e8d9262fa715b098
+timeCreated: 1570044805
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 29 - 8
spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateDrawer.cs

@@ -32,30 +32,39 @@ using UnityEngine;
 using Spine;
 using Spine.Unity;
 using Spine.Unity.Playables;
+using Spine.Unity.Editor;
 
-//[CustomPropertyDrawer(typeof(SpineAnimationStateBehaviour))]
+[CustomPropertyDrawer(typeof(SpineAnimationStateBehaviour))]
 public class SpineAnimationStateDrawer : PropertyDrawer {
-	/*
+	
 	public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
 		const int fieldCount = 8;
 		return fieldCount * EditorGUIUtility.singleLineHeight;
 	}
 
 	public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
-		SerializedProperty skeletonDataAssetProp = property.FindPropertyRelative("skeletonDataAsset");
-		SerializedProperty animationNameProp = property.FindPropertyRelative("animationName");
+		SerializedProperty animationReferenceProp = property.FindPropertyRelative("animationReference");
 		SerializedProperty loopProp = property.FindPropertyRelative("loop");
+
+		SerializedProperty customDurationProp = property.FindPropertyRelative("customDuration");
+		SerializedProperty useBlendDurationProp = property.FindPropertyRelative("useBlendDuration");
+		SerializedProperty mixDurationProp = property.FindPropertyRelative("mixDuration");
 		SerializedProperty eventProp = property.FindPropertyRelative("eventThreshold");
 		SerializedProperty attachmentProp = property.FindPropertyRelative("attachmentThreshold");
 		SerializedProperty drawOrderProp = property.FindPropertyRelative("drawOrderThreshold");
 
+		// initialize useBlendDuration parameter according to preferences
+		SerializedProperty isInitializedProp = property.FindPropertyRelative("isInitialized");
+		if (!isInitializedProp.hasMultipleDifferentValues && isInitializedProp.boolValue == false) {
+			useBlendDurationProp.boolValue = SpineEditorUtilities.Preferences.timelineUseBlendDuration;
+			isInitializedProp.boolValue = true;
+		}
+		
 		Rect singleFieldRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
-		EditorGUI.PropertyField(singleFieldRect, skeletonDataAssetProp);
 
 		float lineHeightWithSpacing = EditorGUIUtility.singleLineHeight + 2f;
 
-		singleFieldRect.y += lineHeightWithSpacing;
-		EditorGUI.PropertyField(singleFieldRect, animationNameProp);
+		EditorGUI.PropertyField(singleFieldRect, animationReferenceProp);
 
 		singleFieldRect.y += lineHeightWithSpacing;
 		EditorGUI.PropertyField(singleFieldRect, loopProp);
@@ -65,6 +74,19 @@ public class SpineAnimationStateDrawer : PropertyDrawer {
 		singleFieldRect.y += lineHeightWithSpacing;
 		EditorGUI.LabelField(singleFieldRect, "Mixing Settings", EditorStyles.boldLabel);
 
+		singleFieldRect.y += lineHeightWithSpacing;
+		EditorGUI.PropertyField(singleFieldRect, customDurationProp);
+
+		bool greyOutCustomDurations = (!customDurationProp.hasMultipleDifferentValues &&
+										customDurationProp.boolValue == false);
+		using (new EditorGUI.DisabledGroupScope(greyOutCustomDurations)) {
+			singleFieldRect.y += lineHeightWithSpacing;
+			EditorGUI.PropertyField(singleFieldRect, useBlendDurationProp);
+
+			singleFieldRect.y += lineHeightWithSpacing;
+			EditorGUI.PropertyField(singleFieldRect, mixDurationProp);
+		}
+
 		singleFieldRect.y += lineHeightWithSpacing;
 		EditorGUI.PropertyField(singleFieldRect, eventProp);
 
@@ -74,5 +96,4 @@ public class SpineAnimationStateDrawer : PropertyDrawer {
 		singleFieldRect.y += lineHeightWithSpacing;
 		EditorGUI.PropertyField(singleFieldRect, drawOrderProp);
 	}
-	*/
 }

+ 51 - 0
spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs

@@ -0,0 +1,51 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, 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.
+ *
+ * THIS SOFTWARE IS 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 THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEditor;
+using Spine.Unity.Playables;
+
+namespace Spine.Unity.Editor {
+
+	[CustomEditor(typeof(SpineSkeletonFlipClip))]
+	[CanEditMultipleObjects]
+	public class SpineSkeletonFlipClipInspector : UnityEditor.Editor {
+
+		protected SerializedProperty templateProp = null;
+
+		public void OnEnable () {
+			templateProp = serializedObject.FindProperty("template");
+		}
+
+		public override void OnInspectorGUI () {
+			serializedObject.Update();
+			EditorGUILayout.PropertyField(templateProp);
+			serializedObject.ApplyModifiedProperties();
+		}
+	}
+}

+ 12 - 0
spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: c6d7cdfbf1ccc0042b92586542f085a4
+timeCreated: 1570045635
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 4 - 1
spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateBehaviour.cs

@@ -44,8 +44,11 @@ namespace Spine.Unity.Playables {
 		public AnimationReferenceAsset animationReference;
 		public bool loop;
 
-		[Header("Mix Properties")]
+		// Mix Properties
 		public bool customDuration = false;
+		public bool useBlendDuration = true;
+		[SerializeField]
+		private bool isInitialized = false; // required to read preferences values from editor side.
 		public float mixDuration = 0.1f;
 
 		[Range(0, 1f)]

+ 9 - 1
spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateClip.cs

@@ -37,13 +37,21 @@ namespace Spine.Unity.Playables {
 	public class SpineAnimationStateClip : PlayableAsset, ITimelineClipAsset {
 		public SpineAnimationStateBehaviour template = new SpineAnimationStateBehaviour();
 
-		public ClipCaps clipCaps { get { return ClipCaps.None; } }
+		public ClipCaps clipCaps { get { return ClipCaps.Blending | ClipCaps.ClipIn | ClipCaps.SpeedMultiplier | (template.loop ? ClipCaps.Looping : 0); } }
 
 		public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
 			var playable = ScriptPlayable<SpineAnimationStateBehaviour>.Create(graph, template);
 			playable.GetBehaviour();
 			return playable;
 		}
+
+		public override double duration {
+			get {
+				if (template.animationReference == null)
+					return 0;
+				return template.animationReference.Animation.Duration;
+			}
+		}
 	}
 
 }

+ 11 - 10
spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs

@@ -70,7 +70,7 @@ namespace Spine.Unity.Playables {
 			for (int i = 0; i < inputCount; i++) {
 				float lastInputWeight = lastInputWeights[i];
 				float inputWeight = playable.GetInputWeight(i);
-				bool trackStarted = inputWeight > lastInputWeight;
+				bool trackStarted = lastInputWeight == 0 && inputWeight > 0;
 				lastInputWeights[i] = inputWeight;
 
 				if (trackStarted) {
@@ -84,9 +84,10 @@ namespace Spine.Unity.Playables {
 						if (clipData.animationReference.Animation != null) {
 							Spine.TrackEntry trackEntry = state.SetAnimation(trackIndex, clipData.animationReference.Animation, clipData.loop);
 
-							//trackEntry.TrackTime = (float)inputPlayable.GetTime(); // More accurate time-start?
 							trackEntry.EventThreshold = clipData.eventThreshold;
 							trackEntry.DrawOrderThreshold = clipData.drawOrderThreshold;
+							trackEntry.TrackTime = (float)inputPlayable.GetTime() * (float)inputPlayable.GetSpeed();
+							trackEntry.TimeScale = (float)inputPlayable.GetSpeed();
 							trackEntry.AttachmentThreshold = clipData.attachmentThreshold;
 
 							if (clipData.customDuration)
@@ -111,15 +112,15 @@ namespace Spine.Unity.Playables {
 			if (spineComponent == null) return;
 
 			int inputCount = playable.GetInputCount();
-			int lastOneWeight = -1;
+			int lastNonZeroWeightTrack = -1;
 
 			for (int i = 0; i < inputCount; i++) {
 				float inputWeight = playable.GetInputWeight(i);
-				if (inputWeight >= 1) lastOneWeight = i;
+				if (inputWeight > 0) lastNonZeroWeightTrack = i;
 			}
 
-			if (lastOneWeight != -1) {
-				ScriptPlayable<SpineAnimationStateBehaviour> inputPlayableClip = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(lastOneWeight);
+			if (lastNonZeroWeightTrack != -1) {
+				ScriptPlayable<SpineAnimationStateBehaviour> inputPlayableClip = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(lastNonZeroWeightTrack);
 				SpineAnimationStateBehaviour clipData = inputPlayableClip.GetBehaviour();
 
 				var skeleton = spineComponent.Skeleton;
@@ -133,16 +134,16 @@ namespace Spine.Unity.Playables {
 				Animation fromAnimation = null;
 				float fromClipTime = 0;
 				bool fromClipLoop = false;
-				if (lastOneWeight != 0 && inputCount > 1) {
-					var fromClip = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(lastOneWeight - 1);
+				if (lastNonZeroWeightTrack != 0 && inputCount > 1) {
+					var fromClip = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(lastNonZeroWeightTrack - 1);
 					var fromClipData = fromClip.GetBehaviour();
 					fromAnimation = fromClipData.animationReference != null ? fromClipData.animationReference.Animation : null;
-					fromClipTime = (float)fromClip.GetTime();
+					fromClipTime = (float)fromClip.GetTime() * (float)fromClip.GetSpeed();
 					fromClipLoop = fromClipData.loop;
 				}
 
 				Animation toAnimation = clipData.animationReference != null ? clipData.animationReference.Animation : null;
-				float toClipTime = (float)inputPlayableClip.GetTime();
+				float toClipTime = (float)inputPlayableClip.GetTime() * (float)inputPlayableClip.GetSpeed();
 				float mixDuration = clipData.mixDuration;
 
 				if (!clipData.customDuration && fromAnimation != null && toAnimation != null) {