소스 검색

Merge branch '3.6' into 3.7-beta

badlogic 7 년 전
부모
커밋
74d71691fb
52개의 변경된 파일1717개의 추가작업 그리고 265개의 파일을 삭제
  1. BIN
      spine-starling/spine-starling-example/lib/spine-starling.swc
  2. 6 4
      spine-unity/Assets/spine-unity/Asset Types/AnimationReferenceAsset.cs
  3. 15 19
      spine-unity/Assets/spine-unity/Components/SkeletonAnimator.cs
  4. 9 2
      spine-unity/Assets/spine-unity/Editor/AnimationReferenceAssetEditor.cs
  5. 17 23
      spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs
  6. 179 175
      spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs
  7. 10 3
      spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs
  8. 14 6
      spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs
  9. 1 1
      spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs
  10. 5 0
      spine-unity/Assets/spine-unity/Modules/Shaders/Sprite/Editor/SpineSpriteShaderGUI.cs
  11. 5 7
      spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs
  12. 1 1
      spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs
  13. 0 24
      spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs
  14. 9 0
      spine-unity/Assets/spine-unity/Modules/Timeline.meta
  15. 10 0
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation.meta
  16. 80 0
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/README.md
  17. 9 0
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/README.md.meta
  18. BIN
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/add-menu.png
  19. 112 0
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/add-menu.png.meta
  20. BIN
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png
  21. 112 0
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png.meta
  22. BIN
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/skeleton-flip-clip-inspector.png
  23. 112 0
      spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/skeleton-flip-clip-inspector.png.meta
  24. 9 0
      spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component.meta
  25. 114 0
      spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SkeletonAnimationPlayableHandle.cs
  26. 12 0
      spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SkeletonAnimationPlayableHandle.cs.meta
  27. 64 0
      spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SpinePlayableHandleBase.cs
  28. 12 0
      spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SpinePlayableHandleBase.cs.meta
  29. 10 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState.meta
  30. 10 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/Editor.meta
  31. 49 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/Editor/SpineAnimationStateDrawer.cs
  32. 13 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/Editor/SpineAnimationStateDrawer.cs.meta
  33. 64 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateBehaviour.cs
  34. 13 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateBehaviour.cs.meta
  35. 52 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateClip.cs
  36. 13 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateClip.cs.meta
  37. 168 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs
  38. 13 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs.meta
  39. 46 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateTrack.cs
  40. 13 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateTrack.cs.meta
  41. 9 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip.meta
  42. 9 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/Editor.meta
  43. 27 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/Editor/SpineSkeletonFlipDrawer.cs
  44. 12 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/Editor/SpineSkeletonFlipDrawer.cs.meta
  45. 11 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipBehaviour.cs
  46. 12 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipBehaviour.cs.meta
  47. 50 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipClip.cs
  48. 12 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipClip.cs.meta
  49. 102 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipMixerBehaviour.cs
  50. 12 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipMixerBehaviour.cs.meta
  51. 68 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipTrack.cs
  52. 12 0
      spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipTrack.cs.meta

BIN
spine-starling/spine-starling-example/lib/spine-starling.swc


+ 6 - 4
spine-unity/Assets/spine-unity/Asset Types/AnimationReferenceAsset.cs

@@ -34,13 +34,15 @@ using UnityEngine;
 
 namespace Spine.Unity {
 	[CreateAssetMenu(menuName = "Spine/Animation Reference Asset")]
-	public class AnimationReferenceAsset : ScriptableObject {
+	public class AnimationReferenceAsset : ScriptableObject, IHasSkeletonDataAsset {
 		const bool QuietSkeletonData = true;
 
 		[SerializeField] protected SkeletonDataAsset skeletonDataAsset;
-		[SerializeField, SpineAnimation(dataField: "skeletonDataAsset")] protected string animationName;
-
+		[SerializeField, SpineAnimation] protected string animationName;
 		private Animation animation;
+
+		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } }
+
 		public Animation Animation {
 			get {
 				#if AUTOINIT_SPINEREFERENCE
@@ -51,7 +53,7 @@ namespace Spine.Unity {
 				return animation;
 			}
 		}
-
+		
 		public void Initialize () {
 			if (skeletonDataAsset == null) return;
 			this.animation = skeletonDataAsset.GetSkeletonData(AnimationReferenceAsset.QuietSkeletonData).FindAnimation(animationName);

+ 15 - 19
spine-unity/Assets/spine-unity/Components/SkeletonAnimator.cs

@@ -98,7 +98,7 @@ namespace Spine.Unity {
 				}
 
 				if (_UpdateComplete != null)
-					_UpdateComplete(this);	
+					_UpdateComplete(this);
 			}
 		}
 
@@ -163,14 +163,14 @@ namespace Spine.Unity {
 						for (int c = 0; c < clipInfoCount; c++) {
 							var info = clipInfo[c];
 							float weight = info.weight * layerWeight; if (weight == 0) continue;
-							previousAnimations.Add(animationTable[NameHashCode(info.clip)]);
+							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(animationTable[NameHashCode(info.clip)]);
+								previousAnimations.Add(GetAnimation(info.clip));
 							}
 						}
 					}
@@ -193,12 +193,12 @@ namespace Spine.Unity {
 						// 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;
-							animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixBlend.Replace, MixDirection.In);
+							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;
-								animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length,nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixBlend.Replace, MixDirection.In);
+								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
@@ -206,13 +206,13 @@ namespace Spine.Unity {
 						int c = 0;
 						for (; c < clipInfoCount; c++) {
 							var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
-							animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, 1f, MixBlend.Replace, MixDirection.In);
+							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;
-							animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixBlend.Replace, MixDirection.In);
+							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;
@@ -221,14 +221,14 @@ namespace Spine.Unity {
 							if (mode == MixMode.SpineStyle) {
 								for (; c < nextClipInfoCount; c++) {
 									var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
-									animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length,nextStateInfo.speed < 0), nextStateInfo.loop, null, 1f, MixBlend.Replace, MixDirection.In);
+									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;
-								animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length,nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixBlend.Replace, MixDirection.In);
+								GetAnimation(info.clip).Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixPose.Current, MixDirection.In);
 							}
 						}
 					}
@@ -268,25 +268,21 @@ namespace Spine.Unity {
 				nextClipInfo = nextClipInfoCache;
 			}
 
