diff --git a/spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs b/spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs index c638f696e..6fa3ea5be 100644 --- a/spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs +++ b/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) diff --git a/spine-unity/Assets/Examples/Scripts/Goblins.cs b/spine-unity/Assets/Examples/Scripts/Goblins.cs index bf3a78079..56a54007c 100644 --- a/spine-unity/Assets/Examples/Scripts/Goblins.cs +++ b/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; } diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs new file mode 100644 index 000000000..dfbac484c --- /dev/null +++ b/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(); + } +} diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs.meta b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs.meta new file mode 100644 index 000000000..35b472565 --- /dev/null +++ b/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: diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs index 2e02866b5..8eddefd85 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs +++ b/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 clipTable = new Dictionary(); + Dictionary animTable = new Dictionary(); + + 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> 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]; diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs index 5394c75e7..8880a09e7 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs +++ b/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 warnings = new List(); - + 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 = ""; 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) { diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index 0ed1a3752..c1ebb804f 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/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 atlasPaths = new List(); List imagePaths = new List(); List skeletonPaths = new List(); @@ -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; - static bool CheckForValidAtlas(string atlasPath) { + SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset); + } + + + 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 MultiAtlasDialog(List requiredPaths, string initialDirectory, string header = "") { + static List MultiAtlasDialog (List requiredPaths, string initialDirectory, string header = "") { List atlasAssets = new List(); @@ -422,7 +436,7 @@ public class SpineEditorUtilities : AssetPostprocessor { sb.AppendLine("\t" + atlasAssets[i].name); } - sb.AppendLine(); + sb.AppendLine(); sb.AppendLine("Missing Regions:"); List missingRegions = new List(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 GetRequiredAtlasRegions(string jsonPath) { + public static List GetRequiredAtlasRegions (string jsonPath) { List requiredPaths = new List(); TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(jsonPath, typeof(TextAsset)); @@ -523,7 +537,7 @@ public class SpineEditorUtilities : AssetPostprocessor { return requiredPaths; } - static AtlasAsset GetMatchingAtlas(List requiredPaths, List atlasAssets) { + static AtlasAsset GetMatchingAtlas (List requiredPaths, List atlasAssets) { AtlasAsset atlasAssetMatch = null; foreach (AtlasAsset a in atlasAssets) { @@ -546,7 +560,7 @@ public class SpineEditorUtilities : AssetPostprocessor { return atlasAssetMatch; } - static List FindAtlasesAtPath(string path) { + static List FindAtlasesAtPath (string path) { List arr = new List(); 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(); 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().runtimeAnimatorController = skeletonDataAsset.controller; + + SkeletonAnimator anim = go.GetComponent(); + 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; + } } \ No newline at end of file diff --git a/spine-unity/Assets/spine-unity/SkeletonAnimation.cs b/spine-unity/Assets/spine-unity/SkeletonAnimation.cs index 0c4c9a44f..07ebca526 100644 --- a/spine-unity/Assets/spine-unity/SkeletonAnimation.cs +++ b/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); } } } diff --git a/spine-unity/Assets/spine-unity/SkeletonAnimationInterface.cs b/spine-unity/Assets/spine-unity/SkeletonAnimationInterface.cs new file mode 100644 index 000000000..e06b3085c --- /dev/null +++ b/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 (); +} \ No newline at end of file diff --git a/spine-unity/Assets/spine-unity/SkeletonAnimationInterface.cs.meta b/spine-unity/Assets/spine-unity/SkeletonAnimationInterface.cs.meta new file mode 100644 index 000000000..6572a198f --- /dev/null +++ b/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: diff --git a/spine-unity/Assets/spine-unity/SkeletonAnimator.cs b/spine-unity/Assets/spine-unity/SkeletonAnimator.cs new file mode 100644 index 000000000..f218ae89a --- /dev/null +++ b/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 animationTable = new Dictionary(); + 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(); + } + + 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); + } + } +} diff --git a/spine-unity/Assets/spine-unity/SkeletonAnimator.cs.meta b/spine-unity/Assets/spine-unity/SkeletonAnimator.cs.meta new file mode 100644 index 000000000..91dba98cc --- /dev/null +++ b/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: diff --git a/spine-unity/Assets/spine-unity/SkeletonDataAsset.cs b/spine-unity/Assets/spine-unity/SkeletonDataAsset.cs index 06afb8275..327ba4640 100644 --- a/spine-unity/Assets/spine-unity/SkeletonDataAsset.cs +++ b/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() { diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs index c274c3db6..d880addb2 100644 --- a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs +++ b/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 utilityBones = new List(); [System.NonSerialized] @@ -98,6 +98,8 @@ public class SkeletonUtility : MonoBehaviour { if (skeletonAnimation == null) { skeletonAnimation = GetComponent(); + if (skeletonAnimation == null) + skeletonAnimation = GetComponent(); } 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(); }