/******************************************************************************
|
* Spine Runtimes Software License v2.5
|
*
|
* Copyright (c) 2013-2016, Esoteric Software
|
* All rights reserved.
|
*
|
* You are granted a perpetual, non-exclusive, non-sublicensable, and
|
* non-transferable license to use, install, execute, and perform the Spine
|
* Runtimes software and derivative works solely for personal or internal
|
* use. Without the written permission of Esoteric Software (see Section 2 of
|
* the Spine Software License Agreement), you may not (a) modify, translate,
|
* adapt, or develop new applications using the Spine Runtimes or otherwise
|
* create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
|
* USE, DATA, OR PROFITS) 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.
|
*****************************************************************************/
|
|
// Contributed by: Mitch Thompson
|
|
#define SPINE_SKELETON_ANIMATOR
|
//#define SPINE_BAKING
|
|
using System;
|
using System.Collections.Generic;
|
using UnityEditor;
|
using UnityEngine;
|
using Spine;
|
|
namespace Spine.Unity.Editor {
|
using Event = UnityEngine.Event;
|
using Icons = SpineEditorUtilities.Icons;
|
|
[CustomEditor(typeof(SkeletonDataAsset)), CanEditMultipleObjects]
|
public class SkeletonDataAssetInspector : UnityEditor.Editor {
|
static bool showAnimationStateData = true;
|
static bool showAnimationList = true;
|
static bool showSlotList = false;
|
static bool showAttachments = false;
|
|
#if SPINE_BAKING
|
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_TK2D
|
SerializedProperty spriteCollection;
|
#endif
|
|
#if SPINE_SKELETON_ANIMATOR
|
static bool isMecanimExpanded = false;
|
SerializedProperty controller;
|
#endif
|
|
bool m_initialized = false;
|
SkeletonDataAsset m_skeletonDataAsset;
|
SkeletonData m_skeletonData;
|
string m_skeletonDataAssetGUID;
|
bool needToSerialize;
|
|
readonly List<string> warnings = new List<string>();
|
|
GUIStyle activePlayButtonStyle, idlePlayButtonStyle;
|
readonly GUIContent DefaultMixLabel = new GUIContent("Default Mix Duration", "Sets 'SkeletonDataAsset.defaultMix' in the asset and 'AnimationState.data.defaultMix' at runtime load time.");
|
|
void OnEnable () {
|
SpineEditorUtilities.ConfirmInitialization();
|
m_skeletonDataAsset = (SkeletonDataAsset)target;
|
|
// Clear empty atlas array items.
|
{
|
bool hasNulls = false;
|
foreach (var a in m_skeletonDataAsset.atlasAssets) {
|
if (a == null) {
|
hasNulls = true;
|
break;
|
}
|
}
|
if (hasNulls) {
|
var trimmedAtlasAssets = new List<AtlasAsset>();
|
foreach (var a in m_skeletonDataAsset.atlasAssets) {
|
if (a != null) trimmedAtlasAssets.Add(a);
|
}
|
m_skeletonDataAsset.atlasAssets = trimmedAtlasAssets.ToArray();
|
}
|
}
|
|
atlasAssets = serializedObject.FindProperty("atlasAssets");
|
skeletonJSON = serializedObject.FindProperty("skeletonJSON");
|
scale = serializedObject.FindProperty("scale");
|
fromAnimation = serializedObject.FindProperty("fromAnimation");
|
toAnimation = serializedObject.FindProperty("toAnimation");
|
duration = serializedObject.FindProperty("duration");
|
defaultMix = serializedObject.FindProperty("defaultMix");
|
|
#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_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset));
|
EditorApplication.update += EditorUpdate;
|
m_skeletonData = m_skeletonDataAsset.GetSkeletonData(false);
|
RepopulateWarnings();
|
}
|
|
void OnDestroy () {
|
m_initialized = false;
|
EditorApplication.update -= EditorUpdate;
|
this.DestroyPreviewInstances();
|
if (this.m_previewUtility != null) {
|
this.m_previewUtility.Cleanup();
|
this.m_previewUtility = null;
|
}
|
}
|
|
override public void OnInspectorGUI () {
|
if (serializedObject.isEditingMultipleObjects) {
|
using (new SpineInspectorUtility.BoxScope()) {
|
EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel);
|
EditorGUILayout.PropertyField(skeletonJSON, new GUIContent(skeletonJSON.displayName, Icons.spine));
|
EditorGUILayout.PropertyField(scale);
|
}
|
|
using (new SpineInspectorUtility.BoxScope()) {
|
EditorGUILayout.LabelField("Atlas", EditorStyles.boldLabel);
|
#if !SPINE_TK2D
|
EditorGUILayout.PropertyField(atlasAssets, true);
|
#else
|
using (new EditorGUI.DisabledGroupScope(spriteCollection.objectReferenceValue != null)) {
|
EditorGUILayout.PropertyField(atlasAssets, true);
|
}
|
EditorGUILayout.LabelField("spine-tk2d", EditorStyles.boldLabel);
|
EditorGUILayout.PropertyField(spriteCollection, true);
|
#endif
|
}
|
|
using (new SpineInspectorUtility.BoxScope()) {
|
EditorGUILayout.LabelField("Mix Settings", EditorStyles.boldLabel);
|
SpineInspectorUtility.PropertyFieldWideLabel(defaultMix, DefaultMixLabel, 160);
|
EditorGUILayout.Space();
|
}
|
return;
|
}
|
|
{
|
// Lazy initialization because accessing EditorStyles values in OnEnable during a recompile causes UnityEditor to throw null exceptions. (Unity 5.3.5)
|
idlePlayButtonStyle = idlePlayButtonStyle ?? new GUIStyle(EditorStyles.miniButton);
|
if (activePlayButtonStyle == null) {
|
activePlayButtonStyle = new GUIStyle(idlePlayButtonStyle);
|
activePlayButtonStyle.normal.textColor = Color.red;
|
}
|
}
|
|
serializedObject.Update();
|
|
EditorGUILayout.LabelField(new GUIContent(target.name + " (SkeletonDataAsset)", Icons.spine), EditorStyles.whiteLargeLabel);
|
if (m_skeletonData != null) {
|
EditorGUILayout.LabelField("(Drag and Drop to instantiate.)", EditorStyles.miniLabel);
|
}
|
|
EditorGUI.BeginChangeCheck();
|
|
// SkeletonData
|
using (new SpineInspectorUtility.BoxScope()) {
|
using (new EditorGUILayout.HorizontalScope()) {
|
EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel);
|
// if (m_skeletonData != null) {
|
// var sd = m_skeletonData;
|
// string m = string.Format("{8} - {0} {1}\nBones: {2}\tConstraints: {5} IK + {6} Path + {7} Transform\nSlots: {3}\t\tSkins: {4}\n",
|
// sd.Version, string.IsNullOrEmpty(sd.Version) ? "" : "export", sd.Bones.Count, sd.Slots.Count, sd.Skins.Count, sd.IkConstraints.Count, sd.PathConstraints.Count, sd.TransformConstraints.Count, skeletonJSON.objectReferenceValue.name);
|
// EditorGUILayout.LabelField(new GUIContent("SkeletonData"), new GUIContent("+", m), EditorStyles.boldLabel);
|
// }
|
}
|
|
EditorGUILayout.PropertyField(skeletonJSON, new GUIContent(skeletonJSON.displayName, Icons.spine));
|
EditorGUILayout.PropertyField(scale);
|
}
|
|
// if (m_skeletonData != null) {
|
// if (SpineInspectorUtility.CenteredButton(new GUIContent("Instantiate", Icons.spine, "Creates a new Spine GameObject in the active scene using this Skeleton Data.\nYou can also instantiate by dragging the SkeletonData asset from Project view into Scene View.")))
|
// SpineEditorUtilities.ShowInstantiateContextMenu(this.m_skeletonDataAsset, Vector3.zero);
|
// }
|
|
// Atlas
|
using (new SpineInspectorUtility.BoxScope()) {
|
EditorGUILayout.LabelField("Atlas", EditorStyles.boldLabel);
|
#if !SPINE_TK2D
|
EditorGUILayout.PropertyField(atlasAssets, true);
|
#else
|
using (new EditorGUI.DisabledGroupScope(spriteCollection.objectReferenceValue != null)) {
|
EditorGUILayout.PropertyField(atlasAssets, true);
|
}
|
EditorGUILayout.LabelField("spine-tk2d", EditorStyles.boldLabel);
|
EditorGUILayout.PropertyField(spriteCollection, true);
|
#endif
|
}
|
|
if (EditorGUI.EndChangeCheck()) {
|
if (serializedObject.ApplyModifiedProperties()) {
|
if (m_previewUtility != null) {
|
m_previewUtility.Cleanup();
|
m_previewUtility = null;
|
}
|
RepopulateWarnings();
|
OnEnable();
|
return;
|
}
|
}
|
|
// Some code depends on the existence of m_skeletonAnimation instance.
|
// If m_skeletonAnimation is lazy-instantiated elsewhere, this can cause contents to change between Layout and Repaint events, causing GUILayout control count errors.
|
InitPreview();
|
if (m_skeletonData != null) {
|
GUILayout.Space(20f);
|
|
using (new SpineInspectorUtility.BoxScope()) {
|
EditorGUILayout.LabelField("Mix Settings", EditorStyles.boldLabel);
|
DrawAnimationStateInfo();
|
EditorGUILayout.Space();
|
}
|
|
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
|
DrawAnimationList();
|
EditorGUILayout.Space();
|
DrawSlotList();
|
EditorGUILayout.Space();
|
DrawUnityTools();
|
} else {
|
#if !SPINE_TK2D
|
// Reimport Button
|
using (new EditorGUI.DisabledGroupScope(skeletonJSON.objectReferenceValue == null)) {
|
if (GUILayout.Button(new GUIContent("Attempt Reimport", 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, Icons.warning));
|
}
|
|
if (!Application.isPlaying)
|
serializedObject.ApplyModifiedProperties();
|
}
|
|
void DrawUnityTools () {
|
#if SPINE_SKELETON_ANIMATOR
|
using (new SpineInspectorUtility.BoxScope()) {
|
isMecanimExpanded = EditorGUILayout.Foldout(isMecanimExpanded, new GUIContent("SkeletonAnimator", Icons.unityIcon));
|
if (isMecanimExpanded) {
|
EditorGUI.indentLevel++;
|
EditorGUILayout.PropertyField(controller, new GUIContent("Controller", Icons.controllerIcon));
|
if (controller.objectReferenceValue == null) {
|
|
// Generate Mecanim Controller Button
|
using (new GUILayout.HorizontalScope()) {
|
GUILayout.Space(EditorGUIUtility.labelWidth);
|
if (GUILayout.Button(new GUIContent("Generate Mecanim Controller"), GUILayout.Height(20)))
|
SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);
|
}
|
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 = isBakingExpanded;
|
isBakingExpanded = EditorGUILayout.Foldout(isBakingExpanded, new GUIContent("Baking", Icons.unityIcon));
|
if (pre != isBakingExpanded)
|
EditorPrefs.SetBool(ShowBakingPrefsKey, isBakingExpanded);
|
|
if (isBakingExpanded) {
|
EditorGUI.indentLevel++;
|
const string BakingWarningMessage =
|
// "WARNING!" +
|
// "\nBaking is NOT the same as SkeletonAnimator!" +
|
// "\n\n" +
|
"The 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 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.Info, true);
|
|
EditorGUI.indentLevel++;
|
bakeAnimations = EditorGUILayout.Toggle("Bake Animations", bakeAnimations);
|
using (new EditorGUI.DisabledGroupScope(!bakeAnimations)) {
|
EditorGUI.indentLevel++;
|
bakeIK = EditorGUILayout.Toggle("Bake IK", bakeIK);
|
bakeEventOptions = (SendMessageOptions)EditorGUILayout.EnumPopup("Event Options", bakeEventOptions);
|
EditorGUI.indentLevel--;
|
}
|
|
// Bake Skin buttons.
|
using (new GUILayout.HorizontalScope()) {
|
if (GUILayout.Button(new GUIContent("Bake All Skins", Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(150)))
|
SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, m_skeletonData.Skins, "", bakeAnimations, bakeIK, bakeEventOptions);
|
|
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];
|
} else
|
skinName = m_skeletonAnimation.skeleton.Skin.Name;
|
|
using (new GUILayout.VerticalScope()) {
|
if (GUILayout.Button(new GUIContent("Bake \"" + skinName + "\"", Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(250)))
|
SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, new ExposedList<Skin>(new [] { bakeSkin }), "", bakeAnimations, bakeIK, bakeEventOptions);
|
using (new GUILayout.HorizontalScope()) {
|
GUILayout.Label(new GUIContent("Skins", Icons.skinsRoot), GUILayout.Width(50));
|
if (GUILayout.Button(skinName, EditorStyles.popup, GUILayout.Width(196))) {
|
DrawSkinDropdown();
|
}
|
}
|
}
|
}
|
}
|
|
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
}
|
#endif
|
}
|
|
void DoReimport () {
|
SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);
|
|
if (m_previewUtility != null) {
|
m_previewUtility.Cleanup();
|
m_previewUtility = null;
|
}
|
|
RepopulateWarnings();
|
OnEnable();
|
|
EditorUtility.SetDirty(m_skeletonDataAsset);
|
}
|
|
void DrawAnimationStateInfo () {
|
showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
|
if (!showAnimationStateData)
|
return;
|
|
EditorGUI.BeginChangeCheck();
|
SpineInspectorUtility.PropertyFieldWideLabel(defaultMix, DefaultMixLabel, 160);
|
|
var animations = new string[m_skeletonData.Animations.Count];
|
for (int i = 0; i < animations.Length; i++)
|
animations[i] = m_skeletonData.Animations.Items[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);
|
using (new EditorGUILayout.HorizontalScope()) {
|
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);
|
}
|
}
|
}
|
|
using (new EditorGUILayout.HorizontalScope()) {
|
EditorGUILayout.Space();
|
if (GUILayout.Button("Add Mix")) {
|
duration.arraySize++;
|
toAnimation.arraySize++;
|
fromAnimation.arraySize++;
|
}
|
EditorGUILayout.Space();
|
}
|
|
if (EditorGUI.EndChangeCheck()) {
|
m_skeletonDataAsset.FillStateData();
|
EditorUtility.SetDirty(m_skeletonDataAsset);
|
serializedObject.ApplyModifiedProperties();
|
needToSerialize = true;
|
}
|
}
|
|
void DrawAnimationList () {
|
showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent(string.Format("Animations [{0}]", m_skeletonData.Animations.Count), Icons.animationRoot));
|
if (!showAnimationList)
|
return;
|
|
if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
|
if (GUILayout.Button(new GUIContent("Setup Pose", Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
|
StopAnimation();
|
m_skeletonAnimation.skeleton.SetToSetupPose();
|
m_requireRefresh = true;
|
}
|
} else {
|
EditorGUILayout.HelpBox("Animations can be previewed if you expand the Preview window below.", MessageType.Info);
|
}
|
|
EditorGUILayout.LabelField("Name", "Duration");
|
foreach (Spine.Animation animation in m_skeletonData.Animations) {
|
using (new GUILayout.HorizontalScope()) {
|
if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
|
var activeTrack = m_skeletonAnimation.state.GetCurrent(0);
|
if (activeTrack != null && activeTrack.Animation == animation) {
|
if (GUILayout.Button("\u25BA", activePlayButtonStyle, GUILayout.Width(24))) {
|
StopAnimation();
|
}
|
} else {
|
if (GUILayout.Button("\u25BA", idlePlayButtonStyle, GUILayout.Width(24))) {
|
PlayAnimation(animation.Name, true);
|
}
|
}
|
} else {
|
GUILayout.Label("-", GUILayout.Width(24));
|
}
|
EditorGUILayout.LabelField(new GUIContent(animation.Name, 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", Icons.slotRoot));
|
|
if (!showSlotList) return;
|
if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null) return;
|
|
EditorGUI.indentLevel++;
|
showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments);
|
var slotAttachments = new List<Attachment>();
|
var slotAttachmentNames = new List<string>();
|
var defaultSkinAttachmentNames = new List<string>();
|
var defaultSkin = m_skeletonData.Skins.Items[0];
|
Skin skin = m_skeletonAnimation.skeleton.Skin ?? defaultSkin;
|
var slotsItems = m_skeletonAnimation.skeleton.Slots.Items;
|
|
for (int i = m_skeletonAnimation.skeleton.Slots.Count - 1; i >= 0; i--) {
|
Slot slot = slotsItems[i];
|
EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, Icons.slot));
|
if (showAttachments) {
|
|
EditorGUI.indentLevel++;
|
slotAttachments.Clear();
|
slotAttachmentNames.Clear();
|
defaultSkinAttachmentNames.Clear();
|
|
skin.FindNamesForSlot(i, slotAttachmentNames);
|
skin.FindAttachmentsForSlot(i, slotAttachments);
|
|
if (skin != defaultSkin) {
|
defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
|
defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
|
defaultSkin.FindAttachmentsForSlot(i, slotAttachments);
|
} else {
|
defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
|
}
|
|
for (int a = 0; a < slotAttachments.Count; a++) {
|
Attachment attachment = slotAttachments[a];
|
string attachmentName = slotAttachmentNames[a];
|
Texture2D icon = Icons.GetAttachmentIcon(attachment);
|
bool initialState = slot.Attachment == attachment;
|
bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(attachmentName, icon), slot.Attachment == attachment);
|
|
if (!defaultSkinAttachmentNames.Contains(attachmentName)) {
|
Rect skinPlaceHolderIconRect = GUILayoutUtility.GetLastRect();
|
skinPlaceHolderIconRect.width = Icons.skinPlaceholder.width;
|
skinPlaceHolderIconRect.height = Icons.skinPlaceholder.height;
|
GUI.DrawTexture(skinPlaceHolderIconRect, Icons.skinPlaceholder);
|
}
|
|
if (toggled != initialState) {
|
slot.Attachment = toggled ? attachment : null;
|
m_requireRefresh = true;
|
}
|
}
|
EditorGUI.indentLevel--;
|
}
|
}
|
EditorGUI.indentLevel--;
|
}
|
|
void RepopulateWarnings () {
|
warnings.Clear();
|
|
if (skeletonJSON.objectReferenceValue == null) {
|
warnings.Add("Missing Skeleton JSON");
|
} else {
|
if (SpineEditorUtilities.IsSpineData((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++) {
|
if (atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue == null) {
|
detectedNullAtlasEntry = true;
|
break;
|
} else {
|
atlasList.Add(((AtlasAsset)atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue).GetAtlas());
|
}
|
}
|
|
if (detectedNullAtlasEntry)
|
warnings.Add("AtlasAsset elements should not be null.");
|
else {
|
// Get requirements.
|
var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue));
|
|
foreach (var atlas in atlasList) {
|
for (int i = 0; i < missingPaths.Count; i++) {
|
if (atlas.FindRegion(missingPaths[i]) != null) {
|
missingPaths.RemoveAt(i);
|
i--;
|
}
|
}
|
}
|
|
foreach (var str in missingPaths)
|
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
|
}
|
}
|
}
|
|
#region Preview Window
|
PreviewRenderUtility m_previewUtility;
|
GameObject m_previewInstance;
|
Vector2 previewDir;
|
SkeletonAnimation m_skeletonAnimation;
|
static readonly int SliderHash = "Slider".GetHashCode();
|
float m_lastTime;
|
bool m_playing;
|
bool m_requireRefresh;
|
Color m_originColor = new Color(0.3f, 0.3f, 0.3f, 1);
|
|
void StopAnimation () {
|
if (m_skeletonAnimation == null) {
|
Debug.LogWarning("Animation was stopped but preview doesn't exist. It's possible that the Preview Panel is closed.");
|
}
|
|
m_skeletonAnimation.state.ClearTrack(0);
|
m_playing = false;
|
}
|
|
List<Spine.Event> m_animEvents = new List<Spine.Event>();
|
List<float> m_animEventFrames = new List<float>();
|
|
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)) {
|
var 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;
|
}
|
|
void InitPreview () {
|
if (this.m_previewUtility == null) {
|
this.m_lastTime = Time.realtimeSinceStartup;
|
this.m_previewUtility = new PreviewRenderUtility(true);
|
var c = this.m_previewUtility.m_Camera;
|
c.orthographic = true;
|
c.orthographicSize = 1;
|
c.cullingMask = -2147483648;
|
c.nearClipPlane = 0.01f;
|
c.farClipPlane = 1000f;
|
this.CreatePreviewInstances();
|
}
|
}
|
|
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, skinName).gameObject;
|
|
if (m_previewInstance != null) {
|
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.GetComponent<Renderer>().enabled = false;
|
m_initialized = true;
|
}
|
|
AdjustCameraGoals(true);
|
}
|
}
|
|
void DestroyPreviewInstances () {
|
if (this.m_previewInstance != null) {
|
DestroyImmediate(this.m_previewInstance);
|
m_previewInstance = null;
|
}
|
m_initialized = false;
|
}
|
|
public override bool HasPreviewGUI () {
|
if (serializedObject.isEditingMultipleObjects) {
|
// JOHN: Implement multi-preview.
|
return false;
|
}
|
|
for (int i = 0; i < atlasAssets.arraySize; i++) {
|
var prop = atlasAssets.GetArrayElementAtIndex(i);
|
if (prop.objectReferenceValue == null)
|
return false;
|
}
|
|
return skeletonJSON.objectReferenceValue != null;
|
}
|
|
Texture m_previewTex = new Texture();
|
|
public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
|
this.InitPreview();
|
|
if (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);
|
// MITCH: left a 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;
|
|
void AdjustCameraGoals (bool calculateMixTime) {
|
if (this.m_previewInstance == null)
|
return;
|
|
if (calculateMixTime) {
|
if (m_skeletonAnimation.state.GetCurrent(0) != null)
|
m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Alpha;
|
}
|
|
GameObject go = this.m_previewInstance;
|
Bounds bounds = go.GetComponent<Renderer>().bounds;
|
m_orthoGoal = bounds.size.y;
|
m_posGoal = bounds.center + new Vector3(0, 0, -10f);
|
}
|
|
void AdjustCameraGoals () {
|
AdjustCameraGoals(false);
|
}
|
|
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 > 0f) {
|
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;
|
}
|
}
|
|
void DoRenderPreview (bool drawHandles) {
|
GameObject go = this.m_previewInstance;
|
|
if (m_requireRefresh && go != null) {
|
go.GetComponent<Renderer>().enabled = true;
|
|
if (!EditorApplication.isPlaying)
|
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();
|
|
if (drawHandles) {
|
Handles.SetCamera(m_previewUtility.m_Camera);
|
SpineHandles.DrawBoundingBoxes(m_skeletonAnimation.transform, m_skeletonAnimation.skeleton);
|
if (showAttachments) SpineHandles.DrawPaths(m_skeletonAnimation.transform, m_skeletonAnimation.skeleton);
|
}
|
|
go.GetComponent<Renderer>().enabled = false;
|
}
|
|
}
|
|
void EditorUpdate () {
|
AdjustCamera();
|
|
if (m_playing) {
|
m_requireRefresh = true;
|
Repaint();
|
} else if (m_requireRefresh) {
|
Repaint();
|
}
|
//else {
|
//only needed if using smooth menus
|
//}
|
|
if (needToSerialize) {
|
needToSerialize = false;
|
serializedObject.ApplyModifiedProperties();
|
}
|
}
|
|
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", Icons.skinsRoot));
|
|
popRect.y += 11;
|
popRect.width = 150;
|
popRect.x += 44;
|
|
if (GUI.Button(popRect, label, EditorStyles.popup)) {
|
DrawSkinDropdown();
|
}
|
}
|
}
|
|
void NormalizedTimeBar (Rect r) {
|
if (m_skeletonAnimation == null)
|
return;
|
|
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.TrackTime / t.TrackEnd);
|
float currentTime = t.TrackTime - (t.TrackEnd * loopCount);
|
float normalizedTime = currentTime / t.Animation.Duration;
|
float wrappedTime = normalizedTime % 1;
|
|
lineRect.x = barRect.x + (width * wrappedTime) - 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++) {
|
float fr = m_animEventFrames[i];
|
var evRect = new Rect(barRect);
|
evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (Icons.userEvent.width / 2), barRect.x, float.MaxValue);
|
evRect.width = Icons.userEvent.width;
|
evRect.height = Icons.userEvent.height;
|
evRect.y += Icons.userEvent.height;
|
GUI.DrawTexture(evRect, Icons.userEvent);
|
|
Event ev = Event.current;
|
if (ev.type == EventType.Repaint) {
|
if (evRect.Contains(ev.mousePosition)) {
|
Rect tooltipRect = new Rect(evRect);
|
GUIStyle tooltipStyle = EditorStyles.helpBox;
|
tooltipRect.width = tooltipStyle.CalcSize(new GUIContent(m_animEvents[i].Data.Name)).x;
|
tooltipRect.y -= 4;
|
tooltipRect.x += 4;
|
GUI.Label(tooltipRect, m_animEvents[i].Data.Name, tooltipStyle);
|
GUI.tooltip = m_animEvents[i].Data.Name;
|
}
|
}
|
}
|
}
|
}
|
|
void MouseScroll (Rect position) {
|
Event current = 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 * 0.06f;
|
m_orthoGoal = Mathf.Max(0.01f, m_orthoGoal);
|
GUIUtility.hotControl = controlID;
|
current.Use();
|
}
|
break;
|
}
|
}
|
|
// 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))
|
{
|
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 () {
|
const float SliderWidth = 100;
|
if (!m_initialized) {
|
GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(SliderWidth));
|
} else {
|
float speed = GUILayout.HorizontalSlider(m_skeletonAnimation.timeScale, 0, 2, GUILayout.MaxWidth(SliderWidth));
|
|
const float SliderSnap = 0.25f;
|
float y = speed / SliderSnap;
|
int q = Mathf.RoundToInt(y);
|
speed = q * SliderSnap;
|
|
m_skeletonAnimation.timeScale = speed;
|
}
|
}
|
|
|
public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
|
var 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);
|
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
|
}
|
|
}
|