372 lines
14 KiB
C#
372 lines
14 KiB
C#
#if ENABLE_ANIMATION_COLLECTION && ENABLE_ANIMATION_BURST
|
|
#define ENABLE_ANIMATION_PERFORMANCE
|
|
#endif
|
|
|
|
using UnityEngine;
|
|
using UnityEditorInternal;
|
|
using UnityEngine.U2D.Animation;
|
|
using UnityEditor.IMGUI.Controls;
|
|
using UnityEngine.U2D;
|
|
using UnityEngine.U2D.Common;
|
|
|
|
namespace UnityEditor.U2D.Animation
|
|
{
|
|
[CustomEditor(typeof(SpriteSkin))]
|
|
[CanEditMultipleObjects]
|
|
class SpriteSkinEditor : Editor
|
|
{
|
|
private static class Contents
|
|
{
|
|
public static readonly GUIContent listHeaderLabel = new GUIContent("Bones", "GameObject Transform to represent the Bones defined by the Sprite that is currently used for deformation.");
|
|
public static readonly GUIContent rootBoneLabel = new GUIContent("Root Bone", "GameObject Transform to represent the Root Bone.");
|
|
public static readonly string spriteNotFound = L10n.Tr("Sprite not found in SpriteRenderer");
|
|
public static readonly string spriteHasNoSkinningInformation = L10n.Tr("Sprite has no Bind Poses");
|
|
public static readonly string spriteHasNoWeights = L10n.Tr("Sprite has no weights");
|
|
public static readonly string rootTransformNotFound = L10n.Tr("Root Bone not set");
|
|
public static readonly string rootTransformNotFoundInArray = L10n.Tr("Bone list doesn't contain a reference to the Root Bone");
|
|
public static readonly string invalidTransformArray = L10n.Tr("Bone list is invalid");
|
|
public static readonly string transformArrayContainsNull = L10n.Tr("Bone list contains unassigned references");
|
|
public static readonly string invalidTransformArrayLength = L10n.Tr("The number of Sprite's Bind Poses and the number of Transforms should match");
|
|
public static readonly GUIContent useManager = new GUIContent("Enable batching", "When enabled, SpriteSkin deformation will be done in batch to improve performance.");
|
|
public static readonly GUIContent alwaysUpdate = new GUIContent("Always Update", "Executes deformation of SpriteSkin even when the associated SpriteRenderer has been culled and is not visible.");
|
|
public static readonly string experimental = L10n.Tr("Experimental");
|
|
}
|
|
|
|
private static Color s_BoundingBoxHandleColor = new Color(255, 255, 255, 150) / 255;
|
|
|
|
private SerializedProperty m_RootBoneProperty;
|
|
private SerializedProperty m_BoneTransformsProperty;
|
|
private SerializedProperty m_AlwaysUpdateProperty;
|
|
private SpriteSkin m_SpriteSkin;
|
|
private ReorderableList m_ReorderableList;
|
|
private Sprite m_CurrentSprite;
|
|
private BoxBoundsHandle m_BoundsHandle = new BoxBoundsHandle();
|
|
private bool m_NeedsRebind = false;
|
|
#if ENABLE_ANIMATION_PERFORMANCE
|
|
private SerializedProperty m_UseBatching;
|
|
private bool m_ExperimentalFold;
|
|
#endif
|
|
private bool m_BoneFold = true;
|
|
|
|
private void OnEnable()
|
|
{
|
|
m_SpriteSkin = (SpriteSkin)target;
|
|
m_SpriteSkin.OnEditorEnable();
|
|
|
|
m_RootBoneProperty = serializedObject.FindProperty("m_RootBone");
|
|
#if ENABLE_ANIMATION_PERFORMANCE
|
|
m_UseBatching = serializedObject.FindProperty("m_UseBatching");
|
|
#endif
|
|
m_BoneTransformsProperty = serializedObject.FindProperty("m_BoneTransforms");
|
|
m_AlwaysUpdateProperty = serializedObject.FindProperty("m_AlwaysUpdate");
|
|
|
|
m_CurrentSprite = m_SpriteSkin.spriteRenderer.sprite;
|
|
m_BoundsHandle.axes = BoxBoundsHandle.Axes.X | BoxBoundsHandle.Axes.Y;
|
|
m_BoundsHandle.SetColor(s_BoundingBoxHandleColor);
|
|
|
|
SetupReorderableList();
|
|
|
|
Undo.undoRedoPerformed += UndoRedoPerformed;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
Undo.undoRedoPerformed -= UndoRedoPerformed;
|
|
}
|
|
|
|
private void UndoRedoPerformed()
|
|
{
|
|
m_CurrentSprite = m_SpriteSkin.spriteRenderer.sprite;
|
|
}
|
|
|
|
private void SetupReorderableList()
|
|
{
|
|
m_ReorderableList = new ReorderableList(serializedObject, m_BoneTransformsProperty, false, true, false, false);
|
|
m_ReorderableList.headerHeight = 1.0f;
|
|
m_ReorderableList.elementHeightCallback = (int index) =>
|
|
{
|
|
return EditorGUIUtility.singleLineHeight + 6;
|
|
};
|
|
m_ReorderableList.drawElementCallback = (Rect rect, int index, bool isactive, bool isfocused) =>
|
|
{
|
|
var content = GUIContent.none;
|
|
|
|
if (m_CurrentSprite != null)
|
|
{
|
|
var bones = m_CurrentSprite.GetBones();
|
|
if (index < bones.Length)
|
|
content = new GUIContent(bones[index].name);
|
|
}
|
|
|
|
rect.y += 2f;
|
|
rect.height = EditorGUIUtility.singleLineHeight;
|
|
SerializedProperty element = m_BoneTransformsProperty.GetArrayElementAtIndex(index);
|
|
EditorGUI.PropertyField(rect, element, content);
|
|
};
|
|
}
|
|
|
|
private void InitializeBoneTransformArray()
|
|
{
|
|
if (m_CurrentSprite)
|
|
{
|
|
var elementCount = m_BoneTransformsProperty.arraySize;
|
|
var bindPoses = m_CurrentSprite.GetBindPoses();
|
|
|
|
if (elementCount != bindPoses.Length)
|
|
{
|
|
m_BoneTransformsProperty.arraySize = bindPoses.Length;
|
|
|
|
for (int i = elementCount; i < m_BoneTransformsProperty.arraySize; ++i)
|
|
m_BoneTransformsProperty.GetArrayElementAtIndex(i).objectReferenceValue = null;
|
|
|
|
m_NeedsRebind = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
serializedObject.Update();
|
|
EditorGUILayout.PropertyField(m_AlwaysUpdateProperty, Contents.alwaysUpdate);
|
|
|
|
var sprite = m_SpriteSkin.spriteRenderer.sprite;
|
|
var spriteChanged = m_CurrentSprite != sprite;
|
|
|
|
if (m_ReorderableList == null || spriteChanged)
|
|
{
|
|
m_CurrentSprite = sprite;
|
|
InitializeBoneTransformArray();
|
|
SetupReorderableList();
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUILayout.PropertyField(m_RootBoneProperty, Contents.rootBoneLabel);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
m_NeedsRebind = true;
|
|
}
|
|
|
|
m_BoneFold = EditorGUILayout.Foldout(m_BoneFold, Contents.listHeaderLabel, true);
|
|
if (m_BoneFold)
|
|
{
|
|
EditorGUILayout.Space();
|
|
if (!serializedObject.isEditingMultipleObjects)
|
|
{
|
|
EditorGUI.BeginDisabledGroup(m_SpriteSkin.rootBone == null);
|
|
m_ReorderableList.DoLayoutList();
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
|
|
EditorGUI.BeginDisabledGroup(!EnableCreateBones());
|
|
DoGenerateBonesButton();
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
EditorGUI.BeginDisabledGroup(!EnableSetBindPose());
|
|
DoResetBindPoseButton();
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
#if ENABLE_ANIMATION_PERFORMANCE
|
|
m_ExperimentalFold = EditorGUILayout.Foldout(m_ExperimentalFold, Contents.experimental, true);
|
|
if (m_ExperimentalFold)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUILayout.PropertyField(m_UseBatching, Contents.useManager);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
foreach (var obj in targets)
|
|
{
|
|
((SpriteSkin)obj).UseBatching(m_UseBatching.boolValue);
|
|
}
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
#endif
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
if (m_NeedsRebind)
|
|
Rebind();
|
|
|
|
if (spriteChanged && !m_SpriteSkin.ignoreNextSpriteChange)
|
|
{
|
|
ResetBounds(Undo.GetCurrentGroupName());
|
|
m_SpriteSkin.ignoreNextSpriteChange = false;
|
|
}
|
|
|
|
DoValidationWarnings();
|
|
}
|
|
|
|
private void Rebind()
|
|
{
|
|
foreach (var t in targets)
|
|
{
|
|
var spriteSkin = t as SpriteSkin;
|
|
|
|
if(spriteSkin.spriteRenderer.sprite == null || spriteSkin.rootBone == null)
|
|
continue;
|
|
|
|
spriteSkin.Rebind();
|
|
ResetBoundsIfNeeded(spriteSkin);
|
|
}
|
|
|
|
m_NeedsRebind = false;
|
|
}
|
|
|
|
private void ResetBounds(string undoName = "Reset Bounds")
|
|
{
|
|
foreach (var t in targets)
|
|
{
|
|
var spriteSkin = t as SpriteSkin;
|
|
|
|
if (!spriteSkin.isValid)
|
|
continue;
|
|
|
|
Undo.RegisterCompleteObjectUndo(spriteSkin, undoName);
|
|
spriteSkin.CalculateBounds();
|
|
|
|
EditorUtility.SetDirty(spriteSkin);
|
|
}
|
|
}
|
|
|
|
private void ResetBoundsIfNeeded(SpriteSkin spriteSkin)
|
|
{
|
|
if (spriteSkin.isValid && spriteSkin.bounds == new Bounds())
|
|
spriteSkin.CalculateBounds();
|
|
}
|
|
|
|
private bool EnableCreateBones()
|
|
{
|
|
foreach (var t in targets)
|
|
{
|
|
var spriteSkin = t as SpriteSkin;
|
|
var sprite = spriteSkin.spriteRenderer.sprite;
|
|
|
|
if (sprite != null && spriteSkin.rootBone == null)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool EnableSetBindPose()
|
|
{
|
|
return IsAnyTargetValid();
|
|
}
|
|
|
|
private bool IsAnyTargetValid()
|
|
{
|
|
foreach (var t in targets)
|
|
{
|
|
var spriteSkin = t as SpriteSkin;
|
|
|
|
if (spriteSkin.isValid)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void DoGenerateBonesButton()
|
|
{
|
|
if (GUILayout.Button("Create Bones", GUILayout.MaxWidth(125f)))
|
|
{
|
|
foreach (var t in targets)
|
|
{
|
|
var spriteSkin = t as SpriteSkin;
|
|
var sprite = spriteSkin.spriteRenderer.sprite;
|
|
|
|
if (sprite == null || spriteSkin.rootBone != null)
|
|
continue;
|
|
|
|
Undo.RegisterCompleteObjectUndo(spriteSkin, "Create Bones");
|
|
|
|
spriteSkin.CreateBoneHierarchy();
|
|
|
|
foreach (var transform in spriteSkin.boneTransforms)
|
|
Undo.RegisterCreatedObjectUndo(transform.gameObject, "Create Bones");
|
|
|
|
ResetBoundsIfNeeded(spriteSkin);
|
|
|
|
EditorUtility.SetDirty(spriteSkin);
|
|
}
|
|
BoneGizmo.instance.boneGizmoController.OnSelectionChanged();
|
|
}
|
|
}
|
|
|
|
private void DoResetBindPoseButton()
|
|
{
|
|
if (GUILayout.Button("Reset Bind Pose", GUILayout.MaxWidth(125f)))
|
|
{
|
|
foreach (var t in targets)
|
|
{
|
|
var spriteSkin = t as SpriteSkin;
|
|
|
|
if (!spriteSkin.isValid)
|
|
continue;
|
|
|
|
Undo.RecordObjects(spriteSkin.boneTransforms, "Reset Bind Pose");
|
|
spriteSkin.ResetBindPose();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DoValidationWarnings()
|
|
{
|
|
EditorGUILayout.Space();
|
|
|
|
bool preAppendObjectName = targets.Length > 1;
|
|
|
|
foreach (var t in targets)
|
|
{
|
|
var spriteSkin = t as SpriteSkin;
|
|
|
|
var validationResult = spriteSkin.Validate();
|
|
|
|
if (validationResult == SpriteSkinValidationResult.Ready)
|
|
continue;
|
|
|
|
var text = "";
|
|
|
|
switch (validationResult)
|
|
{
|
|
case SpriteSkinValidationResult.SpriteNotFound:
|
|
text = Contents.spriteNotFound;
|
|
break;
|
|
case SpriteSkinValidationResult.SpriteHasNoSkinningInformation:
|
|
text = Contents.spriteHasNoSkinningInformation;
|
|
break;
|
|
case SpriteSkinValidationResult.SpriteHasNoWeights:
|
|
text = Contents.spriteHasNoWeights;
|
|
break;
|
|
case SpriteSkinValidationResult.RootTransformNotFound:
|
|
text = Contents.rootTransformNotFound;
|
|
break;
|
|
case SpriteSkinValidationResult.RootNotFoundInTransformArray:
|
|
text = Contents.rootTransformNotFoundInArray;
|
|
break;
|
|
case SpriteSkinValidationResult.InvalidTransformArray:
|
|
text = Contents.invalidTransformArray;
|
|
break;
|
|
case SpriteSkinValidationResult.InvalidTransformArrayLength:
|
|
text = Contents.invalidTransformArrayLength;
|
|
break;
|
|
case SpriteSkinValidationResult.TransformArrayContainsNull:
|
|
text = Contents.transformArrayContainsNull;
|
|
break;
|
|
}
|
|
|
|
if (preAppendObjectName)
|
|
text = spriteSkin.name + ": " + text;
|
|
|
|
EditorGUILayout.HelpBox(text, MessageType.Warning);
|
|
}
|
|
}
|
|
}
|
|
}
|