Răsfoiți Sursa

Added SkeletonAnimator
Changed SkeletonAnimation delegates to pass SkeletonRenderer instead of SkeletonAnimation
Refactored how delegates/events work a bit in SkeletonAnimation by implementing ISkeletonAnimation interface.

Fenrisul 10 ani în urmă
părinte
comite
39e754315e

+ 1 - 1
spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs

@@ -50,7 +50,7 @@ public class DynamicSpineBone : MonoBehaviour {
 		lastPosition = speedReference.position;
 	}
 
-	void UpdateLocal(SkeletonAnimation animation) {
+	void UpdateLocal(SkeletonRenderer renderer) {
 		Vector3 vec = useAcceleration ? acceleration : velocity;
 
 		if (Mathf.Abs(vec.x) < returnThreshhold)

+ 1 - 1
spine-unity/Assets/Examples/Scripts/Goblins.cs

@@ -44,7 +44,7 @@ public class Goblins : MonoBehaviour {
 	}
 
 	// This is called after the animation is applied to the skeleton and can be used to adjust the bones dynamically.
-	public void UpdateLocal (SkeletonAnimation skeletonAnimation) {
+	public void UpdateLocal (SkeletonRenderer skeletonRenderer) {
 		headBone.Rotation += 15;
 	}
 	

+ 55 - 0
spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs

@@ -0,0 +1,55 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * 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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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;
+using UnityEditor;
+using UnityEngine;
+using Spine;
+
+[CustomEditor(typeof(SkeletonAnimator))]
+public class SkeletonAnimatorInspector : SkeletonRendererInspector {
+	protected SerializedProperty animationName, loop, timeScale;
+	protected bool isPrefab;
+
+	protected override void OnEnable () {
+		base.OnEnable();
+		animationName = serializedObject.FindProperty("_animationName");
+		loop = serializedObject.FindProperty("loop");
+		timeScale = serializedObject.FindProperty("timeScale");
+
+		if (PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab)
+			isPrefab = true;
+
+
+	}
+
+	protected override void gui () {
+		base.gui();
+	}
+}

+ 8 - 0
spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6a9ca5213a3a4614c9a9f2e60909bc33
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 95 - 8
spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs

@@ -39,6 +39,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
+using System.IO;
 using Spine;
 
 
@@ -172,7 +173,7 @@ public static class SkeletonBaker {
 			bool newPrefab = false;
 
 			string prefabPath = outputPath + "/" + skeletonDataAsset.skeletonJSON.name + " (" + skin.Name + ").prefab";
-			
+
 
 			Object prefab = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject));
 			if (prefab == null) {
@@ -330,7 +331,7 @@ public static class SkeletonBaker {
 				animator.runtimeAnimatorController = (RuntimeAnimatorController)controller;
 				EditorGUIUtility.PingObject(controller);
 			}
-			
+
 
 			if (newPrefab) {
 				PrefabUtility.ReplacePrefab(prefabRoot, prefab, ReplacePrefabOptions.ConnectToPrefab);
@@ -353,6 +354,92 @@ public static class SkeletonBaker {
 
 	}
 
+	public static void GenerateMecanimAnimationClips (SkeletonDataAsset skeletonDataAsset) {
+		var data = skeletonDataAsset.GetSkeletonData(true);
+		if (data == null) {
+			Debug.LogError("SkeletonData failed!", skeletonDataAsset);
+			return;
+		}
+
+		string dataPath = AssetDatabase.GetAssetPath(skeletonDataAsset);
+		string controllerPath = dataPath.Replace("_SkeletonData", "_Controller").Replace(".asset", ".controller");
+
+
+
+		AnimatorController controller;
+
+		if (skeletonDataAsset.controller != null) {
+			controller = (AnimatorController)skeletonDataAsset.controller;
+		} else {
+			if (File.Exists(controllerPath)) {
+				if (EditorUtility.DisplayDialog("Controller Overwrite Warning", "Unknown Controller already exists at: " + controllerPath, "Update", "Overwrite")) {
+					controller = (AnimatorController)AssetDatabase.LoadAssetAtPath(controllerPath, typeof(RuntimeAnimatorController));
+				} else {
+					controller = (AnimatorController)AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
+				}
+			} else {
+				controller = (AnimatorController)AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
+			}
+			
+		}
+
+		skeletonDataAsset.controller = controller;
+		EditorUtility.SetDirty(skeletonDataAsset);
+
+		Object[] objs = AssetDatabase.LoadAllAssetsAtPath(controllerPath);
+
+		Dictionary<string, AnimationClip> clipTable = new Dictionary<string, AnimationClip>();
+		Dictionary<string, Spine.Animation> animTable = new Dictionary<string, Spine.Animation>();
+
+		foreach (var o in objs) {
+			if (o is AnimationClip) {
+				clipTable.Add(o.name, (AnimationClip)o);
+			}
+		}
+
+		foreach (var anim in data.Animations) {
+			string name = anim.Name;
+			animTable.Add(name, anim);
+
+			if (clipTable.ContainsKey(name) == false) {
+				//generate new dummy clip
+				AnimationClip newClip = new AnimationClip();
+				newClip.name = name;
+				AnimationUtility.SetAnimationType(newClip, ModelImporterAnimationType.Generic);
+				AssetDatabase.AddObjectToAsset(newClip, controller);
+				clipTable.Add(name, newClip);
+			}
+
+			AnimationClip clip = clipTable[name];
+
+			clip.SetCurve("", typeof(GameObject), "dummy", AnimationCurve.Linear(0, 0, anim.Duration, 0));
+			var settings = AnimationUtility.GetAnimationClipSettings(clip);
+			settings.stopTime = anim.Duration;
+
+			SetAnimationSettings(clip, settings);
+
+			AnimationUtility.SetAnimationEvents(clip, new AnimationEvent[0]);
+
+			foreach (Timeline t in anim.Timelines) {
+				if (t is EventTimeline) {
+					ParseEventTimeline((EventTimeline)t, clip, SendMessageOptions.DontRequireReceiver);
+				}
+			}
+
+			EditorUtility.SetDirty(clip);
+
+			clipTable.Remove(name);
+		}
+
+		//clear no longer used animations
+		foreach (var clip in clipTable.Values) {
+			AnimationClip.DestroyImmediate(clip, true);
+		}
+
+		AssetDatabase.Refresh();
+		AssetDatabase.SaveAssets();
+	}
+
 	static Bone extractionBone;
 	static Slot extractionSlot;
 
@@ -656,7 +743,7 @@ public static class SkeletonBaker {
 
 	static AnimationClip ExtractAnimation (string name, SkeletonData skeletonData, Dictionary<int, List<string>> slotLookup, bool bakeIK, SendMessageOptions eventOptions, AnimationClip clip = null) {
 		var animation = skeletonData.FindAnimation(name);
-		
+
 		var timelines = animation.Timelines;
 
 		if (clip == null) {
@@ -709,7 +796,7 @@ public static class SkeletonBaker {
 			} else if (t is AttachmentTimeline) {
 				ParseAttachmentTimeline(skeleton, (AttachmentTimeline)t, slotLookup, clip);
 			} else if (t is EventTimeline) {
-				ParseEventTimeline(skeleton, (EventTimeline)t, clip, eventOptions);
+				ParseEventTimeline((EventTimeline)t, clip, eventOptions);
 			}
 
 		}
@@ -741,7 +828,7 @@ public static class SkeletonBaker {
 		}
 	}
 
-	static void ParseEventTimeline (Skeleton skeleton, EventTimeline timeline, AnimationClip clip, SendMessageOptions eventOptions) {
+	static void ParseEventTimeline (EventTimeline timeline, AnimationClip clip, SendMessageOptions eventOptions) {
 
 		float[] frames = timeline.Frames;
 		var events = timeline.Events;
@@ -755,9 +842,9 @@ public static class SkeletonBaker {
 			ae.functionName = ev.Data.Name;
 			ae.messageOptions = eventOptions;
 
-			if (ev.String != "")
+			if (ev.String != "" && ev.String != null) {
 				ae.stringParameter = ev.String;
-			else {
+			} else {
 				if (ev.Int == 0 && ev.Float == 0) {
 					//do nothing, raw function
 				} else {
@@ -1259,7 +1346,7 @@ public static class SkeletonBaker {
 				listIndex++;
 			} else if (curveType == 1) {
 				//stepped
-				
+
 				Keyframe pk = keys[pIndex];
 
 				float time = frames[f];

+ 38 - 22
spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs

@@ -53,15 +53,16 @@ public class SkeletonDataAssetInspector : Editor {
 	static bool bakeIK = true;
 	static SendMessageOptions bakeEventOptions = SendMessageOptions.DontRequireReceiver;
 
-	private SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
+	private SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix, controller;
 
 	private bool m_initialized = false;
 	private SkeletonDataAsset m_skeletonDataAsset;
 	private SkeletonData m_skeletonData;
 	private string m_skeletonDataAssetGUID;
+	private bool needToSerialize;
 
 	List<string> warnings = new List<string>();
-
+	
 	void OnEnable () {
 
 		SpineEditorUtilities.ConfirmInitialization();
@@ -74,6 +75,7 @@ public class SkeletonDataAssetInspector : Editor {
 			toAnimation = serializedObject.FindProperty("toAnimation");
 			duration = serializedObject.FindProperty("duration");
 			defaultMix = serializedObject.FindProperty("defaultMix");
+			controller = serializedObject.FindProperty("controller");
 
 			m_skeletonDataAsset = (SkeletonDataAsset)target;
 			m_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset));
@@ -103,7 +105,6 @@ public class SkeletonDataAssetInspector : Editor {
 
 	override public void OnInspectorGUI () {
 		serializedObject.Update();
-		SkeletonDataAsset asset = (SkeletonDataAsset)target;
 
 		EditorGUI.BeginChangeCheck();
 		EditorGUILayout.PropertyField(atlasAssets, true);
@@ -126,10 +127,12 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 		if (m_skeletonData != null) {
+			DrawMecanim();
 			DrawAnimationStateInfo();
 			DrawAnimationList();
 			DrawSlotList();
 			DrawBaking();
+			
 		} else {
 
 			DrawReimportButton();
@@ -138,22 +141,24 @@ public class SkeletonDataAssetInspector : Editor {
 				EditorGUILayout.LabelField(new GUIContent(str, SpineEditorUtilities.Icons.warning));
 		}
 
-		if (!Application.isPlaying) {
-			if (serializedObject.ApplyModifiedProperties() ||
-				(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
-					) {
-				asset.Reset();
-			}
+		if(!Application.isPlaying)
+			serializedObject.ApplyModifiedProperties();
+	}
+
+	void DrawMecanim () {
+		EditorGUILayout.PropertyField(controller, new GUIContent("Controller", SpineEditorUtilities.Icons.controllerIcon));		
+		if (controller.objectReferenceValue == null) {
+			if (GUILayout.Button(new GUIContent("Generate Mecanim Controller", SpineEditorUtilities.Icons.controllerIcon), GUILayout.Width(195), GUILayout.Height(20)))
+				SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);
 		}
 	}
 
 	void DrawBaking () {
-		
 		bool pre = showBaking;
 		showBaking = EditorGUILayout.Foldout(showBaking, new GUIContent("Baking", SpineEditorUtilities.Icons.unityIcon));
 		if (pre != showBaking)
 			EditorPrefs.SetBool("SkeletonDataAssetInspector_showBaking", showBaking);
-		
+
 		if (showBaking) {
 			EditorGUI.indentLevel++;
 			bakeAnimations = EditorGUILayout.Toggle("Bake Animations", bakeAnimations);
@@ -170,20 +175,19 @@ public class SkeletonDataAssetInspector : Editor {
 			GUILayout.BeginHorizontal();
 			{
 
-				
+
 				if (GUILayout.Button(new GUIContent("Bake All Skins", SpineEditorUtilities.Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(150)))
 					SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, m_skeletonData.Skins, "", bakeAnimations, bakeIK, bakeEventOptions);
 
 				string skinName = "<No Skin>";
 
 				if (m_skeletonAnimation != null && m_skeletonAnimation.skeleton != null) {
-					
+
 					Skin bakeSkin = m_skeletonAnimation.skeleton.Skin;
-					if (bakeSkin == null){
+					if (bakeSkin == null) {
 						skinName = "Default";
 						bakeSkin = m_skeletonData.Skins[0];
-					}
-					else
+					} else
 						skinName = m_skeletonAnimation.skeleton.Skin.Name;
 
 					bool oops = false;
@@ -209,18 +213,17 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 
-					if(!oops)
+					if (!oops)
 						GUILayout.EndVertical();
 				}
-				
+
 			}
 			GUILayout.EndHorizontal();
 			EditorGUI.indentLevel--;
-
 			EditorGUI.indentLevel--;
 		}
-		
-		
+
+
 	}
 	void DrawReimportButton () {
 		EditorGUI.BeginDisabledGroup(skeletonJSON.objectReferenceValue == null);
@@ -250,6 +253,7 @@ public class SkeletonDataAssetInspector : Editor {
 		if (!showAnimationStateData)
 			return;
 
+		EditorGUI.BeginChangeCheck();
 		EditorGUILayout.PropertyField(defaultMix);
 
 		// Animation names
@@ -282,6 +286,13 @@ public class SkeletonDataAssetInspector : Editor {
 		EditorGUILayout.Space();
 		EditorGUILayout.EndHorizontal();
 
+		if (EditorGUI.EndChangeCheck()) {
+			m_skeletonDataAsset.FillStateData();
+			EditorUtility.SetDirty(m_skeletonDataAsset);
+			serializedObject.ApplyModifiedProperties();
+			needToSerialize = true;
+		}
+			
 	}
 	void DrawAnimationList () {
 		showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
@@ -532,7 +543,7 @@ public class SkeletonDataAssetInspector : Editor {
 			try {
 				string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");
 
-				m_previewInstance = SpineEditorUtilities.SpawnAnimatedSkeleton((SkeletonDataAsset)target, skinName).gameObject;
+				m_previewInstance = SpineEditorUtilities.InstantiateSkeletonAnimation((SkeletonDataAsset)target, skinName).gameObject;
 				m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
 				m_previewInstance.layer = 0x1f;
 
@@ -689,6 +700,11 @@ public class SkeletonDataAssetInspector : Editor {
 		} else {
 			//only needed if using smooth menus
 		}
+
+		if (needToSerialize) {
+			needToSerialize = false;
+			serializedObject.ApplyModifiedProperties();
+		}
 	}
 
 	void DrawSkinToolbar (Rect r) {

+ 144 - 45
spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs

@@ -43,8 +43,6 @@ using System.Linq;
 using System.Reflection;
 using Spine;
 
-using System.Security.Cryptography;
-
 [InitializeOnLoad]
 public class SpineEditorUtilities : AssetPostprocessor {
 
@@ -73,6 +71,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		public static Texture2D hingeChain;
 		public static Texture2D subMeshRenderer;
 		public static Texture2D unityIcon;
+		public static Texture2D controllerIcon;
 
 		public static Mesh boneMesh {
 			get {
@@ -115,7 +114,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 		internal static Material _boneMaterial;
 
-		public static void Initialize() {
+		public static void Initialize () {
 			skeleton = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeleton.png");
 			nullBone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-null.png");
 			bone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-bone.png");
@@ -141,6 +140,8 @@ public class SpineEditorUtilities : AssetPostprocessor {
 			subMeshRenderer = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-subMeshRenderer.png");
 
 			unityIcon = EditorGUIUtility.FindTexture("SceneAsset Icon");
+
+			controllerIcon = EditorGUIUtility.FindTexture("AnimatorController Icon");
 		}
 	}
 
@@ -153,11 +154,11 @@ public class SpineEditorUtilities : AssetPostprocessor {
 	public static string defaultShader = "Spine/Skeleton";
 	public static bool initialized;
 
-	static SpineEditorUtilities() {
+	static SpineEditorUtilities () {
 		Initialize();
 	}
-	
-	static void Initialize(){
+
+	static void Initialize () {
 		DirectoryInfo rootDir = new DirectoryInfo(Application.dataPath);
 		FileInfo[] files = rootDir.GetFiles("SpineEditorUtilities.cs", SearchOption.AllDirectories);
 		editorPath = Path.GetDirectoryName(files[0].FullName.Replace("\\", "/").Replace(Application.dataPath, "Assets"));
@@ -174,13 +175,13 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		HierarchyWindowChanged();
 		initialized = true;
 	}
-	
-	public static void ConfirmInitialization(){
-		if(!initialized || Icons.skeleton == null)
-		Initialize();
+
+	public static void ConfirmInitialization () {
+		if (!initialized || Icons.skeleton == null)
+			Initialize();
 	}
 
-	static void HierarchyWindowChanged() {
+	static void HierarchyWindowChanged () {
 		skeletonRendererTable.Clear();
 		skeletonUtilityBoneTable.Clear();
 
@@ -194,7 +195,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 			skeletonUtilityBoneTable.Add(b.gameObject.GetInstanceID(), b);
 	}
 
-	static void HierarchyWindowItemOnGUI(int instanceId, Rect selectionRect) {
+	static void HierarchyWindowItemOnGUI (int instanceId, Rect selectionRect) {
 		if (skeletonRendererTable.ContainsKey(instanceId)) {
 			Rect r = new Rect(selectionRect);
 			r.x = r.width - 15;
@@ -225,13 +226,10 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 	}
 
-	static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
+	static void OnPostprocessAllAssets (string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
 		ImportSpineContent(imported, false);
 	}
-	public static void ImportSpineContent(string[] imported, bool reimport = false) {
-
-		MD5 md5 = MD5.Create();
-
+	public static void ImportSpineContent (string[] imported, bool reimport = false) {
 		List<string> atlasPaths = new List<string>();
 		List<string> imagePaths = new List<string>();
 		List<string> skeletonPaths = new List<string>();
@@ -277,7 +275,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 				ResetExistingSkeletonData(sp);
 				continue;
 			}
-				
+
 
 			string dir = Path.GetDirectoryName(sp);
 
@@ -319,7 +317,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 							Debug.Log("Skipped importing: " + Path.GetFileName(sp));
 							resolved = true;
 							break;
-						
+
 
 						case 2:
 							//abort
@@ -337,7 +335,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		//TODO:  any post processing of images
 	}
 
-	static bool CheckForValidSkeletonData(string skeletonJSONPath) {
+	static bool CheckForValidSkeletonData (string skeletonJSONPath) {
 
 		string dir = Path.GetDirectoryName(skeletonJSONPath);
 		TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
@@ -377,14 +375,30 @@ public class SpineEditorUtilities : AssetPostprocessor {
 						Selection.activeObject = null;
 
 					skeletonDataAsset.Reset();
-				}
 					
+					string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(skeletonDataAsset));
+					string lastHash = EditorPrefs.GetString(guid + "_hash");
+
+					if (lastHash != skeletonDataAsset.GetSkeletonData(true).Hash) {
+						//do any upkeep on synchronized assets
+						UpdateMecanimClips(skeletonDataAsset);
+					}
+
+					EditorPrefs.SetString(guid + "_hash", skeletonDataAsset.GetSkeletonData(true).Hash);
+				}
 			}
 		}
 	}
 
+	static void UpdateMecanimClips (SkeletonDataAsset skeletonDataAsset) {
+		if (skeletonDataAsset.controller == null)
+			return;
+
+		SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
+	}
+
 
-	static bool CheckForValidAtlas(string atlasPath) {
+	static bool CheckForValidAtlas (string atlasPath) {
 
 		string dir = Path.GetDirectoryName(atlasPath);
 		TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
@@ -405,7 +419,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return false;
 	}
 
-	static List<AtlasAsset> MultiAtlasDialog(List<string> requiredPaths, string initialDirectory, string header = "") {
+	static List<AtlasAsset> MultiAtlasDialog (List<string> requiredPaths, string initialDirectory, string header = "") {
 
 		List<AtlasAsset> atlasAssets = new List<AtlasAsset>();
 
@@ -422,7 +436,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 				sb.AppendLine("\t" + atlasAssets[i].name);
 			}
 
-				sb.AppendLine();
+			sb.AppendLine();
 			sb.AppendLine("Missing Regions:");
 
 			List<string> missingRegions = new List<string>(requiredPaths);
@@ -447,7 +461,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 			int result = EditorUtility.DisplayDialogComplex("Atlas Selection", sb.ToString(), "Select", "Finish", "Abort");
 
-			switch(result){
+			switch (result) {
 				case 0:
 					AtlasAsset selectedAtlasAsset = GetAtlasDialog(lastAtlasPath);
 					if (selectedAtlasAsset != null) {
@@ -476,12 +490,12 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 
 		}
-		
+
 
 		return atlasAssets;
 	}
 
-	static AtlasAsset GetAtlasDialog(string dirPath) {
+	static AtlasAsset GetAtlasDialog (string dirPath) {
 		string path = EditorUtility.OpenFilePanel("Select AtlasAsset...", dirPath, "asset");
 		if (path == "")
 			return null;
@@ -497,7 +511,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return (AtlasAsset)obj;
 	}
 
-	public static List<string> GetRequiredAtlasRegions(string jsonPath) {
+	public static List<string> GetRequiredAtlasRegions (string jsonPath) {
 		List<string> requiredPaths = new List<string>();
 
 		TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(jsonPath, typeof(TextAsset));
@@ -523,7 +537,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 		return requiredPaths;
 	}
-	static AtlasAsset GetMatchingAtlas(List<string> requiredPaths, List<AtlasAsset> atlasAssets) {
+	static AtlasAsset GetMatchingAtlas (List<string> requiredPaths, List<AtlasAsset> atlasAssets) {
 		AtlasAsset atlasAssetMatch = null;
 
 		foreach (AtlasAsset a in atlasAssets) {
@@ -546,7 +560,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return atlasAssetMatch;
 	}
 
-	static List<AtlasAsset> FindAtlasesAtPath(string path) {
+	static List<AtlasAsset> FindAtlasesAtPath (string path) {
 		List<AtlasAsset> arr = new List<AtlasAsset>();
 
 		DirectoryInfo dir = new DirectoryInfo(path);
@@ -568,7 +582,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return arr;
 	}
 
-	public static bool IsSpineJSON(TextAsset asset) {
+	public static bool IsSpineJSON (TextAsset asset) {
 		object obj = Json.Deserialize(new StringReader(asset.text));
 		if (obj == null) {
 			Debug.LogError("Is not valid JSON");
@@ -588,7 +602,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return true;
 	}
 
-	static AtlasAsset IngestSpineAtlas(TextAsset atlasText) {
+	static AtlasAsset IngestSpineAtlas (TextAsset atlasText) {
 		if (atlasText == null) {
 			Debug.LogWarning("Atlas source cannot be null!");
 			return null;
@@ -666,7 +680,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset));
 	}
 
-	static SkeletonDataAsset IngestSpineProject(TextAsset spineJson, params AtlasAsset[] atlasAssets) {
+	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";
@@ -699,20 +713,20 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		}
 	}
 
-	[MenuItem("Assets/Spine/Spawn")]
-	static void SpawnAnimatedSkeleton() {
+	[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)")]
+	static void InstantiateSkeletonAnimation () {
 		Object[] arr = Selection.objects;
 		foreach (Object o in arr) {
 			string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
 			string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");
 
-			SpawnAnimatedSkeleton((SkeletonDataAsset)o, skinName);
+			InstantiateSkeletonAnimation((SkeletonDataAsset)o, skinName);
 			SceneView.RepaintAll();
 		}
 	}
 
-	[MenuItem("Assets/Spine/Spawn", true)]
-	static bool ValidateSpawnAnimatedSkeleton() {
+	[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", true)]
+	static bool ValidateInstantiateSkeletonAnimation () {
 		Object[] arr = Selection.objects;
 
 		if (arr.Length == 0)
@@ -726,11 +740,11 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return true;
 	}
 
-	public static SkeletonAnimation SpawnAnimatedSkeleton(SkeletonDataAsset skeletonDataAsset, string skinName) {
-		return SpawnAnimatedSkeleton(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
+	public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, string skinName) {
+		return InstantiateSkeletonAnimation(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
 	}
 
-	public static SkeletonAnimation SpawnAnimatedSkeleton(SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
+	public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
 		GameObject go = new GameObject(skeletonDataAsset.name.Replace("_SkeletonData", ""), typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
 		SkeletonAnimation anim = go.GetComponent<SkeletonAnimation>();
 		anim.skeletonDataAsset = skeletonDataAsset;
@@ -746,18 +760,18 @@ public class SpineEditorUtilities : AssetPostprocessor {
 			}
 		}
 
-		
+
 
 		anim.calculateNormals = requiresNormals;
 
 		SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
 
 		if (data == null) {
-			for(int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++){
+			for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
 				string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
 				skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
-			}			
-			
+			}
+
 			data = skeletonDataAsset.GetSkeletonData(true);
 		}
 
@@ -779,4 +793,89 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 		return anim;
 	}
+
+	[MenuItem("Assets/Spine/Instantiate (Mecanim)")]
+	static void InstantiateSkeletonAnimator () {
+		Object[] arr = Selection.objects;
+		foreach (Object o in arr) {
+			string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
+			string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");
+
+			InstantiateSkeletonAnimator((SkeletonDataAsset)o, skinName);
+			SceneView.RepaintAll();
+		}
+	}
+
+	[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", true)]
+	static bool ValidateInstantiateSkeletonAnimator () {
+		Object[] arr = Selection.objects;
+
+		if (arr.Length == 0)
+			return false;
+
+		foreach (Object o in arr) {
+			if (o.GetType() != typeof(SkeletonDataAsset))
+				return false;
+		}
+
+		return true;
+	}
+
+	public static SkeletonAnimator InstantiateSkeletonAnimator (SkeletonDataAsset skeletonDataAsset, string skinName) {
+		return InstantiateSkeletonAnimator(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
+	}
+
+	public static SkeletonAnimator InstantiateSkeletonAnimator (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
+		GameObject go = new GameObject(skeletonDataAsset.name.Replace("_SkeletonData", ""), typeof(MeshFilter), typeof(MeshRenderer), typeof(Animator), typeof(SkeletonAnimator));
+
+		if(skeletonDataAsset.controller == null){
+			SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
+		}
+
+		go.GetComponent<Animator>().runtimeAnimatorController = skeletonDataAsset.controller;
+
+		SkeletonAnimator anim = go.GetComponent<SkeletonAnimator>();
+		anim.skeletonDataAsset = skeletonDataAsset;
+
+		bool requiresNormals = false;
+
+		foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
+			foreach (Material m in atlasAsset.materials) {
+				if (m.shader.name.Contains("Lit")) {
+					requiresNormals = true;
+					break;
+				}
+			}
+		}
+
+		anim.calculateNormals = requiresNormals;
+
+		SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+
+		if (data == null) {
+			for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
+				string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
+				skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
+			}
+
+			data = skeletonDataAsset.GetSkeletonData(true);
+		}
+
+		if (skin == null)
+			skin = data.DefaultSkin;
+
+		if (skin == null)
+			skin = data.Skins[0];
+
+		anim.Reset();
+
+		anim.skeleton.SetSkin(skin);
+		anim.initialSkinName = skin.Name;
+
+		anim.skeleton.Update(1);
+		anim.skeleton.UpdateWorldTransform();
+		anim.LateUpdate();
+
+		return anim;
+	}
 }

+ 27 - 11
spine-unity/Assets/spine-unity/SkeletonAnimation.cs

@@ -36,16 +36,32 @@ using Spine;
 
 [ExecuteInEditMode]
 [AddComponentMenu("Spine/SkeletonAnimation")]
-public class SkeletonAnimation : SkeletonRenderer {
+public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation {
 	public float timeScale = 1;
 	public bool loop;
 	public Spine.AnimationState state;
 
-	public delegate void UpdateBonesDelegate (SkeletonAnimation skeleton);
 
-	public UpdateBonesDelegate UpdateLocal;
-	public UpdateBonesDelegate UpdateWorld;
-	public UpdateBonesDelegate UpdateComplete;
+
+	public event UpdateBonesDelegate UpdateLocal {
+		add { _UpdateLocal += value; }
+		remove { _UpdateLocal -= value; }
+	}
+
+	public event UpdateBonesDelegate UpdateWorld {
+		add { _UpdateWorld += value; }
+		remove { _UpdateWorld -= value; }
+	}
+
+	public event UpdateBonesDelegate UpdateComplete {
+		add { _UpdateComplete += value; }
+		remove { _UpdateComplete -= value; }
+	}
+
+	protected event UpdateBonesDelegate _UpdateLocal;
+	protected event UpdateBonesDelegate _UpdateWorld;
+	protected event UpdateBonesDelegate _UpdateComplete;
+
 	[SerializeField]
 	private String
 		_animationName;
@@ -91,18 +107,18 @@ public class SkeletonAnimation : SkeletonRenderer {
 		state.Update(deltaTime);
 		state.Apply(skeleton);
 
-		if (UpdateLocal != null) 
-			UpdateLocal(this);
+		if (_UpdateLocal != null) 
+			_UpdateLocal(this);
 
 		skeleton.UpdateWorldTransform();
 
-		if (UpdateWorld != null) { 
-			UpdateWorld(this);
+		if (_UpdateWorld != null) { 
+			_UpdateWorld(this);
 			skeleton.UpdateWorldTransform();
 		}
 
-		if (UpdateComplete != null) { 
-			UpdateComplete(this);
+		if (_UpdateComplete != null) { 
+			_UpdateComplete(this);
 		}
 	}
 }

+ 11 - 0
spine-unity/Assets/spine-unity/SkeletonAnimationInterface.cs

@@ -0,0 +1,11 @@
+using UnityEngine;
+using System.Collections;
+
+public delegate void UpdateBonesDelegate (SkeletonRenderer skeletonRenderer);
+public interface ISkeletonAnimation {
+	event UpdateBonesDelegate UpdateLocal;
+	event UpdateBonesDelegate UpdateWorld;
+	event UpdateBonesDelegate UpdateComplete;
+
+	void LateUpdate ();
+}

+ 8 - 0
spine-unity/Assets/spine-unity/SkeletonAnimationInterface.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a7b480b941568134891f411137bfbf55
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 100 - 0
spine-unity/Assets/spine-unity/SkeletonAnimator.cs

@@ -0,0 +1,100 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+using Spine;
+
+[RequireComponent(typeof(Animator))]
+public class SkeletonAnimator : SkeletonRenderer, ISkeletonAnimation {
+
+	public event UpdateBonesDelegate UpdateLocal {
+		add { _UpdateLocal += value; }
+		remove { _UpdateLocal -= value; }
+	}
+
+	public event UpdateBonesDelegate UpdateWorld {
+		add { _UpdateWorld += value; }
+		remove { _UpdateWorld -= value; }
+	}
+
+	public event UpdateBonesDelegate UpdateComplete {
+		add { _UpdateComplete += value; }
+		remove { _UpdateComplete -= value; }
+	}
+
+	protected event UpdateBonesDelegate _UpdateLocal;
+	protected event UpdateBonesDelegate _UpdateWorld;
+	protected event UpdateBonesDelegate _UpdateComplete;
+
+	Dictionary<string, Spine.Animation> animationTable = new Dictionary<string, Spine.Animation>();
+	Animator animator;
+
+	public override void Reset () {
+		base.Reset();
+		if (!valid)
+			return;
+
+		animationTable.Clear();
+
+		var data = skeletonDataAsset.GetSkeletonData(true);
+
+		foreach (var a in data.Animations) {
+			animationTable.Add(a.Name, a);
+		}
+
+		animator = GetComponent<Animator>();
+	}
+
+	void Update () {
+		if (skeleton == null)
+			return;
+
+		skeleton.Update(Time.deltaTime);
+
+		//apply
+		int layerCount = animator.layerCount;
+		float deltaTime = Time.deltaTime;
+		for (int i = 0; i < layerCount; i++) {
+
+			float layerWeight = animator.GetLayerWeight(i);
+			if (i == 0)
+				layerWeight = 1;
+
+			var stateInfo = animator.GetCurrentAnimatorStateInfo(i);
+			var clipInfo = animator.GetCurrentAnimationClipState(i);
+			var nextStateInfo = animator.GetNextAnimatorStateInfo(i);
+			var nextClipInfo = animator.GetNextAnimationClipState(i);
+
+			foreach (var info in clipInfo) {
+				float weight = info.weight * layerWeight;
+				if (weight == 0)
+					continue;
+
+				float time = stateInfo.normalizedTime * info.clip.length;
+				animationTable[info.clip.name].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, null, weight);
+			}
+
+			foreach (var info in nextClipInfo) {
+				float weight = info.weight * layerWeight;
+				if (weight == 0)
+					continue;
+
+				float time = nextStateInfo.normalizedTime * info.clip.length;
+				animationTable[info.clip.name].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, null, weight);
+			}
+		}
+
+		if (_UpdateLocal != null)
+			_UpdateLocal(this);
+
+		skeleton.UpdateWorldTransform();
+
+		if (_UpdateWorld != null) {
+			_UpdateWorld(this);
+			skeleton.UpdateWorldTransform();
+		}
+
+		if (_UpdateComplete != null) {
+			_UpdateComplete(this);
+		}
+	}
+}

+ 8 - 0
spine-unity/Assets/spine-unity/SkeletonAnimator.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f9db98c60740638449864eb028fbe7ad
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 10 - 5
spine-unity/Assets/spine-unity/SkeletonDataAsset.cs

@@ -42,6 +42,7 @@ public class SkeletonDataAsset : ScriptableObject {
 	public String[] toAnimation;
 	public float[] duration;
 	public float defaultMix;
+	public RuntimeAnimatorController controller;
 	private SkeletonData skeletonData;
 	private AnimationStateData stateData;
 
@@ -86,9 +87,6 @@ public class SkeletonDataAsset : ScriptableObject {
 			}
 		}
 
-
-
-
 		if (skeletonData != null)
 			return skeletonData;
 
@@ -103,14 +101,21 @@ public class SkeletonDataAsset : ScriptableObject {
 		}
 
 		stateData = new AnimationStateData(skeletonData);
+		FillStateData();
+
+		return skeletonData;
+	}
+
+	public void FillStateData () {
+		if (stateData == null)
+			return;
+
 		stateData.DefaultMix = defaultMix;
 		for (int i = 0, n = fromAnimation.Length; i < n; i++) {
 			if (fromAnimation[i].Length == 0 || toAnimation[i].Length == 0)
 				continue;
 			stateData.SetMix(fromAnimation[i], toAnimation[i], duration[i]);
 		}
-
-		return skeletonData;
 	}
 
 	public AnimationStateData GetAnimationStateData() {

+ 7 - 5
spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs

@@ -38,7 +38,7 @@ using System.Collections;
 using System.Collections.Generic;
 using Spine;
 
-[RequireComponent(typeof(SkeletonAnimation))]
+[RequireComponent(typeof(ISkeletonAnimation))]
 [ExecuteInEditMode]
 public class SkeletonUtility : MonoBehaviour {
 
@@ -80,7 +80,7 @@ public class SkeletonUtility : MonoBehaviour {
 	[HideInInspector]
 	public SkeletonRenderer skeletonRenderer;
 	[HideInInspector]
-	public SkeletonAnimation skeletonAnimation;
+	public ISkeletonAnimation skeletonAnimation;
 	[System.NonSerialized]
 	public List<SkeletonUtilityBone> utilityBones = new List<SkeletonUtilityBone>();
 	[System.NonSerialized]
@@ -98,6 +98,8 @@ public class SkeletonUtility : MonoBehaviour {
 
 		if (skeletonAnimation == null) {
 			skeletonAnimation = GetComponent<SkeletonAnimation>();
+			if (skeletonAnimation == null)
+				skeletonAnimation = GetComponent<SkeletonAnimator>();
 		}
 
 		skeletonRenderer.OnReset -= HandleRendererReset;
@@ -209,7 +211,7 @@ public class SkeletonUtility : MonoBehaviour {
 
 	}
 
-	void UpdateLocal (SkeletonAnimation anim) {
+	void UpdateLocal (SkeletonRenderer anim) {
 
 		if (needToReprocessBones)
 			CollectBones();
@@ -224,14 +226,14 @@ public class SkeletonUtility : MonoBehaviour {
 		UpdateAllBones();
 	}
 
-	void UpdateWorld (SkeletonAnimation anim) {
+	void UpdateWorld (SkeletonRenderer anim) {
 		UpdateAllBones();
 
 		foreach (SkeletonUtilityConstraint c in utilityConstraints)
 			c.DoUpdate();
 	}
 
-	void UpdateComplete (SkeletonAnimation anim) {
+	void UpdateComplete (SkeletonRenderer anim) {
 		UpdateAllBones();
 	}