diff --git a/spine-unity/Assets/spine-unity/AtlasAsset.cs b/spine-unity/Assets/spine-unity/AtlasAsset.cs index 6a3980f0d..64d3cddaa 100644 --- a/spine-unity/Assets/spine-unity/AtlasAsset.cs +++ b/spine-unity/Assets/spine-unity/AtlasAsset.cs @@ -70,6 +70,77 @@ public class AtlasAsset : ScriptableObject { return null; } } + + public Sprite GenerateSprite (string name, out Material material) { + AtlasRegion region = atlas.FindRegion(name); + + Sprite sprite = null; + material = null; + + if (region != null) { + //sprite.rect + } + + return sprite; + } + + public Mesh GenerateMesh(string name, Mesh mesh, out Material material, float scale = 0.01f){ + AtlasRegion region = atlas.FindRegion(name); + material = null; + if (region != null) { + if (mesh == null) { + mesh = new Mesh(); + mesh.name = name; + } + + Vector3[] verts = new Vector3[4]; + Vector2[] uvs = new Vector2[4]; + Color[] colors = new Color[4]{Color.white, Color.white, Color.white, Color.white}; + int[] triangles = new int[6] { 0, 1, 2, 2, 3, 0 }; + + float left, right, top, bottom; + left = region.width / -2f; + right = left * -1f; + top = region.height / 2f; + bottom = top * -1; + + verts[0] = new Vector3(left, bottom, 0) * scale; + verts[1] = new Vector3(left, top, 0) * scale; + verts[2] = new Vector3(right, top, 0) * scale; + verts[3] = new Vector3(right, bottom, 0) * scale; + float u, v, u2, v2; + u = region.u; + v = region.v; + u2 = region.u2; + v2 = region.v2; + + if (!region.rotate) { + uvs[0] = new Vector2(u, v2); + uvs[1] = new Vector2(u, v); + uvs[2] = new Vector2(u2, v); + uvs[3] = new Vector2(u2, v2); + } else { + uvs[0] = new Vector2(u2, v2); + uvs[1] = new Vector2(u, v2); + uvs[2] = new Vector2(u, v); + uvs[3] = new Vector2(u2, v); + } + + mesh.triangles = new int[0]; + mesh.vertices = verts; + mesh.uv = uvs; + mesh.colors = colors; + mesh.triangles = triangles; + mesh.RecalculateNormals(); + mesh.RecalculateBounds(); + + material = (Material)region.page.rendererObject; + } else { + mesh = null; + } + + return mesh; + } } public class MaterialsTextureLoader : TextureLoader { diff --git a/spine-unity/Assets/spine-unity/Editor/AtlasAssetInspector.cs b/spine-unity/Assets/spine-unity/Editor/AtlasAssetInspector.cs index 055d50cce..93bab1a35 100644 --- a/spine-unity/Assets/spine-unity/Editor/AtlasAssetInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/AtlasAssetInspector.cs @@ -34,6 +34,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.IO; using UnityEditor; using UnityEngine; using Spine; @@ -42,13 +43,43 @@ using Spine; [CustomEditor(typeof(AtlasAsset))] public class AtlasAssetInspector : Editor { private SerializedProperty atlasFile, materials; + private AtlasAsset atlasAsset; + private List baked; + private List bakedObjects; void OnEnable () { SpineEditorUtilities.ConfirmInitialization(); atlasFile = serializedObject.FindProperty("atlasFile"); materials = serializedObject.FindProperty("materials"); + atlasAsset = (AtlasAsset)target; + UpdateBakedList(); } + void UpdateBakedList () { + AtlasAsset asset = (AtlasAsset)target; + baked = new List(); + bakedObjects = new List(); + if (atlasFile.objectReferenceValue != null) { + Atlas atlas = asset.GetAtlas(); + FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic); + List regions = (List)field.GetValue(atlas); + string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); + string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath); + string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name); + + + for (int i = 0; i < regions.Count; i++) { + AtlasRegion region = regions[i]; + string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/"); + GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject)); + baked.Add(prefab != null); + bakedObjects.Add(prefab); + } + } + } + + + override public void OnInspectorGUI () { serializedObject.Update(); AtlasAsset asset = (AtlasAsset)target; @@ -72,22 +103,71 @@ public class AtlasAssetInspector : Editor { return; } } - - if (atlasFile.objectReferenceValue != null) { Atlas atlas = asset.GetAtlas(); FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic); List regions = (List)field.GetValue(atlas); - EditorGUILayout.LabelField("Regions"); + EditorGUILayout.LabelField(new GUIContent("Region Baking", SpineEditorUtilities.Icons.unityIcon)); EditorGUI.indentLevel++; + AtlasPage lastPage = null; for (int i = 0; i < regions.Count; i++) { - EditorGUILayout.LabelField(regions[i].name); + if (lastPage != regions[i].page) { + if (lastPage != null) { + EditorGUILayout.Separator(); + EditorGUILayout.Separator(); + } + lastPage = regions[i].page; + Material mat = ((Material)lastPage.rendererObject); + if (mat != null) { + GUILayout.BeginHorizontal(); + { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250)); + EditorGUI.EndDisabledGroup(); + } + GUILayout.EndHorizontal(); + + } else { + EditorGUILayout.LabelField(new GUIContent("Page missing material!", SpineEditorUtilities.Icons.warning)); + } + } + GUILayout.BeginHorizontal(); + { + //EditorGUILayout.ToggleLeft(baked[i] ? "" : regions[i].name, baked[i]); + bool result = baked[i] ? EditorGUILayout.ToggleLeft("", baked[i], GUILayout.Width(24)) : EditorGUILayout.ToggleLeft(" " + regions[i].name, baked[i]); + if(baked[i]){ + EditorGUILayout.ObjectField(bakedObjects[i], typeof(GameObject), false, GUILayout.Width(250)); + } + if (result && !baked[i]) { + //bake + baked[i] = true; + bakedObjects[i] = SpineEditorUtilities.BakeRegion(atlasAsset, regions[i]); + EditorGUIUtility.PingObject(bakedObjects[i]); + } else if (!result && baked[i]) { + //unbake + bool unbakeResult = EditorUtility.DisplayDialog("Delete Baked Region", "Do you want to delete the prefab for " + regions[i].name, "Yes", "Cancel"); + switch (unbakeResult) { + case true: + //delete + string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); + string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath); + string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name); + string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(regions[i]) + ".prefab").Replace("\\", "/"); + AssetDatabase.DeleteAsset(bakedPrefabPath); + baked[i] = false; + break; + case false: + //do nothing + break; + } + } + } + GUILayout.EndHorizontal(); } EditorGUI.indentLevel--; } - if (serializedObject.ApplyModifiedProperties() || (UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed") ) { diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs index 168c14472..382fa45d5 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs @@ -856,6 +856,7 @@ public static class SkeletonBaker { var ev = events[i]; AnimationEvent ae = new AnimationEvent(); + //TODO: Deal with Mecanim's zero-time missed event ae.time = frames[i]; ae.functionName = ev.Data.Name; ae.messageOptions = eventOptions; @@ -1457,4 +1458,4 @@ public static class SkeletonBaker { return GetPathRecurse(b.Parent) + "/" + b.Name; } -} \ No newline at end of file +} diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index 0cf7e6416..733c83093 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -426,7 +426,9 @@ public class SpineEditorUtilities : AssetPostprocessor { static bool CheckForValidAtlas (string atlasPath) { - + return false; + //////////////DEPRECATED - always check for new atlas data now + /* string dir = Path.GetDirectoryName(atlasPath); TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset)); DirectoryInfo dirInfo = new DirectoryInfo(dir); @@ -438,12 +440,37 @@ public class SpineEditorUtilities : AssetPostprocessor { var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object)); if (obj is AtlasAsset) { var atlasAsset = (AtlasAsset)obj; - if (atlasAsset.atlasFile == textAsset) + if (atlasAsset.atlasFile == textAsset) { + + + Atlas atlas = atlasAsset.GetAtlas(); + FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic); + List regions = (List)field.GetValue(atlas); + string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); + string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath); + string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name); + + for (int i = 0; i < regions.Count; i++) { + AtlasRegion region = regions[i]; + string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/"); + GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject)); + + if (prefab != null) { + Debug.Log("Updating: " + region.name); + BakeRegion(atlasAsset, region); + } + } + + return true; + } + } } return false; + + */ } static List MultiAtlasDialog (List requiredPaths, string initialDirectory, string header = "") { @@ -657,9 +684,14 @@ public class SpineEditorUtilities : AssetPostprocessor { AtlasAsset atlasAsset = (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset)); + List vestigialMaterials = new List(); if (atlasAsset == null) atlasAsset = AtlasAsset.CreateInstance(); + else { + foreach (Material m in atlasAsset.materials) + vestigialMaterials.Add(m); + } atlasAsset.atlasFile = atlasText; @@ -702,6 +734,8 @@ public class SpineEditorUtilities : AssetPostprocessor { if (mat == null) { mat = new Material(Shader.Find(defaultShader)); AssetDatabase.CreateAsset(mat, materialPath); + } else { + vestigialMaterials.Remove(mat); } mat.mainTexture = texture; @@ -712,16 +746,96 @@ public class SpineEditorUtilities : AssetPostprocessor { atlasAsset.materials[i] = mat; } + for (int i = 0; i < vestigialMaterials.Count; i++) + AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(vestigialMaterials[i])); + if (AssetDatabase.GetAssetPath(atlasAsset) == "") AssetDatabase.CreateAsset(atlasAsset, atlasPath); else atlasAsset.Reset(); + EditorUtility.SetDirty(atlasAsset); + AssetDatabase.SaveAssets(); + + //iterate regions and bake marked + Atlas atlas = atlasAsset.GetAtlas(); + FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic); + List regions = (List)field.GetValue(atlas); + string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); + string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath); + string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name); + + bool hasBakedRegions = false; + for (int i = 0; i < regions.Count; i++) { + AtlasRegion region = regions[i]; + string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/"); + GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject)); + + if (prefab != null) { + BakeRegion(atlasAsset, region, false); + hasBakedRegions = true; + } + } + + if (hasBakedRegions) { + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + return (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset)); } + public static GameObject BakeRegion (AtlasAsset atlasAsset, AtlasRegion region, bool autoSave = true) { + Atlas atlas = atlasAsset.GetAtlas(); + string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); + string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath); + string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name); + string bakedPrefabPath = Path.Combine(bakedDirPath, GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/"); + + GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject)); + GameObject root; + Mesh mesh; + bool isNewPrefab = false; + + if (!Directory.Exists(bakedDirPath)) + Directory.CreateDirectory(bakedDirPath); + + if (prefab == null) { + root = new GameObject("temp", typeof(MeshFilter), typeof(MeshRenderer)); + prefab = (GameObject)PrefabUtility.CreatePrefab(bakedPrefabPath, root); + isNewPrefab = true; + Object.DestroyImmediate(root); + } + + mesh = (Mesh)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(Mesh)); + + Material mat = null; + mesh = atlasAsset.GenerateMesh(region.name, mesh, out mat); + if (isNewPrefab) { + AssetDatabase.AddObjectToAsset(mesh, prefab); + prefab.GetComponent().sharedMesh = mesh; + } + + EditorUtility.SetDirty(mesh); + EditorUtility.SetDirty(prefab); + + if (autoSave) { + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + + prefab.GetComponent().sharedMaterial = mat; + + return prefab; + } + + public static string GetPathSafeRegionName (AtlasRegion region) { + return region.name.Replace("/", "_"); + } + static SkeletonDataAsset IngestSpineProject (TextAsset spineJson, params AtlasAsset[] atlasAssets) { string primaryName = Path.GetFileNameWithoutExtension(spineJson.name); string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));