-			int NameHashCode (AnimationClip clip) {
+			Spine.Animation GetAnimation (AnimationClip clip) {
 				int clipNameHashCode;
 				if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
 					clipNameHashCode = clip.name.GetHashCode();
 					clipNameHashCodeTable.Add(clip, clipNameHashCode);
 				}
-				return 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();
-				}
+				public bool Equals (AnimationClip x, AnimationClip y) { return x.GetInstanceID() == y.GetInstanceID(); }
+				public int GetHashCode (AnimationClip o) { return o.GetInstanceID(); }
 			}
 
 			class IntEqualityComparer : IEqualityComparer<int> {

+ 9 - 2
spine-unity/Assets/spine-unity/Editor/AnimationReferenceAssetEditor.cs

@@ -63,8 +63,15 @@ namespace Spine.Unity.Editor {
 		public override void OnInspectorGUI () {
 			animationNameProperty = animationNameProperty ?? serializedObject.FindProperty("animationName");
 			string animationName = animationNameProperty.stringValue;
-			Animation animation = ThisSkeletonDataAsset.GetSkeletonData(true).FindAnimation(animationName);
-			bool animationNotFound = animation == null;
+
+			Animation animation = null;
+			if (ThisSkeletonDataAsset != null) {
+				var skeletonData = ThisSkeletonDataAsset.GetSkeletonData(true);
+				if (skeletonData != null) {
+					animation = skeletonData.FindAnimation(animationName);
+				}
+			} 
+			bool animationNotFound = (animation == null);
 
 			if (changeNextFrame) {
 				changeNextFrame = false;

+ 17 - 23
spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs

@@ -73,15 +73,14 @@ namespace Spine.Unity.Editor {
 		#region SkeletonAnimator's Mecanim Clips
 		#if SPINE_SKELETON_ANIMATOR
 		public static void GenerateMecanimAnimationClips (SkeletonDataAsset skeletonDataAsset) {
-			//skeletonDataAsset.Clear();
 			var data = skeletonDataAsset.GetSkeletonData(true);
 			if (data == null) {
-				Debug.LogError("SkeletonData failed!", skeletonDataAsset);
+				Debug.LogError("SkeletonData loading failed!", skeletonDataAsset);
 				return;
 			}
 
 			string dataPath = AssetDatabase.GetAssetPath(skeletonDataAsset);
-			string controllerPath = dataPath.Replace("_SkeletonData", "_Controller").Replace(".asset", ".controller");
+			string controllerPath = dataPath.Replace(SpineEditorUtilities.SkeletonDataSuffix, "_Controller").Replace(".asset", ".controller");
 			UnityEditor.Animations.AnimatorController controller;
 			if (skeletonDataAsset.controller != null) {
 				controller = (UnityEditor.Animations.AnimatorController)skeletonDataAsset.controller;
@@ -123,40 +122,35 @@ namespace Spine.Unity.Editor {
 				}
 			}
 
-			foreach (var anim in data.Animations) {
-				string name = anim.Name;
-				spineAnimationTable.Add(name, anim);
+			foreach (var animations in data.Animations) {
+				string animationName = animations.Name; // Review for unsafe names. Requires runtime implementation too.
+				spineAnimationTable.Add(animationName, animations);
 
-				if (unityAnimationClipTable.ContainsKey(name) == false) {
-					//generate new dummy clip
-					AnimationClip newClip = new AnimationClip();
-					newClip.name = name;
+				if (unityAnimationClipTable.ContainsKey(animationName) == false) {
+					AnimationClip newClip = new AnimationClip {
+						name = animationName
+					};
+					//AssetDatabase.CreateAsset(newClip, Path.GetDirectoryName(dataPath) + "/" + animationName + ".asset");
 					AssetDatabase.AddObjectToAsset(newClip, controller);
-					unityAnimationClipTable.Add(name, newClip);
+					unityAnimationClipTable.Add(animationName, newClip);
 				}
 
-				AnimationClip clip = unityAnimationClipTable[name];
-
-				clip.SetCurve("", typeof(GameObject), "dummy", AnimationCurve.Linear(0, 0, anim.Duration, 0));
+				AnimationClip clip = unityAnimationClipTable[animationName];
+				clip.SetCurve("", typeof(GameObject), "dummy", AnimationCurve.Linear(0, 0, animations.Duration, 0));
 				var settings = AnimationUtility.GetAnimationClipSettings(clip);
-				settings.stopTime = anim.Duration;
-
+				settings.stopTime = animations.Duration;
 				SetAnimationSettings(clip, settings);
 
 				AnimationUtility.SetAnimationEvents(clip, new AnimationEvent[0]);
-
-				foreach (Timeline t in anim.Timelines) {
-					if (t is EventTimeline) {
+				foreach (Timeline t in animations.Timelines) {
+					if (t is EventTimeline)
 						ParseEventTimeline((EventTimeline)t, clip, SendMessageOptions.DontRequireReceiver);
-					}
 				}
 
 				EditorUtility.SetDirty(clip);
-
-				unityAnimationClipTable.Remove(name);
+				unityAnimationClipTable.Remove(animationName);
 			}
 
-			//clear no longer used animations
 			foreach (var clip in unityAnimationClipTable.Values) {
 				AnimationClip.DestroyImmediate(clip, true);
 			}

+ 179 - 175
spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs

@@ -36,7 +36,6 @@ using System.Collections.Generic;
 using UnityEditor;
 using UnityEngine;
 
-
 using Spine;
 
 namespace Spine.Unity.Editor {
@@ -80,6 +79,12 @@ namespace Spine.Unity.Editor {
 
 		void OnDestroy () {
 			HandleOnDestroyPreview();
+			AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
+			EditorApplication.update -= preview.HandleEditorUpdate;
+		}
+
+		private void OnDomainUnload (object sender, EventArgs e) {
+			OnDestroy();
 		}
 
 		void InitializeEditor () {
@@ -105,7 +110,11 @@ namespace Spine.Unity.Editor {
 			#else
 			// Analysis disable once ConvertIfToOrExpression
 			if (newAtlasAssets) atlasAssets.isExpanded = true;
-			#endif
+#endif
+
+			// This handles the case where the managed editor assembly is unloaded before recompilation when code changes.
+			AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
+			AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
 
 			EditorApplication.update -= preview.HandleEditorUpdate;
 			EditorApplication.update += preview.HandleEditorUpdate;
@@ -678,23 +687,43 @@ namespace Spine.Unity.Editor {
 			set { if (IsValid) skeletonAnimation.timeScale = value; }
 		}
 
-		public bool IsPlayingAnimation {
-			get {
+		public bool IsPlayingAnimation { get {
 				if (!IsValid) return false;
 				var currentTrack = skeletonAnimation.AnimationState.GetCurrent(0);
 				return currentTrack != null && currentTrack.TimeScale > 0;
 			}
 		}
 
-		public TrackEntry ActiveTrack {
-			get { return IsValid ? skeletonAnimation.AnimationState.GetCurrent(0) : null; }
-		}
+		public TrackEntry ActiveTrack { get { return IsValid ? skeletonAnimation.AnimationState.GetCurrent(0) : null; } }
 
 		public Vector3 PreviewCameraPosition {
 			get { return PreviewUtilityCamera.transform.position; }
 			set { PreviewUtilityCamera.transform.position = value; }
 		}
 
+		public void HandleDrawSettings () {
+			const float SliderWidth = 150;
+			const float SliderSnap = 0.25f;
+			const float SliderMin = 0f;
+			const float SliderMax = 2f;
+
+			if (IsValid) {
+				float timeScale = GUILayout.HorizontalSlider(TimeScale, SliderMin, SliderMax, GUILayout.MaxWidth(SliderWidth));
+				timeScale = Mathf.RoundToInt(timeScale / SliderSnap) * SliderSnap;
+				TimeScale = timeScale;
+			}
+		}
+
+		public void HandleEditorUpdate () {
+			AdjustCamera();
+			if (IsPlayingAnimation) {
+				RefreshOnNextUpdate();
+				Repaint();
+			} else if (requiresRefresh) {
+				Repaint();
+			}
+		}
+
 		public void Initialize (Action repaintCallback, SkeletonDataAsset skeletonDataAsset, string skinName = "") {
 			if (skeletonDataAsset == null) return;
 			if (skeletonDataAsset.GetSkeletonData(false) == null) {
@@ -715,7 +744,7 @@ namespace Spine.Unity.Editor {
 				previewRenderUtility = new PreviewRenderUtility(true);
 				animationLastTime = Time.realtimeSinceStartup;
 
-				const int PreviewLayer = 31;
+				const int PreviewLayer = 30;
 				const int PreviewCameraCullingMask = 1 << PreviewLayer;
 
 				{
@@ -743,49 +772,136 @@ namespace Spine.Unity.Editor {
 							previewGameObject.GetComponent<Renderer>().enabled = false;
 						}
 
-						AdjustCameraGoals(true);
+						if (this.ActiveTrack != null) cameraAdjustEndFrame = EditorApplication.timeSinceStartup + skeletonAnimation.AnimationState.GetCurrent(0).Alpha;
+						AdjustCameraGoals();
 					} catch {
 						DestroyPreviewGameObject();
 					}
 
+					RefreshOnNextUpdate();
 				}
 			}
 		}
 
-		public void HandleDrawSettings () {
-			const float SliderWidth = 150;
-			const float SliderSnap = 0.25f;
-			const float SliderMin = 0f;
-			const float SliderMax = 2f;
-
-			if (IsValid) {
-				float timeScale = GUILayout.HorizontalSlider(TimeScale, SliderMin, SliderMax, GUILayout.MaxWidth(SliderWidth));
-				timeScale = Mathf.RoundToInt(timeScale / SliderSnap) * SliderSnap;
-				TimeScale = timeScale;
+		public void HandleInteractivePreviewGUI (Rect r, GUIStyle background) {
+			if (Event.current.type == EventType.Repaint) {
+				if (requiresRefresh) {
+					previewRenderUtility.BeginPreview(r, background);
+					DoRenderPreview(true);
+					previewTexture = previewRenderUtility.EndPreview();
+					requiresRefresh = false;
+				}
+				if (previewTexture != null)
+					GUI.DrawTexture(r, previewTexture, ScaleMode.StretchToFill, false);
 			}
+
+			DrawSkinToolbar(r);
+			//DrawSetupPoseButton(r);
+			DrawTimeBar(r);
+			HandleMouseScroll(r);
 		}
 
-		public void OnDestroy () {
-			DisposePreviewRenderUtility();
-			DestroyPreviewGameObject();
+		public Texture2D GetStaticPreview (int width, int height) {
+			var c = this.PreviewUtilityCamera;
+			if (c == null)
+				return null;
+
+			RefreshOnNextUpdate();
+			AdjustCameraGoals();
+			c.orthographicSize = cameraOrthoGoal / 2;
+			c.transform.position = cameraPositionGoal;
+			previewRenderUtility.BeginStaticPreview(new Rect(0, 0, width, height));
+			DoRenderPreview(false);
+			var tex = previewRenderUtility.EndStaticPreview();
+			return tex;
 		}
 
-		public void Clear () {
-			DisposePreviewRenderUtility();
-			DestroyPreviewGameObject();
+		public void DoRenderPreview (bool drawHandles) {
+			if (this.PreviewUtilityCamera.activeTexture == null || this.PreviewUtilityCamera.targetTexture == null)
+				return;
+
+			GameObject go = previewGameObject;
+			if (requiresRefresh && go != null) {
+				var renderer = go.GetComponent<Renderer>();
+				renderer.enabled = true;
+
+				if (!EditorApplication.isPlaying) {
+					skeletonAnimation.Update((Time.realtimeSinceStartup - animationLastTime));
+					skeletonAnimation.LateUpdate();
+					animationLastTime = Time.realtimeSinceStartup;
+				}
+				
+				var thisPreviewUtilityCamera = this.PreviewUtilityCamera;
+
+				if (drawHandles) {
+					Handles.SetCamera(thisPreviewUtilityCamera);
+					Handles.color = OriginColor;
+
+					// Draw Cross
+					float scale = skeletonDataAsset.scale;
+					float cl = 1000 * scale;
+					Handles.DrawLine(new Vector3(-cl, 0), new Vector3(cl, 0));
+					Handles.DrawLine(new Vector3(0, cl), new Vector3(0, -cl));
+				}
+
+				thisPreviewUtilityCamera.Render();
+
+				if (drawHandles) {
+					Handles.SetCamera(thisPreviewUtilityCamera);
+					SpineHandles.DrawBoundingBoxes(skeletonAnimation.transform, skeletonAnimation.skeleton);
+					if (SkeletonDataAssetInspector.showAttachments)
+						SpineHandles.DrawPaths(skeletonAnimation.transform, skeletonAnimation.skeleton);
+				}
+
+				renderer.enabled = false;
+			}
 		}
 
-		void DisposePreviewRenderUtility () {
-			if (previewRenderUtility != null) {
-				previewRenderUtility.Cleanup();
-				previewRenderUtility = null;
+		public void AdjustCamera () {
+			if (previewRenderUtility == null)
+				return;
+
+			if (EditorApplication.timeSinceStartup < cameraAdjustEndFrame)
+				AdjustCameraGoals();
+
+			lastCameraPositionGoal = cameraPositionGoal;
+			lastCameraOrthoGoal = cameraOrthoGoal;
+
+			var c = this.PreviewUtilityCamera;
+			float orthoSet = Mathf.Lerp(c.orthographicSize, cameraOrthoGoal, 0.1f);
+
+			c.orthographicSize = orthoSet;
+
+			float dist = Vector3.Distance(c.transform.position, cameraPositionGoal);
+			if (dist > 0f) {
+				Vector3 pos = Vector3.Lerp(c.transform.position, cameraPositionGoal, 0.1f);
+				pos.x = 0;
+				c.transform.position = pos;
+				c.transform.rotation = Quaternion.identity;
+				RefreshOnNextUpdate();
 			}
 		}
 
-		void DestroyPreviewGameObject () {
-			if (previewGameObject != null) {
-				GameObject.DestroyImmediate(previewGameObject);
-				previewGameObject = null;
+		void AdjustCameraGoals () {
+			if (previewGameObject == null) return;
+
+			Bounds bounds = previewGameObject.GetComponent<Renderer>().bounds;
+			cameraOrthoGoal = bounds.size.y;
+			cameraPositionGoal = bounds.center + new Vector3(0, 0, -10f);
+		}
+
+		void HandleMouseScroll (Rect position) {
+			Event current = Event.current;
+			int controlID = GUIUtility.GetControlID(SliderHash, FocusType.Passive);
+			switch (current.GetTypeForControl(controlID)) {
+				case EventType.ScrollWheel:
+					if (position.Contains(current.mousePosition)) {
+						cameraOrthoGoal += current.delta.y * 0.06f;
+						cameraOrthoGoal = Mathf.Max(0.01f, cameraOrthoGoal);
+						GUIUtility.hotControl = controlID;
+						current.Use();
+					}
+					break;
 			}
 		}
 
@@ -820,7 +936,7 @@ namespace Spine.Unity.Editor {
 
 			var targetAnimation = skeletonData.FindAnimation(animationName);
 			if (targetAnimation != null) {
-				var currentTrack = skeletonAnimation.AnimationState.GetCurrent(0);
+				var currentTrack = this.ActiveTrack;
 				bool isEmpty = (currentTrack == null);
 				bool isNewAnimation = isEmpty || currentTrack.Animation != targetAnimation;
 
@@ -859,128 +975,6 @@ namespace Spine.Unity.Editor {
 
 		}
 
-		public void HandleInteractivePreviewGUI (Rect r, GUIStyle background) {
-			if (Event.current.type == EventType.Repaint) {
-				if (requiresRefresh) {
-					previewRenderUtility.BeginPreview(r, background);
-					DoRenderPreview(true);
-					previewTexture = previewRenderUtility.EndPreview();
-					requiresRefresh = false;
-				}
-				if (previewTexture != null)
-					GUI.DrawTexture(r, previewTexture, ScaleMode.StretchToFill, false);
-			}
-
-			DrawSkinToolbar(r);
-			//DrawSetupPoseButton(r);
-			DrawTimeBar(r);
-			MouseScroll(r);
-		}
-
-		void AdjustCameraGoals (bool calculateMixTime = false) {
-			if (previewGameObject == null)
-				return;
-
-			if (calculateMixTime) {
-				if (skeletonAnimation.AnimationState.GetCurrent(0) != null)
-					cameraAdjustEndFrame = EditorApplication.timeSinceStartup + skeletonAnimation.AnimationState.GetCurrent(0).Alpha;
-			}
-
-			Bounds bounds = previewGameObject.GetComponent<Renderer>().bounds;
-			cameraOrthoGoal = bounds.size.y;
-			cameraPositionGoal = bounds.center + new Vector3(0, 0, -10f);
-		}
-
-		public void AdjustCamera () {
-			if (previewRenderUtility == null)
-				return;
-
-			if (EditorApplication.timeSinceStartup < cameraAdjustEndFrame)
-				AdjustCameraGoals();
-
-			lastCameraPositionGoal = cameraPositionGoal;
-			lastCameraOrthoGoal = cameraOrthoGoal;
-
-			var c = this.PreviewUtilityCamera;
-			float orthoSet = Mathf.Lerp(c.orthographicSize, cameraOrthoGoal, 0.1f);
-
-			c.orthographicSize = orthoSet;
-
-			float dist = Vector3.Distance(c.transform.position, cameraPositionGoal);
-			if (dist > 0f) {
-				Vector3 pos = Vector3.Lerp(c.transform.position, cameraPositionGoal, 0.1f);
-				pos.x = 0;
-				c.transform.position = pos;
-				c.transform.rotation = Quaternion.identity;
-				RefreshOnNextUpdate();
-			}
-		}
-
-		public Texture2D GetStaticPreview (int width, int height) {
-			var c = this.PreviewUtilityCamera;
-			if (c == null) return null;
-
-			RefreshOnNextUpdate();
-			AdjustCameraGoals();
-			c.orthographicSize = cameraOrthoGoal / 2;
-			c.transform.position = cameraPositionGoal;
-			previewRenderUtility.BeginStaticPreview(new Rect(0, 0, width, height));
-			DoRenderPreview(false);
-			var tex = previewRenderUtility.EndStaticPreview();
-			return tex;
-		}
-
-		public void HandleEditorUpdate () {
-			AdjustCamera();
-			if (IsPlayingAnimation) {
-				RefreshOnNextUpdate();
-				Repaint();
-			} else if (requiresRefresh) {
-				Repaint();
-			}
-		}
-
-		public void DoRenderPreview (bool drawHandles) {
-			if (this.PreviewUtilityCamera.activeTexture == null || this.PreviewUtilityCamera.targetTexture == null )
-				return;
-
-			GameObject go = previewGameObject;
-
-			if (requiresRefresh && go != null) {
-				go.GetComponent<Renderer>().enabled = true;
-
-				if (!EditorApplication.isPlaying)
-					skeletonAnimation.Update((Time.realtimeSinceStartup - animationLastTime));
-
-				animationLastTime = Time.realtimeSinceStartup;
-
-				if (!EditorApplication.isPlaying)
-					skeletonAnimation.LateUpdate();
-
-				var thisPreviewUtilityCamera = this.PreviewUtilityCamera;
-
-				if (drawHandles) {			
-					Handles.SetCamera(thisPreviewUtilityCamera);
-					Handles.color = OriginColor;
-
-					float scale = skeletonDataAsset.scale;
-					Handles.DrawLine(new Vector3(-1000 * scale, 0, 0), new Vector3(1000 * scale, 0, 0));
-					Handles.DrawLine(new Vector3(0, 1000 * scale, 0), new Vector3(0, -1000 * scale, 0));
-				}
-
-				thisPreviewUtilityCamera.Render();
-
-				if (drawHandles) {
-					Handles.SetCamera(thisPreviewUtilityCamera);
-					SpineHandles.DrawBoundingBoxes(skeletonAnimation.transform, skeletonAnimation.skeleton);
-					if (SkeletonDataAssetInspector.showAttachments) SpineHandles.DrawPaths(skeletonAnimation.transform, skeletonAnimation.skeleton);
-				}
-
-				go.GetComponent<Renderer>().enabled = false;
-			}
-
-		}
-
 		void DrawSkinToolbar (Rect r) {
 			if (!this.IsValid) return;
 
@@ -1053,7 +1047,7 @@ namespace Spine.Unity.Editor {
 			GUI.Box(barRect, "");
 
 			Rect lineRect = new Rect(barRect);
-			float width = lineRect.width;
+			float lineRectWidth = lineRect.width;
 			TrackEntry t = skeletonAnimation.AnimationState.GetCurrent(0);
 
 			if (t != null) {
@@ -1062,7 +1056,7 @@ namespace Spine.Unity.Editor {
 				float normalizedTime = currentTime / t.Animation.Duration;
 				float wrappedTime = normalizedTime % 1;
 
-				lineRect.x = barRect.x + (width * wrappedTime) - 0.5f;
+				lineRect.x = barRect.x + (lineRectWidth * wrappedTime) - 0.5f;
 				lineRect.width = 2;
 
 				GUI.color = Color.red;
@@ -1071,13 +1065,14 @@ namespace Spine.Unity.Editor {
 
 				for (int i = 0; i < currentAnimationEvents.Count; i++) {
 					float fr = currentAnimationEventTimes[i];
+					var userEventIcon = Icons.userEvent;
 					var evRect = new Rect(barRect) {
-						x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (Icons.userEvent.width / 2), barRect.x, float.MaxValue),
-						y = barRect.y + Icons.userEvent.height,
-						width = Icons.userEvent.width,
-						height = Icons.userEvent.height
+						x = Mathf.Clamp(((fr / t.Animation.Duration) * lineRectWidth) - (userEventIcon.width / 2), barRect.x, float.MaxValue),
+						y = barRect.y + userEventIcon.height,
+						width = userEventIcon.width,
+						height = userEventIcon.height
 					};
-					GUI.DrawTexture(evRect, Icons.userEvent);
+					GUI.DrawTexture(evRect, userEventIcon);
 
 					Event ev = Event.current;
 					if (ev.type == EventType.Repaint) {
@@ -1095,18 +1090,27 @@ namespace Spine.Unity.Editor {
 			}
 		}
 
-		void MouseScroll (Rect position) {
-			Event current = Event.current;
-			int controlID = GUIUtility.GetControlID(SliderHash, FocusType.Passive);
-			switch (current.GetTypeForControl(controlID)) {
-			case EventType.ScrollWheel:
-				if (position.Contains(current.mousePosition)) {
-					cameraOrthoGoal += current.delta.y * 0.06f;
-					cameraOrthoGoal = Mathf.Max(0.01f, cameraOrthoGoal);
-					GUIUtility.hotControl = controlID;
-					current.Use();
-				}
-				break;
+		public void OnDestroy () {
+			DisposePreviewRenderUtility();
+			DestroyPreviewGameObject();
+		}
+
+		public void Clear () {
+			DisposePreviewRenderUtility();
+			DestroyPreviewGameObject();
+		}
+
+		void DisposePreviewRenderUtility () {
+			if (previewRenderUtility != null) {
+				previewRenderUtility.Cleanup();
+				previewRenderUtility = null;
+			}
+		}
+
+		void DestroyPreviewGameObject () {
+			if (previewGameObject != null) {
+				GameObject.DestroyImmediate(previewGameObject);
+				previewGameObject = null;
 			}
 		}
 	}

+ 10 - 3
spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs

@@ -89,9 +89,16 @@ namespace Spine.Unity.Editor {
 					return;
 				}
 
-			} else if (property.serializedObject.targetObject is Component) {
-				var component = (Component)property.serializedObject.targetObject;
-				var hasSkeletonDataAsset = component.GetComponentInChildren(typeof(IHasSkeletonDataAsset)) as IHasSkeletonDataAsset;
+			} else {
+				var targetObject = property.serializedObject.targetObject;
+
+				IHasSkeletonDataAsset hasSkeletonDataAsset = targetObject as IHasSkeletonDataAsset;
+				if (hasSkeletonDataAsset == null) {
+					var component = targetObject as Component;
+					if (component != null)
+						hasSkeletonDataAsset = component.GetComponentInChildren(typeof(IHasSkeletonDataAsset)) as IHasSkeletonDataAsset;
+				}
+
 				if (hasSkeletonDataAsset != null)
 					skeletonDataAsset = hasSkeletonDataAsset.SkeletonDataAsset;
 			}

+ 14 - 6
spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs

@@ -487,11 +487,19 @@ namespace Spine.Unity.Editor {
 				skeletonUtilityBoneTable.Clear();
 				boundingBoxFollowerTable.Clear();
 
+				#if UNITY_2018
+				EditorApplication.hierarchyChanged -= HierarchyIconsOnChanged;
+				#else
 				EditorApplication.hierarchyWindowChanged -= HierarchyIconsOnChanged;
+				#endif
 				EditorApplication.hierarchyWindowItemOnGUI -= HierarchyIconsOnGUI;
 
 				if (!Application.isPlaying && showHierarchyIcons) {
+					#if UNITY_2018
+					EditorApplication.hierarchyChanged += HierarchyIconsOnChanged;
+					#else
 					EditorApplication.hierarchyWindowChanged += HierarchyIconsOnChanged;
+					#endif
 					EditorApplication.hierarchyWindowItemOnGUI += HierarchyIconsOnGUI;
 					HierarchyIconsOnChanged();
 				}
@@ -1189,10 +1197,11 @@ namespace Spine.Unity.Editor {
 		#endregion
 
 		#region Import SkeletonData (json or binary)
+		public const string SkeletonDataSuffix = "_SkeletonData";
 		static SkeletonDataAsset IngestSpineProject (TextAsset spineJson, params AtlasAsset[] atlasAssets) {
 			string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
 			string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
-			string filePath = assetPath + "/" + primaryName + "_SkeletonData.asset";
+			string filePath = assetPath + "/" + primaryName + SkeletonDataSuffix + ".asset";
 
 			#if SPINE_TK2D
 			if (spineJson != null) {
@@ -1554,12 +1563,11 @@ namespace Spine.Unity.Editor {
 		}
 		#endregion
 
-		//public static string GetPathSafeRegionName (AtlasRegion region) {
-		//	return region.name.Replace("/", "_");
-		//}
-
 		public static string GetPathSafeName (string name) {
-			return name.Replace("/", "_");
+			foreach (char c in System.IO.Path.GetInvalidFileNameChars()) { // Doesn't handle more obscure file name limitations.
+				name = name.Replace(c, '_');
+			}
+			return name;
 		}
 	}
 

+ 1 - 1
spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs

@@ -1395,7 +1395,7 @@ namespace Spine.Unity {
 				var instruction = instructionsItems[startSubmesh + i];
 				submeshesItems[i] = instruction;
 				#if SPINE_TRIANGLECHECK
-				this.hasActiveClipping = instruction.hasClipping;
+				this.hasActiveClipping |= instruction.hasClipping;
 				submeshesItems[i].rawFirstVertexIndex = runningVertexCount; // Ensure current instructions have correct cached values.
 				runningVertexCount += instruction.rawVertexCount; // vertexCount will also be used for the rest of this method.
 				#endif

+ 5 - 0
spine-unity/Assets/spine-unity/Modules/Shaders/Sprite/Editor/SpineSpriteShaderGUI.cs

@@ -680,7 +680,12 @@ public class SpineSpriteShaderGUI : ShaderGUI {
 
 		if (emission && !mixedValue) {
 			EditorGUI.BeginChangeCheck();
+
+#if UNITY_2018
+			_materialEditor.TexturePropertyWithHDRColor(_emissionText, _emissionMap, _emissionColor, true);
+#else
 			_materialEditor.TexturePropertyWithHDRColor(_emissionText, _emissionMap, _emissionColor, new ColorPickerHDRConfig(0, 1, 0.01010101f, 3), true);
+#endif
 			_materialEditor.FloatProperty(_emissionPower, _emissionPowerText.text);
 			dataChanged |= EditorGUI.EndChangeCheck();
 		}

+ 5 - 7
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs

@@ -66,8 +66,9 @@ namespace Spine.Unity {
 				bone = skeletonGraphic.Skeleton.FindBone(boneName);
 
 			#if UNITY_EDITOR
-			if (Application.isEditor)
+			if (Application.isEditor) {
 				LateUpdate();
+			}
 			#endif
 		}
 
@@ -91,7 +92,9 @@ namespace Spine.Unity {
 			var thisTransform = this.transform as RectTransform;
 			if (thisTransform == null) return;
 
-			float scale = skeletonGraphic.canvas.referencePixelsPerUnit;
+			var canvas = skeletonGraphic.canvas;
+			if (canvas == null) canvas = skeletonGraphic.GetComponentInParent<Canvas>();
+			float scale = canvas.referencePixelsPerUnit;
 
 			if (skeletonTransformIsParent) {
 				// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
@@ -113,12 +116,7 @@ namespace Spine.Unity {
 
 				if (followBoneRotation) {
 					Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
-					#if UNITY_5_6_OR_NEWER
 					thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, skeletonTransform.rotation.eulerAngles.z + boneWorldRotation));
-					#else
-					thisTransform.position = targetWorldPosition;
-					thisTransform.rotation = Quaternion.Euler(worldRotation.x, worldRotation.y, skeletonTransform.rotation.eulerAngles.z + bone.WorldRotationX);
-					#endif
 				} else {
 					thisTransform.position = targetWorldPosition;
 				}

+ 1 - 1
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs

@@ -115,7 +115,7 @@ namespace Spine.Unity {
 		public static SkeletonGraphic AddSkeletonGraphicComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset) {
 			var c = gameObject.AddComponent<SkeletonGraphic>();
 			if (skeletonDataAsset != null) {
-				c.skeletonDataAsset = skeletonDataAsset;
+				c.skeletonDataAsset = skeletonDataAsset;				
 				c.Initialize(false);
 			}
 			return c;

+ 0 - 24
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs

@@ -138,18 +138,13 @@ namespace Spine.Unity.Modules {
 			skeletonRenderer.GenerateMeshOverride += HandleRender;
 			#endif
 
-
-			#if UNITY_5_4_OR_NEWER
 			if (copyMeshRendererFlags) {
 				var lightProbeUsage = mainMeshRenderer.lightProbeUsage;
 				bool receiveShadows = mainMeshRenderer.receiveShadows;
-
-				#if UNITY_5_5_OR_NEWER
 				var reflectionProbeUsage = mainMeshRenderer.reflectionProbeUsage;
 				var shadowCastingMode = mainMeshRenderer.shadowCastingMode;
 				var motionVectorGenerationMode = mainMeshRenderer.motionVectorGenerationMode;
 				var probeAnchor = mainMeshRenderer.probeAnchor;
-				#endif
 
 				for (int i = 0; i < partsRenderers.Count; i++) {
 					var currentRenderer = partsRenderers[i];
@@ -158,31 +153,12 @@ namespace Spine.Unity.Modules {
 					var mr = currentRenderer.MeshRenderer;
 					mr.lightProbeUsage = lightProbeUsage;
 					mr.receiveShadows = receiveShadows;
-
-					#if UNITY_5_5_OR_NEWER
 					mr.reflectionProbeUsage = reflectionProbeUsage;
 					mr.shadowCastingMode = shadowCastingMode;
 					mr.motionVectorGenerationMode = motionVectorGenerationMode;
 					mr.probeAnchor = probeAnchor;
-					#endif
 				}
 			}
-			#else
-			if (copyMeshRendererFlags) {
-				var useLightProbes = mainMeshRenderer.useLightProbes;
-				bool receiveShadows = mainMeshRenderer.receiveShadows;
-
-				for (int i = 0; i < partsRenderers.Count; i++) {
-					var currentRenderer = partsRenderers[i];
-					if (currentRenderer == null) continue; // skip null items.
-
-					var mr = currentRenderer.MeshRenderer;
-					mr.useLightProbes = useLightProbes;
-					mr.receiveShadows = receiveShadows;
-				}
-			}
-			#endif
-
 		}
 
 		void OnDisable () {

+ 9 - 0
spine-unity/Assets/spine-unity/Modules/Timeline.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 9192f99585e00e2468a2e2592cfce807
+folderAsset: yes
+timeCreated: 1505434717
+licenseType: Free
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 10 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation.meta

@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: a363511f60be37a4199a1a4ad0188b40
+folderAsset: yes
+timeCreated: 1511505394
+licenseType: Free
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 80 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/README.md

@@ -0,0 +1,80 @@
+This Documentation is for the Spine Timeline features.
+If this documentation contains mistakes or doesn't cover some questions, please feel to comment below, open an issue or post in the official [Spine-Unity forums](http://esotericsoftware.com/forum/viewforum.php?f=3).
+
+# Spine-Unity Timeline Playables
+
+![](add-menu.png)  
+
+## Spine Animation Track
+Controls the skeleton pose a given Spine component with animations.
+
+**Status:**
+- Currently only SkeletonAnimation (via SkeletonAnimationPlayableHandle)
+- Mixing has some significant bugs. It should work fine if you don't include mixing, or if all your animations have perfectly matching dopesheet properties.
+
+**To use:**
+1. Add `SkeletonAnimationPlayableHandle` component to your SkeletonAnimation GameObject.
+2. With an existing Unity Playable Direction, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation Track**.
+3. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine Skeleton Flip Track.
+4. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation Clip Clip**.
+5. Adjust the start and end times of the new clip, name it appropriately at the top of the Inspector.
+6. Click on the clip inspector's SkeletonDataAsset field and choose your target skeleton's SkeletonDataAsset. This will enable the animation name dropdown to appear.
+7. Choose the appropriate animation name, loop, and mix settings.
+8. For easier readability, rename your clip to the animation name or something descriptive. 
+
+**Track Behavior**
+- Currently buggy
+- 
+
+
+## Spine Skeleton Flip Track
+![](skeleton-flip-clip-inspector.png)  
+Controls skeleton flip a given Spine component.
+
+**Status:**
+- Currently only SkeletonAnimation (via SkeletonAnimationPlayableHandle)
+
+**To use:**
+1. Add `SkeletonAnimationPlayableHandle` component to your SkeletonAnimation GameObject.
+2. With an existing Unity Playable Director, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Skeleton Flip Track**.
+3. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine Skeleton Flip Track.
+4. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Skeleton Flip Clip Clip**.
+5. Adjust the start and end times of the new clip, name it appropriately at the top of the Inspector, and choose the desired FlipX and FlipY values.
+
+**Track Behavior**
+- The specified skeleton flip values will be applied for every frame within the duration of each track.
+- At the end of the timeline, the track will revert the skeleton flip to the flip values it captures when it starts playing that timeline. 
+
+## Spine AnimationState Track
+![](animationstate-clip-inspector.png)  
+Sets Animations on the target SkeletonAnimation's AnimationState (via SetAnimation).
+
+**Status:**
+- Currently only SkeletonAnimation (directly)
+
+**To use:**
+1. With an existing Unity Playable Director, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation State Track**.
+2. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine AnimationState Track.
+3. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation State Clip Clip**.
+4. Adjust the start and end times of the new clip, name it appropriately at the top of the Inspector.
+5. Click on the clip inspector's SkeletonDataAsset field and choose your target skeleton's SkeletonDataAsset. This will enable the animation name dropdown to appear.
+6. Choose the appropriate animation name, loop, and mix settings.
+- For easier readability, rename your clip to the animation name or something descriptive.
+- To avoid having to do steps 4-6 repeatedly, use the Duplicate function (`CTRL`/`CMD` + `D`)  
+
+**Track Behavior**
+- `AnimationState.SetAnimation` will be called at the beginning of every clip based on the animationName.
+- Clip durations don't matter. Animations won't be cleared where there is no active clip at certain slices of time.
+- **EMPTY ANIMATION**: If a clip has no name specified, it will call SetEmptyAnimation instead.
+- **ERROR HANDLING**: If the animation with the provided animationName is not found, it will do nothing (the previous animation will continue playing normally).
+- Animations playing before the timeline starts playing will not be interrupted until the first clip starts playing.
+- At the end of the last clip and at the end of the timeline, nothing happens. This means the effect of the last clip's SetAnimation call will persist until you give other commands to that AnimationState.
+- If "custom duration" is unchecked, it will do a normal lookup of the AnimationState data's specified transition-pair mix setting, or the default mix.
+- Edit mode preview mixing may look different from Play Mode mixing. Please check in actual Play Mode to see the real results.
+
+## Known Issues
+Spine Timeline support is currently experimental and has some known issues and inconveniences.
+- The Console logs an incorrect/harmless error `DrivenPropertyManager has failed to register property "m_Script" of object "Spine GameObject (spineboy-pro)" with driver "" because the property doesn't exist.`. This is a known issue on Unity's end. See more here: https://forum.unity.com/threads/default-playables-text-switcher-track-error.502903/
+- These Spine Tracks (like other custom Unity Timeline Playable types) do not have labels on them. Unity currently doesn't have API to specify their labels yet.
+- Each track clip currently requires you to specify a reference to SkeletonData so its inspector can show you a convenient list of animation names. This is because track clips are agnostic of its track and target component/track binding, and provides no way of automatically finding it while in the editor. The clips will still function correctly without the SkeletonDataAsset references; you just won't get the dropdown of animation names in the editor.
+- Each track clip cannot be automatically named based on the chosen animationName. The Timeline object editors currently doesn't provide access to the clip names to do this automatically.

+ 9 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/README.md.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 970f0962a0a79bf42bdedfc9ed439f56
+timeCreated: 1511505255
+licenseType: Free
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/add-menu.png


+ 112 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/add-menu.png.meta

@@ -0,0 +1,112 @@
+fileFormatVersion: 2
+guid: ab296920a52d8204ea35ed4385481be3
+timeCreated: 1511505440
+licenseType: Free
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 4
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 1
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: 1
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: -1
+    aniso: -1
+    mipBias: -1
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spritePixelsToUnits: 100
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 0
+  textureShape: 1
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Standalone
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Android
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Windows Store Apps
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: WebGL
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+  spritePackingTag: 
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png


+ 112 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png.meta

@@ -0,0 +1,112 @@
+fileFormatVersion: 2
+guid: ca162358b7b05034cae4e25bfb8b98f8
+timeCreated: 1511508127
+licenseType: Free
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 4
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 1
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: 1
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: -1
+    aniso: -1
+    mipBias: -1
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spritePixelsToUnits: 100
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 0
+  textureShape: 1
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Standalone
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Android
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Windows Store Apps
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: WebGL
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+  spritePackingTag: 
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/skeleton-flip-clip-inspector.png


+ 112 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/Documentation/skeleton-flip-clip-inspector.png.meta

@@ -0,0 +1,112 @@
+fileFormatVersion: 2
+guid: 80e301d4353d83640bb50224021a8379
+timeCreated: 1511506555
+licenseType: Free
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 4
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 1
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: 1
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: -1
+    aniso: -1
+    mipBias: -1
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spritePixelsToUnits: 100
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 0
+  textureShape: 1
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Standalone
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Android
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: Windows Store Apps
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  - buildTarget: WebGL
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+  spritePackingTag: 
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: ff79ab0c89530f24d8f60162bd4c1009
+folderAsset: yes
+timeCreated: 1507311562
+licenseType: Free
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 114 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SkeletonAnimationPlayableHandle.cs

@@ -0,0 +1,114 @@
+/******************************************************************************
+ * 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 System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+//using UnityEngine.Playables;
+
+using Spine;
+using Spine.Unity;
+using Spine.Unity.Playables;
+
+namespace Spine.Unity.Playables {
+
+	[AddComponentMenu("Spine/Playables/SkeletonAnimation Playable Handle (Playables)")]
+	public class SkeletonAnimationPlayableHandle : SpinePlayableHandleBase {
+		#region Inspector
+		public SkeletonAnimation skeletonAnimation;
+		//public float fadeOutDuration = 0.5f;
+
+		#if UNITY_EDITOR
+		void OnValidate () {
+			if (this.skeletonAnimation == null)
+				skeletonAnimation = GetComponent<SkeletonAnimation>();
+		}
+		#endif
+
+		#endregion
+
+		//readonly HashSet<int> frameAppliedProperties = new HashSet<int>();
+
+		public override Skeleton Skeleton {	get { return skeletonAnimation.Skeleton; } }
+		public override SkeletonData SkeletonData { get { return skeletonAnimation.Skeleton.data; } }
+
+		#if UNITY_2017 || UNITY_2018
+		void Awake () {
+			if (skeletonAnimation == null)
+				skeletonAnimation = GetComponent<SkeletonAnimation>();
+
+			//frameAppliedProperties.Clear();
+		}
+
+		//Skeleton skeleton;
+		//int frameTrackCount = 0;
+		//int frameCurrentInputs = 0;
+		//bool firstCleared = false;
+		//int lastApplyFrame = 0;
+		//public override void ProcessFrame (Playable playable, FrameData info, SpineAnimationMixerBehaviour mixer) {
+		//	if (skeletonAnimation == null) return;
+		//	if (skeleton == null) skeleton = skeletonAnimation.Skeleton;
+
+		//	// New frame.
+		//	if (lastApplyFrame != Time.frameCount) {
+		//		if (frameTrackCount > 0)
+		//			frameAppliedProperties.Clear();					
+
+		//		frameCurrentInputs = 0;
+		//		frameTrackCount = 0;	
+		//	}
+		//	lastApplyFrame = Time.frameCount;
+
+		//	int currentInputs = mixer.ApplyPlayableFrame(playable, skeleton, frameAppliedProperties, frameTrackCount);
+		//	frameCurrentInputs += currentInputs;
+
+		//	// EXPERIMENTAL: Handle overriding SkeletonAnimation.AnimationState.
+		//	if (frameCurrentInputs > 0) {
+		//		var state = skeletonAnimation.AnimationState;
+
+		//		if (!firstCleared) {
+		//			firstCleared = true;
+		//			for (int i = 0; i < 4; i++) {
+		//				if (state.GetCurrent(i) != null) state.SetEmptyAnimation(i, fadeOutDuration);
+		//			}
+		//		}
+
+		//		// Update again whenever an animation is playing in the AnimationState. Quite wasteful.
+		//		//if (state.GetCurrent(0) != null) {
+		//			skeleton.UpdateWorldTransform();
+		//		//}
+		//	}
+
+		//	frameTrackCount++;
+		//}
+#endif
+	}
+
+}

+ 12 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SkeletonAnimationPlayableHandle.cs.meta

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

+ 64 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SpinePlayableHandleBase.cs

@@ -0,0 +1,64 @@
+/******************************************************************************
+ * 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 System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+//using UnityEngine.Playables;
+
+namespace Spine.Unity.Playables {
+
+	public delegate void SpineEventDelegate (Spine.Event e);
+
+	/// <summary>Base class for Spine Playable Handle components, commonly for integrating with UnityEngine Timeline.</summary>
+	public abstract class SpinePlayableHandleBase : MonoBehaviour {
+		
+		/// <summary>Gets the SkeletonData of the targeted Spine component.</summary>
+		public abstract SkeletonData SkeletonData { get; }
+
+		public abstract Skeleton Skeleton { get; }
+
+		/// <summary>MixerBehaviour ProcessFrame method handler.</summary>
+		/// <returns>Returns true if a playable was applied previously</returns>
+		//public abstract void ProcessFrame (Playable playable, FrameData info, SpineAnimationMixerBehaviour mixer);
+
+		/// <summary>Subscribe to this to handle user events played by the Unity playable</summary>
+		public event SpineEventDelegate AnimationEvents;
+
+		public virtual void HandleEvents (ExposedList<Event> eventBuffer) {
+			if (eventBuffer == null || AnimationEvents == null) return;
+			for (int i = 0, n = eventBuffer.Count; i < n; i++)
+				AnimationEvents.Invoke(eventBuffer.Items[i]);
+		}
+
+	}
+}
+

+ 12 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/PlayableHandle Component/SpinePlayableHandleBase.cs.meta

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

+ 10 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState.meta

@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 47adfa0aa094be548be25e178c1435d2
+folderAsset: yes
+timeCreated: 1510816616
+licenseType: Free
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 10 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/Editor.meta

@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 54bc1978049774f4aa13bfd014a5773a
+folderAsset: yes
+timeCreated: 1510816616
+licenseType: Free
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 49 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/Editor/SpineAnimationStateDrawer.cs

@@ -0,0 +1,49 @@
+using UnityEditor;
+using UnityEngine;
+using Spine;
+using Spine.Unity;
+using Spine.Unity.Playables;
+
+//[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 loopProp = property.FindPropertyRelative("loop");
+		SerializedProperty eventProp = property.FindPropertyRelative("eventThreshold");
+		SerializedProperty attachmentProp = property.FindPropertyRelative("attachmentThreshold");
+		SerializedProperty drawOrderProp = property.FindPropertyRelative("drawOrderThreshold");
+
+		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);
+
+		singleFieldRect.y += lineHeightWithSpacing;
+		EditorGUI.PropertyField(singleFieldRect, loopProp);
+
+		singleFieldRect.y += lineHeightWithSpacing * 0.5f;
+
+		singleFieldRect.y += lineHeightWithSpacing;
+		EditorGUI.LabelField(singleFieldRect, "Mixing Settings", EditorStyles.boldLabel);
+
+		singleFieldRect.y += lineHeightWithSpacing;
+		EditorGUI.PropertyField(singleFieldRect, eventProp);
+
+		singleFieldRect.y += lineHeightWithSpacing;
+		EditorGUI.PropertyField(singleFieldRect, attachmentProp);
+
+		singleFieldRect.y += lineHeightWithSpacing;
+		EditorGUI.PropertyField(singleFieldRect, drawOrderProp);
+	}
+	*/
+}

+ 13 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/Editor/SpineAnimationStateDrawer.cs.meta

@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: cba97e3d11d6d4d428fcfe1a7be16db0
+timeCreated: 1510816616
+licenseType: Free
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 64 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateBehaviour.cs

@@ -0,0 +1,64 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if UNITY_2017 || UNITY_2018
+using System;
+using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+using Spine;
+using Spine.Unity;
+using System.Collections.Generic;
+
+namespace Spine.Unity.Playables {
+
+	using Animation = Spine.Animation;
+
+	[Serializable]
+	public class SpineAnimationStateBehaviour : PlayableBehaviour {
+		public AnimationReferenceAsset animationReference;
+		public bool loop;
+
+		[Header("Mix Properties")]
+		public bool customDuration = false;
+		public float mixDuration = 0.1f;
+
+		[Range(0, 1f)]
+		public float attachmentThreshold = 0.5f;
+
+		[Range(0, 1f)]
+		public float eventThreshold = 0.5f;
+
+		[Range(0, 1f)]
+		public float drawOrderThreshold = 0.5f;
+	}
+
+}
+#endif

+ 13 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateBehaviour.cs.meta

@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 82bd6e7ec1121b5428c447b7bd16b042
+timeCreated: 1510816616
+licenseType: Free
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 52 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateClip.cs

@@ -0,0 +1,52 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if UNITY_2017 || UNITY_2018
+using System;
+using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+
+namespace Spine.Unity.Playables {
+	[Serializable]
+	public class SpineAnimationStateClip : PlayableAsset, ITimelineClipAsset {
+		public SpineAnimationStateBehaviour template = new SpineAnimationStateBehaviour();
+
+		public ClipCaps clipCaps { get { return ClipCaps.None; } }
+
+		public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
+			var playable = ScriptPlayable<SpineAnimationStateBehaviour>.Create(graph, template);
+			playable.GetBehaviour(); //SpineAnimationStateBehaviour clone = playable.GetBehaviour();
+			return playable;
+		}
+	}
+
+}
+#endif

+ 13 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateClip.cs.meta

@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 585e20c924ba86a44926c850aa8e1d84
+timeCreated: 1510816616
+licenseType: Free
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 168 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs

@@ -0,0 +1,168 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+ #define SPINE_EDITMODEPOSE
+
+#if UNITY_2017 || UNITY_2018
+using System;
+using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+
+namespace Spine.Unity.Playables {
+	public class SpineAnimationStateMixerBehaviour : PlayableBehaviour {
+
+		float[] lastInputWeights;
+
+		// NOTE: This function is called at runtime and edit time. Keep that in mind when setting the values of properties.
+		public override void ProcessFrame (Playable playable, FrameData info, object playerData) {
+			var spineComponent = playerData as SkeletonAnimation;
+			if (spineComponent == null) return;
+
+			var skeleton = spineComponent.Skeleton;
+			var state = spineComponent.AnimationState;
+
+			if (!Application.isPlaying) {
+#if SPINE_EDITMODEPOSE
+				PreviewEditModePose(playable, spineComponent);
+#endif
+				return;
+			}
+
+			int inputCount = playable.GetInputCount();
+
+			// Ensure correct buffer size.
+			if (this.lastInputWeights == null || this.lastInputWeights.Length < inputCount) {
+				this.lastInputWeights = new float[inputCount];
+
+				for (int i = 0; i < inputCount; i++)
+					this.lastInputWeights[i] = default(float);				
+			}
+			var lastInputWeights = this.lastInputWeights;
+
+			// Check all clips. If a clip that was weight 0 turned into weight 1, call SetAnimation.
+			for (int i = 0; i < inputCount; i++) {
+				float lastInputWeight = lastInputWeights[i];
+				float inputWeight = playable.GetInputWeight(i);
+				bool trackStarted = inputWeight > lastInputWeight;
+				lastInputWeights[i] = inputWeight;
+
+				if (trackStarted) {
+					ScriptPlayable<SpineAnimationStateBehaviour> inputPlayable = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(i);
+					SpineAnimationStateBehaviour clipData = inputPlayable.GetBehaviour();
+
+					if (clipData.animationReference == null) {
+						float mixDuration = clipData.customDuration ? clipData.mixDuration : state.Data.DefaultMix;
+						state.SetEmptyAnimation(0, mixDuration);
+						continue;
+					} else {
+						if (clipData.animationReference.Animation != null) {
+							Spine.TrackEntry trackEntry = state.SetAnimation(0, clipData.animationReference.Animation, clipData.loop);
+
+							//trackEntry.TrackTime = (float)inputPlayable.GetTime(); // More accurate time-start?
+							trackEntry.EventThreshold = clipData.eventThreshold;
+							trackEntry.DrawOrderThreshold = clipData.drawOrderThreshold;
+							trackEntry.AttachmentThreshold = clipData.attachmentThreshold;
+
+							if (clipData.customDuration)
+								trackEntry.MixDuration = clipData.mixDuration;
+						}
+						//else Debug.LogWarningFormat("Animation named '{0}' not found", clipData.animationName);
+					}
+				}
+			}
+		}
+
+#if SPINE_EDITMODEPOSE
+		public void PreviewEditModePose (Playable playable, SkeletonAnimation spineComponent) {
+			if (Application.isPlaying) return;
+			if (spineComponent == null) return;
+
+			int inputCount = playable.GetInputCount();
+			int lastOneWeight = -1;
+
+			for (int i = 0; i < inputCount; i++) {
+				float inputWeight = playable.GetInputWeight(i);
+				if (inputWeight >= 1) lastOneWeight = i;
+			}
+
+			if (lastOneWeight != -1) {
+				ScriptPlayable<SpineAnimationStateBehaviour> inputPlayableClip = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(lastOneWeight);
+				SpineAnimationStateBehaviour clipData = inputPlayableClip.GetBehaviour();
+
+				var skeleton = spineComponent.Skeleton;
+
+				bool skeletonDataMismatch = clipData.animationReference != null && spineComponent.SkeletonDataAsset.GetSkeletonData(true) != clipData.animationReference.SkeletonDataAsset.GetSkeletonData(true);
+				if (skeletonDataMismatch) {
+					Debug.LogWarningFormat("SpineAnimationStateMixerBehaviour tried to apply an animation for the wrong skeleton. Expected {0}. Was {1}", spineComponent.SkeletonDataAsset, clipData.animationReference.SkeletonDataAsset);
+				}
+
+				// Getting the from-animation here because it's required to get the mix information from AnimationStateData.
+				Animation fromAnimation = null;
+				float fromClipTime = 0;
+				bool fromClipLoop = false;
+				if (lastOneWeight != 0 && inputCount > 1) {
+					var fromClip = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(lastOneWeight - 1);
+					var fromClipData = fromClip.GetBehaviour();
+					fromAnimation = fromClipData.animationReference.Animation;
+					fromClipTime = (float)fromClip.GetTime();
+					fromClipLoop = fromClipData.loop;
+				}
+
+				Animation toAnimation = clipData.animationReference.Animation;
+				float toClipTime = (float)inputPlayableClip.GetTime();
+				float mixDuration = clipData.mixDuration;
+
+				if (!clipData.customDuration && fromAnimation != null) {
+					mixDuration = spineComponent.AnimationState.Data.GetMix(fromAnimation, toAnimation);
+				}
+
+				// Approximate what AnimationState might do at runtime.
+				if (fromAnimation != null && mixDuration > 0 && toClipTime < mixDuration) {
+					skeleton.SetToSetupPose();
+					float fauxFromAlpha = (1f - toClipTime/mixDuration);
+					fauxFromAlpha = fauxFromAlpha > 0.5f ? 1f : fauxFromAlpha * 2f;  // fake value, but reduce dip.
+					fromAnimation.Apply(skeleton, 0, fromClipTime, fromClipLoop, null, fauxFromAlpha, MixPose.Setup, MixDirection.Out); //fromAnimation.PoseSkeleton(skeleton, fromClipTime, fromClipLoop);
+					toAnimation.Apply(skeleton, 0, toClipTime, clipData.loop, null, toClipTime/mixDuration, MixPose.Current, MixDirection.In);
+				} else {
+					skeleton.SetToSetupPose();
+					toAnimation.PoseSkeleton(skeleton, toClipTime, clipData.loop);
+				}
+
+			}
+			// Do nothing outside of the first clip and the last clip.
+
+		}
+#endif
+
+	}
+
+}
+#endif

+ 13 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs.meta

@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: f61d2f4ac1c4ad044bf4ae6e63b0eca8
+timeCreated: 1510816616
+licenseType: Free
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 46 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateTrack.cs

@@ -0,0 +1,46 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if UNITY_2017 || UNITY_2018
+using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+
+namespace Spine.Unity.Playables {
+	[TrackColor(0.9960785f, 0.2509804f, 0.003921569f)]
+	[TrackClipType(typeof(SpineAnimationStateClip))]
+	[TrackBindingType(typeof(SkeletonAnimation))]
+	public class SpineAnimationStateTrack : TrackAsset {
+		public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) {
+			return ScriptPlayable<SpineAnimationStateMixerBehaviour>.Create (graph, inputCount);
+		}
+	}
+}
+#endif

+ 13 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineAnimationState/SpineAnimationStateTrack.cs.meta

@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: dfabb056a779ae34b9ebd7de425c868b
+timeCreated: 1510816616
+licenseType: Free
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 3099d730dd2a9e349b3d918960cb42fa
+folderAsset: yes
+timeCreated: 1500876410
+licenseType: Free
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/Editor.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: fc5fac7eface2ba4fbb6146017e41192
+folderAsset: yes
+timeCreated: 1500876410
+licenseType: Free
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 27 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/Editor/SpineSkeletonFlipDrawer.cs

@@ -0,0 +1,27 @@
+#if UNITY_2017 || UNITY_2018
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.Playables;
+
+[CustomPropertyDrawer(typeof(SpineSkeletonFlipBehaviour))]
+public class SpineSkeletonFlipDrawer : PropertyDrawer
+{
+    public override float GetPropertyHeight (SerializedProperty property, GUIContent label)
+    {
+        int fieldCount = 1;
+        return fieldCount * EditorGUIUtility.singleLineHeight;
+    }
+
+    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
+    {
+		SerializedProperty flipXProp = property.FindPropertyRelative("flipX");
+		SerializedProperty flipYProp = property.FindPropertyRelative("flipY");
+
+        Rect singleFieldRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
+        EditorGUI.PropertyField(singleFieldRect, flipXProp);
+
+		singleFieldRect.y += EditorGUIUtility.singleLineHeight;
+		EditorGUI.PropertyField(singleFieldRect, flipYProp);
+    }
+}
+#endif

+ 12 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/Editor/SpineSkeletonFlipDrawer.cs.meta

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

+ 11 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipBehaviour.cs

@@ -0,0 +1,11 @@
+#if UNITY_2017 || UNITY_2018
+using System;
+using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+
+[Serializable]
+public class SpineSkeletonFlipBehaviour : PlayableBehaviour {
+    public bool flipX, flipY;
+}
+#endif

+ 12 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipBehaviour.cs.meta

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

+ 50 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipClip.cs

@@ -0,0 +1,50 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if UNITY_2017 || UNITY_2018
+using System;
+using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+
+[Serializable]
+public class SpineSkeletonFlipClip : PlayableAsset, ITimelineClipAsset {
+	public SpineSkeletonFlipBehaviour template = new SpineSkeletonFlipBehaviour();
+
+	public ClipCaps clipCaps {
+		get { return ClipCaps.None; }
+	}
+
+	public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
+		var playable = ScriptPlayable<SpineSkeletonFlipBehaviour>.Create(graph, template);
+		return playable;
+	}
+}
+#endif

+ 12 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipClip.cs.meta

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

+ 102 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipMixerBehaviour.cs

@@ -0,0 +1,102 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if UNITY_2017 || UNITY_2018
+ using System;
+using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+
+using Spine.Unity;
+
+namespace Spine.Unity.Playables {
+	public class SpineSkeletonFlipMixerBehaviour : PlayableBehaviour {
+		bool defaultFlipX, defaultFlipY;
+
+		SpinePlayableHandleBase playableHandle;
+		bool m_FirstFrameHappened;
+
+		public override void ProcessFrame (Playable playable, FrameData info, object playerData) {
+			playableHandle = playerData as SpinePlayableHandleBase;
+
+			if (playableHandle == null)
+				return;
+
+			var skeleton = playableHandle.Skeleton;
+
+			if (!m_FirstFrameHappened) {
+				defaultFlipX = skeleton.flipX;
+				defaultFlipY = skeleton.flipY;
+				m_FirstFrameHappened = true;
+			}
+
+			int inputCount = playable.GetInputCount();
+
+			float totalWeight = 0f;
+			float greatestWeight = 0f;
+			int currentInputs = 0;
+
+			for (int i = 0; i < inputCount; i++) {
+				float inputWeight = playable.GetInputWeight(i);
+				ScriptPlayable<SpineSkeletonFlipBehaviour> inputPlayable = (ScriptPlayable<SpineSkeletonFlipBehaviour>)playable.GetInput(i);
+				SpineSkeletonFlipBehaviour input = inputPlayable.GetBehaviour();
+
+				totalWeight += inputWeight;
+
+				if (inputWeight > greatestWeight) {
+					skeleton.flipX = input.flipX;
+					skeleton.flipY = input.flipY;
+					greatestWeight = inputWeight;
+				}
+
+				if (!Mathf.Approximately(inputWeight, 0f))
+					currentInputs++;
+			}
+
+			if (currentInputs != 1 && 1f - totalWeight > greatestWeight) {
+				skeleton.flipX = defaultFlipX;
+				skeleton.flipY = defaultFlipY;
+			}
+		}
+
+		public override void OnGraphStop (Playable playable) {
+			m_FirstFrameHappened = false;
+
+			if (playableHandle == null)
+				return;
+
+			var skeleton = playableHandle.Skeleton;
+			skeleton.flipX = defaultFlipX;
+			skeleton.flipY = defaultFlipY;
+		}
+	}
+
+}
+#endif

+ 12 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipMixerBehaviour.cs.meta

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

+ 68 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipTrack.cs

@@ -0,0 +1,68 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if UNITY_2017 || UNITY_2018
+ using UnityEngine;
+using UnityEngine.Playables;
+using UnityEngine.Timeline;
+using System.Collections.Generic;
+
+using Spine.Unity;
+
+namespace Spine.Unity.Playables {
+	
+	[TrackColor(0.855f, 0.8623f, 0.87f)]
+	[TrackClipType(typeof(SpineSkeletonFlipClip))]
+	[TrackBindingType(typeof(SpinePlayableHandleBase))]
+	public class SpineSkeletonFlipTrack : TrackAsset {
+		public override Playable CreateTrackMixer (PlayableGraph graph, GameObject go, int inputCount) {
+			return ScriptPlayable<SpineSkeletonFlipMixerBehaviour>.Create(graph, inputCount);
+		}
+
+		public override void GatherProperties (PlayableDirector director, IPropertyCollector driver) {
+#if UNITY_EDITOR
+			SpinePlayableHandleBase trackBinding = director.GetGenericBinding(this) as SpinePlayableHandleBase;
+			if (trackBinding == null)
+				return;
+
+			var serializedObject = new UnityEditor.SerializedObject(trackBinding);
+			var iterator = serializedObject.GetIterator();
+			while (iterator.NextVisible(true)) {
+				if (iterator.hasVisibleChildren)
+					continue;
+
+				driver.AddFromName<SpinePlayableHandleBase>(trackBinding.gameObject, iterator.propertyPath);
+			}
+#endif
+			base.GatherProperties(director, driver);
+		}
+	}
+}
+#endif

+ 12 - 0
spine-unity/Assets/spine-unity/Modules/Timeline/SpineSkeletonFlip/SpineSkeletonFlipTrack.cs.meta

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