spine-runtimes/spine-tk2d/Assets/spine-tk2d/Editor/SkeletonDataAssetInspector.cs

610 lines
20 KiB
C#

/******************************************************************************
* 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.
*****************************************************************************/
/*****************************************************************************
* Automatic import and advanced preview added by Mitch Thompson
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
using System;
using System.Collections.Generic;
using UnityEditor;
#if !UNITY_4_3
using UnityEditor.AnimatedValues;
#endif
using UnityEngine;
using Spine;
[CustomEditor(typeof(SkeletonDataAsset))]
public class SkeletonDataAssetInspector : Editor {
private SerializedProperty spriteCollection, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
private bool showAnimationStateData = true;
#if UNITY_4_3
private bool m_showAnimationList = true;
#else
private AnimBool m_showAnimationList = new AnimBool(true);
#endif
private bool m_initialized = false;
private SkeletonDataAsset m_skeletonDataAsset;
private string m_skeletonDataAssetGUID;
void OnEnable () {
try {
spriteCollection = serializedObject.FindProperty("spriteCollection");
skeletonJSON = serializedObject.FindProperty("skeletonJSON");
scale = serializedObject.FindProperty("scale");
fromAnimation = serializedObject.FindProperty("fromAnimation");
toAnimation = serializedObject.FindProperty("toAnimation");
duration = serializedObject.FindProperty("duration");
defaultMix = serializedObject.FindProperty("defaultMix");
m_skeletonDataAsset = (SkeletonDataAsset)target;
m_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset));
EditorApplication.update += Update;
} catch {
}
}
void OnDestroy () {
m_initialized = false;
EditorApplication.update -= Update;
this.DestroyPreviewInstances();
if (this.m_previewUtility != null) {
this.m_previewUtility.Cleanup();
this.m_previewUtility = null;
}
}
override public void OnInspectorGUI () {
serializedObject.Update();
SkeletonDataAsset asset = (SkeletonDataAsset)target;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(spriteCollection);
EditorGUILayout.PropertyField(skeletonJSON);
EditorGUILayout.PropertyField(scale);
if (EditorGUI.EndChangeCheck()) {
if (m_previewUtility != null) {
m_previewUtility.Cleanup();
m_previewUtility = null;
}
}
SkeletonData skeletonData = asset.GetSkeletonData(asset.spriteCollection == null || asset.skeletonJSON == null);
if (skeletonData != null) {
showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
if (showAnimationStateData) {
EditorGUILayout.PropertyField(defaultMix);
// Animation names
String[] animations = new String[skeletonData.Animations.Count];
for (int i = 0; i < animations.Length; i++)
animations[i] = skeletonData.Animations[i].Name;
for (int i = 0; i < fromAnimation.arraySize; i++) {
SerializedProperty from = fromAnimation.GetArrayElementAtIndex(i);
SerializedProperty to = toAnimation.GetArrayElementAtIndex(i);
SerializedProperty durationProp = duration.GetArrayElementAtIndex(i);
EditorGUILayout.BeginHorizontal();
from.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, from.stringValue), 0), animations)];
to.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, to.stringValue), 0), animations)];
durationProp.floatValue = EditorGUILayout.FloatField(durationProp.floatValue);
if (GUILayout.Button("Delete")) {
duration.DeleteArrayElementAtIndex(i);
toAnimation.DeleteArrayElementAtIndex(i);
fromAnimation.DeleteArrayElementAtIndex(i);
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button("Add Mix")) {
duration.arraySize++;
toAnimation.arraySize++;
fromAnimation.arraySize++;
}
EditorGUILayout.Space();
EditorGUILayout.EndHorizontal();
}
if (GUILayout.Button(new GUIContent("Setup Pose", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
StopAnimation();
m_skeletonAnimation.skeleton.SetToSetupPose();
m_requireRefresh = true;
}
#if UNITY_4_3
m_showAnimationList = EditorGUILayout.Foldout(m_showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
if(m_showAnimationList){
#else
m_showAnimationList.target = EditorGUILayout.Foldout(m_showAnimationList.target, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
if (EditorGUILayout.BeginFadeGroup(m_showAnimationList.faded)) {
#endif
EditorGUILayout.LabelField("Name", "Duration");
foreach (Spine.Animation a in skeletonData.Animations) {
GUILayout.BeginHorizontal();
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.black;
if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
StopAnimation();
}
GUI.contentColor = Color.white;
} else {
if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
PlayAnimation(a.Name, true);
}
}
} else {
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, ' ')));
GUILayout.EndHorizontal();
}
}
#if !UNITY_4_3
EditorGUILayout.EndFadeGroup();
#endif
}
if (!Application.isPlaying) {
if (serializedObject.ApplyModifiedProperties() ||
(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
) {
asset.Reset();
}
}
}
//preview window stuff
private PreviewRenderUtility m_previewUtility;
private GameObject m_previewInstance;
private Vector2 previewDir;
private SkeletonAnimation m_skeletonAnimation;
private SkeletonData m_skeletonData;
private static int sliderHash = "Slider".GetHashCode();
private float m_lastTime;
private bool m_playing;
private bool m_requireRefresh;
private Color m_originColor = new Color(0.3f, 0.3f, 0.3f, 1);
private void StopAnimation () {
m_skeletonAnimation.state.ClearTrack(0);
m_playing = false;
}
List<Spine.Event> m_animEvents = new List<Spine.Event>();
List<float> m_animEventFrames = new List<float>();
private void PlayAnimation (string animName, bool loop) {
m_animEvents.Clear();
m_animEventFrames.Clear();
m_skeletonAnimation.state.SetAnimation(0, animName, loop);
Spine.Animation a = m_skeletonAnimation.state.GetCurrent(0).Animation;
foreach (Timeline t in a.Timelines) {
if (t.GetType() == typeof(EventTimeline)) {
EventTimeline et = (EventTimeline)t;
for (int i = 0; i < et.Events.Length; i++) {
m_animEvents.Add(et.Events[i]);
m_animEventFrames.Add(et.Frames[i]);
}
}
}
m_playing = true;
}
private void InitPreview () {
if (this.m_previewUtility == null) {
this.m_lastTime = Time.realtimeSinceStartup;
this.m_previewUtility = new PreviewRenderUtility(true);
this.m_previewUtility.m_Camera.isOrthoGraphic = true;
this.m_previewUtility.m_Camera.orthographicSize = (scale.floatValue * (m_skeletonDataAsset.spriteCollection.invOrthoSize * m_skeletonDataAsset.spriteCollection.halfTargetHeight)) / 100;
this.m_previewUtility.m_Camera.cullingMask = -2147483648;
this.CreatePreviewInstances();
}
}
private void CreatePreviewInstances () {
this.DestroyPreviewInstances();
if (this.m_previewInstance == null) {
string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");
m_previewInstance = SpineEditorUtilities.SpawnAnimatedSkeleton((SkeletonDataAsset)target, skinName).gameObject;
//m_previewInstance.transform.localScale = Vector3.one * 0.01f;
m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
m_previewInstance.layer = 0x1f;
m_skeletonAnimation = m_previewInstance.GetComponent<SkeletonAnimation>();
m_skeletonAnimation.initialSkinName = skinName;
m_skeletonAnimation.LateUpdate();
m_skeletonData = m_skeletonAnimation.skeletonDataAsset.GetSkeletonData(true);
m_previewInstance.renderer.enabled = false;
m_initialized = true;
AdjustCameraGoals(true);
}
}
private void DestroyPreviewInstances () {
if (this.m_previewInstance != null) {
DestroyImmediate(this.m_previewInstance);
m_previewInstance = null;
}
m_initialized = false;
}
public override bool HasPreviewGUI () {
//TODO: validate json data
return skeletonJSON.objectReferenceValue != null;
}
Texture m_previewTex = new Texture();
public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
this.InitPreview();
if (UnityEngine.Event.current.type == EventType.Repaint) {
if (m_requireRefresh) {
this.m_previewUtility.BeginPreview(r, background);
this.DoRenderPreview(true);
this.m_previewTex = this.m_previewUtility.EndPreview();
m_requireRefresh = false;
}
if (this.m_previewTex != null)
GUI.DrawTexture(r, m_previewTex, ScaleMode.StretchToFill, false);
}
DrawSkinToolbar(r);
NormalizedTimeBar(r);
//TODO: implement panning
// this.previewDir = Drag2D(this.previewDir, r);
MouseScroll(r);
}
float m_orthoGoal = 1;
Vector3 m_posGoal = new Vector3(0, 0, -10);
double m_adjustFrameEndTime = 0;
private void AdjustCameraGoals (bool calculateMixTime) {
if (calculateMixTime) {
if (m_skeletonAnimation.state.GetCurrent(0) != null) {
m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Mix;
}
}
GameObject go = this.m_previewInstance;
Bounds bounds = go.renderer.bounds;
m_orthoGoal = bounds.size.y;
m_posGoal = bounds.center + new Vector3(0, 0, -10);
}
private void AdjustCameraGoals () {
AdjustCameraGoals(false);
}
private void AdjustCamera () {
if (m_previewUtility == null)
return;
if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime) {
AdjustCameraGoals();
}
float orthoSet = Mathf.Lerp(this.m_previewUtility.m_Camera.orthographicSize, m_orthoGoal, 0.1f);
this.m_previewUtility.m_Camera.orthographicSize = orthoSet;
float dist = Vector3.Distance(m_previewUtility.m_Camera.transform.position, m_posGoal);
if (dist > m_skeletonDataAsset.scale) {
Vector3 pos = Vector3.Lerp(this.m_previewUtility.m_Camera.transform.position, m_posGoal, 0.1f);
pos.x = 0;
this.m_previewUtility.m_Camera.transform.position = pos;
this.m_previewUtility.m_Camera.transform.rotation = Quaternion.identity;
m_requireRefresh = true;
}
}
private void DoRenderPreview (bool drawHandles) {
GameObject go = this.m_previewInstance;
if (m_requireRefresh) {
go.renderer.enabled = true;
if (EditorApplication.isPlaying) {
//do nothing
} else {
m_skeletonAnimation.Update((Time.realtimeSinceStartup - m_lastTime));
}
m_lastTime = Time.realtimeSinceStartup;
if (!EditorApplication.isPlaying)
m_skeletonAnimation.LateUpdate();
if (drawHandles) {
Handles.SetCamera(m_previewUtility.m_Camera);
Handles.color = m_originColor;
Handles.DrawLine(new Vector3(-1000 * m_skeletonDataAsset.scale, 0, 0), new Vector3(1000 * m_skeletonDataAsset.scale, 0, 0));
Handles.DrawLine(new Vector3(0, 1000 * m_skeletonDataAsset.scale, 0), new Vector3(0, -1000 * m_skeletonDataAsset.scale, 0));
}
this.m_previewUtility.m_Camera.Render();
go.renderer.enabled = false;
}
}
void Update () {
AdjustCamera();
if (m_playing) {
m_requireRefresh = true;
Repaint();
} else if (m_requireRefresh) {
Repaint();
} else {
#if !UNITY_4_3
if (m_showAnimationList.isAnimating)
Repaint();
#endif
}
}
void DrawSkinToolbar (Rect r) {
if (m_skeletonAnimation == null)
return;
if (m_skeletonAnimation.skeleton != null) {
string label = (m_skeletonAnimation.skeleton != null && m_skeletonAnimation.skeleton.Skin != null) ? m_skeletonAnimation.skeleton.Skin.Name : "default";
Rect popRect = new Rect(r);
popRect.y += 32;
popRect.x += 4;
popRect.height = 24;
popRect.width = 40;
EditorGUI.DropShadowLabel(popRect, new GUIContent("Skin", SpineEditorUtilities.Icons.skinsRoot));
popRect.y += 11;
popRect.width = 150;
popRect.x += 44;
if (GUI.Button(popRect, label, EditorStyles.popup)) {
SelectSkinContext();
}
}
}
void SelectSkinContext () {
GenericMenu 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.Reset();
m_requireRefresh = true;
EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
}
void NormalizedTimeBar (Rect r) {
Rect barRect = new Rect(r);
barRect.height = 32;
barRect.x += 4;
barRect.width -= 4;
GUI.Box(barRect, "");
Rect lineRect = new Rect(barRect);
float width = lineRect.width;
TrackEntry t = m_skeletonAnimation.state.GetCurrent(0);
if (t != null) {
int loopCount = (int)(t.Time / t.EndTime);
float currentTime = t.Time - (t.EndTime * loopCount);
float normalizedTime = currentTime / t.Animation.Duration;
lineRect.x = barRect.x + (width * normalizedTime) - 0.5f;
lineRect.width = 2;
GUI.color = Color.red;
GUI.DrawTexture(lineRect, EditorGUIUtility.whiteTexture);
GUI.color = Color.white;
for (int i = 0; i < m_animEvents.Count; i++) {
//TODO: Tooltip
//Spine.Event spev = animEvents[i];
float fr = m_animEventFrames[i];
Rect 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;
evRect.height = SpineEditorUtilities.Icons._event.height;
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);
}
}
*/
}
}
}
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 * ((SkeletonDataAsset)target).scale * 10;
GUIUtility.hotControl = controlID;
current.Use();
}
break;
}
}
//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))
{
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;
}
*/
public override GUIContent GetPreviewTitle () {
return new GUIContent("Preview");
}
public override void OnPreviewSettings () {
if (!m_initialized) {
GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64));
} else {
float speed = GUILayout.HorizontalSlider(m_skeletonAnimation.timeScale, 0, 2, GUILayout.MaxWidth(64));
//snap to nearest 0.25
float y = speed / 0.25f;
int q = Mathf.RoundToInt(y);
speed = q * 0.25f;
m_skeletonAnimation.timeScale = speed;
}
}
//TODO: Fix first-import error
//TODO: Update preview without thumbnail
public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
this.InitPreview();
if (this.m_previewUtility.m_Camera == null)
return null;
m_requireRefresh = true;
this.DoRenderPreview(false);
AdjustCameraGoals(false);
this.m_previewUtility.m_Camera.orthographicSize = m_orthoGoal / 2;
this.m_previewUtility.m_Camera.transform.position = m_posGoal;
this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height));
this.DoRenderPreview(false);
//TODO: Figure out why this is throwing errors on first attempt
// if(m_previewUtility != null){
// Handles.SetCamera(this.m_previewUtility.m_Camera);
// Handles.BeginGUI();
// GUI.DrawTexture(new Rect(40,60,width,height), SpineEditorUtilities.Icons.spine, ScaleMode.StretchToFill);
// Handles.EndGUI();
// }
tex = this.m_previewUtility.EndStaticPreview();
return tex;
}
}