[unity/tk2d] Update editor code. Some cleanup and fixes. (#608)

* Initial cleanup and review.

* More cleanup and review.

* Removed missing atlas warnings for TK2D. Better button labels. Separated specialized/utility shaders.

* Clearer wording. More checks.

* Fix for Inspector reflowing between GUI events.

* Basic TK2D warning message.

* Fix play buttons. Removed useless reimport button for TK2D. Minor fixes.
This commit is contained in:
John 2016-06-10 16:40:10 +08:00 committed by GitHub
parent 2a6e2b8dfc
commit 5cceb22406
8 changed files with 478 additions and 511 deletions

View File

@ -24,20 +24,21 @@ namespace Spine.Unity.Editor {
static bool showAttachments = false;
#if SPINE_BAKING
static bool showBaking = false;
static bool isBakingExpanded = false;
static bool bakeAnimations = true;
static bool bakeIK = true;
static SendMessageOptions bakeEventOptions = SendMessageOptions.DontRequireReceiver;
const string ShowBakingPrefsKey = "SkeletonDataAssetInspector_showUnity";
#endif
SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
#if SPINE_SKELETON_ANIMATOR
static bool showMecanim = false;
SerializedProperty controller;
#if SPINE_TK2D
SerializedProperty spriteCollection;
#endif
#if SPINE_TK2D
private SerializedProperty spriteCollection;
#if SPINE_SKELETON_ANIMATOR
static bool isMecanimExpanded = false;
SerializedProperty controller;
#endif
bool m_initialized = false;
@ -48,42 +49,48 @@ namespace Spine.Unity.Editor {
List<string> warnings = new List<string>();
GUIStyle activePlayButtonStyle, idlePlayButtonStyle;
void OnEnable () {
SpineEditorUtilities.ConfirmInitialization();
atlasAssets = serializedObject.FindProperty("atlasAssets");
atlasAssets.isExpanded = true;
skeletonJSON = serializedObject.FindProperty("skeletonJSON");
scale = serializedObject.FindProperty("scale");
fromAnimation = serializedObject.FindProperty("fromAnimation");
toAnimation = serializedObject.FindProperty("toAnimation");
duration = serializedObject.FindProperty("duration");
defaultMix = serializedObject.FindProperty("defaultMix");
idlePlayButtonStyle = new GUIStyle(EditorStyles.toolbarButton);
activePlayButtonStyle = new GUIStyle(EditorStyles.toolbarButton);
activePlayButtonStyle.normal.textColor = Color.red;
#if SPINE_SKELETON_ANIMATOR
controller = serializedObject.FindProperty("controller");
#endif
#if SPINE_TK2D
atlasAssets.isExpanded = false;
spriteCollection = serializedObject.FindProperty("spriteCollection");
#else
atlasAssets.isExpanded = true;
#endif
#if SPINE_BAKING
isBakingExpanded = EditorPrefs.GetBool(ShowBakingPrefsKey, false);
#endif
m_skeletonDataAsset = (SkeletonDataAsset)target;
m_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset));
EditorApplication.update += Update;
EditorApplication.update += EditorUpdate;
m_skeletonData = m_skeletonDataAsset.GetSkeletonData(false);
#if SPINE_BAKING
showBaking = EditorPrefs.GetBool("SkeletonDataAssetInspector_showUnity", false);
#endif
InitPreview();
RepopulateWarnings();
}
void OnDestroy () {
m_initialized = false;
EditorApplication.update -= Update;
EditorApplication.update -= EditorUpdate;
this.DestroyPreviewInstances();
if (this.m_previewUtility != null) {
this.m_previewUtility.Cleanup();
@ -101,26 +108,26 @@ namespace Spine.Unity.Editor {
using (new EditorGUI.DisabledGroupScope(spriteCollection.objectReferenceValue != null)) {
EditorGUILayout.PropertyField(atlasAssets, true);
}
EditorGUILayout.LabelField("spine-tk2d", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(spriteCollection, true);
#endif
EditorGUILayout.Space();
EditorGUILayout.PropertyField(skeletonJSON);
EditorGUILayout.PropertyField(scale);
EditorGUILayout.Space();
if (EditorGUI.EndChangeCheck()) {
if (serializedObject.ApplyModifiedProperties()) {
if (m_previewUtility != null) {
m_previewUtility.Cleanup();
m_previewUtility = null;
}
RepopulateWarnings();
OnEnable();
return;
}
}
if (m_skeletonData != null) {
DrawAnimationStateInfo();
DrawAnimationList();
@ -129,10 +136,22 @@ namespace Spine.Unity.Editor {
} else {
DrawReimportButton();
//Show Warnings
foreach (var str in warnings)
EditorGUILayout.LabelField(new GUIContent(str, SpineEditorUtilities.Icons.warning));
#if !SPINE_TK2D
// Reimport Button
using (new EditorGUI.DisabledGroupScope(skeletonJSON.objectReferenceValue == null)) {
if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) {
DoReimport();
return;
}
}
#else
EditorGUILayout.HelpBox("Couldn't load SkeletonData.", MessageType.Error);
#endif
// List warnings.
foreach (var line in warnings)
EditorGUILayout.LabelField(new GUIContent(line, SpineEditorUtilities.Icons.warning));
}
if(!Application.isPlaying)
@ -141,37 +160,58 @@ namespace Spine.Unity.Editor {
void DrawUnityTools () {
#if SPINE_SKELETON_ANIMATOR
showMecanim = EditorGUILayout.Foldout(showMecanim, new GUIContent("SkeletonAnimator", SpineEditorUtilities.Icons.unityIcon));
if (showMecanim) {
isMecanimExpanded = EditorGUILayout.Foldout(isMecanimExpanded, new GUIContent("SkeletonAnimator", SpineEditorUtilities.Icons.unityIcon));
if (isMecanimExpanded) {
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(controller, new GUIContent("Controller", SpineEditorUtilities.Icons.controllerIcon));
if (controller.objectReferenceValue == null) {
// Generate Mecanim Controller Button
using (new GUILayout.HorizontalScope()) {
GUILayout.Space(32);
if (GUILayout.Button(new GUIContent("Generate Mecanim Controller"), GUILayout.Width(195), GUILayout.Height(20)))
GUILayout.Space(EditorGUIUtility.labelWidth);
if (GUILayout.Button(new GUIContent("Generate Mecanim Controller"), GUILayout.Height(20)))
SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);
}
EditorGUILayout.LabelField("SkeletonAnimator is the Mecanim alternative to SkeletonAnimation. It is not required.", EditorStyles.miniLabel);
EditorGUILayout.HelpBox("SkeletonAnimator is the Mecanim alternative to SkeletonAnimation.\nIt is not required.", MessageType.Info);
} else {
// Update AnimationClips button.
using (new GUILayout.HorizontalScope()) {
GUILayout.Space(EditorGUIUtility.labelWidth);
if (GUILayout.Button(new GUIContent("Force Update AnimationClips"), GUILayout.Height(20)))
SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);
}
}
EditorGUI.indentLevel--;
}
#endif
#if SPINE_BAKING
bool pre = showBaking;
showBaking = EditorGUILayout.Foldout(showBaking, new GUIContent("Baking", SpineEditorUtilities.Icons.unityIcon));
if (pre != showBaking)
EditorPrefs.SetBool("SkeletonDataAssetInspector_showUnity", showBaking);
bool pre = isBakingExpanded;
isBakingExpanded = EditorGUILayout.Foldout(isBakingExpanded, new GUIContent("Baking", SpineEditorUtilities.Icons.unityIcon));
if (pre != isBakingExpanded)
EditorPrefs.SetBool(ShowBakingPrefsKey, isBakingExpanded);
if (showBaking) {
if (isBakingExpanded) {
EditorGUI.indentLevel++;
EditorGUILayout.HelpBox("WARNING!\n\nBaking is NOT the same as SkeletonAnimator!\nDoes not support the following:\n\tFlipX or Y\n\tInheritScale\n\tColor Keys\n\tDraw Order Keys\n\tIK and Curves are sampled at 60fps and are not realtime.\n\tPlease read SkeletonBaker.cs comments for full details.\n\nThe main use of Baking is to export Spine projects to be used without the Spine Runtime (ie: for sale on the Asset Store, or background objects that are animated only with a wind noise generator)", MessageType.Warning, true);
const string BakingWarningMessage =
"WARNING!" +
"\nBaking is NOT the same as SkeletonAnimator!" +
"\n\nThe main use of Baking is to export Spine projects to be used without the Spine Runtime (ie: for sale on the Asset Store, or background objects that are animated only with a wind noise generator)" +
"\n\nBaking also does not support the following:" +
"\n\tDisabled transform inheritance" +
"\n\tShear" +
"\n\tColor Keys" +
"\n\tDraw Order Keys" +
"\n\tAll Constraint types" +
"\n\nCurves are sampled at 60fps and are not realtime." +
"\nPlease read SkeletonBaker.cs comments for full details.";
EditorGUILayout.HelpBox(BakingWarningMessage, MessageType.Warning, true);
EditorGUI.indentLevel++;
bakeAnimations = EditorGUILayout.Toggle("Bake Animations", bakeAnimations);
@ -181,16 +221,20 @@ namespace Spine.Unity.Editor {
bakeEventOptions = (SendMessageOptions)EditorGUILayout.EnumPopup("Event Options", bakeEventOptions);
EditorGUI.indentLevel--;
}
// Bake Skin buttons.
using (new GUILayout.HorizontalScope()) {
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) { // If m_skeletonAnimation is lazy-instantiated, this can cause contents to change between Layout and Repaint events, which can cause scope errors.
// If m_skeletonAnimation is lazy-instantiated elsewhere, this can cause contents to change between Layout and Repaint events, causing scope errors.
if (m_skeletonData != null && m_skeletonAnimation == null)
InitPreview();
if (m_skeletonAnimation != null && m_skeletonAnimation.skeleton != null) {
Skin bakeSkin = m_skeletonAnimation.skeleton.Skin;
string skinName = "<No Skin>";
if (bakeSkin == null) {
skinName = "Default";
bakeSkin = m_skeletonData.Skins.Items[0];
@ -203,7 +247,7 @@ namespace Spine.Unity.Editor {
using (new GUILayout.HorizontalScope()) {
GUILayout.Label(new GUIContent("Skins", SpineEditorUtilities.Icons.skinsRoot), GUILayout.Width(50));
if (GUILayout.Button(skinName, EditorStyles.popup, GUILayout.Width(196))) {
SelectSkinContext();
DrawSkinDropdown();
}
}
}
@ -216,16 +260,6 @@ namespace Spine.Unity.Editor {
#endif
}
void DrawReimportButton () {
bool isJsonFieldEmpty = skeletonJSON.objectReferenceValue == null;
using (new EditorGUI.DisabledGroupScope(isJsonFieldEmpty)) {
if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) {
DoReimport();
return;
}
}
}
void DoReimport () {
SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);
@ -248,7 +282,6 @@ namespace Spine.Unity.Editor {
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(defaultMix);
// Animation names
var animations = new string[m_skeletonData.Animations.Count];
for (int i = 0; i < animations.Length; i++)
animations[i] = m_skeletonData.Animations.Items[i].Name;
@ -268,6 +301,7 @@ namespace Spine.Unity.Editor {
}
}
}
using (new EditorGUILayout.HorizontalScope()) {
EditorGUILayout.Space();
if (GUILayout.Button("Add Mix")) {
@ -284,7 +318,6 @@ namespace Spine.Unity.Editor {
serializedObject.ApplyModifiedProperties();
needToSerialize = true;
}
}
void DrawAnimationList () {
@ -303,39 +336,35 @@ namespace Spine.Unity.Editor {
}
EditorGUILayout.LabelField("Name", "Duration");
foreach (Spine.Animation a in m_skeletonData.Animations) {
foreach (Spine.Animation animation in m_skeletonData.Animations) {
using (new GUILayout.HorizontalScope()) {
if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
if (m_skeletonAnimation.state.GetCurrent(0) != null && m_skeletonAnimation.state.GetCurrent(0).Animation == a) {
GUI.contentColor = Color.red;
if (GUILayout.Button("\u25BA", EditorStyles.toolbarButton, GUILayout.Width(24))) {
var activeTrack = m_skeletonAnimation.state.GetCurrent(0);
if (activeTrack != null && activeTrack.Animation == animation) {
if (GUILayout.Button("\u25BA", activePlayButtonStyle, GUILayout.Width(24))) {
StopAnimation();
}
GUI.contentColor = Color.white;
} else {
if (GUILayout.Button("\u25BA", EditorStyles.toolbarButton, GUILayout.Width(24))) {
PlayAnimation(a.Name, true);
if (GUILayout.Button("\u25BA", idlePlayButtonStyle, GUILayout.Width(24))) {
PlayAnimation(animation.Name, true);
}
}
} else {
GUILayout.Label("?", GUILayout.Width(24));
GUILayout.Label("-", GUILayout.Width(24));
}
EditorGUILayout.LabelField(new GUIContent(a.Name, SpineEditorUtilities.Icons.animation), new GUIContent(a.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(a.Duration * 30)) + ")").PadLeft(12, ' ')));
EditorGUILayout.LabelField(new GUIContent(animation.Name, SpineEditorUtilities.Icons.animation), new GUIContent(animation.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(animation.Duration * 30)) + ")").PadLeft(12, ' ')));
}
}
}
void DrawSlotList () {
showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot));
if (!showSlotList)
return;
if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null)
return;
if (!showSlotList) return;
if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null) return;
EditorGUI.indentLevel++;
try {
showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments);
} catch {
@ -346,10 +375,7 @@ namespace Spine.Unity.Editor {
List<string> slotAttachmentNames = new List<string>();
List<string> defaultSkinAttachmentNames = new List<string>();
var defaultSkin = m_skeletonData.Skins.Items[0];
Skin skin = m_skeletonAnimation.skeleton.Skin;
if (skin == null) {
skin = defaultSkin;
}
Skin skin = m_skeletonAnimation.skeleton.Skin ?? defaultSkin;
for (int i = m_skeletonAnimation.skeleton.Slots.Count - 1; i >= 0; i--) {
Slot slot = m_skeletonAnimation.skeleton.Slots.Items[i];
@ -364,7 +390,6 @@ namespace Spine.Unity.Editor {
skin.FindNamesForSlot(i, slotAttachmentNames);
skin.FindAttachmentsForSlot(i, slotAttachments);
if (skin != defaultSkin) {
defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
@ -375,7 +400,7 @@ namespace Spine.Unity.Editor {
for (int a = 0; a < slotAttachments.Count; a++) {
Attachment attachment = slotAttachments[a];
string name = slotAttachmentNames[a];
string attachmentName = slotAttachmentNames[a];
Texture2D icon = null;
var type = attachment.GetType();
@ -391,15 +416,15 @@ namespace Spine.Unity.Editor {
else
icon = SpineEditorUtilities.Icons.warning;
//TODO: Waterboard Nate
// MITCH: left todo: Waterboard Nate
//if (name != attachment.Name)
//icon = SpineEditorUtilities.Icons.skinPlaceholder;
bool initialState = slot.Attachment == attachment;
bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment);
bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(attachmentName, icon), slot.Attachment == attachment);
if (!defaultSkinAttachmentNames.Contains(name)) {
if (!defaultSkinAttachmentNames.Contains(attachmentName)) {
Rect skinPlaceHolderIconRect = GUILayoutUtility.GetLastRect();
skinPlaceHolderIconRect.width = SpineEditorUtilities.Icons.skinPlaceholder.width;
skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height;
@ -407,11 +432,7 @@ namespace Spine.Unity.Editor {
}
if (toggled != initialState) {
if (toggled) {
slot.Attachment = attachment;
} else {
slot.Attachment = null;
}
slot.Attachment = toggled ? attachment : null;
m_requireRefresh = true;
}
}
@ -420,18 +441,17 @@ namespace Spine.Unity.Editor {
}
EditorGUI.indentLevel--;
}
void RepopulateWarnings () {
warnings.Clear();
if (skeletonJSON.objectReferenceValue == null)
if (skeletonJSON.objectReferenceValue == null) {
warnings.Add("Missing Skeleton JSON");
else {
} else {
if (SpineEditorUtilities.IsValidSpineData((TextAsset)skeletonJSON.objectReferenceValue) == false) {
warnings.Add("Skeleton data file is not a valid JSON or binary file.");
} else {
#if !SPINE_TK2D
bool detectedNullAtlasEntry = false;
var atlasList = new List<Atlas>();
for (int i = 0; i < atlasAssets.arraySize; i++) {
@ -446,7 +466,7 @@ namespace Spine.Unity.Editor {
if (detectedNullAtlasEntry)
warnings.Add("AtlasAsset elements cannot be Null");
else {
//get requirements
// Get requirements.
var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue));
foreach (var atlas in atlasList) {
@ -462,11 +482,18 @@ namespace Spine.Unity.Editor {
warnings.Add("Missing Region: '" + str + "'");
}
#else
if (spriteCollection.objectReferenceValue == null) {
warnings.Add("SkeletonDataAsset requires tk2DSpriteCollectionData.");
} else {
warnings.Add("Your sprite collection may have missing images.");
}
#endif
}
}
}
// Preview window stuff
#region Preview Window
PreviewRenderUtility m_previewUtility;
GameObject m_previewInstance;
Vector2 previewDir;
@ -526,10 +553,15 @@ namespace Spine.Unity.Editor {
void CreatePreviewInstances () {
this.DestroyPreviewInstances();
var skeletonDataAsset = (SkeletonDataAsset)target;
if (skeletonDataAsset.GetSkeletonData(false) == null)
return;
if (this.m_previewInstance == null) {
string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");
m_previewInstance = SpineEditorUtilities.InstantiateSkeletonAnimation((SkeletonDataAsset)target, skinName).gameObject;
m_previewInstance = SpineEditorUtilities.InstantiateSkeletonAnimation(skeletonDataAsset, skinName).gameObject;
m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
m_previewInstance.layer = 0x1f;
@ -555,7 +587,7 @@ namespace Spine.Unity.Editor {
}
public override bool HasPreviewGUI () {
//TODO: validate json data
// MITCH: left todo: validate json data
for (int i = 0; i < atlasAssets.arraySize; i++) {
var prop = atlasAssets.GetArrayElementAtIndex(i);
@ -584,8 +616,8 @@ namespace Spine.Unity.Editor {
DrawSkinToolbar(r);
NormalizedTimeBar(r);
//TODO: implement panning
// this.previewDir = Drag2D(this.previewDir, r);
// MITCH: left a todo: Implement panning
// this.previewDir = Drag2D(this.previewDir, r);
MouseScroll(r);
}
@ -593,14 +625,13 @@ namespace Spine.Unity.Editor {
Vector3 m_posGoal = new Vector3(0, 0, -10);
double m_adjustFrameEndTime = 0;
private void AdjustCameraGoals (bool calculateMixTime) {
void AdjustCameraGoals (bool calculateMixTime) {
if (this.m_previewInstance == null)
return;
if (calculateMixTime) {
if (m_skeletonAnimation.state.GetCurrent(0) != null) {
if (m_skeletonAnimation.state.GetCurrent(0) != null)
m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Mix;
}
}
GameObject go = this.m_previewInstance;
@ -609,17 +640,16 @@ namespace Spine.Unity.Editor {
m_posGoal = bounds.center + new Vector3(0, 0, -10);
}
private void AdjustCameraGoals () {
void AdjustCameraGoals () {
AdjustCameraGoals(false);
}
private void AdjustCamera () {
void AdjustCamera () {
if (m_previewUtility == null)
return;
if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime) {
if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime)
AdjustCameraGoals();
}
float orthoSet = Mathf.Lerp(this.m_previewUtility.m_Camera.orthographicSize, m_orthoGoal, 0.1f);
@ -635,17 +665,14 @@ namespace Spine.Unity.Editor {
}
}
private void DoRenderPreview (bool drawHandles) {
void DoRenderPreview (bool drawHandles) {
GameObject go = this.m_previewInstance;
if (m_requireRefresh && go != null) {
go.GetComponent<Renderer>().enabled = true;
if (EditorApplication.isPlaying) {
//do nothing
} else {
if (!EditorApplication.isPlaying)
m_skeletonAnimation.Update((Time.realtimeSinceStartup - m_lastTime));
}
m_lastTime = Time.realtimeSinceStartup;
@ -666,10 +693,8 @@ namespace Spine.Unity.Editor {
Handles.SetCamera(m_previewUtility.m_Camera);
foreach (var slot in m_skeletonAnimation.skeleton.Slots) {
var boundingBoxAttachment = slot.Attachment as BoundingBoxAttachment;
if (boundingBoxAttachment != null) {
if (boundingBoxAttachment != null)
DrawBoundingBox (slot.Bone, boundingBoxAttachment);
}
}
}
@ -692,9 +717,8 @@ namespace Spine.Unity.Editor {
vert.x = worldVerts[i];
vert.y = worldVerts[i + 1];
if (i > 0) {
if (i > 0)
Handles.DrawLine(lastVert, vert);
}
lastVert = vert;
}
@ -703,7 +727,7 @@ namespace Spine.Unity.Editor {
}
void Update () {
void EditorUpdate () {
AdjustCamera();
if (m_playing) {
@ -740,29 +764,11 @@ namespace Spine.Unity.Editor {
popRect.x += 44;
if (GUI.Button(popRect, label, EditorStyles.popup)) {
SelectSkinContext();
DrawSkinDropdown();
}
}
}
void SelectSkinContext () {
var menu = new GenericMenu();
foreach (Skin s in m_skeletonData.Skins) {
menu.AddItem(new GUIContent(s.Name), this.m_skeletonAnimation.skeleton.Skin == s, SetSkin, (object)s);
}
menu.ShowAsContext();
}
void SetSkin (object o) {
Skin skin = (Skin)o;
m_skeletonAnimation.initialSkinName = skin.Name;
m_skeletonAnimation.Initialize(true);
m_requireRefresh = true;
EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
}
void NormalizedTimeBar (Rect r) {
if (m_skeletonAnimation == null)
return;
@ -792,11 +798,10 @@ namespace Spine.Unity.Editor {
GUI.color = Color.white;
for (int i = 0; i < m_animEvents.Count; i++) {
//TODO: Tooltip
// MITCH: left todo: Tooltip
//Spine.Event spev = animEvents[i];
float fr = m_animEventFrames[i];
var evRect = new Rect(barRect);
evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (SpineEditorUtilities.Icons._event.width / 2), barRect.x, float.MaxValue);
evRect.width = SpineEditorUtilities.Icons._event.width;
@ -804,20 +809,17 @@ namespace Spine.Unity.Editor {
evRect.y += SpineEditorUtilities.Icons._event.height;
GUI.DrawTexture(evRect, SpineEditorUtilities.Icons._event);
//TODO: Tooltip
/*
UnityEngine.Event ev = UnityEngine.Event.current;
if(ev.isMouse){
if(evRect.Contains(ev.mousePosition)){
Rect tooltipRect = new Rect(evRect);
tooltipRect.width = 500;
tooltipRect.y -= 4;
tooltipRect.x += 4;
GUI.Label(tooltipRect, spev.Data.Name);
}
}
*/
// MITCH: left todo: Tooltip
// UnityEngine.Event ev = UnityEngine.Event.current;
// if (ev.isMouse) {
// if (evRect.Contains(ev.mousePosition)) {
// Rect tooltipRect = new Rect(evRect);
// tooltipRect.width = 500;
// tooltipRect.y -= 4;
// tooltipRect.x += 4;
// GUI.Label(tooltipRect, spev.Data.Name);
// }
// }
}
}
}
@ -825,11 +827,9 @@ namespace Spine.Unity.Editor {
void MouseScroll (Rect position) {
UnityEngine.Event current = UnityEngine.Event.current;
int controlID = GUIUtility.GetControlID(SliderHash, FocusType.Passive);
switch (current.GetTypeForControl(controlID)) {
case EventType.ScrollWheel:
if (position.Contains(current.mousePosition)) {
m_orthoGoal += current.delta.y;
m_orthoGoal = Mathf.Max(0.01f, m_orthoGoal);
GUIUtility.hotControl = controlID;
@ -837,50 +837,49 @@ namespace Spine.Unity.Editor {
}
break;
}
}
//TODO: Implement preview panning
// MITCH: left todo: Implement preview panning
/*
static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
{
int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
UnityEngine.Event current = UnityEngine.Event.current;
switch (current.GetTypeForControl(controlID))
static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
{
case EventType.MouseDown:
if (position.Contains(current.mousePosition) && (position.width > 50f))
int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
UnityEngine.Event current = UnityEngine.Event.current;
switch (current.GetTypeForControl(controlID))
{
GUIUtility.hotControl = controlID;
current.Use();
EditorGUIUtility.SetWantsMouseJumping(1);
}
return scrollPosition;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID)
{
GUIUtility.hotControl = 0;
}
EditorGUIUtility.SetWantsMouseJumping(0);
return scrollPosition;
case EventType.MouseMove:
return scrollPosition;
case EventType.MouseDrag:
if (GUIUtility.hotControl == controlID)
{
scrollPosition -= (Vector2) (((current.delta * (!current.shift ? ((float) 1) : ((float) 3))) / Mathf.Min(position.width, position.height)) * 140f);
scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90f, 90f);
current.Use();
GUI.changed = true;
case EventType.MouseDown:
if (position.Contains(current.mousePosition) && (position.width > 50f))
{
GUIUtility.hotControl = controlID;
current.Use();
EditorGUIUtility.SetWantsMouseJumping(1);
}
return scrollPosition;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID)
{
GUIUtility.hotControl = 0;
}
EditorGUIUtility.SetWantsMouseJumping(0);
return scrollPosition;
case EventType.MouseMove:
return scrollPosition;
case EventType.MouseDrag:
if (GUIUtility.hotControl == controlID)
{
scrollPosition -= (Vector2) (((current.delta * (!current.shift ? ((float) 1) : ((float) 3))) / Mathf.Min(position.width, position.height)) * 140f);
scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90f, 90f);
current.Use();
GUI.changed = true;
}
return scrollPosition;
}
return scrollPosition;
}
return scrollPosition;
}
*/
*/
public override GUIContent GetPreviewTitle () {
return new GUIContent("Preview");
@ -901,8 +900,8 @@ namespace Spine.Unity.Editor {
}
}
//TODO: Fix first-import error
//TODO: Update preview without thumbnail
// MITCH: left todo: Fix first-import error
// MITCH: left todo: Update preview without thumbnail
public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
var tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
@ -920,7 +919,7 @@ namespace Spine.Unity.Editor {
this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height));
this.DoRenderPreview(false);
//TODO: Figure out why this is throwing errors on first attempt
//MITCH: left todo: Figure out why this is throwing errors on first attempt
// if(m_previewUtility != null){
// Handles.SetCamera(this.m_previewUtility.m_Camera);
// Handles.BeginGUI();
@ -930,6 +929,27 @@ namespace Spine.Unity.Editor {
tex = this.m_previewUtility.EndStaticPreview();
return tex;
}
#endregion
#region Skin Dropdown Context Menu
void DrawSkinDropdown () {
var menu = new GenericMenu();
foreach (Skin s in m_skeletonData.Skins)
menu.AddItem(new GUIContent(s.Name), this.m_skeletonAnimation.skeleton.Skin == s, SetSkin, s);
menu.ShowAsContext();
}
void SetSkin (object o) {
Skin skin = (Skin)o;
m_skeletonAnimation.initialSkinName = skin.Name;
m_skeletonAnimation.Initialize(true);
m_requireRefresh = true;
EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
}
#endregion
}
}

View File

@ -48,6 +48,7 @@ using Spine;
namespace Spine.Unity.Editor {
// Analysis disable once ConvertToStaticType
[InitializeOnLoad]
public class SpineEditorUtilities : AssetPostprocessor {
@ -78,47 +79,42 @@ namespace Spine.Unity.Editor {
public static Texture2D unityIcon;
public static Texture2D controllerIcon;
public static Mesh boneMesh {
internal static Mesh _boneMesh;
public static Mesh BoneMesh {
get {
if (_boneMesh == null) {
_boneMesh = new Mesh();
_boneMesh.vertices = new Vector3[4] {
Vector3.zero,
_boneMesh.vertices = new [] {
new Vector3(0, 0, 0),
new Vector3(-0.1f, 0.1f, 0),
Vector3.up,
new Vector3(0, 1, 0),
new Vector3(0.1f, 0.1f, 0)
};
_boneMesh.uv = new Vector2[4];
_boneMesh.triangles = new int[6] { 0, 1, 2, 2, 3, 0 };
_boneMesh.triangles = new [] { 0, 1, 2, 2, 3, 0 };
_boneMesh.RecalculateBounds();
_boneMesh.RecalculateNormals();
}
return _boneMesh;
}
}
internal static Mesh _boneMesh;
public static Material boneMaterial {
internal static Material _boneMaterial;
public static Material BoneMaterial {
get {
if (_boneMaterial == null) {
#if UNITY_4_3
_boneMaterial = new Material(Shader.Find("Particles/Alpha Blended"));
_boneMaterial.SetColor("_TintColor", new Color(0.4f, 0.4f, 0.4f, 0.25f));
#else
_boneMaterial = new Material(Shader.Find("Spine/Bones"));
_boneMaterial = new Material(Shader.Find("Hidden/Spine/Bones"));
_boneMaterial.SetColor("_Color", new Color(0.4f, 0.4f, 0.4f, 0.25f));
#endif
}
return _boneMaterial;
}
}
internal static Material _boneMaterial;
public static void Initialize () {
skeleton = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeleton.png");
nullBone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-null.png");
@ -152,17 +148,19 @@ namespace Spine.Unity.Editor {
public static string editorPath = "";
public static string editorGUIPath = "";
public static bool initialized;
static HashSet<string> assetsImportedInWrongState;
static Dictionary<int, GameObject> skeletonRendererTable;
static Dictionary<int, SkeletonUtilityBone> skeletonUtilityBoneTable;
static Dictionary<int, BoundingBoxFollower> boundingBoxFollowerTable;
const string DEFAULT_MIX_KEY = "SPINE_DEFAULT_MIX";
public static float defaultScale = 0.01f;
public static float defaultMix = 0.2f;
public static string defaultShader = "Spine/Skeleton";
public static bool initialized;
const string DEFAULT_MIX_KEY = "SPINE_DEFAULT_MIX";
#region Initialization
static SpineEditorUtilities () {
Initialize();
}
@ -193,8 +191,39 @@ namespace Spine.Unity.Editor {
if (!initialized || Icons.skeleton == null)
Initialize();
}
#endregion
#region Hierarchy Icon
#region Spine Preferences and Defaults
static bool preferencesLoaded = false;
[PreferenceItem("Spine")]
static void PreferencesGUI () {
if (!preferencesLoaded) {
preferencesLoaded = true;
defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, 0.2f);
}
EditorGUILayout.LabelField("Auto-Import Settings", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
defaultMix = EditorGUILayout.FloatField("Default Mix", defaultMix);
if (EditorGUI.EndChangeCheck())
EditorPrefs.SetFloat(DEFAULT_MIX_KEY, defaultMix);
GUILayout.Space(20);
EditorGUILayout.LabelField("3rd Party Settings", EditorStyles.boldLabel);
GUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("TK2D");
if (GUILayout.Button("Enable", GUILayout.Width(64)))
EnableTK2D();
if (GUILayout.Button("Disable", GUILayout.Width(64)))
DisableTK2D();
GUILayout.EndHorizontal();
}
#endregion
#region Hierarchy Icons
static void HierarchyWindowChanged () {
skeletonRendererTable.Clear();
skeletonUtilityBoneTable.Clear();
@ -218,48 +247,37 @@ namespace Spine.Unity.Editor {
Rect r = new Rect(selectionRect);
r.x = r.width - 15;
r.width = 15;
GUI.Label(r, Icons.spine);
} else if (skeletonUtilityBoneTable.ContainsKey(instanceId)) {
Rect r = new Rect(selectionRect);
r.x -= 26;
if (skeletonUtilityBoneTable[instanceId] != null) {
if (skeletonUtilityBoneTable[instanceId].transform.childCount == 0)
r.x += 13;
r.y += 2;
r.width = 13;
r.height = 13;
if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) {
if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow)
GUI.DrawTexture(r, Icons.bone);
} else {
else
GUI.DrawTexture(r, Icons.poseBones);
}
}
} else if (boundingBoxFollowerTable.ContainsKey(instanceId)) {
Rect r = new Rect(selectionRect);
r.x -= 26;
if (boundingBoxFollowerTable[instanceId] != null) {
if (boundingBoxFollowerTable[instanceId].transform.childCount == 0)
r.x += 13;
r.y += 2;
r.width = 13;
r.height = 13;
GUI.DrawTexture(r, Icons.boundingBox);
}
}
}
#endregion
#region Auto-Import Entry Point
static void OnPostprocessAllAssets (string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
if (imported.Length == 0)
return;
@ -298,9 +316,8 @@ namespace Spine.Unity.Editor {
string extension = Path.GetExtension(str).ToLower();
switch (extension) {
case ".txt":
if (str.EndsWith(".atlas.txt")) {
if (str.EndsWith(".atlas.txt", System.StringComparison.Ordinal))
atlasPaths.Add(str);
}
break;
case ".png":
case ".jpg":
@ -311,28 +328,24 @@ namespace Spine.Unity.Editor {
skeletonPaths.Add(str);
break;
case ".bytes":
if (str.ToLower().EndsWith(".skel.bytes")) {
if (str.ToLower().EndsWith(".skel.bytes", System.StringComparison.Ordinal)) {
if (IsValidSpineData((TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset))))
skeletonPaths.Add(str);
}
break;
}
}
List<AtlasAsset> atlases = new List<AtlasAsset>();
//import atlases first
// Import atlases first.
var atlases = new List<AtlasAsset>();
foreach (string ap in atlasPaths) {
if (!reimport && CheckForValidAtlas(ap))
continue;
// MITCH: left note: Always import atlas data now.
TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset));
AtlasAsset atlas = IngestSpineAtlas(atlasText);
atlases.Add(atlas);
}
//import skeletons and match them with atlases
// Import skeletons and match them with atlases.
bool abortSkeletonImport = false;
foreach (string sp in skeletonPaths) {
if (!reimport && CheckForValidSkeletonData(sp)) {
@ -340,19 +353,27 @@ namespace Spine.Unity.Editor {
continue;
}
string dir = Path.GetDirectoryName(sp);
#if SPINE_TK2D
IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, null);
#else
var localAtlases = FindAtlasesAtPath(dir);
var requiredPaths = GetRequiredAtlasRegions(sp);
var atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);
if (atlasMatch != null) {
IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
} else {
bool resolved = false;
while (!resolved) {
int result = EditorUtility.DisplayDialogComplex("Skeleton JSON Import Error!", "Could not find matching AtlasAsset for " + Path.GetFileNameWithoutExtension(sp), "Select", "Skip", "Abort");
var filename = Path.GetFileNameWithoutExtension(sp);
int result = EditorUtility.DisplayDialogComplex(
string.Format("Missing AtlasAsset for \"{0}\"", filename),
string.Format("Could not find matching AtlasAsset for \"{0}\"", filename),
"Choose AtlaseAssets...", "Skip this", "Stop importing all"
);
switch (result) {
case -1:
Debug.Log("Select Atlas");
@ -366,25 +387,19 @@ namespace Spine.Unity.Editor {
IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
}
}
break;
case 0:
case 0: // Choose AtlaseAssets...
var atlasList = MultiAtlasDialog(requiredPaths, Path.GetDirectoryName(sp), Path.GetFileNameWithoutExtension(sp));
if (atlasList != null)
IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasList.ToArray());
resolved = true;
break;
case 1:
case 1: // Skip
Debug.Log("Skipped importing: " + Path.GetFileName(sp));
resolved = true;
break;
case 2:
//abort
case 2: // Stop importing all
abortSkeletonImport = true;
resolved = true;
break;
@ -394,46 +409,22 @@ namespace Spine.Unity.Editor {
if (abortSkeletonImport)
break;
#endif
}
//TODO: any post processing of images
}
static bool CheckForValidSkeletonData (string skeletonJSONPath) {
string dir = Path.GetDirectoryName(skeletonJSONPath);
TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
DirectoryInfo dirInfo = new DirectoryInfo(dir);
FileInfo[] files = dirInfo.GetFiles("*.asset");
foreach (var f in files) {
string localPath = dir + "/" + f.Name;
var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
if (obj is SkeletonDataAsset) {
var skeletonDataAsset = (SkeletonDataAsset)obj;
if (skeletonDataAsset.skeletonJSON == textAsset)
return true;
}
}
return false;
// MITCH: left a todo: any post processing of images
}
static void ResetExistingSkeletonData (string skeletonJSONPath) {
string dir = Path.GetDirectoryName(skeletonJSONPath);
TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
DirectoryInfo dirInfo = new DirectoryInfo(dir);
FileInfo[] files = dirInfo.GetFiles("*.asset");
foreach (var f in files) {
string localPath = dir + "/" + f.Name;
var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
if (obj is SkeletonDataAsset) {
var skeletonDataAsset = (SkeletonDataAsset)obj;
var skeletonDataAsset = obj as SkeletonDataAsset;
if (skeletonDataAsset != null) {
if (skeletonDataAsset.skeletonJSON == textAsset) {
if (Selection.activeObject == skeletonDataAsset)
Selection.activeObject = null;
@ -461,10 +452,14 @@ namespace Spine.Unity.Editor {
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
string currentHash = skeletonData != null ? skeletonData.Hash : null;
if (currentHash == null || lastHash != currentHash) {
//do any upkeep on synchronized assets
#if SPINE_SKELETONANIMATOR
if (currentHash == null || lastHash != currentHash)
UpdateMecanimClips(skeletonDataAsset);
}
#endif
// if (currentHash == null || lastHash != currentHash)
// Do any upkeep on synchronized assets
if (currentHash != null) {
EditorPrefs.SetString(guid + "_hash", currentHash);
@ -473,99 +468,62 @@ namespace Spine.Unity.Editor {
}
}
}
#endregion
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);
FileInfo[] files = dirInfo.GetFiles("*.asset");
foreach (var f in files) {
string localPath = dir + "/" + f.Name;
var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
if (obj is AtlasAsset) {
var atlasAsset = (AtlasAsset)obj;
if (atlasAsset.atlasFile == textAsset) {
Atlas atlas = atlasAsset.GetAtlas();
FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
List<AtlasRegion> regions = (List<AtlasRegion>)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<AtlasAsset> MultiAtlasDialog (List<string> requiredPaths, string initialDirectory, string header = "") {
#region Match SkeletonData with Atlases
static List<AtlasAsset> MultiAtlasDialog (List<string> requiredPaths, string initialDirectory, string filename = "") {
List<AtlasAsset> atlasAssets = new List<AtlasAsset>();
bool resolved = false;
string lastAtlasPath = initialDirectory;
while (!resolved) {
StringBuilder sb = new StringBuilder();
sb.AppendLine(header);
sb.AppendLine("Atlases:");
if (atlasAssets.Count == 0) {
sb.AppendLine("\t--none--");
}
for (int i = 0; i < atlasAssets.Count; i++) {
sb.AppendLine("\t" + atlasAssets[i].name);
}
sb.AppendLine();
sb.AppendLine("Missing Regions:");
// Build dialog box message.
var missingRegions = new List<string>(requiredPaths);
var dialogText = new StringBuilder();
{
dialogText.AppendLine(string.Format("SkeletonDataAsset for \"{0}\"", filename));
dialogText.AppendLine("has missing regions.");
dialogText.AppendLine();
dialogText.AppendLine("Current Atlases:");
List<string> missingRegions = new List<string>(requiredPaths);
if (atlasAssets.Count == 0)
dialogText.AppendLine("\t--none--");
foreach (var atlasAsset in atlasAssets) {
var atlas = atlasAsset.GetAtlas();
for (int i = 0; i < missingRegions.Count; i++) {
if (atlas.FindRegion(missingRegions[i]) != null) {
missingRegions.RemoveAt(i);
i--;
for (int i = 0; i < atlasAssets.Count; i++)
dialogText.AppendLine("\t" + atlasAssets[i].name);
dialogText.AppendLine();
dialogText.AppendLine("Missing Regions:");
foreach (var atlasAsset in atlasAssets) {
var atlas = atlasAsset.GetAtlas();
for (int i = 0; i < missingRegions.Count; i++) {
if (atlas.FindRegion(missingRegions[i]) != null) {
missingRegions.RemoveAt(i);
i--;
}
}
}
int n = missingRegions.Count;
if (n == 0) break;
const int MaxListLength = 15;
for (int i = 0; (i < n && i < MaxListLength); i++)
dialogText.AppendLine("\t" + missingRegions[i]);
if (n > MaxListLength) dialogText.AppendLine(string.Format("\t... {0} more...", n - MaxListLength));
}
if (missingRegions.Count == 0) {
break;
}
for (int i = 0; i < missingRegions.Count; i++) {
sb.AppendLine("\t" + missingRegions[i]);
}
int result = EditorUtility.DisplayDialogComplex("Atlas Selection", sb.ToString(), "Select", "Finish", "Abort");
// Show dialog box.
int result = EditorUtility.DisplayDialogComplex(
"SkeletonDataAsset has missing Atlas.",
dialogText.ToString(),
"Browse...", "Import anyway", "Cancel"
);
switch (result) {
case 0:
case 0: // Browse...
AtlasAsset selectedAtlasAsset = GetAtlasDialog(lastAtlasPath);
if (selectedAtlasAsset != null) {
var atlas = selectedAtlasAsset.GetAtlas();
@ -576,32 +534,26 @@ namespace Spine.Unity.Editor {
break;
}
}
atlasAssets.Add(selectedAtlasAsset);
}
break;
case 1:
case 1: // Import anyway
resolved = true;
break;
case 2:
case 2: // Cancel
atlasAssets = null;
resolved = true;
break;
}
}
return atlasAssets;
}
static AtlasAsset GetAtlasDialog (string dirPath) {
string path = EditorUtility.OpenFilePanel("Select AtlasAsset...", dirPath, "asset");
if (path == "")
return null;
if (path == "") return null; // Canceled or closed by user.
int subLen = Application.dataPath.Length - 6;
string assetRelativePath = path.Substring(subLen, path.Length - subLen).Replace("\\", "/");
@ -641,10 +593,8 @@ namespace Spine.Unity.Editor {
foreach (KeyValuePair<string, object> attachmentEntry in ((Dictionary<string, object>)slotEntry.Value)) {
var data = ((Dictionary<string, object>)attachmentEntry.Value);
if (data.ContainsKey("type")) {
if ((string)data["type"] == "boundingbox") {
if ((string)data["type"] == "boundingbox")
continue;
}
}
if (data.ContainsKey("path"))
requiredPaths.Add((string)data["path"]);
@ -652,13 +602,13 @@ namespace Spine.Unity.Editor {
requiredPaths.Add((string)data["name"]);
else
requiredPaths.Add(attachmentEntry.Key);
//requiredPaths.Add((string)sdf["path"]);
}
}
}
return requiredPaths;
}
static AtlasAsset GetMatchingAtlas (List<string> requiredPaths, List<AtlasAsset> atlasAssets) {
AtlasAsset atlasAssetMatch = null;
@ -676,65 +626,55 @@ namespace Spine.Unity.Editor {
atlasAssetMatch = a;
break;
}
}
return atlasAssetMatch;
}
public class AtlasRequirementLoader : AttachmentLoader {
List<string> requirementList;
public AtlasRequirementLoader (List<string> requirementList) {
this.requirementList = requirementList;
}
public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
requirementList.Add(path);
return new RegionAttachment(name);
}
public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
requirementList.Add(path);
return new MeshAttachment(name);
}
public WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, string name, string path) {
requirementList.Add(path);
return new WeightedMeshAttachment(name);
}
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
return new BoundingBoxAttachment(name);
}
}
#endregion
#region Import Atlases
static List<AtlasAsset> FindAtlasesAtPath (string path) {
List<AtlasAsset> arr = new List<AtlasAsset>();
DirectoryInfo dir = new DirectoryInfo(path);
FileInfo[] assetInfoArr = dir.GetFiles("*.asset");
int subLen = Application.dataPath.Length - 6;
foreach (var f in assetInfoArr) {
string assetRelativePath = f.FullName.Substring(subLen, f.FullName.Length - subLen).Replace("\\", "/");
Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset));
if (obj != null) {
if (obj != null)
arr.Add(obj as AtlasAsset);
}
}
return arr;
}
public static bool IsValidSpineData (TextAsset asset) {
if (asset.name.Contains(".skel")) return true;
object obj = null;
try {
obj = Json.Deserialize(new StringReader(asset.text));
} catch (System.Exception) {
}
if (obj == null) {
Debug.LogError("Is not valid JSON");
return false;
}
var root = obj as Dictionary<string, object>;
if (root == null) {
Debug.LogError("Parser returned an incorrect type.");
return false;
}
if (!root.ContainsKey("skeleton"))
return false;
// var skeletonInfo = (Dictionary<string, object>)root["skeleton"];
// string spineVersion = (string)skeletonInfo["spine"];
// TODO: Warn users of old version incompatibility.
return true;
}
static AtlasAsset IngestSpineAtlas (TextAsset atlasText) {
if (atlasText == null) {
Debug.LogWarning("Atlas source cannot be null!");
@ -805,7 +745,6 @@ namespace Spine.Unity.Editor {
mat.mainTexture = texture;
EditorUtility.SetDirty(mat);
AssetDatabase.SaveAssets();
atlasAsset.materials[i] = mat;
@ -820,11 +759,9 @@ namespace Spine.Unity.Editor {
atlasAsset.Reset();
EditorUtility.SetDirty(atlasAsset);
AssetDatabase.SaveAssets();
//iterate regions and bake marked
// Iterate regions and bake marked.
Atlas atlas = atlasAsset.GetAtlas();
FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
@ -837,7 +774,6 @@ namespace Spine.Unity.Editor {
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;
@ -851,7 +787,9 @@ namespace Spine.Unity.Editor {
return (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset));
}
#endregion
#region Bake Atlas Region
public static GameObject BakeRegion (AtlasAsset atlasAsset, AtlasRegion region, bool autoSave = true) {
Atlas atlas = atlasAsset.GetAtlas();
string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
@ -869,7 +807,7 @@ namespace Spine.Unity.Editor {
if (prefab == null) {
root = new GameObject("temp", typeof(MeshFilter), typeof(MeshRenderer));
prefab = (GameObject)PrefabUtility.CreatePrefab(bakedPrefabPath, root);
prefab = PrefabUtility.CreatePrefab(bakedPrefabPath, root);
isNewPrefab = true;
Object.DestroyImmediate(root);
}
@ -891,23 +829,45 @@ namespace Spine.Unity.Editor {
AssetDatabase.Refresh();
}
prefab.GetComponent<MeshRenderer>().sharedMaterial = mat;
return prefab;
}
#endregion
public static string GetPathSafeRegionName (AtlasRegion region) {
return region.name.Replace("/", "_");
}
#region Import SkeletonData (json or binary)
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";
if (spineJson != null && atlasAssets != null) {
#if SPINE_TK2D
if (spineJson != null) {
SkeletonDataAsset skeletonDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
if (skeletonDataAsset == null) {
skeletonDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
skeletonDataAsset.skeletonJSON = spineJson;
skeletonDataAsset.fromAnimation = new string[0];
skeletonDataAsset.toAnimation = new string[0];
skeletonDataAsset.duration = new float[0];
skeletonDataAsset.defaultMix = defaultMix;
skeletonDataAsset.scale = defaultScale;
AssetDatabase.CreateAsset(skeletonDataAsset, filePath);
AssetDatabase.SaveAssets();
} else {
skeletonDataAsset.Reset();
skeletonDataAsset.GetSkeletonData(true);
}
return skeletonDataAsset;
} else {
EditorUtility.DisplayDialog("Error!", "Tried to ingest null Spine data.", "OK");
return null;
}
#else
if (spineJson != null && atlasAssets != null) {
SkeletonDataAsset skelDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
if (skelDataAsset == null) {
skelDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
@ -932,7 +892,52 @@ namespace Spine.Unity.Editor {
EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK");
return null;
}
#endif
}
#endregion
#region Checking Methods
static bool CheckForValidSkeletonData (string skeletonJSONPath) {
string dir = Path.GetDirectoryName(skeletonJSONPath);
TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
DirectoryInfo dirInfo = new DirectoryInfo(dir);
FileInfo[] files = dirInfo.GetFiles("*.asset");
foreach (var path in files) {
string localPath = dir + "/" + path.Name;
var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
var skeletonDataAsset = obj as SkeletonDataAsset;
if (skeletonDataAsset != null && skeletonDataAsset.skeletonJSON == textAsset)
return true;
}
return false;
}
public static bool IsValidSpineData (TextAsset asset) {
if (asset.name.Contains(".skel")) return true;
object obj = null;
obj = Json.Deserialize(new StringReader(asset.text));
if (obj == null) {
Debug.LogError("Is not valid JSON.");
return false;
}
var root = obj as Dictionary<string, object>;
if (root == null) {
Debug.LogError("Parser returned an incorrect type.");
return false;
}
return root.ContainsKey("skeleton");
// TODO: Warn users of old version incompatibility.
// var skeletonInfo = (Dictionary<string, object>)root["skeleton"];
// string spineVersion = (string)skeletonInfo["spine"];
}
#endregion
#region SkeletonAnimation Menu
[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", false, 10)]
@ -963,18 +968,20 @@ namespace Spine.Unity.Editor {
}
public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, string skinName) {
return InstantiateSkeletonAnimation(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
var skeletonData = skeletonDataAsset.GetSkeletonData(true);
var skin = skeletonData != null ? skeletonData.FindSkin(skinName) : null;
return InstantiateSkeletonAnimation(skeletonDataAsset, skin);
}
public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
GameObject go = new GameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
SkeletonAnimation anim = go.GetComponent<SkeletonAnimation>();
anim.skeletonDataAsset = skeletonDataAsset;
SkeletonAnimation newSkeletonAnimation = go.GetComponent<SkeletonAnimation>();
newSkeletonAnimation.skeletonDataAsset = skeletonDataAsset;
bool requiresNormals = false;
foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
foreach (AtlasAsset atlasAsset in newSkeletonAnimation.skeletonDataAsset.atlasAssets) {
foreach (Material m in atlasAsset.materials) {
if (m.shader.name.Contains("Lit")) {
requiresNormals = true;
@ -982,10 +989,8 @@ namespace Spine.Unity.Editor {
}
}
}
anim.calculateNormals = requiresNormals;
newSkeletonAnimation.calculateNormals = requiresNormals;
SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
@ -994,8 +999,12 @@ namespace Spine.Unity.Editor {
string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
}
data = skeletonDataAsset.GetSkeletonData(false);
}
data = skeletonDataAsset.GetSkeletonData(true);
if (data == null) {
Debug.LogWarning("Tried to instantiate a skeleton from an invalid SkeletonDataAsset.");
return null;
}
if (skin == null)
@ -1004,17 +1013,17 @@ namespace Spine.Unity.Editor {
if (skin == null)
skin = data.Skins.Items[0];
anim.Initialize(false);
newSkeletonAnimation.Initialize(false);
anim.skeleton.SetSkin(skin);
anim.initialSkinName = skin.Name;
newSkeletonAnimation.skeleton.SetSkin(skin);
newSkeletonAnimation.initialSkinName = skin.Name;
anim.skeleton.Update(1);
anim.state.Update(1);
anim.state.Apply(anim.skeleton);
anim.skeleton.UpdateWorldTransform();
newSkeletonAnimation.skeleton.Update(1);
newSkeletonAnimation.state.Update(1);
newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);
newSkeletonAnimation.skeleton.UpdateWorldTransform();
return anim;
return newSkeletonAnimation;
}
#endregion
@ -1115,37 +1124,7 @@ namespace Spine.Unity.Editor {
#endif
#endregion
#region Spine Preferences
static bool preferencesLoaded = false;
[PreferenceItem("Spine")]
static void PreferencesGUI () {
if (!preferencesLoaded) {
preferencesLoaded = true;
defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, 0.2f);
}
EditorGUILayout.LabelField("Auto-Import Settings", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
defaultMix = EditorGUILayout.FloatField("Default Mix", defaultMix);
if (EditorGUI.EndChangeCheck())
EditorPrefs.SetFloat(DEFAULT_MIX_KEY, defaultMix);
GUILayout.Space(20);
EditorGUILayout.LabelField("3rd Party Settings", EditorStyles.boldLabel);
GUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("TK2D");
if (GUILayout.Button("Enable", GUILayout.Width(64)))
EnableTK2D();
if (GUILayout.Button("Disable", GUILayout.Width(64)))
DisableTK2D();
GUILayout.EndHorizontal();
}
#endregion
//TK2D Support
#region TK2D Support
const string SPINE_TK2D_DEFINE = "SPINE_TK2D";
static void EnableTK2D () {
@ -1195,32 +1174,10 @@ namespace Spine.Unity.Editor {
Debug.LogWarning("Already Removed Scripting Define Symbol " + SPINE_TK2D_DEFINE);
}
}
#endregion
public class AtlasRequirementLoader : AttachmentLoader {
List<string> requirementList;
public AtlasRequirementLoader (List<string> requirementList) {
this.requirementList = requirementList;
}
public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
requirementList.Add(path);
return new RegionAttachment(name);
}
public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
requirementList.Add(path);
return new MeshAttachment(name);
}
public WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, string name, string path) {
requirementList.Add(path);
return new WeightedMeshAttachment(name);
}
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
return new BoundingBoxAttachment(name);
}
public static string GetPathSafeRegionName (AtlasRegion region) {
return region.name.Replace("/", "_");
}
}

View File

@ -1,5 +1,5 @@
//Shader written by Alex Dixon
Shader "Spine/SkeletonGhost"
Shader "Spine/Special/SkeletonGhost"
{
Properties
{
@ -32,14 +32,12 @@ Shader "Spine/SkeletonGhost"
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float4 color : COLOR;
};
struct vertex_output
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};

View File

@ -38,7 +38,7 @@ namespace Spine.Unity.Modules {
void Start () {
if (ghostShader == null)
ghostShader = Shader.Find("Spine/SkeletonGhost");
ghostShader = Shader.Find("Spine/Special/SkeletonGhost");
skeletonRenderer = GetComponent<SkeletonRenderer>();
meshFilter = GetComponent<MeshFilter>();

View File

@ -1,4 +1,4 @@
Shader "Spine/Bones" {
Shader "Hidden/Spine/Bones" {
Properties {
_Color ("Color", Color) = (0.5,0.5,0.5,0.5)
_MainTex ("Particle Texture", 2D) = "white" {}
@ -56,7 +56,6 @@ Category {
}
sampler2D_float _CameraDepthTexture;
fixed4 frag (v2f i) : SV_Target
{

View File

@ -1,21 +1,12 @@
Shader "Spine/HiddenPass" {
Shader "Spine/Special/HiddenPass" {
SubShader
{
Tags {"Queue" = "Geometry-1" }
Lighting Off
Pass
{
ZWrite Off
ColorMask 0
}
}
}

View File

@ -257,7 +257,9 @@ namespace Spine.Unity {
var workingSubmeshInstructions = workingInstruction.submeshInstructions; // Items array should not be cached. There is dynamic writing to this list.
workingSubmeshInstructions.Clear(false);
#if !SPINE_TK2D
bool isCustomSlotMaterialsPopulated = customSlotMaterials.Count > 0;
#endif
int vertexCount = 0;
int submeshVertexCount = 0;

View File

@ -114,8 +114,8 @@ namespace Spine.Unity.Editor {
Vector3 forward = transform.TransformDirection(rot * Vector3.right);
forward *= flipRotation;
SpineEditorUtilities.Icons.boneMaterial.SetPass(0);
Graphics.DrawMeshNow(SpineEditorUtilities.Icons.boneMesh, Matrix4x4.TRS(vec, Quaternion.LookRotation(transform.forward, forward), Vector3.one * b.Data.Length * b.WorldScaleX));
SpineEditorUtilities.Icons.BoneMaterial.SetPass(0);
Graphics.DrawMeshNow(SpineEditorUtilities.Icons.BoneMesh, Matrix4x4.TRS(vec, Quaternion.LookRotation(transform.forward, forward), Vector3.one * b.Data.Length * b.WorldScaleX));
}
}