You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1157 lines
41 KiB
1157 lines
41 KiB
/******************************************************************************
|
|
* 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.
|
|
*****************************************************************************/
|
|
|
|
#define SPINE_SKELETON_ANIMATOR
|
|
|
|
using System;
|
|
using System.Reflection;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
using Spine;
|
|
|
|
namespace Spine.Unity.Editor {
|
|
using Event = UnityEngine.Event;
|
|
using Icons = SpineEditorUtilities.Icons;
|
|
using Animation = Spine.Animation;
|
|
|
|
[CustomEditor(typeof(SkeletonDataAsset)), CanEditMultipleObjects]
|
|
public class SkeletonDataAssetInspector : UnityEditor.Editor {
|
|
internal static bool showAnimationStateData = true;
|
|
internal static bool showAnimationList = true;
|
|
internal static bool showSlotList = false;
|
|
internal static bool showAttachments = false;
|
|
|
|
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
|
|
|
|
SkeletonDataAsset targetSkeletonDataAsset;
|
|
SkeletonData targetSkeletonData;
|
|
|
|
readonly List<string> warnings = new List<string>();
|
|
readonly SkeletonInspectorPreview preview = new SkeletonInspectorPreview();
|
|
|
|
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.");
|
|
|
|
string TargetAssetGUID { get { return AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(targetSkeletonDataAsset)); } }
|
|
string LastSkinKey { get { return TargetAssetGUID + "_lastSkin"; } }
|
|
string LastSkinName { get { return EditorPrefs.GetString(LastSkinKey, ""); } }
|
|
|
|
void OnEnable () {
|
|
InitializeEditor();
|
|
}
|
|
|
|
void OnDestroy () {
|
|
HandleOnDestroyPreview();
|
|
AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
|
|
EditorApplication.update -= preview.HandleEditorUpdate;
|
|
}
|
|
|
|
private void OnDomainUnload (object sender, EventArgs e) {
|
|
OnDestroy();
|
|
}
|
|
|
|
void InitializeEditor () {
|
|
SpineEditorUtilities.ConfirmInitialization();
|
|
targetSkeletonDataAsset = (SkeletonDataAsset)target;
|
|
|
|
bool newAtlasAssets = atlasAssets == null;
|
|
if (newAtlasAssets) 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
|
|
if (newAtlasAssets) atlasAssets.isExpanded = false;
|
|
spriteCollection = serializedObject.FindProperty("spriteCollection");
|
|
#else
|
|
// Analysis disable once ConvertIfToOrExpression
|
|
if (newAtlasAssets) atlasAssets.isExpanded = true;
|
|
#endif
|
|
|
|
// This handles the case where the managed editor assembly is unloaded before recompilation when code changes.
|
|
AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
|
|
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
|
|
|
|
EditorApplication.update -= preview.HandleEditorUpdate;
|
|
EditorApplication.update += preview.HandleEditorUpdate;
|
|
preview.OnSkinChanged -= HandlePreviewSkinChanged;
|
|
preview.OnSkinChanged += HandlePreviewSkinChanged;
|
|
|
|
PopulateWarnings();
|
|
if (targetSkeletonDataAsset.skeletonJSON == null) {
|
|
targetSkeletonData = null;
|
|
return;
|
|
}
|
|
|
|
targetSkeletonData = warnings.Count == 0 ? targetSkeletonDataAsset.GetSkeletonData(false) : null;
|
|
|
|
if (targetSkeletonData != null && warnings.Count <= 0) {
|
|
preview.Initialize(this.Repaint, targetSkeletonDataAsset, this.LastSkinName);
|
|
}
|
|
|
|
}
|
|
|
|
void Clear () {
|
|
preview.Clear();
|
|
targetSkeletonDataAsset.Clear();
|
|
targetSkeletonData = null;
|
|
}
|
|
|
|
override public void OnInspectorGUI () {
|
|
// Multi-Editing
|
|
if (serializedObject.isEditingMultipleObjects) {
|
|
OnInspectorGUIMulti();
|
|
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();
|
|
|
|
// Header
|
|
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(target.name + " (SkeletonDataAsset)", Icons.spine), EditorStyles.whiteLargeLabel);
|
|
if (targetSkeletonData != null) EditorGUILayout.LabelField("(Drag and Drop to instantiate.)", EditorStyles.miniLabel);
|
|
|
|
// Main Serialized Fields
|
|
using (var changeCheck = new EditorGUI.ChangeCheckScope()) {
|
|
using (new SpineInspectorUtility.BoxScope())
|
|
DrawSkeletonDataFields();
|
|
|
|
using (new SpineInspectorUtility.BoxScope()) {
|
|
DrawAtlasAssetsFields();
|
|
HandleAtlasAssetsNulls();
|
|
}
|
|
|
|
if (changeCheck.changed) {
|
|
if (serializedObject.ApplyModifiedProperties()) {
|
|
this.Clear();
|
|
this.InitializeEditor();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unity Quirk: Some code depends on valid preview. If preview is initialized elsewhere, this can cause contents to change between Layout and Repaint events, causing GUILayout control count errors.
|
|
if (warnings.Count <= 0)
|
|
preview.Initialize(this.Repaint, targetSkeletonDataAsset, this.LastSkinName);
|
|
|
|
if (targetSkeletonData != null) {
|
|
GUILayout.Space(20f);
|
|
|
|
using (new SpineInspectorUtility.BoxScope(false)) {
|
|
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Mix Settings", Icons.animationRoot), EditorStyles.boldLabel);
|
|
DrawAnimationStateInfo();
|
|
EditorGUILayout.Space();
|
|
}
|
|
|
|
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
|
|
DrawAnimationList();
|
|
if (targetSkeletonData.Animations.Count > 0) {
|
|
const string AnimationReferenceButtonText = "Create Animation Reference Assets";
|
|
const string AnimationReferenceTooltipText = "AnimationReferenceAsset acts as Unity asset for a reference to a Spine.Animation. This can be used in inspectors.\n\nIt serializes a reference to a SkeletonDataAsset and an animationName.\n\nAt runtime, a reference to its Spine.Animation is loaded and cached into the object to be used as needed. This skips the need to find and cache animation references in individual MonoBehaviours.";
|
|
if (GUILayout.Button(SpineInspectorUtility.TempContent(AnimationReferenceButtonText, Icons.animationRoot, AnimationReferenceTooltipText), GUILayout.Width(250), GUILayout.Height(26))) {
|
|
CreateAnimationReferenceAssets();
|
|
}
|
|
}
|
|
EditorGUILayout.Space();
|
|
DrawSlotList();
|
|
EditorGUILayout.Space();
|
|
|
|
DrawUnityTools();
|
|
|
|
} else {
|
|
#if !SPINE_TK2D
|
|
// Draw Reimport Button
|
|
using (new EditorGUI.DisabledGroupScope(skeletonJSON.objectReferenceValue == null)) {
|
|
if (GUILayout.Button(SpineInspectorUtility.TempContent("Attempt Reimport", Icons.warning)))
|
|
DoReimport();
|
|
}
|
|
#else
|
|
EditorGUILayout.HelpBox("Couldn't load SkeletonData.", MessageType.Error);
|
|
#endif
|
|
|
|
DrawWarningList();
|
|
}
|
|
|
|
if (!Application.isPlaying)
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
void CreateAnimationReferenceAssets () {
|
|
const string AssetFolderName = "ReferenceAssets";
|
|
string parentFolder = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(targetSkeletonDataAsset));
|
|
string dataPath = parentFolder + "/" + AssetFolderName;
|
|
if (!AssetDatabase.IsValidFolder(dataPath)) {
|
|
AssetDatabase.CreateFolder(parentFolder, AssetFolderName);
|
|
}
|
|
|
|
FieldInfo nameField = typeof(AnimationReferenceAsset).GetField("animationName", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
FieldInfo skeletonDataAssetField = typeof(AnimationReferenceAsset).GetField("skeletonDataAsset", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
foreach (var animation in targetSkeletonData.Animations) {
|
|
string assetPath = string.Format("{0}/{1}.asset", dataPath, SpineEditorUtilities.GetPathSafeName(animation.Name));
|
|
AnimationReferenceAsset existingAsset = AssetDatabase.LoadAssetAtPath<AnimationReferenceAsset>(assetPath);
|
|
if (existingAsset == null) {
|
|
AnimationReferenceAsset newAsset = ScriptableObject.CreateInstance<AnimationReferenceAsset>();
|
|
skeletonDataAssetField.SetValue(newAsset, targetSkeletonDataAsset);
|
|
nameField.SetValue(newAsset, animation.Name);
|
|
AssetDatabase.CreateAsset(newAsset, assetPath);
|
|
}
|
|
}
|
|
|
|
var folderObject = AssetDatabase.LoadAssetAtPath(dataPath, typeof(UnityEngine.Object));
|
|
if (folderObject != null) {
|
|
Selection.activeObject = folderObject;
|
|
EditorGUIUtility.PingObject(folderObject);
|
|
}
|
|
}
|
|
|
|
void OnInspectorGUIMulti () {
|
|
|
|
// Skeleton data file field.
|
|
using (new SpineInspectorUtility.BoxScope()) {
|
|
EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel);
|
|
EditorGUILayout.PropertyField(skeletonJSON, SpineInspectorUtility.TempContent(skeletonJSON.displayName, Icons.spine));
|
|
EditorGUILayout.PropertyField(scale);
|
|
}
|
|
|
|
// Texture source field.
|
|
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
|
|
}
|
|
|
|
// Mix settings.
|
|
using (new SpineInspectorUtility.BoxScope()) {
|
|
EditorGUILayout.LabelField("Mix Settings", EditorStyles.boldLabel);
|
|
SpineInspectorUtility.PropertyFieldWideLabel(defaultMix, DefaultMixLabel, 160);
|
|
EditorGUILayout.Space();
|
|
}
|
|
|
|
}
|
|
|
|
void DrawSkeletonDataFields () {
|
|
using (new EditorGUILayout.HorizontalScope()) {
|
|
EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel);
|
|
if (targetSkeletonData != null) {
|
|
var sd = targetSkeletonData;
|
|
string m = string.Format("{8} - {0} {1}\nBones: {2}\nConstraints: \n {5} IK \n {6} Path \n {7} Transform\n\nSlots: {3}\nSkins: {4}\n\nAnimations: {9}",
|
|
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, sd.Animations.Count);
|
|
EditorGUILayout.LabelField(GUIContent.none, new GUIContent(Icons.info, m), GUILayout.Width(30f));
|
|
}
|
|
}
|
|
EditorGUILayout.PropertyField(skeletonJSON, SpineInspectorUtility.TempContent(skeletonJSON.displayName, Icons.spine));
|
|
EditorGUILayout.PropertyField(scale);
|
|
}
|
|
|
|
void DrawAtlasAssetsFields () {
|
|
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 (atlasAssets.arraySize == 0)
|
|
EditorGUILayout.HelpBox("AtlasAssets array is empty. Skeleton's attachments will load without being mapped to images.", MessageType.Info);
|
|
}
|
|
|
|
void HandleAtlasAssetsNulls () {
|
|
bool hasNulls = false;
|
|
foreach (var a in targetSkeletonDataAsset.atlasAssets) {
|
|
if (a == null) {
|
|
hasNulls = true;
|
|
break;
|
|
}
|
|
}
|
|
if (hasNulls) {
|
|
if (targetSkeletonDataAsset.atlasAssets.Length == 1) {
|
|
EditorGUILayout.HelpBox("Atlas array cannot have null entries!", MessageType.None);
|
|
}
|
|
else {
|
|
EditorGUILayout.HelpBox("Atlas array should not have null entries!", MessageType.Error);
|
|
if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Remove null entries"))) {
|
|
var trimmedAtlasAssets = new List<AtlasAsset>();
|
|
foreach (var a in targetSkeletonDataAsset.atlasAssets) {
|
|
if (a != null)
|
|
trimmedAtlasAssets.Add(a);
|
|
}
|
|
targetSkeletonDataAsset.atlasAssets = trimmedAtlasAssets.ToArray();
|
|
serializedObject.Update();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawAnimationStateInfo () {
|
|
using (new SpineInspectorUtility.IndentScope())
|
|
showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
|
|
|
|
if (!showAnimationStateData)
|
|
return;
|
|
|
|
using (var cc = new EditorGUI.ChangeCheckScope()) {
|
|
using (new SpineInspectorUtility.IndentScope())
|
|
SpineInspectorUtility.PropertyFieldWideLabel(defaultMix, DefaultMixLabel, 160);
|
|
|
|
// Do not use EditorGUIUtility.indentLevel. It will add spaces on every field.
|
|
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()) {
|
|
GUILayout.Space(16f);
|
|
EditorGUILayout.PropertyField(from, GUIContent.none);
|
|
EditorGUILayout.PropertyField(to, GUIContent.none);
|
|
durationProp.floatValue = EditorGUILayout.FloatField(durationProp.floatValue, GUILayout.MinWidth(25f), GUILayout.MaxWidth(60f));
|
|
if (GUILayout.Button("Delete", EditorStyles.miniButton)) {
|
|
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 (cc.changed) {
|
|
targetSkeletonDataAsset.FillStateData();
|
|
EditorUtility.SetDirty(targetSkeletonDataAsset);
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawAnimationList () {
|
|
showAnimationList = EditorGUILayout.Foldout(showAnimationList, SpineInspectorUtility.TempContent(string.Format("Animations [{0}]", targetSkeletonData.Animations.Count), Icons.animationRoot));
|
|
if (!showAnimationList)
|
|
return;
|
|
|
|
bool isPreviewWindowOpen = preview.IsValid;
|
|
|
|
if (isPreviewWindowOpen) {
|
|
if (GUILayout.Button(SpineInspectorUtility.TempContent("Setup Pose", Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
|
|
preview.ClearAnimationSetupPose();
|
|
preview.RefreshOnNextUpdate();
|
|
}
|
|
} else {
|
|
EditorGUILayout.HelpBox("Animations can be previewed if you expand the Preview window below.", MessageType.Info);
|
|
}
|
|
|
|
EditorGUILayout.LabelField("Name", " Duration");
|
|
//bool nonessential = targetSkeletonData.ImagesPath != null; // Currently the only way to determine if skeleton data has nonessential data. (Spine 3.6)
|
|
//float fps = targetSkeletonData.Fps;
|
|
//if (nonessential && fps == 0) fps = 30;
|
|
|
|
var activeTrack = preview.ActiveTrack;
|
|
foreach (Animation animation in targetSkeletonData.Animations) {
|
|
using (new GUILayout.HorizontalScope()) {
|
|
if (isPreviewWindowOpen) {
|
|
bool active = activeTrack != null && activeTrack.Animation == animation;
|
|
//bool sameAndPlaying = active && activeTrack.TimeScale > 0f;
|
|
if (GUILayout.Button("\u25BA", active ? activePlayButtonStyle : idlePlayButtonStyle, GUILayout.Width(24))) {
|
|
preview.PlayPauseAnimation(animation.Name, true);
|
|
activeTrack = preview.ActiveTrack;
|
|
}
|
|
} else {
|
|
GUILayout.Label("-", GUILayout.Width(24));
|
|
}
|
|
//string frameCountString = (fps > 0) ? ("(" + (Mathf.RoundToInt(animation.Duration * fps)) + ")").PadLeft(12, ' ') : string.Empty;
|
|
//EditorGUILayout.LabelField(new GUIContent(animation.Name, Icons.animation), SpineInspectorUtility.TempContent(animation.Duration.ToString("f3") + "s" + frameCountString));
|
|
EditorGUILayout.LabelField(new GUIContent(animation.Name, Icons.animation), SpineInspectorUtility.TempContent(animation.Duration.ToString("f3") + "s"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawSlotList () {
|
|
showSlotList = EditorGUILayout.Foldout(showSlotList, SpineInspectorUtility.TempContent("Slots", Icons.slotRoot));
|
|
|
|
if (!showSlotList) return;
|
|
if (!preview.IsValid) 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 = targetSkeletonData.Skins.Items[0];
|
|
Skin skin = preview.Skeleton.Skin ?? defaultSkin;
|
|
var slotsItems = preview.Skeleton.Slots.Items;
|
|
|
|
for (int i = preview.Skeleton.Slots.Count - 1; i >= 0; i--) {
|
|
Slot slot = slotsItems[i];
|
|
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(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(SpineInspectorUtility.TempContent(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;
|
|
preview.RefreshOnNextUpdate();
|
|
}
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
void DrawUnityTools () {
|
|
#if SPINE_SKELETON_ANIMATOR
|
|
using (new SpineInspectorUtility.BoxScope()) {
|
|
isMecanimExpanded = EditorGUILayout.Foldout(isMecanimExpanded, SpineInspectorUtility.TempContent("SkeletonAnimator", SpineInspectorUtility.UnityIcon<SceneAsset>()));
|
|
if (isMecanimExpanded) {
|
|
EditorGUI.indentLevel++;
|
|
EditorGUILayout.PropertyField(controller, SpineInspectorUtility.TempContent("Controller", SpineInspectorUtility.UnityIcon<Animator>()));
|
|
if (controller.objectReferenceValue == null) {
|
|
|
|
// Generate Mecanim Controller Button
|
|
using (new GUILayout.HorizontalScope()) {
|
|
GUILayout.Space(EditorGUIUtility.labelWidth);
|
|
if (GUILayout.Button(SpineInspectorUtility.TempContent("Generate Mecanim Controller"), GUILayout.Height(20)))
|
|
SkeletonBaker.GenerateMecanimAnimationClips(targetSkeletonDataAsset);
|
|
}
|
|
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(SpineInspectorUtility.TempContent("Force Update AnimationClips"), GUILayout.Height(20)))
|
|
SkeletonBaker.GenerateMecanimAnimationClips(targetSkeletonDataAsset);
|
|
}
|
|
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void DrawWarningList () {
|
|
foreach (string line in warnings)
|
|
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(line, Icons.warning));
|
|
}
|
|
|
|
void PopulateWarnings () {
|
|
warnings.Clear();
|
|
|
|
if (skeletonJSON.objectReferenceValue == null) {
|
|
warnings.Add("Missing Skeleton JSON");
|
|
} else {
|
|
var fieldValue = (TextAsset)skeletonJSON.objectReferenceValue;
|
|
if (!SpineEditorUtilities.SkeletonDataFileValidator.IsSpineData(fieldValue)) {
|
|
warnings.Add("Skeleton data file is not a valid JSON or binary file.");
|
|
} else {
|
|
#if SPINE_TK2D
|
|
bool searchForSpineAtlasAssets = true;
|
|
bool isSpriteCollectionNull = spriteCollection.objectReferenceValue == null;
|
|
if (!isSpriteCollectionNull) searchForSpineAtlasAssets = false;
|
|
#else
|
|
// Analysis disable once ConvertToConstant.Local
|
|
bool searchForSpineAtlasAssets = true;
|
|
#endif
|
|
|
|
if (searchForSpineAtlasAssets) {
|
|
bool detectedNullAtlasEntry = false;
|
|
var atlasList = new List<Atlas>();
|
|
var actualAtlasAssets = targetSkeletonDataAsset.atlasAssets;
|
|
|
|
for (int i = 0; i < actualAtlasAssets.Length; i++) {
|
|
if (targetSkeletonDataAsset.atlasAssets[i] == null) {
|
|
detectedNullAtlasEntry = true;
|
|
break;
|
|
} else {
|
|
atlasList.Add(actualAtlasAssets[i].GetAtlas());
|
|
}
|
|
}
|
|
|
|
if (detectedNullAtlasEntry) {
|
|
warnings.Add("AtlasAsset elements should not be null.");
|
|
} else {
|
|
List<string> missingPaths = null;
|
|
if (atlasAssets.arraySize > 0) {
|
|
missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath(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--;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if SPINE_TK2D
|
|
if (missingPaths.Count > 0)
|
|
warnings.Add("Missing regions. SkeletonDataAsset requires tk2DSpriteCollectionData or Spine AtlasAssets.");
|
|
#endif
|
|
}
|
|
|
|
if (missingPaths != null) {
|
|
foreach (string str in missingPaths)
|
|
warnings.Add("Missing Region: '" + str + "'");
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoReimport () {
|
|
SpineEditorUtilities.ImportSpineContent(new [] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);
|
|
preview.Clear();
|
|
InitializeEditor();
|
|
EditorUtility.SetDirty(targetSkeletonDataAsset);
|
|
}
|
|
|
|
void HandlePreviewSkinChanged (string skinName) {
|
|
EditorPrefs.SetString(LastSkinKey, skinName);
|
|
}
|
|
|
|
#region Preview Handlers
|
|
void HandleOnDestroyPreview () {
|
|
EditorApplication.update -= preview.HandleEditorUpdate;
|
|
preview.OnDestroy();
|
|
}
|
|
|
|
override public bool HasPreviewGUI () {
|
|
if (serializedObject.isEditingMultipleObjects)
|
|
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;
|
|
}
|
|
|
|
override public void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
|
|
if (warnings.Count <= 0) {
|
|
preview.Initialize(this.Repaint, targetSkeletonDataAsset, this.LastSkinName);
|
|
preview.HandleInteractivePreviewGUI(r, background);
|
|
}
|
|
}
|
|
|
|
override public GUIContent GetPreviewTitle () { return SpineInspectorUtility.TempContent("Preview"); }
|
|
public override void OnPreviewSettings () { preview.HandleDrawSettings(); }
|
|
public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) { return preview.GetStaticPreview(width, height); }
|
|
#endregion
|
|
}
|
|
|
|
internal class SkeletonInspectorPreview {
|
|
Color OriginColor = new Color(0.3f, 0.3f, 0.3f, 1);
|
|
static readonly int SliderHash = "Slider".GetHashCode();
|
|
|
|
SkeletonDataAsset skeletonDataAsset;
|
|
SkeletonData skeletonData;
|
|
|
|
SkeletonAnimation skeletonAnimation;
|
|
GameObject previewGameObject;
|
|
internal bool requiresRefresh;
|
|
|
|
#if !(UNITY_2017_4 || UNITY_2018)
|
|
float animationLastTime;
|
|
#endif
|
|
|
|
static float CurrentTime { get { return (float)EditorApplication.timeSinceStartup; } }
|
|
|
|
Action Repaint;
|
|
public event Action<string> OnSkinChanged;
|
|
|
|
Texture previewTexture;
|
|
PreviewRenderUtility previewRenderUtility;
|
|
Camera PreviewUtilityCamera {
|
|
get {
|
|
if (previewRenderUtility == null) return null;
|
|
#if UNITY_2017_1_OR_NEWER
|
|
return previewRenderUtility.camera;
|
|
#else
|
|
return previewRenderUtility.m_Camera;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static Vector3 lastCameraPositionGoal;
|
|
static float lastCameraOrthoGoal;
|
|
float cameraOrthoGoal = 1;
|
|
Vector3 cameraPositionGoal = new Vector3(0, 0, -10);
|
|
double cameraAdjustEndFrame = 0;
|
|
|
|
List<Spine.Event> currentAnimationEvents = new List<Spine.Event>();
|
|
List<float> currentAnimationEventTimes = new List<float>();
|
|
List<SpineEventTooltip> currentAnimationEventTooltips = new List<SpineEventTooltip>();
|
|
|
|
public bool IsValid { get { return skeletonAnimation != null && skeletonAnimation.valid; } }
|
|
|
|
public Skeleton Skeleton { get { return IsValid ? skeletonAnimation.Skeleton : null; } }
|
|
|
|
public float TimeScale {
|
|
get { return IsValid ? skeletonAnimation.timeScale : 1f; }
|
|
set { if (IsValid) skeletonAnimation.timeScale = value; }
|
|
}
|
|
|
|
public bool IsPlayingAnimation { get {
|
|
if (!IsValid) return false;
|
|
var currentTrack = skeletonAnimation.AnimationState.GetCurrent(0);
|
|
return currentTrack != null && currentTrack.TimeScale > 0;
|
|
}
|
|
}
|
|
|
|
public TrackEntry ActiveTrack { get { return IsValid ? skeletonAnimation.AnimationState.GetCurrent(0) : null; } }
|
|
|
|
public Vector3 PreviewCameraPosition {
|
|
get { return PreviewUtilityCamera.transform.position; }
|
|
set { PreviewUtilityCamera.transform.position = value; }
|
|
}
|
|
|
|
public void HandleDrawSettings () {
|
|
const float SliderWidth = 150;
|
|
const float SliderSnap = 0.25f;
|
|
const float SliderMin = 0f;
|
|
const float SliderMax = 2f;
|
|
|
|
if (IsValid) {
|
|
float timeScale = GUILayout.HorizontalSlider(TimeScale, SliderMin, SliderMax, GUILayout.MaxWidth(SliderWidth));
|
|
timeScale = Mathf.RoundToInt(timeScale / SliderSnap) * SliderSnap;
|
|
TimeScale = timeScale;
|
|
}
|
|
}
|
|
|
|
public void HandleEditorUpdate () {
|
|
AdjustCamera();
|
|
if (IsPlayingAnimation) {
|
|
RefreshOnNextUpdate();
|
|
Repaint();
|
|
} else if (requiresRefresh) {
|
|
Repaint();
|
|
}
|
|
}
|
|
|
|
public void Initialize (Action repaintCallback, SkeletonDataAsset skeletonDataAsset, string skinName = "") {
|
|
if (skeletonDataAsset == null) return;
|
|
if (skeletonDataAsset.GetSkeletonData(false) == null) {
|
|
DestroyPreviewGameObject();
|
|
return;
|
|
}
|
|
|
|
this.Repaint = repaintCallback;
|
|
this.skeletonDataAsset = skeletonDataAsset;
|
|
this.skeletonData = skeletonDataAsset.GetSkeletonData(false);
|
|
|
|
if (skeletonData == null) {
|
|
DestroyPreviewGameObject();
|
|
return;
|
|
}
|
|
|
|
if (previewRenderUtility == null) {
|
|
previewRenderUtility = new PreviewRenderUtility(true);
|
|
#if !(UNITY_2017_4 || UNITY_2018)
|
|
animationLastTime = CurrentTime;
|
|
#endif
|
|
|
|
const int PreviewLayer = 30;
|
|
const int PreviewCameraCullingMask = 1 << PreviewLayer;
|
|
|
|
{
|
|
var c = this.PreviewUtilityCamera;
|
|
c.orthographic = true;
|
|
c.cullingMask = PreviewCameraCullingMask;
|
|
c.nearClipPlane = 0.01f;
|
|
c.farClipPlane = 1000f;
|
|
c.orthographicSize = lastCameraOrthoGoal;
|
|
c.transform.position = lastCameraPositionGoal;
|
|
}
|
|
|
|
DestroyPreviewGameObject();
|
|
|
|
if (previewGameObject == null) {
|
|
try {
|
|
previewGameObject = SpineEditorUtilities.InstantiateSkeletonAnimation(skeletonDataAsset, skinName).gameObject;
|
|
|
|
if (previewGameObject != null) {
|
|
previewGameObject.hideFlags = HideFlags.HideAndDontSave;
|
|
previewGameObject.layer = PreviewLayer;
|
|
skeletonAnimation = previewGameObject.GetComponent<SkeletonAnimation>();
|
|
skeletonAnimation.initialSkinName = skinName;
|
|
skeletonAnimation.LateUpdate();
|
|
previewGameObject.GetComponent<Renderer>().enabled = false;
|
|
|
|
#if UNITY_2017_4 || UNITY_2018
|
|
previewRenderUtility.AddSingleGO(previewGameObject);
|
|
#endif
|
|
}
|
|
|
|
if (this.ActiveTrack != null) cameraAdjustEndFrame = EditorApplication.timeSinceStartup + skeletonAnimation.AnimationState.GetCurrent(0).Alpha;
|
|
AdjustCameraGoals();
|
|
} catch {
|
|
DestroyPreviewGameObject();
|
|
}
|
|
|
|
RefreshOnNextUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void HandleInteractivePreviewGUI (Rect r, GUIStyle background) {
|
|
if (Event.current.type == EventType.Repaint) {
|
|
if (requiresRefresh) {
|
|
previewRenderUtility.BeginPreview(r, background);
|
|
DoRenderPreview(true);
|
|
previewTexture = previewRenderUtility.EndPreview();
|
|
requiresRefresh = false;
|
|
}
|
|
if (previewTexture != null)
|
|
GUI.DrawTexture(r, previewTexture, ScaleMode.StretchToFill, false);
|
|
}
|
|
|
|
DrawSkinToolbar(r);
|
|
//DrawSetupPoseButton(r);
|
|
DrawTimeBar(r);
|
|
HandleMouseScroll(r);
|
|
}
|
|
|
|
public Texture2D GetStaticPreview (int width, int height) {
|
|
var c = this.PreviewUtilityCamera;
|
|
if (c == null)
|
|
return null;
|
|
|
|
RefreshOnNextUpdate();
|
|
AdjustCameraGoals();
|
|
c.orthographicSize = cameraOrthoGoal / 2;
|
|
c.transform.position = cameraPositionGoal;
|
|
previewRenderUtility.BeginStaticPreview(new Rect(0, 0, width, height));
|
|
DoRenderPreview(false);
|
|
var tex = previewRenderUtility.EndStaticPreview();
|
|
|
|
return tex;
|
|
}
|
|
|
|
public void DoRenderPreview (bool drawHandles) {
|
|
if (this.PreviewUtilityCamera.activeTexture == null || this.PreviewUtilityCamera.targetTexture == null)
|
|
return;
|
|
|
|
GameObject go = previewGameObject;
|
|
if (requiresRefresh && go != null) {
|
|
var renderer = go.GetComponent<Renderer>();
|
|
renderer.enabled = true;
|
|
|
|
|
|
if (!EditorApplication.isPlaying) {
|
|
#if !(UNITY_2017_4 || UNITY_2018)
|
|
float current = CurrentTime;
|
|
float deltaTime = (current - animationLastTime);
|
|
skeletonAnimation.Update(deltaTime);
|
|
animationLastTime = current;
|
|
#endif
|
|
skeletonAnimation.LateUpdate();
|
|
}
|
|
|
|
var thisPreviewUtilityCamera = this.PreviewUtilityCamera;
|
|
|
|
if (drawHandles) {
|
|
Handles.SetCamera(thisPreviewUtilityCamera);
|
|
Handles.color = OriginColor;
|
|
|
|
// Draw Cross
|
|
float scale = skeletonDataAsset.scale;
|
|
float cl = 1000 * scale;
|
|
Handles.DrawLine(new Vector3(-cl, 0), new Vector3(cl, 0));
|
|
Handles.DrawLine(new Vector3(0, cl), new Vector3(0, -cl));
|
|
}
|
|
|
|
thisPreviewUtilityCamera.Render();
|
|
|
|
if (drawHandles) {
|
|
Handles.SetCamera(thisPreviewUtilityCamera);
|
|
SpineHandles.DrawBoundingBoxes(skeletonAnimation.transform, skeletonAnimation.skeleton);
|
|
if (SkeletonDataAssetInspector.showAttachments)
|
|
SpineHandles.DrawPaths(skeletonAnimation.transform, skeletonAnimation.skeleton);
|
|
}
|
|
|
|
renderer.enabled = false;
|
|
}
|
|
}
|
|
|
|
public void AdjustCamera () {
|
|
if (previewRenderUtility == null)
|
|
return;
|
|
|
|
if (CurrentTime < cameraAdjustEndFrame)
|
|
AdjustCameraGoals();
|
|
|
|
lastCameraPositionGoal = cameraPositionGoal;
|
|
lastCameraOrthoGoal = cameraOrthoGoal;
|
|
|
|
var c = this.PreviewUtilityCamera;
|
|
float orthoSet = Mathf.Lerp(c.orthographicSize, cameraOrthoGoal, 0.1f);
|
|
|
|
c.orthographicSize = orthoSet;
|
|
|
|
float dist = Vector3.Distance(c.transform.position, cameraPositionGoal);
|
|
if (dist > 0f) {
|
|
Vector3 pos = Vector3.Lerp(c.transform.position, cameraPositionGoal, 0.1f);
|
|
pos.x = 0;
|
|
c.transform.position = pos;
|
|
c.transform.rotation = Quaternion.identity;
|
|
RefreshOnNextUpdate();
|
|
}
|
|
}
|
|
|
|
void AdjustCameraGoals () {
|
|
if (previewGameObject == null) return;
|
|
|
|
Bounds bounds = previewGameObject.GetComponent<Renderer>().bounds;
|
|
cameraOrthoGoal = bounds.size.y;
|
|
cameraPositionGoal = bounds.center + new Vector3(0, 0, -10f);
|
|
}
|
|
|
|
void HandleMouseScroll (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)) {
|
|
cameraOrthoGoal += current.delta.y * 0.06f;
|
|
cameraOrthoGoal = Mathf.Max(0.01f, cameraOrthoGoal);
|
|
GUIUtility.hotControl = controlID;
|
|
current.Use();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void RefreshOnNextUpdate () {
|
|
requiresRefresh = true;
|
|
}
|
|
|
|
public void ClearAnimationSetupPose () {
|
|
if (skeletonAnimation == null) {
|
|
Debug.LogWarning("Animation was stopped but preview doesn't exist. It's possible that the Preview Panel is closed.");
|
|
}
|
|
|
|
skeletonAnimation.AnimationState.ClearTracks();
|
|
skeletonAnimation.Skeleton.SetToSetupPose();
|
|
}
|
|
|
|
public void PlayPauseAnimation (string animationName, bool loop) {
|
|
if (skeletonData == null) return;
|
|
|
|
if (skeletonAnimation == null) {
|
|
//Debug.LogWarning("Animation was stopped but preview doesn't exist. It's possible that the Preview Panel is closed.");
|
|
return;
|
|
}
|
|
|
|
if (!skeletonAnimation.valid) return;
|
|
|
|
if (string.IsNullOrEmpty(animationName)) {
|
|
skeletonAnimation.Skeleton.SetToSetupPose();
|
|
skeletonAnimation.AnimationState.ClearTracks();
|
|
return;
|
|
}
|
|
|
|
var targetAnimation = skeletonData.FindAnimation(animationName);
|
|
if (targetAnimation != null) {
|
|
var currentTrack = this.ActiveTrack;
|
|
bool isEmpty = (currentTrack == null);
|
|
bool isNewAnimation = isEmpty || currentTrack.Animation != targetAnimation;
|
|
|
|
var skeleton = skeletonAnimation.Skeleton;
|
|
var animationState = skeletonAnimation.AnimationState;
|
|
|
|
if (isEmpty) {
|
|
skeleton.SetToSetupPose();
|
|
animationState.SetAnimation(0, targetAnimation, loop);
|
|
} else {
|
|
bool sameAnimation = (currentTrack.Animation == targetAnimation);
|
|
if (sameAnimation) {
|
|
currentTrack.TimeScale = (currentTrack.TimeScale == 0) ? 1f : 0f; // pause/play
|
|
} else {
|
|
currentTrack.TimeScale = 1f;
|
|
animationState.SetAnimation(0, targetAnimation, loop);
|
|
}
|
|
}
|
|
|
|
if (isNewAnimation) {
|
|
currentAnimationEvents.Clear();
|
|
currentAnimationEventTimes.Clear();
|
|
foreach (Timeline timeline in targetAnimation.Timelines) {
|
|
var eventTimeline = timeline as EventTimeline;
|
|
if (eventTimeline != null) {
|
|
for (int i = 0; i < eventTimeline.Events.Length; i++) {
|
|
currentAnimationEvents.Add(eventTimeline.Events[i]);
|
|
currentAnimationEventTimes.Add(eventTimeline.Frames[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Debug.LogFormat("The Spine.Animation named '{0}' was not found for this Skeleton.", animationName);
|
|
}
|
|
|
|
}
|
|
|
|
void DrawSkinToolbar (Rect r) {
|
|
if (!this.IsValid) return;
|
|
|
|
var skeleton = this.Skeleton;
|
|
string label = (skeleton.Skin != null) ? skeleton.Skin.Name : "default";
|
|
|
|
Rect popRect = new Rect(r);
|
|
popRect.y += 32;
|
|
popRect.x += 4;
|
|
popRect.height = 24;
|
|
popRect.width = 40;
|
|
EditorGUI.DropShadowLabel(popRect, SpineInspectorUtility.TempContent("Skin"));
|
|
|
|
popRect.y += 11;
|
|
popRect.width = 150;
|
|
popRect.x += 44;
|
|
|
|
if (GUI.Button(popRect, SpineInspectorUtility.TempContent(label, Icons.skin), EditorStyles.popup)) {
|
|
DrawSkinDropdown();
|
|
}
|
|
}
|
|
|
|
void DrawSetupPoseButton (Rect r) {
|
|
if (!this.IsValid)
|
|
return;
|
|
|
|
var skeleton = this.Skeleton;
|
|
|
|
Rect popRect = new Rect(r);
|
|
popRect.y += 64;
|
|
popRect.x += 4;
|
|
popRect.height = 24;
|
|
popRect.width = 40;
|
|
|
|
//popRect.y += 11;
|
|
popRect.width = 150;
|
|
//popRect.x += 44;
|
|
|
|
if (GUI.Button(popRect, SpineInspectorUtility.TempContent("Reset to SetupPose", Icons.skeleton))) {
|
|
ClearAnimationSetupPose();
|
|
RefreshOnNextUpdate();
|
|
}
|
|
}
|
|
|
|
void DrawSkinDropdown () {
|
|
var menu = new GenericMenu();
|
|
foreach (Skin s in skeletonData.Skins)
|
|
menu.AddItem(new GUIContent(s.Name, Icons.skin), skeletonAnimation.skeleton.Skin == s, HandleSkinDropdownSelection, s);
|
|
|
|
menu.ShowAsContext();
|
|
}
|
|
|
|
void HandleSkinDropdownSelection (object o) {
|
|
Skin skin = (Skin)o;
|
|
skeletonAnimation.initialSkinName = skin.Name;
|
|
skeletonAnimation.Initialize(true);
|
|
RefreshOnNextUpdate();
|
|
if (OnSkinChanged != null) OnSkinChanged(skin.Name);
|
|
}
|
|
|
|
void DrawTimeBar (Rect r) {
|
|
if (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 lineRectWidth = lineRect.width;
|
|
TrackEntry t = skeletonAnimation.AnimationState.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 % 1f;
|
|
|
|
lineRect.x = barRect.x + (lineRectWidth * wrappedTime) - 0.5f;
|
|
lineRect.width = 2;
|
|
|
|
GUI.color = Color.red;
|
|
GUI.DrawTexture(lineRect, EditorGUIUtility.whiteTexture);
|
|
GUI.color = Color.white;
|
|
|
|
currentAnimationEventTooltips = currentAnimationEventTooltips ?? new List<SpineEventTooltip>();
|
|
currentAnimationEventTooltips.Clear();
|
|
for (int i = 0; i < currentAnimationEvents.Count; i++) {
|
|
float eventTime = currentAnimationEventTimes[i];
|
|
var userEventIcon = Icons.userEvent;
|
|
var evRect = new Rect(barRect) {
|
|
x = Mathf.Max(((eventTime / t.Animation.Duration) * lineRectWidth) - (userEventIcon.width / 2), barRect.x),
|
|
y = barRect.y + userEventIcon.height,
|
|
width = userEventIcon.width,
|
|
height = userEventIcon.height
|
|
};
|
|
GUI.DrawTexture(evRect, userEventIcon);
|
|
|
|
Event ev = Event.current;
|
|
if (ev.type == EventType.Repaint) {
|
|
if (evRect.Contains(ev.mousePosition)) {
|
|
string eventName = currentAnimationEvents[i].Data.Name;
|
|
Rect tooltipRect = new Rect(evRect) {
|
|
width = EditorStyles.helpBox.CalcSize(new GUIContent(eventName)).x
|
|
};
|
|
tooltipRect.y -= 4;
|
|
tooltipRect.y -= tooltipRect.height * currentAnimationEventTooltips.Count; // Avoid several overlapping tooltips.
|
|
tooltipRect.x += 4;
|
|
|
|
// Handle tooltip overflowing to the right.
|
|
float rightEdgeOverflow = (tooltipRect.x + tooltipRect.width) - (barRect.x + barRect.width);
|
|
if (rightEdgeOverflow > 0)
|
|
tooltipRect.x -= rightEdgeOverflow;
|
|
|
|
currentAnimationEventTooltips.Add(new SpineEventTooltip { rect = tooltipRect, text = eventName });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw tooltips.
|
|
for (int i = 0; i < currentAnimationEventTooltips.Count; i++) {
|
|
GUI.Label(currentAnimationEventTooltips[i].rect, currentAnimationEventTooltips[i].text, EditorStyles.helpBox);
|
|
GUI.tooltip = currentAnimationEventTooltips[i].text;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnDestroy () {
|
|
DisposePreviewRenderUtility();
|
|
DestroyPreviewGameObject();
|
|
}
|
|
|
|
public void Clear () {
|
|
DisposePreviewRenderUtility();
|
|
DestroyPreviewGameObject();
|
|
}
|
|
|
|
void DisposePreviewRenderUtility () {
|
|
if (previewRenderUtility != null) {
|
|
previewRenderUtility.Cleanup();
|
|
previewRenderUtility = null;
|
|
}
|
|
}
|
|
|
|
void DestroyPreviewGameObject () {
|
|
if (previewGameObject != null) {
|
|
GameObject.DestroyImmediate(previewGameObject);
|
|
previewGameObject = null;
|
|
}
|
|
}
|
|
|
|
internal struct SpineEventTooltip {
|
|
public Rect rect;
|
|
public string text;
|
|
}
|
|
}
|
|
|
|
}
|