Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0e95036e72b08544a9d295dd4366f40
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb646ac6e394e534b80d5cac61478488
|
||||
folderAsset: yes
|
||||
timeCreated: 1563305058
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,227 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
[CustomPropertyDrawer(typeof(AnimationReferenceAsset))]
|
||||
public class AnimationReferenceAssetDrawer : PropertyDrawer {
|
||||
|
||||
const string NoneString = "<None>";
|
||||
const string ReferenceAssetsFolderName = SpineEditorUtilities.ReferenceAssetsFolderName;
|
||||
|
||||
static GUIStyle errorPopupStyle;
|
||||
GUIStyle ErrorPopupStyle {
|
||||
get {
|
||||
if (errorPopupStyle == null) errorPopupStyle = new GUIStyle(EditorStyles.popup);
|
||||
errorPopupStyle.normal.textColor = Color.red;
|
||||
errorPopupStyle.hover.textColor = Color.red;
|
||||
errorPopupStyle.focused.textColor = Color.red;
|
||||
errorPopupStyle.active.textColor = Color.red;
|
||||
return errorPopupStyle;
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonDataAsset resolvedSkeletonDataAsset;
|
||||
|
||||
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
|
||||
if (property.propertyType != SerializedPropertyType.ObjectReference) {
|
||||
EditorGUI.LabelField(position, label.text, "ERROR: Must be an object reference.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isSkeletonDataMismatch;
|
||||
SkeletonDataAsset skeletonDataAsset = ResolveSkeletonDataAsset(property, out isSkeletonDataMismatch);
|
||||
SkeletonData skeletonData = skeletonDataAsset != null ? skeletonDataAsset.GetSkeletonData(true) : null;
|
||||
label = EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
if (skeletonData == null || skeletonData.Animations.Count == 0) {
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
} else {
|
||||
Rect objectFieldRect;
|
||||
Rect dropdownRect;
|
||||
SplitRect(position, out objectFieldRect, out dropdownRect);
|
||||
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
SplitRect(position, out objectFieldRect, out dropdownRect);
|
||||
|
||||
EditorGUI.PropertyField(objectFieldRect, property, GUIContent.none);
|
||||
|
||||
string currentAnimationName = GetAnimationName(property);
|
||||
GUIContent dropdownLabel = string.IsNullOrEmpty(currentAnimationName) ?
|
||||
new GUIContent(NoneString, SpineEditorUtilities.Icons.animation) :
|
||||
new GUIContent(currentAnimationName, SpineEditorUtilities.Icons.animation);
|
||||
|
||||
GUIStyle usedStyle =
|
||||
(isSkeletonDataMismatch && SpineEditorUtilities.Preferences.skeletonDataAssetMismatchWarning) ?
|
||||
ErrorPopupStyle : EditorStyles.popup;
|
||||
if (GUI.Button(dropdownRect, dropdownLabel, usedStyle)) {
|
||||
ShowAnimationMenu(property, skeletonDataAsset, skeletonData);
|
||||
}
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
static void SplitRect (Rect position, out Rect left, out Rect right) {
|
||||
float dropdownWidth = Mathf.Min(position.width * 0.5f, 200);
|
||||
left = new Rect(position.x, position.y, position.width - dropdownWidth - 2, position.height);
|
||||
right = new Rect(position.x + position.width - dropdownWidth, position.y, dropdownWidth, position.height);
|
||||
}
|
||||
|
||||
SkeletonDataAsset ResolveSkeletonDataAsset (SerializedProperty property, out bool skeletonDataAssetMismatch) {
|
||||
skeletonDataAssetMismatch = false;
|
||||
SkeletonDataAsset expectedSkeletonDataAsset = GetSkeletonDataAssetFromGameObject(property);
|
||||
|
||||
AnimationReferenceAsset currentAsset = property.objectReferenceValue as AnimationReferenceAsset;
|
||||
if (currentAsset != null && currentAsset.SkeletonDataAsset != null) {
|
||||
resolvedSkeletonDataAsset = currentAsset.SkeletonDataAsset;
|
||||
// If other SkeletonDataAsset than expected, use assigned asset but show warning color in Inspector.
|
||||
if (expectedSkeletonDataAsset && resolvedSkeletonDataAsset != expectedSkeletonDataAsset)
|
||||
skeletonDataAssetMismatch = true;
|
||||
} else {
|
||||
resolvedSkeletonDataAsset = expectedSkeletonDataAsset;
|
||||
}
|
||||
return resolvedSkeletonDataAsset;
|
||||
}
|
||||
|
||||
SkeletonDataAsset GetSkeletonDataAssetFromGameObject (SerializedProperty property) {
|
||||
Object targetObject = property.serializedObject.targetObject;
|
||||
IHasSkeletonDataAsset skeletonDataAssetComponent = targetObject as IHasSkeletonDataAsset;
|
||||
if (skeletonDataAssetComponent == null) {
|
||||
Component component = targetObject as Component;
|
||||
if (component != null)
|
||||
skeletonDataAssetComponent = component.GetComponentInParent<IHasSkeletonDataAsset>();
|
||||
}
|
||||
if (skeletonDataAssetComponent != null) {
|
||||
return skeletonDataAssetComponent.SkeletonDataAsset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static string GetAnimationName (SerializedProperty property) {
|
||||
AnimationReferenceAsset asset = property.objectReferenceValue as AnimationReferenceAsset;
|
||||
if (asset == null) return null;
|
||||
return asset.AnimationName;
|
||||
}
|
||||
|
||||
void ShowAnimationMenu (SerializedProperty property, SkeletonDataAsset skeletonDataAsset, SkeletonData skeletonData) {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(new GUIContent(NoneString), property.objectReferenceValue == null, () => {
|
||||
property.objectReferenceValue = null;
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
|
||||
ExposedList<Animation> animations = skeletonData.Animations;
|
||||
string currentAnimationName = GetAnimationName(property);
|
||||
|
||||
for (int i = 0; i < animations.Count; i++) {
|
||||
string animName = animations.Items[i].Name;
|
||||
bool isSelected = animName == currentAnimationName;
|
||||
menu.AddItem(new GUIContent(animName), isSelected, HandleAnimationSelected,
|
||||
new AnimationSelectContext(property, skeletonDataAsset, animName));
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
static void HandleAnimationSelected (object contextObj) {
|
||||
AnimationSelectContext context = (AnimationSelectContext)contextObj;
|
||||
AnimationReferenceAsset asset = FindAnimationReferenceAsset(context.skeletonDataAsset, context.animationName);
|
||||
if (asset == null) {
|
||||
Debug.LogWarning(string.Format("AnimationReferenceAsset for animation '{0}' not found. " +
|
||||
"Please select the SkeletonDataAsset and in the Inspector hit 'Create Animation Reference " +
|
||||
"Assets', or manually assign an AnimationReferenceAsset if using shared AnimationReferenceAssets.",
|
||||
context.animationName), context.skeletonDataAsset);
|
||||
return;
|
||||
}
|
||||
context.property.objectReferenceValue = asset;
|
||||
context.property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
static AnimationReferenceAsset FindAnimationReferenceAsset (SkeletonDataAsset targetSkeletonDataAsset,
|
||||
string targetAnimationName) {
|
||||
|
||||
string skeletonDataAssetPath = AssetDatabase.GetAssetPath(targetSkeletonDataAsset);
|
||||
string parentFolder = Path.GetDirectoryName(skeletonDataAssetPath);
|
||||
|
||||
// Search AnimationReferenceAssetContainer sub-assets
|
||||
string skeletonDataAssetName = Path.GetFileNameWithoutExtension(skeletonDataAssetPath);
|
||||
string baseName = skeletonDataAssetName.Replace(AssetUtility.SkeletonDataSuffix, "");
|
||||
string containerPath = string.Format("{0}/{1}{2}.asset", parentFolder, baseName,
|
||||
SpineEditorUtilities.AnimationReferenceContainerSuffix);
|
||||
AnimationReferenceAsset foundAsset = FindAnimationReferenceInSubAssets(containerPath, targetSkeletonDataAsset, targetAnimationName);
|
||||
if (foundAsset != null) return foundAsset;
|
||||
|
||||
// Search standalone files in same asset directory
|
||||
string dataPath = parentFolder + "/" + ReferenceAssetsFolderName;
|
||||
string safeName = AssetUtility.GetPathSafeName(targetAnimationName);
|
||||
string assetPath = string.Format("{0}/{1}.asset", dataPath, safeName);
|
||||
AnimationReferenceAsset existingAsset = AssetDatabase.LoadAssetAtPath<AnimationReferenceAsset>(assetPath);
|
||||
if (existingAsset != null) return existingAsset;
|
||||
|
||||
// Global fallback: search the project for matching AnimationReferenceAsset including sub-assets
|
||||
string[] guids = AssetDatabase.FindAssets("t:AnimationReferenceAsset");
|
||||
foreach (string guid in guids) {
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
foundAsset = FindAnimationReferenceInSubAssets(path, targetSkeletonDataAsset, targetAnimationName);
|
||||
if (foundAsset != null) return foundAsset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static AnimationReferenceAsset FindAnimationReferenceInSubAssets (string assetPath,
|
||||
SkeletonDataAsset targetSkeletonDataAsset, string targetAnimationName) {
|
||||
|
||||
UnityEngine.Object[] allAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
foreach (UnityEngine.Object obj in allAssets) {
|
||||
AnimationReferenceAsset asset = obj as AnimationReferenceAsset;
|
||||
if (asset == null) continue;
|
||||
if (asset.SkeletonDataAsset == targetSkeletonDataAsset && asset.AnimationName == targetAnimationName)
|
||||
return asset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
struct AnimationSelectContext {
|
||||
public SerializedProperty property;
|
||||
public SkeletonDataAsset skeletonDataAsset;
|
||||
public string animationName;
|
||||
|
||||
public AnimationSelectContext (SerializedProperty property, SkeletonDataAsset skeletonDataAsset, string animationName) {
|
||||
this.property = property;
|
||||
this.skeletonDataAsset = skeletonDataAsset;
|
||||
this.animationName = animationName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df65d8f8319dac74296b5ef428050221
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,182 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Editor = UnityEditor.Editor;
|
||||
|
||||
[CustomEditor(typeof(AnimationReferenceAsset))]
|
||||
public class AnimationReferenceAssetEditor : Editor {
|
||||
|
||||
const string InspectorHelpText = "This is a Spine-Unity Animation Reference Asset. It serializes a reference to a SkeletonData asset and an animationName. It does not contain actual animation data. At runtime, it stores a reference to a Spine.Animation.\n\n" +
|
||||
"You can use this in your AnimationState calls instead of a string animation name or a Spine.Animation reference. Use its implicit conversion into Spine.Animation or its .Animation property.\n\n" +
|
||||
"Use AnimationReferenceAssets as an alternative to storing strings or finding animations and caching per component. This only does the lookup by string once, and allows you to store and manage animations via asset references.";
|
||||
|
||||
readonly SkeletonInspectorPreview preview = new SkeletonInspectorPreview();
|
||||
FieldInfo skeletonDataAssetField = typeof(AnimationReferenceAsset).GetField("skeletonDataAsset", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
FieldInfo nameField = typeof(AnimationReferenceAsset).GetField("animationName", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
AnimationReferenceAsset ThisAnimationReferenceAsset { get { return target as AnimationReferenceAsset; } }
|
||||
SkeletonDataAsset ThisSkeletonDataAsset { get { return skeletonDataAssetField.GetValue(ThisAnimationReferenceAsset) as SkeletonDataAsset; } }
|
||||
string ThisAnimationName { get { return nameField.GetValue(ThisAnimationReferenceAsset) as string; } }
|
||||
|
||||
bool changeNextFrame = false;
|
||||
SerializedProperty animationNameProperty;
|
||||
SkeletonDataAsset lastSkeletonDataAsset;
|
||||
SkeletonData lastSkeletonData;
|
||||
|
||||
void OnEnable () { HandleOnEnablePreview(); }
|
||||
void OnDestroy () {
|
||||
HandleOnDestroyPreview();
|
||||
AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
|
||||
EditorApplication.update -= preview.HandleEditorUpdate;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
animationNameProperty = animationNameProperty ?? serializedObject.FindProperty("animationName");
|
||||
string animationName = animationNameProperty.stringValue;
|
||||
|
||||
Animation animation = null;
|
||||
if (ThisSkeletonDataAsset != null) {
|
||||
SkeletonData skeletonData = ThisSkeletonDataAsset.GetSkeletonData(true);
|
||||
if (skeletonData != null) {
|
||||
animation = skeletonData.FindAnimation(animationName);
|
||||
}
|
||||
}
|
||||
bool animationNotFound = (animation == null);
|
||||
|
||||
if (changeNextFrame) {
|
||||
changeNextFrame = false;
|
||||
|
||||
if (ThisSkeletonDataAsset != lastSkeletonDataAsset || ThisSkeletonDataAsset.GetSkeletonData(true) != lastSkeletonData) {
|
||||
preview.Clear();
|
||||
preview.Initialize(Repaint, ThisSkeletonDataAsset, LastSkinName);
|
||||
|
||||
if (animationNotFound) {
|
||||
animationNameProperty.stringValue = "";
|
||||
preview.ClearAnimationSetupPose();
|
||||
}
|
||||
}
|
||||
|
||||
preview.ClearAnimationSetupPose();
|
||||
|
||||
if (!string.IsNullOrEmpty(animationNameProperty.stringValue))
|
||||
preview.PlayPauseAnimation(animationNameProperty.stringValue, true);
|
||||
}
|
||||
|
||||
//EditorGUILayout.HelpBox(AnimationReferenceAssetEditor.InspectorHelpText, MessageType.Info, true);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
changeNextFrame = true;
|
||||
}
|
||||
|
||||
// Draw extra info below default inspector.
|
||||
EditorGUILayout.Space();
|
||||
if (ThisSkeletonDataAsset == null) {
|
||||
EditorGUILayout.HelpBox("SkeletonDataAsset is missing.", MessageType.Error);
|
||||
} else if (string.IsNullOrEmpty(animationName)) {
|
||||
EditorGUILayout.HelpBox("No animation selected.", MessageType.Warning);
|
||||
} else if (animationNotFound) {
|
||||
EditorGUILayout.HelpBox(string.Format("Animation named {0} was not found for this Skeleton.", animationNameProperty.stringValue), MessageType.Warning);
|
||||
} else {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
if (!string.Equals(AssetUtility.GetPathSafeName(animationName), ThisAnimationReferenceAsset.name, System.StringComparison.OrdinalIgnoreCase))
|
||||
EditorGUILayout.HelpBox("Animation name value does not match this asset's name. Inspectors using this asset may be misleading.", MessageType.None);
|
||||
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(animationName, SpineEditorUtilities.Icons.animation));
|
||||
if (animation != null) {
|
||||
EditorGUILayout.LabelField(string.Format("Timelines: {0}", animation.Timelines.Count));
|
||||
EditorGUILayout.LabelField(string.Format("Duration: {0} sec", animation.Duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSkeletonDataAsset = ThisSkeletonDataAsset;
|
||||
lastSkeletonData = ThisSkeletonDataAsset.GetSkeletonData(true);
|
||||
}
|
||||
|
||||
#region Preview Handlers
|
||||
string TargetAssetGUID { get { return AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(ThisSkeletonDataAsset)); } }
|
||||
string LastSkinKey { get { return TargetAssetGUID + "_lastSkin"; } }
|
||||
string LastSkinName { get { return EditorPrefs.GetString(LastSkinKey, ""); } }
|
||||
|
||||
void HandleOnEnablePreview () {
|
||||
if (ThisSkeletonDataAsset != null && ThisSkeletonDataAsset.skeletonJSON == null)
|
||||
return;
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
|
||||
// This handles the case where the managed editor assembly is unloaded before recompilation when code changes.
|
||||
AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
|
||||
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
|
||||
|
||||
preview.Initialize(this.Repaint, ThisSkeletonDataAsset, LastSkinName);
|
||||
preview.PlayPauseAnimation(ThisAnimationName, true);
|
||||
preview.OnSkinChanged -= HandleOnSkinChanged;
|
||||
preview.OnSkinChanged += HandleOnSkinChanged;
|
||||
EditorApplication.update -= preview.HandleEditorUpdate;
|
||||
EditorApplication.update += preview.HandleEditorUpdate;
|
||||
}
|
||||
|
||||
private void OnDomainUnload (object sender, EventArgs e) {
|
||||
OnDestroy();
|
||||
}
|
||||
|
||||
private void HandleOnSkinChanged (string skinName) {
|
||||
EditorPrefs.SetString(LastSkinKey, skinName);
|
||||
preview.PlayPauseAnimation(ThisAnimationName, true);
|
||||
}
|
||||
|
||||
void HandleOnDestroyPreview () {
|
||||
EditorApplication.update -= preview.HandleEditorUpdate;
|
||||
preview.OnDestroy();
|
||||
}
|
||||
|
||||
override public bool HasPreviewGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) return false;
|
||||
return ThisSkeletonDataAsset != null && ThisSkeletonDataAsset.GetSkeletonData(true) != null;
|
||||
}
|
||||
|
||||
override public void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
|
||||
preview.Initialize(this.Repaint, ThisSkeletonDataAsset);
|
||||
preview.HandleInteractivePreviewGUI(r, background);
|
||||
}
|
||||
|
||||
public override 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9511532e80feed24881a5863f5485446
|
||||
timeCreated: 1523316585
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomPropertyDrawer(typeof(MaterialOverrideSet))]
|
||||
public class MaterialOverrideSetDrawer : PropertyDrawer {
|
||||
private const float Padding = 5f;
|
||||
private const float ButtonWidth = 20f;
|
||||
|
||||
public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
|
||||
SerializedProperty dictionaryKeysProp = property.FindPropertyRelative("dictionaryKeys");
|
||||
return EditorGUIUtility.singleLineHeight * (dictionaryKeysProp.arraySize + 2);
|
||||
}
|
||||
|
||||
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
SerializedProperty nameProperty = property.FindPropertyRelative("name");
|
||||
SerializedProperty dictionaryKeysProperty = property.FindPropertyRelative("dictionaryKeys");
|
||||
SerializedProperty dictionaryValuesProperty = property.FindPropertyRelative("dictionaryValues");
|
||||
|
||||
Rect labelPosition = new Rect(position.x, position.y, position.width * 0.5f, EditorGUIUtility.singleLineHeight);
|
||||
Rect namePosition = new Rect(position.x + position.width * 0.5f, position.y, position.width * 0.5f, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.LabelField(labelPosition, label);
|
||||
nameProperty.stringValue = EditorGUI.TextField(namePosition, GUIContent.none, nameProperty.stringValue);
|
||||
|
||||
Rect contentPosition = EditorGUI.IndentedRect(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight, position.width, position.height - EditorGUIUtility.singleLineHeight));
|
||||
float lineHeight = EditorGUIUtility.singleLineHeight;
|
||||
|
||||
for (int i = 0; i < dictionaryKeysProperty.arraySize; i++) {
|
||||
Rect keyRect = new Rect(contentPosition.x, contentPosition.y + i * lineHeight, contentPosition.width * 0.5f, lineHeight);
|
||||
Rect valueRect = new Rect(contentPosition.x + contentPosition.width * 0.5f, contentPosition.y + i * lineHeight, contentPosition.width * 0.5f - ButtonWidth, lineHeight);
|
||||
Rect removeButtonRect = new Rect(contentPosition.xMax - ButtonWidth, contentPosition.y + i * lineHeight, ButtonWidth, lineHeight);
|
||||
|
||||
EditorGUI.PropertyField(keyRect, dictionaryKeysProperty.GetArrayElementAtIndex(i), GUIContent.none);
|
||||
EditorGUI.PropertyField(valueRect, dictionaryValuesProperty.GetArrayElementAtIndex(i), GUIContent.none);
|
||||
|
||||
if (GUI.Button(removeButtonRect, "-")) {
|
||||
dictionaryKeysProperty.DeleteArrayElementAtIndex(i);
|
||||
dictionaryValuesProperty.DeleteArrayElementAtIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int indent = 15;
|
||||
Rect addButtonRect = new Rect(contentPosition.x + indent, contentPosition.y + dictionaryKeysProperty.arraySize * lineHeight, contentPosition.width - indent, lineHeight);
|
||||
if (GUI.Button(addButtonRect, "+ Add Entry")) {
|
||||
dictionaryKeysProperty.InsertArrayElementAtIndex(dictionaryKeysProperty.arraySize);
|
||||
dictionaryValuesProperty.InsertArrayElementAtIndex(dictionaryValuesProperty.arraySize);
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90d399311223a9040a5e7d32742a1bbe
|
||||
timeCreated: 1686129585
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01cbef8f24d105f4bafa9668d669e040
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,477 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
#define USES_ENTITY_ID
|
||||
#endif
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
#define TEXTUREIMPORTER_SPRITESHEET_OBSOLETE
|
||||
#endif
|
||||
|
||||
//#define BAKE_ALL_BUTTON
|
||||
//#define REGION_BAKING_MESH
|
||||
|
||||
using Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(SpineAtlasAsset)), CanEditMultipleObjects]
|
||||
public class SpineAtlasAssetInspector : UnityEditor.Editor {
|
||||
SerializedProperty atlasFile, materials, materialOverrides, textureLoadingMode, onDemandTextureLoader;
|
||||
SpineAtlasAsset atlasAsset;
|
||||
|
||||
GUIContent spriteSlicesLabel;
|
||||
GUIContent SpriteSlicesLabel {
|
||||
get {
|
||||
if (spriteSlicesLabel == null) {
|
||||
spriteSlicesLabel = new GUIContent(
|
||||
"Apply Regions as Texture Sprite Slices",
|
||||
SpineEditorUtilities.Icons.unity,
|
||||
"Adds Sprite slices to atlas texture(s). " +
|
||||
"Updates existing slices if ones with matching names exist. \n\n" +
|
||||
"If your atlas was exported with Premultiply Alpha, " +
|
||||
"your SpriteRenderer should use the generated Spine _Material asset (or any Material with a PMA shader) instead of Sprites-Default.");
|
||||
}
|
||||
return spriteSlicesLabel;
|
||||
}
|
||||
}
|
||||
|
||||
static List<AtlasRegion> GetRegions (Atlas atlas) {
|
||||
FieldInfo regionsField = SpineInspectorUtility.GetNonPublicField(typeof(Atlas), "regions");
|
||||
return (List<AtlasRegion>)regionsField.GetValue(atlas);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
atlasFile = serializedObject.FindProperty("atlasFile");
|
||||
materials = serializedObject.FindProperty("materials");
|
||||
textureLoadingMode = serializedObject.FindProperty("textureLoadingMode");
|
||||
onDemandTextureLoader = serializedObject.FindProperty("onDemandTextureLoader");
|
||||
materials.isExpanded = true;
|
||||
materialOverrides = serializedObject.FindProperty("serializedMaterialOverrides");
|
||||
atlasAsset = (SpineAtlasAsset)target;
|
||||
#if REGION_BAKING_MESH
|
||||
UpdateBakedList();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if REGION_BAKING_MESH
|
||||
private List<bool> baked;
|
||||
private List<GameObject> bakedObjects;
|
||||
|
||||
void UpdateBakedList () {
|
||||
AtlasAsset asset = (AtlasAsset)target;
|
||||
baked = new List<bool>();
|
||||
bakedObjects = new List<GameObject>();
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
List<AtlasRegion> regions = this.Regions;
|
||||
string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
|
||||
string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
|
||||
string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
AtlasRegion region = regions[i];
|
||||
string bakedPrefabPath = Path.Combine(bakedDirPath, AssetUtility.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/");
|
||||
GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));
|
||||
baked.Add(prefab != null);
|
||||
bakedObjects.Add(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
DrawDefaultInspector();
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
atlasAsset = (atlasAsset == null) ? (SpineAtlasAsset)target : atlasAsset;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(atlasFile);
|
||||
EditorGUILayout.PropertyField(materials, true);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
atlasAsset.Clear();
|
||||
atlasAsset.GetAtlas();
|
||||
}
|
||||
|
||||
if (materials.arraySize == 0) {
|
||||
EditorGUILayout.HelpBox("No materials", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < materials.arraySize; i++) {
|
||||
SerializedProperty prop = materials.GetArrayElementAtIndex(i);
|
||||
Material material = (Material)prop.objectReferenceValue;
|
||||
if (material == null) {
|
||||
EditorGUILayout.HelpBox("Materials cannot be null.", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(materialOverrides, true);
|
||||
|
||||
if (textureLoadingMode != null) {
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(textureLoadingMode);
|
||||
EditorGUILayout.PropertyField(onDemandTextureLoader);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Set Mipmap Bias to " + SpinePreferences.DEFAULT_MIPMAPBIAS, tooltip: "This may help textures with mipmaps be less blurry when used for 2D sprites."))) {
|
||||
foreach (Material m in atlasAsset.materials) {
|
||||
Texture texture = m.mainTexture;
|
||||
#if USES_ENTITY_ID
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetEntityId());
|
||||
#else
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetInstanceID());
|
||||
#endif
|
||||
TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(texturePath);
|
||||
importer.mipMapBias = SpinePreferences.DEFAULT_MIPMAPBIAS;
|
||||
EditorUtility.SetDirty(texture);
|
||||
}
|
||||
Debug.Log("Texture mipmap bias set to " + SpinePreferences.DEFAULT_MIPMAPBIAS);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpriteSlicesLabel)) {
|
||||
Atlas atlas = atlasAsset.GetAtlas();
|
||||
foreach (Material m in atlasAsset.materials)
|
||||
UpdateSpriteSlices(m.mainTexture, atlas);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
#if REGION_BAKING_MESH
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
Atlas atlas = asset.GetAtlas();
|
||||
FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
|
||||
List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
|
||||
EditorGUILayout.LabelField(new GUIContent("Region Baking", SpineEditorUtilities.Icons.unityIcon));
|
||||
EditorGUI.indentLevel++;
|
||||
AtlasPage lastPage = null;
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
if (lastPage != regions[i].page) {
|
||||
if (lastPage != null) {
|
||||
EditorGUILayout.Separator();
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
lastPage = regions[i].page;
|
||||
Material mat = ((Material)lastPage.rendererObject);
|
||||
if (mat != null) {
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250));
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
} else {
|
||||
EditorGUILayout.LabelField(new GUIContent("Page missing material!", SpineEditorUtilities.Icons.warning));
|
||||
}
|
||||
}
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
//EditorGUILayout.ToggleLeft(baked[i] ? "" : regions[i].name, baked[i]);
|
||||
bool result = baked[i] ? EditorGUILayout.ToggleLeft("", baked[i], GUILayout.Width(24)) : EditorGUILayout.ToggleLeft(" " + regions[i].name, baked[i]);
|
||||
if(baked[i]){
|
||||
EditorGUILayout.ObjectField(bakedObjects[i], typeof(GameObject), false, GUILayout.Width(250));
|
||||
}
|
||||
if (result && !baked[i]) {
|
||||
//bake
|
||||
baked[i] = true;
|
||||
bakedObjects[i] = SpineEditorUtilities.BakeRegion(atlasAsset, regions[i]);
|
||||
EditorGUIUtility.PingObject(bakedObjects[i]);
|
||||
} else if (!result && baked[i]) {
|
||||
//unbake
|
||||
bool unbakeResult = EditorUtility.DisplayDialog("Delete Baked Region", "Do you want to delete the prefab for " + regions[i].name, "Yes", "Cancel");
|
||||
switch (unbakeResult) {
|
||||
case true:
|
||||
//delete
|
||||
string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
|
||||
string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
|
||||
string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
|
||||
string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(regions[i]) + ".prefab").Replace("\\", "/");
|
||||
AssetDatabase.DeleteAsset(bakedPrefabPath);
|
||||
baked[i] = false;
|
||||
break;
|
||||
case false:
|
||||
//do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
#if BAKE_ALL_BUTTON
|
||||
// Check state
|
||||
bool allBaked = true;
|
||||
bool allUnbaked = true;
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
allBaked &= baked[i];
|
||||
allUnbaked &= !baked[i];
|
||||
}
|
||||
|
||||
if (!allBaked && GUILayout.Button("Bake All")) {
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
if (!baked[i]) {
|
||||
baked[i] = true;
|
||||
bakedObjects[i] = SpineEditorUtilities.BakeRegion(atlasAsset, regions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!allUnbaked && GUILayout.Button("Unbake All")) {
|
||||
bool unbakeResult = EditorUtility.DisplayDialog("Delete All Baked Regions", "Are you sure you want to unbake all region prefabs? This cannot be undone.", "Yes", "Cancel");
|
||||
switch (unbakeResult) {
|
||||
case true:
|
||||
//delete
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
if (baked[i]) {
|
||||
string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
|
||||
string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
|
||||
string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
|
||||
string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(regions[i]) + ".prefab").Replace("\\", "/");
|
||||
AssetDatabase.DeleteAsset(bakedPrefabPath);
|
||||
baked[i] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case false:
|
||||
//do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
#else
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
|
||||
|
||||
int baseIndent = EditorGUI.indentLevel;
|
||||
|
||||
List<AtlasRegion> regions = SpineAtlasAssetInspector.GetRegions(atlasAsset.GetAtlas());
|
||||
int regionsCount = regions.Count;
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.LabelField("Atlas Regions", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField(string.Format("{0} regions total", regionsCount));
|
||||
}
|
||||
AtlasPage lastPage = null;
|
||||
for (int i = 0; i < regionsCount; i++) {
|
||||
if (lastPage != regions[i].page) {
|
||||
if (lastPage != null) {
|
||||
EditorGUILayout.Separator();
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
lastPage = regions[i].page;
|
||||
Material mat = ((Material)lastPage.rendererObject);
|
||||
if (mat != null) {
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
using (new GUILayout.HorizontalScope())
|
||||
using (new EditorGUI.DisabledGroupScope(true))
|
||||
EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250));
|
||||
EditorGUI.indentLevel = baseIndent + 1;
|
||||
} else {
|
||||
EditorGUILayout.HelpBox("Page missing material!", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
string regionName = regions[i].name;
|
||||
Texture2D icon = SpineEditorUtilities.Icons.image;
|
||||
if (regionName.EndsWith(" ")) {
|
||||
regionName = string.Format("'{0}'", regions[i].name);
|
||||
icon = SpineEditorUtilities.Icons.warning;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon, "Region name ends with whitespace. This may cause errors. Please check your source image filenames."));
|
||||
} else {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon));
|
||||
}
|
||||
|
||||
}
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current))
|
||||
atlasAsset.Clear();
|
||||
}
|
||||
|
||||
static public void UpdateSpriteSlices (Texture texture, Atlas atlas) {
|
||||
#if USES_ENTITY_ID
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetEntityId());
|
||||
#else
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetInstanceID());
|
||||
#endif
|
||||
TextureImporter t = (TextureImporter)TextureImporter.GetAtPath(texturePath);
|
||||
t.spriteImportMode = SpriteImportMode.Multiple;
|
||||
|
||||
List<AtlasRegion> regions = SpineAtlasAssetInspector.GetRegions(atlas);
|
||||
int updatedCount = 0;
|
||||
int addedCount = 0;
|
||||
|
||||
#if TEXTUREIMPORTER_SPRITESHEET_OBSOLETE
|
||||
// Avoid assembly reference to Unity.2D.Sprite.Editor which causes an error on Unity 2018.3.
|
||||
Type factoryType = null;
|
||||
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
|
||||
factoryType = assembly.GetType("UnityEditor.U2D.Sprites.SpriteDataProviderFactories");
|
||||
if (factoryType != null) break;
|
||||
}
|
||||
if (factoryType == null) {
|
||||
Debug.LogWarning("SpriteDataProviderFactories type not found. Sprite slices could not be applied.");
|
||||
return;
|
||||
}
|
||||
// The following reflection code is equivalent to this:
|
||||
// SpriteDataProviderFactories factory = new SpriteDataProviderFactories();
|
||||
// factory.Init();
|
||||
// ISpriteEditorDataProvider dataProvider = factory.GetSpriteEditorDataProviderFromObject(t);
|
||||
// dataProvider.InitSpriteEditorDataProvider();
|
||||
//
|
||||
// SpriteRect[] spriteRects = dataProvider.GetSpriteRects();
|
||||
// List<SpriteRect> sprites = new List<SpriteRect>(spriteRects);
|
||||
object factory = Activator.CreateInstance(factoryType);
|
||||
factoryType.GetMethod("Init").Invoke(factory, null);
|
||||
object dataProvider = factoryType.GetMethod("GetSpriteEditorDataProviderFromObject").Invoke(factory, new object[] { t });
|
||||
Type providerInterface = dataProvider.GetType().GetInterface("ISpriteEditorDataProvider");
|
||||
providerInterface.GetMethod("InitSpriteEditorDataProvider").Invoke(dataProvider, null);
|
||||
Array spriteRectsArray = (Array)providerInterface.GetMethod("GetSpriteRects").Invoke(dataProvider, null);
|
||||
Type spriteRectType = spriteRectsArray.GetType().GetElementType();
|
||||
// Find a base type with a default constructor for creating new instances,
|
||||
// since the array element type may be a derived type without one.
|
||||
Type spriteRectBaseType = spriteRectType;
|
||||
while (spriteRectBaseType != null && spriteRectBaseType != typeof(object)
|
||||
&& spriteRectBaseType.GetConstructor(Type.EmptyTypes) == null)
|
||||
spriteRectBaseType = spriteRectBaseType.BaseType;
|
||||
PropertyInfo nameProperty = spriteRectBaseType.GetProperty("name");
|
||||
PropertyInfo rectProperty = spriteRectBaseType.GetProperty("rect");
|
||||
PropertyInfo pivotProperty = spriteRectBaseType.GetProperty("pivot");
|
||||
|
||||
List<object> sprites = new List<object>();
|
||||
for (int i = 0; i < spriteRectsArray.Length; i++)
|
||||
sprites.Add(spriteRectsArray.GetValue(i));
|
||||
|
||||
foreach (AtlasRegion r in regions) {
|
||||
string pageName = System.IO.Path.GetFileNameWithoutExtension(r.page.name);
|
||||
string textureName = texture.name;
|
||||
bool pageMatch = string.Equals(pageName, textureName, StringComparison.Ordinal);
|
||||
|
||||
int spriteIndex = pageMatch ? sprites.FindIndex(
|
||||
(s) => string.Equals((string)nameProperty.GetValue(s), r.name, StringComparison.Ordinal)
|
||||
) : -1;
|
||||
bool spriteNameMatchExists = spriteIndex >= 0;
|
||||
|
||||
if (pageMatch) {
|
||||
Rect spriteRect = new Rect();
|
||||
spriteRect.width = r.width;
|
||||
spriteRect.height = r.height;
|
||||
spriteRect.x = r.x;
|
||||
spriteRect.y = r.page.height - spriteRect.height - r.y;
|
||||
|
||||
if (spriteNameMatchExists) {
|
||||
object s = sprites[spriteIndex];
|
||||
rectProperty.SetValue(s, spriteRect);
|
||||
updatedCount++;
|
||||
} else {
|
||||
object newSpriteRect = Activator.CreateInstance(spriteRectBaseType);
|
||||
nameProperty.SetValue(newSpriteRect, r.name);
|
||||
rectProperty.SetValue(newSpriteRect, spriteRect);
|
||||
pivotProperty.SetValue(newSpriteRect, new Vector2(0.5f, 0.5f));
|
||||
sprites.Add(newSpriteRect);
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array resultArray = Array.CreateInstance(spriteRectBaseType, sprites.Count);
|
||||
for (int i = 0; i < sprites.Count; i++)
|
||||
resultArray.SetValue(sprites[i], i);
|
||||
// The following reflection code is equivalent to this:
|
||||
// dataProvider.SetSpriteRects(spriteRects);
|
||||
// dataProvider.Apply();
|
||||
providerInterface.GetMethod("SetSpriteRects").Invoke(dataProvider, new object[] { resultArray });
|
||||
providerInterface.GetMethod("Apply").Invoke(dataProvider, null);
|
||||
#else
|
||||
SpriteMetaData[] spriteSheet = t.spritesheet;
|
||||
List<SpriteMetaData> sprites = new List<SpriteMetaData>(spriteSheet);
|
||||
|
||||
foreach (AtlasRegion r in regions) {
|
||||
string pageName = System.IO.Path.GetFileNameWithoutExtension(r.page.name);
|
||||
string textureName = texture.name;
|
||||
bool pageMatch = string.Equals(pageName, textureName, StringComparison.Ordinal);
|
||||
|
||||
int spriteIndex = pageMatch ? sprites.FindIndex(
|
||||
(s) => string.Equals(s.name, r.name, StringComparison.Ordinal)
|
||||
) : -1;
|
||||
bool spriteNameMatchExists = spriteIndex >= 0;
|
||||
|
||||
if (pageMatch) {
|
||||
Rect spriteRect = new Rect();
|
||||
spriteRect.width = r.width;
|
||||
spriteRect.height = r.height;
|
||||
spriteRect.x = r.x;
|
||||
spriteRect.y = r.page.height - spriteRect.height - r.y;
|
||||
|
||||
if (spriteNameMatchExists) {
|
||||
SpriteMetaData s = sprites[spriteIndex];
|
||||
s.rect = spriteRect;
|
||||
sprites[spriteIndex] = s;
|
||||
updatedCount++;
|
||||
} else {
|
||||
sprites.Add(new SpriteMetaData {
|
||||
name = r.name,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
rect = spriteRect
|
||||
});
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.spritesheet = sprites.ToArray();
|
||||
#endif
|
||||
EditorUtility.SetDirty(t);
|
||||
AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate);
|
||||
EditorGUIUtility.PingObject(texture);
|
||||
Debug.Log(string.Format("Applied sprite slices to {2}. {0} added. {1} updated.", addedCount, updatedCount, texture.name));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca9b3ce36d70a05408e3bdd5e92c7f64
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(SpineSpriteAtlasAsset)), CanEditMultipleObjects]
|
||||
public class SpineSpriteAtlasAssetInspector : UnityEditor.Editor {
|
||||
SerializedProperty atlasFile, materials, materialOverrides;
|
||||
SpineSpriteAtlasAsset atlasAsset;
|
||||
|
||||
static List<AtlasRegion> GetRegions (Atlas atlas) {
|
||||
FieldInfo regionsField = SpineInspectorUtility.GetNonPublicField(typeof(Atlas), "regions");
|
||||
return (List<AtlasRegion>)regionsField.GetValue(atlas);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
atlasFile = serializedObject.FindProperty("spriteAtlasFile");
|
||||
materials = serializedObject.FindProperty("materials");
|
||||
materials.isExpanded = true;
|
||||
materialOverrides = serializedObject.FindProperty("materialOverrides");
|
||||
atlasAsset = (SpineSpriteAtlasAsset)target;
|
||||
|
||||
if (!SpineSpriteAtlasAsset.AnySpriteAtlasNeedsRegionsLoaded())
|
||||
return;
|
||||
EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted;
|
||||
EditorApplication.update += SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted;
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted;
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
DrawDefaultInspector();
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
atlasAsset = (atlasAsset == null) ? (SpineSpriteAtlasAsset)target : atlasAsset;
|
||||
|
||||
if (atlasAsset.RegionsNeedLoading) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Load regions by entering Play mode"), GUILayout.Height(20))) {
|
||||
EditorApplication.isPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(atlasFile);
|
||||
EditorGUILayout.PropertyField(materials, true);
|
||||
EditorGUILayout.PropertyField(materialOverrides, true);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
atlasAsset.Clear();
|
||||
atlasAsset.GetAtlas();
|
||||
atlasAsset.updateRegionsInPlayMode = true;
|
||||
}
|
||||
|
||||
if (materials.arraySize == 0) {
|
||||
EditorGUILayout.HelpBox("No materials", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < materials.arraySize; i++) {
|
||||
SerializedProperty prop = materials.GetArrayElementAtIndex(i);
|
||||
Material material = (Material)prop.objectReferenceValue;
|
||||
if (material == null) {
|
||||
EditorGUILayout.HelpBox("Materials cannot be null.", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
int baseIndent = EditorGUI.indentLevel;
|
||||
|
||||
List<AtlasRegion> regions = SpineSpriteAtlasAssetInspector.GetRegions(atlasAsset.GetAtlas());
|
||||
int regionsCount = regions.Count;
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.LabelField("Atlas Regions", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField(string.Format("{0} regions total", regionsCount));
|
||||
}
|
||||
AtlasPage lastPage = null;
|
||||
for (int i = 0; i < regionsCount; i++) {
|
||||
if (lastPage != regions[i].page) {
|
||||
if (lastPage != null) {
|
||||
EditorGUILayout.Separator();
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
lastPage = regions[i].page;
|
||||
Material mat = ((Material)lastPage.rendererObject);
|
||||
if (mat != null) {
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
using (new GUILayout.HorizontalScope())
|
||||
using (new EditorGUI.DisabledGroupScope(true))
|
||||
EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250));
|
||||
EditorGUI.indentLevel = baseIndent + 1;
|
||||
} else {
|
||||
EditorGUILayout.HelpBox("Page missing material!", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
string regionName = regions[i].name;
|
||||
Texture2D icon = SpineEditorUtilities.Icons.image;
|
||||
if (regionName.EndsWith(" ")) {
|
||||
regionName = string.Format("'{0}'", regions[i].name);
|
||||
icon = SpineEditorUtilities.Icons.warning;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon, "Region name ends with whitespace. This may cause errors. Please check your source image filenames."));
|
||||
} else {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon));
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
}
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current))
|
||||
atlasAsset.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f063dc5ff6881db4a9ee2e059812cba2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0134640f881c8d24d812a6f9af9d0761
|
||||
folderAsset: yes
|
||||
timeCreated: 1563304704
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,214 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
using Editor = UnityEditor.Editor;
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(BoneFollowerGraphic)), CanEditMultipleObjects]
|
||||
public class BoneFollowerGraphicInspector : Editor {
|
||||
|
||||
SerializedProperty boneName, skeletonGraphic, followXYPosition, followZPosition, followAttachmentZSpacing,
|
||||
followBoneRotation, followLocalScale, followParentWorldScale, followSkeletonFlip, maintainedAxisOrientation;
|
||||
BoneFollowerGraphic targetBoneFollower;
|
||||
bool needsReset;
|
||||
|
||||
#region Context Menu Item
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add BoneFollower GameObject")]
|
||||
static void AddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonGraphic skeletonGraphic = cmd.context as SkeletonGraphic;
|
||||
GameObject go = EditorInstantiation.NewGameObject("BoneFollower", true, typeof(RectTransform));
|
||||
Transform t = go.transform;
|
||||
t.SetParent(skeletonGraphic.transform);
|
||||
t.localPosition = Vector3.zero;
|
||||
|
||||
BoneFollowerGraphic f = go.AddComponent<BoneFollowerGraphic>();
|
||||
f.skeletonGraphic = skeletonGraphic;
|
||||
f.SetBone(skeletonGraphic.Skeleton.RootBone.Data.Name);
|
||||
|
||||
EditorGUIUtility.PingObject(t);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoneFollowerGraphic");
|
||||
}
|
||||
|
||||
// Validate
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add BoneFollower GameObject", true)]
|
||||
static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonGraphic skeletonGraphic = cmd.context as SkeletonGraphic;
|
||||
return skeletonGraphic.IsValid;
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
skeletonGraphic = serializedObject.FindProperty("skeletonGraphic");
|
||||
boneName = serializedObject.FindProperty("boneName");
|
||||
followBoneRotation = serializedObject.FindProperty("followBoneRotation");
|
||||
followXYPosition = serializedObject.FindProperty("followXYPosition");
|
||||
followZPosition = serializedObject.FindProperty("followZPosition");
|
||||
followAttachmentZSpacing = serializedObject.FindProperty("followAttachmentZSpacing");
|
||||
followLocalScale = serializedObject.FindProperty("followLocalScale");
|
||||
followParentWorldScale = serializedObject.FindProperty("followParentWorldScale");
|
||||
followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
|
||||
maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation");
|
||||
|
||||
targetBoneFollower = (BoneFollowerGraphic)target;
|
||||
if (targetBoneFollower.SkeletonGraphic != null)
|
||||
targetBoneFollower.SkeletonGraphic.Initialize(false);
|
||||
|
||||
if (!targetBoneFollower.valid || needsReset) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI () {
|
||||
BoneFollowerGraphic tbf = target as BoneFollowerGraphic;
|
||||
SkeletonGraphic skeletonGraphicComponent = tbf.SkeletonGraphic;
|
||||
if (skeletonGraphicComponent == null) return;
|
||||
|
||||
Transform transform = skeletonGraphicComponent.transform;
|
||||
Skeleton skeleton = skeletonGraphicComponent.Skeleton;
|
||||
float positionScale = skeletonGraphicComponent.MeshScale;
|
||||
Vector2 positionOffset = skeletonGraphicComponent.GetScaledPivotOffset();
|
||||
|
||||
if (string.IsNullOrEmpty(boneName.stringValue)) {
|
||||
SpineHandles.DrawBones(transform, skeleton, positionScale, positionOffset);
|
||||
SpineHandles.DrawBoneNames(transform, skeleton, positionScale, positionOffset);
|
||||
Handles.Label(tbf.transform.position, "No bone selected", EditorStyles.helpBox);
|
||||
} else {
|
||||
Bone targetBone = tbf.bone;
|
||||
if (targetBone == null) return;
|
||||
|
||||
SpineHandles.DrawBoneWireframe(transform, targetBone, SpineHandles.TransformContraintColor, positionScale, positionOffset);
|
||||
Handles.Label(targetBone.GetWorldPosition(transform, positionScale, positionOffset),
|
||||
targetBone.Data.Name, SpineHandles.BoneNameStyle);
|
||||
}
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
if (needsReset) {
|
||||
needsReset = false;
|
||||
foreach (Object o in targets) {
|
||||
BoneFollower bf = (BoneFollower)o;
|
||||
bf.Initialize();
|
||||
bf.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsReset && Event.current.type == EventType.Layout) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
serializedObject.Update();
|
||||
|
||||
// Find Renderer
|
||||
if (skeletonGraphic.objectReferenceValue == null) {
|
||||
SkeletonGraphic parentRenderer = targetBoneFollower.GetComponentInParent<SkeletonGraphic>();
|
||||
if (parentRenderer != null && parentRenderer.gameObject != targetBoneFollower.gameObject) {
|
||||
skeletonGraphic.objectReferenceValue = parentRenderer;
|
||||
Debug.Log("Inspector automatically assigned BoneFollowerGraphic.SkeletonGraphic");
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(skeletonGraphic);
|
||||
SkeletonGraphic skeletonGraphicComponent = skeletonGraphic.objectReferenceValue as SkeletonGraphic;
|
||||
if (skeletonGraphicComponent != null) {
|
||||
if (skeletonGraphicComponent.gameObject == targetBoneFollower.gameObject) {
|
||||
skeletonGraphic.objectReferenceValue = null;
|
||||
EditorUtility.DisplayDialog("Invalid assignment.", "BoneFollowerGraphic can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your BoneFollower, or choose a SkeletonGraphic from a different GameObject.", "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetBoneFollower.valid) {
|
||||
needsReset = true;
|
||||
}
|
||||
|
||||
if (targetBoneFollower.valid) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(boneName);
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(followBoneRotation);
|
||||
EditorGUILayout.PropertyField(followXYPosition);
|
||||
EditorGUILayout.PropertyField(followZPosition);
|
||||
if (followZPosition.boolValue == true) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(followAttachmentZSpacing, new GUIContent("Attachment Z Spacing"));
|
||||
}
|
||||
EditorGUILayout.PropertyField(followLocalScale);
|
||||
EditorGUILayout.PropertyField(followParentWorldScale);
|
||||
EditorGUILayout.PropertyField(followSkeletonFlip);
|
||||
if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) &&
|
||||
(followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(maintainedAxisOrientation);
|
||||
}
|
||||
|
||||
//BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
|
||||
} else {
|
||||
SkeletonGraphic boneFollowerSkeletonGraphic = targetBoneFollower.skeletonGraphic;
|
||||
if (boneFollowerSkeletonGraphic == null) {
|
||||
EditorGUILayout.HelpBox("SkeletonGraphic is unassigned. Please assign a SkeletonRenderer (SkeletonAnimation or SkeletonMecanim).", MessageType.Warning);
|
||||
} else {
|
||||
boneFollowerSkeletonGraphic.Initialize(false);
|
||||
|
||||
if (boneFollowerSkeletonGraphic.skeletonDataAsset == null)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonGraphic does not have SkeletonData assigned to it.", MessageType.Warning);
|
||||
|
||||
if (!boneFollowerSkeletonGraphic.IsValid)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonGraphic is invalid. Check target SkeletonGraphic, its SkeletonData asset or the console for other errors.", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
Event current = Event.current;
|
||||
bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed");
|
||||
if (wasUndo)
|
||||
targetBoneFollower.Initialize();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da44a8561fd243c43a1f77bda36de0eb
|
||||
timeCreated: 1499279157
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,235 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
using Editor = UnityEditor.Editor;
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(BoneFollower)), CanEditMultipleObjects]
|
||||
public class BoneFollowerInspector : Editor {
|
||||
SerializedProperty boneName, skeletonRenderer, followXYPosition, followZPosition, followAttachmentZSpacing,
|
||||
followBoneRotation, followLocalScale, followParentWorldScale, followSkeletonFlip, maintainedAxisOrientation;
|
||||
BoneFollower targetBoneFollower;
|
||||
bool needsReset;
|
||||
|
||||
#region Context Menu Item
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add BoneFollower GameObject")]
|
||||
static void AddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
GameObject go = EditorInstantiation.NewGameObject("New BoneFollower", true);
|
||||
Transform t = go.transform;
|
||||
t.SetParent(skeletonRenderer.transform);
|
||||
t.localPosition = Vector3.zero;
|
||||
|
||||
BoneFollower f = go.AddComponent<BoneFollower>();
|
||||
f.skeletonRenderer = skeletonRenderer;
|
||||
|
||||
EditorGUIUtility.PingObject(t);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoneFollower");
|
||||
}
|
||||
|
||||
// Validate
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add BoneFollower GameObject", true)]
|
||||
static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
return skeletonRenderer.IsValid;
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/BoneFollower/Rename BoneFollower GameObject")]
|
||||
static void RenameGameObject (MenuCommand cmd) {
|
||||
AutonameGameObject(cmd.context as BoneFollower);
|
||||
}
|
||||
#endregion
|
||||
|
||||
static void AutonameGameObject (BoneFollower boneFollower) {
|
||||
if (boneFollower == null) return;
|
||||
|
||||
string boneName = boneFollower.boneName;
|
||||
boneFollower.gameObject.name = string.IsNullOrEmpty(boneName) ? "BoneFollower" : string.Format("{0} (BoneFollower)", boneName);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
boneName = serializedObject.FindProperty("boneName");
|
||||
followBoneRotation = serializedObject.FindProperty("followBoneRotation");
|
||||
followXYPosition = serializedObject.FindProperty("followXYPosition");
|
||||
followZPosition = serializedObject.FindProperty("followZPosition");
|
||||
followAttachmentZSpacing = serializedObject.FindProperty("followAttachmentZSpacing");
|
||||
followLocalScale = serializedObject.FindProperty("followLocalScale");
|
||||
followParentWorldScale = serializedObject.FindProperty("followParentWorldScale");
|
||||
followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
|
||||
maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation");
|
||||
|
||||
targetBoneFollower = (BoneFollower)target;
|
||||
if (targetBoneFollower.SkeletonRenderer != null)
|
||||
targetBoneFollower.SkeletonRenderer.Initialize(false);
|
||||
|
||||
if (!targetBoneFollower.valid || needsReset) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI () {
|
||||
BoneFollower tbf = target as BoneFollower;
|
||||
SkeletonRenderer skeletonRendererComponent = tbf.skeletonRenderer;
|
||||
if (skeletonRendererComponent == null) return;
|
||||
|
||||
Transform transform = skeletonRendererComponent.transform;
|
||||
Skeleton skeleton = skeletonRendererComponent.Skeleton;
|
||||
|
||||
if (string.IsNullOrEmpty(boneName.stringValue)) {
|
||||
SpineHandles.DrawBones(transform, skeleton);
|
||||
SpineHandles.DrawBoneNames(transform, skeleton);
|
||||
Handles.Label(tbf.transform.position, "No bone selected", EditorStyles.helpBox);
|
||||
} else {
|
||||
Bone targetBone = tbf.bone;
|
||||
if (targetBone == null) return;
|
||||
SpineHandles.DrawBoneWireframe(transform, targetBone, SpineHandles.TransformContraintColor);
|
||||
Handles.Label(targetBone.GetWorldPosition(transform), targetBone.Data.Name, SpineHandles.BoneNameStyle);
|
||||
}
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
if (needsReset) {
|
||||
needsReset = false;
|
||||
foreach (UnityEngine.Object o in targets) {
|
||||
BoneFollower bf = (BoneFollower)o;
|
||||
bf.Initialize();
|
||||
bf.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsReset && Event.current.type == EventType.Layout) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
serializedObject.Update();
|
||||
|
||||
// Find Renderer
|
||||
if (skeletonRenderer.objectReferenceValue == null) {
|
||||
SkeletonRenderer parentRenderer = targetBoneFollower.GetComponentInParent<SkeletonRenderer>();
|
||||
if (parentRenderer != null && parentRenderer.gameObject != targetBoneFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = parentRenderer;
|
||||
Debug.Log("Inspector automatically assigned BoneFollower.SkeletonRenderer");
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(skeletonRenderer);
|
||||
SkeletonRenderer skeletonRendererReference = skeletonRenderer.objectReferenceValue as SkeletonRenderer;
|
||||
if (skeletonRendererReference != null) {
|
||||
if (skeletonRendererReference.gameObject == targetBoneFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = null;
|
||||
EditorUtility.DisplayDialog("Invalid assignment.", "BoneFollower can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your BoneFollower, or choose a SkeletonRenderer from a different GameObject.", "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetBoneFollower.valid) {
|
||||
needsReset = true;
|
||||
}
|
||||
|
||||
if (targetBoneFollower.valid) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(boneName);
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(followBoneRotation);
|
||||
EditorGUILayout.PropertyField(followXYPosition);
|
||||
EditorGUILayout.PropertyField(followZPosition);
|
||||
if (followZPosition.boolValue == true) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(followAttachmentZSpacing, new GUIContent("Attachment Z Spacing"));
|
||||
}
|
||||
EditorGUILayout.PropertyField(followLocalScale);
|
||||
EditorGUILayout.PropertyField(followParentWorldScale);
|
||||
EditorGUILayout.PropertyField(followSkeletonFlip);
|
||||
if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) &&
|
||||
(followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(maintainedAxisOrientation);
|
||||
}
|
||||
|
||||
BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
|
||||
} else {
|
||||
SkeletonRenderer boneFollowerSkeletonRenderer = targetBoneFollower.skeletonRenderer;
|
||||
if (boneFollowerSkeletonRenderer == null) {
|
||||
EditorGUILayout.HelpBox("SkeletonRenderer is unassigned. Please assign a SkeletonRenderer (SkeletonAnimation or SkeletonMecanim).", MessageType.Warning);
|
||||
} else {
|
||||
boneFollowerSkeletonRenderer.Initialize(false);
|
||||
|
||||
if (boneFollowerSkeletonRenderer.SkeletonDataAsset == null)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonRenderer does not have SkeletonData assigned to it.", MessageType.Warning);
|
||||
|
||||
if (!boneFollowerSkeletonRenderer.IsValid)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonRenderer is invalid. Check target SkeletonRenderer, its SkeletonData asset or the console for other errors.", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
Event current = Event.current;
|
||||
bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed");
|
||||
if (wasUndo)
|
||||
targetBoneFollower.Initialize();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
internal static void RecommendRigidbodyButton (Component component) {
|
||||
bool hasCollider2D = component.GetComponent<Collider2D>() != null || component.GetComponent<BoundingBoxFollower>() != null;
|
||||
bool hasCollider3D = !hasCollider2D && component.GetComponent<Collider>();
|
||||
bool missingRigidBody = (hasCollider2D && component.GetComponent<Rigidbody2D>() == null) || (hasCollider3D && component.GetComponent<Rigidbody>() == null);
|
||||
if (missingRigidBody) {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
EditorGUILayout.HelpBox("Collider detected. Unity recommends adding a Rigidbody to the Transforms of any colliders that are intended to be dynamically repositioned and rotated.", MessageType.Warning);
|
||||
System.Type rbType = hasCollider2D ? typeof(Rigidbody2D) : typeof(Rigidbody);
|
||||
string rbLabel = string.Format("Add {0}", rbType.Name);
|
||||
GUIContent rbContent = SpineInspectorUtility.TempContent(rbLabel, SpineInspectorUtility.UnityIcon(rbType), "Add a rigidbody to this GameObject to be the Physics body parent of the attached collider.");
|
||||
if (SpineInspectorUtility.CenteredButton(rbContent)) component.gameObject.AddComponent(rbType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c71ca35fd6241cb49a0b0756a664fcf7
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,280 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
#define USE_COLLIDER_COMPOSITE_OPERATION
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(BoundingBoxFollowerGraphic))]
|
||||
public class BoundingBoxFollowerGraphicInspector : UnityEditor.Editor {
|
||||
SerializedProperty skeletonGraphic, slotName,
|
||||
isTrigger, usedByEffector, usedByComposite, clearStateOnDisable;
|
||||
BoundingBoxFollowerGraphic follower;
|
||||
bool rebuildRequired = false;
|
||||
bool addBoneFollower = false;
|
||||
bool sceneRepaintRequired = false;
|
||||
bool debugIsExpanded;
|
||||
|
||||
GUIContent addBoneFollowerLabel;
|
||||
GUIContent AddBoneFollowerLabel {
|
||||
get {
|
||||
if (addBoneFollowerLabel == null) addBoneFollowerLabel = new GUIContent("Add Bone Follower", Icons.bone);
|
||||
return addBoneFollowerLabel;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeEditor () {
|
||||
skeletonGraphic = serializedObject.FindProperty("skeletonGraphic");
|
||||
slotName = serializedObject.FindProperty("slotName");
|
||||
isTrigger = serializedObject.FindProperty("isTrigger");
|
||||
usedByEffector = serializedObject.FindProperty("usedByEffector");
|
||||
usedByComposite = serializedObject.FindProperty("usedByComposite");
|
||||
clearStateOnDisable = serializedObject.FindProperty("clearStateOnDisable");
|
||||
follower = (BoundingBoxFollowerGraphic)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
#else
|
||||
bool isInspectingPrefab = false;
|
||||
#endif
|
||||
|
||||
// Note: when calling InitializeEditor() in OnEnable, it throws exception
|
||||
// "SerializedObjectNotCreatableException: Object at index 0 is null".
|
||||
InitializeEditor();
|
||||
|
||||
// Try to auto-assign SkeletonGraphic field.
|
||||
if (skeletonGraphic.objectReferenceValue == null) {
|
||||
SkeletonGraphic foundSkeletonGraphic = follower.GetComponentInParent<SkeletonGraphic>();
|
||||
if (foundSkeletonGraphic != null)
|
||||
Debug.Log("BoundingBoxFollowerGraphic automatically assigned: " + foundSkeletonGraphic.gameObject.name);
|
||||
else if (Event.current.type == EventType.Repaint)
|
||||
Debug.Log("No Spine GameObject detected. Make sure to set this GameObject as a child of the Spine GameObject; or set BoundingBoxFollowerGraphic's 'Skeleton Graphic' field in the inspector.");
|
||||
|
||||
skeletonGraphic.objectReferenceValue = foundSkeletonGraphic;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
}
|
||||
|
||||
SkeletonGraphic skeletonGraphicValue = skeletonGraphic.objectReferenceValue as SkeletonGraphic;
|
||||
if (skeletonGraphicValue != null && skeletonGraphicValue.gameObject == follower.gameObject) {
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
EditorGUILayout.HelpBox("It's ideal to add BoundingBoxFollowerGraphic to a separate child GameObject of the Spine GameObject.", MessageType.Warning);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Move BoundingBoxFollowerGraphic to new GameObject", Icons.boundingBox), GUILayout.Height(30f))) {
|
||||
AddBoundingBoxFollowerGraphicChild(skeletonGraphicValue, follower);
|
||||
DestroyImmediate(follower);
|
||||
return;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(skeletonGraphic);
|
||||
EditorGUILayout.PropertyField(slotName, new GUIContent("Slot"));
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
if (!isInspectingPrefab)
|
||||
rebuildRequired = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.LabelWidthScope(150f)) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(isTrigger);
|
||||
EditorGUILayout.PropertyField(usedByEffector);
|
||||
EditorGUILayout.PropertyField(usedByComposite);
|
||||
bool colliderParamChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(clearStateOnDisable, new GUIContent(clearStateOnDisable.displayName, "Enable this if you are pooling your Spine GameObject"));
|
||||
bool clearStateChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
if (clearStateChanged || colliderParamChanged) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
if (colliderParamChanged)
|
||||
foreach (PolygonCollider2D col in follower.colliderTable.Values) {
|
||||
col.isTrigger = isTrigger.boolValue;
|
||||
col.usedByEffector = usedByEffector.boolValue;
|
||||
#if USE_COLLIDER_COMPOSITE_OPERATION
|
||||
col.compositeOperation = usedByComposite.boolValue ?
|
||||
Collider2D.CompositeOperation.Merge : Collider2D.CompositeOperation.None;
|
||||
#else
|
||||
col.usedByComposite = usedByComposite.boolValue;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInspectingPrefab) {
|
||||
follower.colliderTable.Clear();
|
||||
follower.nameTable.Clear();
|
||||
EditorGUILayout.HelpBox("BoundingBoxAttachments cannot be previewed in prefabs.", MessageType.Info);
|
||||
|
||||
// How do you prevent components from being saved into the prefab? No such HideFlag. DontSaveInEditor | DontSaveInBuild does not work. DestroyImmediate does not work.
|
||||
PolygonCollider2D collider = follower.GetComponent<PolygonCollider2D>();
|
||||
if (collider != null) Debug.LogWarning("Found BoundingBoxFollowerGraphic collider components in prefab. These are disposed and regenerated at runtime.");
|
||||
|
||||
} else {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
if (debugIsExpanded = EditorGUILayout.Foldout(debugIsExpanded, "Debug Colliders")) {
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField(string.Format("Attachment Names ({0} PolygonCollider2D)", follower.colliderTable.Count));
|
||||
EditorGUI.BeginChangeCheck();
|
||||
foreach (KeyValuePair<BoundingBoxAttachment, string> kp in follower.nameTable) {
|
||||
string attachmentName = kp.Value;
|
||||
PolygonCollider2D collider = follower.colliderTable[kp.Key];
|
||||
bool isPlaceholder = attachmentName != kp.Key.Name;
|
||||
collider.enabled = EditorGUILayout.ToggleLeft(new GUIContent(!isPlaceholder ? attachmentName : string.Format("{0} [{1}]", attachmentName, kp.Key.Name), isPlaceholder ? Icons.skinPlaceholder : Icons.boundingBox), collider.enabled);
|
||||
}
|
||||
sceneRepaintRequired |= EditorGUI.EndChangeCheck();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (follower.Slot == null)
|
||||
follower.Initialize(false);
|
||||
bool hasBoneFollower = follower.GetComponent<BoneFollowerGraphic>() != null;
|
||||
if (!hasBoneFollower) {
|
||||
bool buttonDisabled = follower.Slot == null;
|
||||
using (new EditorGUI.DisabledGroupScope(buttonDisabled)) {
|
||||
addBoneFollower |= SpineInspectorUtility.LargeCenteredButton(AddBoneFollowerLabel, true);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Event.current.type == EventType.Repaint) {
|
||||
if (addBoneFollower) {
|
||||
BoneFollowerGraphic boneFollower = follower.gameObject.AddComponent<BoneFollowerGraphic>();
|
||||
boneFollower.skeletonGraphic = skeletonGraphicValue;
|
||||
boneFollower.SetBone(follower.Slot.Data.BoneData.Name);
|
||||
addBoneFollower = false;
|
||||
}
|
||||
|
||||
if (sceneRepaintRequired) {
|
||||
SceneView.RepaintAll();
|
||||
sceneRepaintRequired = false;
|
||||
}
|
||||
|
||||
if (rebuildRequired) {
|
||||
follower.Initialize();
|
||||
rebuildRequired = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Menus
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add BoundingBoxFollowerGraphic GameObject")]
|
||||
static void AddBoundingBoxFollowerGraphicChild (MenuCommand command) {
|
||||
GameObject go = AddBoundingBoxFollowerGraphicChild((SkeletonGraphic)command.context);
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollowerGraphic");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add all BoundingBoxFollowerGraphic GameObjects")]
|
||||
static void AddAllBoundingBoxFollowerGraphicChildren (MenuCommand command) {
|
||||
List<GameObject> objects = AddAllBoundingBoxFollowerGraphicChildren((SkeletonGraphic)command.context);
|
||||
foreach (GameObject go in objects)
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollowerGraphic");
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static GameObject AddBoundingBoxFollowerGraphicChild (SkeletonGraphic skeletonGraphic,
|
||||
BoundingBoxFollowerGraphic original = null, string name = "BoundingBoxFollowerGraphic",
|
||||
string slotName = null) {
|
||||
|
||||
GameObject go = EditorInstantiation.NewGameObject(name, true);
|
||||
go.transform.SetParent(skeletonGraphic.transform, false);
|
||||
go.AddComponent<RectTransform>();
|
||||
BoundingBoxFollowerGraphic newFollower = go.AddComponent<BoundingBoxFollowerGraphic>();
|
||||
|
||||
if (original != null) {
|
||||
newFollower.slotName = original.slotName;
|
||||
newFollower.isTrigger = original.isTrigger;
|
||||
newFollower.usedByEffector = original.usedByEffector;
|
||||
newFollower.usedByComposite = original.usedByComposite;
|
||||
newFollower.clearStateOnDisable = original.clearStateOnDisable;
|
||||
}
|
||||
if (slotName != null)
|
||||
newFollower.slotName = slotName;
|
||||
|
||||
newFollower.skeletonGraphic = skeletonGraphic;
|
||||
newFollower.Initialize();
|
||||
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
return go;
|
||||
}
|
||||
|
||||
public static List<GameObject> AddAllBoundingBoxFollowerGraphicChildren (
|
||||
SkeletonGraphic skeletonGraphic, BoundingBoxFollowerGraphic original = null) {
|
||||
|
||||
List<GameObject> createdGameObjects = new List<GameObject>();
|
||||
foreach (Skin skin in skeletonGraphic.Skeleton.Data.Skins) {
|
||||
ICollection<Skin.SkinEntry> attachments = skin.Attachments;
|
||||
foreach (Skin.SkinEntry entry in attachments) {
|
||||
BoundingBoxAttachment boundingBoxAttachment = entry.Attachment as BoundingBoxAttachment;
|
||||
if (boundingBoxAttachment == null)
|
||||
continue;
|
||||
int slotIndex = entry.SlotIndex;
|
||||
Slot slot = skeletonGraphic.Skeleton.Slots.Items[slotIndex];
|
||||
string slotName = slot.Data.Name;
|
||||
GameObject go = AddBoundingBoxFollowerGraphicChild(skeletonGraphic,
|
||||
original, boundingBoxAttachment.Name, slotName);
|
||||
BoneFollowerGraphic boneFollower = go.AddComponent<BoneFollowerGraphic>();
|
||||
boneFollower.skeletonGraphic = skeletonGraphic;
|
||||
boneFollower.SetBone(slot.Data.BoneData.Name);
|
||||
createdGameObjects.Add(go);
|
||||
}
|
||||
}
|
||||
return createdGameObjects;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c4f5b276299bc048ad00f3cd2d1ea09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,279 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
#define USE_COLLIDER_COMPOSITE_OPERATION
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(BoundingBoxFollower))]
|
||||
public class BoundingBoxFollowerInspector : UnityEditor.Editor {
|
||||
SerializedProperty skeletonRenderer, slotName,
|
||||
isTrigger, usedByEffector, usedByComposite, clearStateOnDisable;
|
||||
BoundingBoxFollower follower;
|
||||
bool rebuildRequired = false;
|
||||
bool addBoneFollower = false;
|
||||
bool sceneRepaintRequired = false;
|
||||
bool debugIsExpanded;
|
||||
|
||||
GUIContent addBoneFollowerLabel;
|
||||
GUIContent AddBoneFollowerLabel {
|
||||
get {
|
||||
if (addBoneFollowerLabel == null) addBoneFollowerLabel = new GUIContent("Add Bone Follower", Icons.bone);
|
||||
return addBoneFollowerLabel;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeEditor () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
slotName = serializedObject.FindProperty("slotName");
|
||||
isTrigger = serializedObject.FindProperty("isTrigger");
|
||||
usedByEffector = serializedObject.FindProperty("usedByEffector");
|
||||
usedByComposite = serializedObject.FindProperty("usedByComposite");
|
||||
clearStateOnDisable = serializedObject.FindProperty("clearStateOnDisable");
|
||||
follower = (BoundingBoxFollower)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
#else
|
||||
bool isInspectingPrefab = false;
|
||||
#endif
|
||||
|
||||
// Note: when calling InitializeEditor() in OnEnable, it throws exception
|
||||
// "SerializedObjectNotCreatableException: Object at index 0 is null".
|
||||
InitializeEditor();
|
||||
|
||||
// Try to auto-assign SkeletonRenderer field.
|
||||
if (skeletonRenderer.objectReferenceValue == null) {
|
||||
SkeletonRenderer foundSkeletonRenderer = follower.GetComponentInParent<SkeletonRenderer>();
|
||||
if (foundSkeletonRenderer != null)
|
||||
Debug.Log("BoundingBoxFollower automatically assigned: " + foundSkeletonRenderer.gameObject.name);
|
||||
else if (Event.current.type == EventType.Repaint)
|
||||
Debug.Log("No Spine GameObject detected. Make sure to set this GameObject as a child of the Spine GameObject; or set BoundingBoxFollower's 'Skeleton Renderer' field in the inspector.");
|
||||
|
||||
skeletonRenderer.objectReferenceValue = foundSkeletonRenderer;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
}
|
||||
|
||||
SkeletonRenderer skeletonRendererValue = skeletonRenderer.objectReferenceValue as SkeletonRenderer;
|
||||
if (skeletonRendererValue != null && skeletonRendererValue.gameObject == follower.gameObject) {
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
EditorGUILayout.HelpBox("It's ideal to add BoundingBoxFollower to a separate child GameObject of the Spine GameObject.", MessageType.Warning);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Move BoundingBoxFollower to new GameObject", Icons.boundingBox), GUILayout.Height(30f))) {
|
||||
AddBoundingBoxFollowerChild(skeletonRendererValue, follower);
|
||||
DestroyImmediate(follower);
|
||||
return;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(skeletonRenderer);
|
||||
EditorGUILayout.PropertyField(slotName, new GUIContent("Slot"));
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
if (!isInspectingPrefab)
|
||||
rebuildRequired = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.LabelWidthScope(150f)) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(isTrigger);
|
||||
EditorGUILayout.PropertyField(usedByEffector);
|
||||
EditorGUILayout.PropertyField(usedByComposite);
|
||||
bool colliderParamChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(clearStateOnDisable, new GUIContent(clearStateOnDisable.displayName, "Enable this if you are pooling your Spine GameObject"));
|
||||
bool clearStateChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
if (clearStateChanged || colliderParamChanged) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
if (colliderParamChanged)
|
||||
foreach (PolygonCollider2D col in follower.colliderTable.Values) {
|
||||
col.isTrigger = isTrigger.boolValue;
|
||||
col.usedByEffector = usedByEffector.boolValue;
|
||||
#if USE_COLLIDER_COMPOSITE_OPERATION
|
||||
col.compositeOperation = usedByComposite.boolValue ?
|
||||
Collider2D.CompositeOperation.Merge : Collider2D.CompositeOperation.None;
|
||||
#else
|
||||
col.usedByComposite = usedByComposite.boolValue;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInspectingPrefab) {
|
||||
follower.colliderTable.Clear();
|
||||
follower.nameTable.Clear();
|
||||
EditorGUILayout.HelpBox("BoundingBoxAttachments cannot be previewed in prefabs.", MessageType.Info);
|
||||
|
||||
// How do you prevent components from being saved into the prefab? No such HideFlag. DontSaveInEditor | DontSaveInBuild does not work. DestroyImmediate does not work.
|
||||
PolygonCollider2D collider = follower.GetComponent<PolygonCollider2D>();
|
||||
if (collider != null) Debug.LogWarning("Found BoundingBoxFollower collider components in prefab. These are disposed and regenerated at runtime.");
|
||||
|
||||
} else {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
if (debugIsExpanded = EditorGUILayout.Foldout(debugIsExpanded, "Debug Colliders")) {
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField(string.Format("Attachment Names ({0} PolygonCollider2D)", follower.colliderTable.Count));
|
||||
EditorGUI.BeginChangeCheck();
|
||||
foreach (KeyValuePair<BoundingBoxAttachment, string> pair in follower.nameTable) {
|
||||
string attachmentName = pair.Value;
|
||||
PolygonCollider2D collider = follower.colliderTable[pair.Key];
|
||||
bool isPlaceholder = attachmentName != pair.Key.Name;
|
||||
collider.enabled = EditorGUILayout.ToggleLeft(new GUIContent(!isPlaceholder ? attachmentName : string.Format("{0} [{1}]", attachmentName, pair.Key.Name), isPlaceholder ? Icons.skinPlaceholder : Icons.boundingBox), collider.enabled);
|
||||
}
|
||||
sceneRepaintRequired |= EditorGUI.EndChangeCheck();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (follower.Slot == null)
|
||||
follower.Initialize(false);
|
||||
bool hasBoneFollower = follower.GetComponent<BoneFollower>() != null;
|
||||
if (!hasBoneFollower) {
|
||||
bool buttonDisabled = follower.Slot == null;
|
||||
using (new EditorGUI.DisabledGroupScope(buttonDisabled)) {
|
||||
addBoneFollower |= SpineInspectorUtility.LargeCenteredButton(AddBoneFollowerLabel, true);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Event.current.type == EventType.Repaint) {
|
||||
if (addBoneFollower) {
|
||||
BoneFollower boneFollower = follower.gameObject.AddComponent<BoneFollower>();
|
||||
boneFollower.skeletonRenderer = skeletonRendererValue;
|
||||
boneFollower.SetBone(follower.Slot.Data.BoneData.Name);
|
||||
addBoneFollower = false;
|
||||
}
|
||||
|
||||
if (sceneRepaintRequired) {
|
||||
SceneView.RepaintAll();
|
||||
sceneRepaintRequired = false;
|
||||
}
|
||||
|
||||
if (rebuildRequired) {
|
||||
follower.Initialize();
|
||||
rebuildRequired = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Menus
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add BoundingBoxFollower GameObject")]
|
||||
static void AddBoundingBoxFollowerChild (MenuCommand command) {
|
||||
GameObject go = AddBoundingBoxFollowerChild((SkeletonRenderer)command.context);
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollower");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add all BoundingBoxFollower GameObjects")]
|
||||
static void AddAllBoundingBoxFollowerChildren (MenuCommand command) {
|
||||
List<GameObject> objects = AddAllBoundingBoxFollowerChildren((SkeletonRenderer)command.context);
|
||||
foreach (GameObject go in objects)
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollower");
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static GameObject AddBoundingBoxFollowerChild (SkeletonRenderer skeletonRenderer,
|
||||
BoundingBoxFollower original = null, string name = "BoundingBoxFollower",
|
||||
string slotName = null) {
|
||||
|
||||
GameObject go = EditorInstantiation.NewGameObject(name, true);
|
||||
go.transform.SetParent(skeletonRenderer.transform, false);
|
||||
BoundingBoxFollower newFollower = go.AddComponent<BoundingBoxFollower>();
|
||||
|
||||
if (original != null) {
|
||||
newFollower.slotName = original.slotName;
|
||||
newFollower.isTrigger = original.isTrigger;
|
||||
newFollower.usedByEffector = original.usedByEffector;
|
||||
newFollower.usedByComposite = original.usedByComposite;
|
||||
newFollower.clearStateOnDisable = original.clearStateOnDisable;
|
||||
}
|
||||
if (slotName != null)
|
||||
newFollower.slotName = slotName;
|
||||
|
||||
newFollower.skeletonRenderer = skeletonRenderer;
|
||||
newFollower.Initialize();
|
||||
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
return go;
|
||||
}
|
||||
|
||||
public static List<GameObject> AddAllBoundingBoxFollowerChildren (
|
||||
SkeletonRenderer skeletonRenderer, BoundingBoxFollower original = null) {
|
||||
|
||||
List<GameObject> createdGameObjects = new List<GameObject>();
|
||||
foreach (Skin skin in skeletonRenderer.Skeleton.Data.Skins) {
|
||||
ICollection<Skin.SkinEntry> attachments = skin.Attachments;
|
||||
foreach (Skin.SkinEntry entry in attachments) {
|
||||
BoundingBoxAttachment boundingBoxAttachment = entry.Attachment as BoundingBoxAttachment;
|
||||
if (boundingBoxAttachment == null)
|
||||
continue;
|
||||
int slotIndex = entry.SlotIndex;
|
||||
Slot slot = skeletonRenderer.Skeleton.Slots.Items[slotIndex];
|
||||
string slotName = slot.Data.Name;
|
||||
GameObject go = AddBoundingBoxFollowerChild(skeletonRenderer,
|
||||
original, boundingBoxAttachment.Name, slotName);
|
||||
BoneFollower boneFollower = go.AddComponent<BoneFollower>();
|
||||
boneFollower.skeletonRenderer = skeletonRenderer;
|
||||
boneFollower.SetBone(slot.Data.BoneData.Name);
|
||||
createdGameObjects.Add(go);
|
||||
}
|
||||
}
|
||||
return createdGameObjects;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 670a3cefa3853bd48b5da53a424fd542
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,537 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#else
|
||||
#define NO_PREFAB_MESH
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#define PER_MATERIAL_PROPERTY_BLOCKS
|
||||
#endif
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
public class ISkeletonRendererInspector : UnityEditor.Editor {
|
||||
public static bool advancedFoldout;
|
||||
protected bool loadingFailed = false;
|
||||
|
||||
const string SeparatorSlotNamesFieldName = "separatorSlotNames";
|
||||
|
||||
protected SerializedProperty skeletonDataAsset, initialSkinName;
|
||||
protected SerializedProperty initialFlipX, initialFlipY;
|
||||
protected SerializedProperty updateWhenInvisible, separatorSlotNames, enableSeparatorSlots;
|
||||
protected SerializedProperty clearStateOnDisable, fixDrawOrder;
|
||||
protected SerializedProperty useClipping, zSpacing, immutableTriangles;
|
||||
protected SerializedProperty threadedMeshGeneration;
|
||||
// Vertex Data parameters
|
||||
protected SerializedProperty tintBlack, canvasGroupCompatible, pmaVertexColors, addNormals, calculateTangents;
|
||||
protected SerializedProperty physicsPositionInheritanceFactor, physicsRotationInheritanceFactor,
|
||||
physicsPositionInheritanceLimit, physicsRotationInheritanceLimit, physicsMovementRelativeTo;
|
||||
|
||||
protected bool isInspectingPrefab;
|
||||
protected bool forceReloadQueued = false;
|
||||
|
||||
private GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent;
|
||||
|
||||
protected readonly GUIContent ClearStateOnDisableLabel = new GUIContent(
|
||||
"Clear State On Disable", "Use this if you are pooling or enabling/disabling your Spine GameObject.");
|
||||
|
||||
protected readonly GUIContent UseClippingLabel = new GUIContent("Use Clipping",
|
||||
"When disabled, clipping attachments are ignored. This may be used to save performance.");
|
||||
protected readonly GUIContent ZSpacingLabel = new GUIContent("Z Spacing",
|
||||
"A value other than 0 adds a space between each rendered attachment to prevent Z Fighting when using shaders" +
|
||||
" that read or write to the depth buffer. Large values may cause unwanted parallax and spaces depending on " +
|
||||
"camera setup.");
|
||||
protected readonly GUIContent ThreadedMeshGenerationLabel = new GUIContent("Use Threading",
|
||||
"When enabled, mesh generation is performed on multiple threads in parallel.");
|
||||
|
||||
protected readonly GUIContent TintBlackLabel = new GUIContent("Tint Black (!)",
|
||||
"Adds black tint vertex data to the mesh as UV2 and UV3. Black tinting requires that the shader interpret " +
|
||||
"UV2 and UV3 as black tint colors for this effect to work. You may then want to use the " +
|
||||
"[Spine/SkeletonGraphic Tint Black] shader.");
|
||||
protected readonly GUIContent CanvasGroupCompatibleLabel = new GUIContent("CanvasGroup Compatible",
|
||||
"Enable when using SkeletonGraphic under a CanvasGroup. " +
|
||||
"When enabled, PMA Vertex Color alpha value is stored at uv2.g instead of color.a to capture " +
|
||||
"CanvasGroup modifying color.a. Also helps to detect correct parameter setting combinations.");
|
||||
protected readonly GUIContent PMAVertexColorsLabel = new GUIContent("PMA Vertex Colors",
|
||||
"Use this if you are using the default Spine/Skeleton shader or any premultiply-alpha shader.");
|
||||
protected readonly GUIContent AddNormalsLabel = new GUIContent("Add Normals",
|
||||
"Use this if your shader requires vertex normals. A more efficient solution for 2D setups is to modify the " +
|
||||
"shader to assume a single normal value for the whole mesh.");
|
||||
protected readonly GUIContent CalculateTangentsLabel = new GUIContent("Solve Tangents",
|
||||
"Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that " +
|
||||
"require vertex tangents.");
|
||||
|
||||
protected readonly GUIContent ImmutableTrianglesLabel = new GUIContent("Immutable Triangles",
|
||||
"Enable to optimize rendering for skeletons that never change attachment visibility");
|
||||
|
||||
|
||||
private static GUIContent EnableSeparatorSlotsLabel;
|
||||
private GUIContent UpdateWhenInvisibleLabel, FixDrawOrderLabel;
|
||||
|
||||
readonly GUIContent PhysicsPositionInheritanceFactorLabel = new GUIContent("Position",
|
||||
"When set to non-zero, Transform position movement in X and Y direction is applied to skeleton " +
|
||||
"PhysicsConstraints, multiplied by these " +
|
||||
"\nX and Y scale factors to the right. Typical (X,Y) values are " +
|
||||
"\n(1,1) to apply XY movement normally, " +
|
||||
"\n(2,2) to apply movement with double intensity, " +
|
||||
"\n(1,0) to apply only horizontal movement, or" +
|
||||
"\n(0,0) to not apply any Transform position movement at all.");
|
||||
readonly GUIContent PhysicsRotationInheritanceFactorLabel = new GUIContent("Rotation",
|
||||
"When set to non-zero, Transform rotation movement is applied to skeleton PhysicsConstraints, " +
|
||||
"multiplied by this scale factor to the right. Typical values are " +
|
||||
"\n1 to apply movement normally, " +
|
||||
"\n2 to apply movement with double intensity, or " +
|
||||
"\n0 to not apply any Transform rotation movement at all.");
|
||||
readonly GUIContent PhysicsPositionInheritanceLimitLabel = new GUIContent("Limit",
|
||||
"Limits Transform position movement in X and Y direction that is applied to skeleton PhysicsConstraints, " +
|
||||
"after it has been multiplied by Position inheritance above.");
|
||||
readonly GUIContent PhysicsRotationInheritanceLimitLabel = new GUIContent("Limit",
|
||||
"Limits Transform rotation that is applied to skeleton PhysicsConstraints, " +
|
||||
"after it has been multiplied by Rotation inheritance above.");
|
||||
readonly GUIContent PhysicsMovementRelativeToLabel = new GUIContent("Movement relative to",
|
||||
"Reference transform relative to which physics movement will be calculated, or null to use world location.");
|
||||
|
||||
protected SerializedProperty meshSettings;
|
||||
|
||||
const string ReloadButtonString = "Reload";
|
||||
static GUILayoutOption reloadButtonWidth;
|
||||
static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } }
|
||||
static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } }
|
||||
|
||||
protected virtual bool TargetIsValid {
|
||||
get {
|
||||
foreach (var o in targets) {
|
||||
var component = (ISkeletonRenderer)o;
|
||||
if (!component.IsValid)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
isInspectingPrefab = false;
|
||||
#else
|
||||
isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
#endif
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
loadingFailed = false;
|
||||
|
||||
// Labels
|
||||
SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", Icons.spine);
|
||||
SkeletonUtilityButtonContent = new GUIContent("Add Skeleton Utility", Icons.skeletonUtility);
|
||||
|
||||
UpdateWhenInvisibleLabel = new GUIContent("Update When Invisible", "Update mode used when the MeshRenderer becomes invisible. Update mode is automatically reset to UpdateMode.FullUpdate when the mesh becomes visible again.");
|
||||
FixDrawOrderLabel = new GUIContent("Fix Draw Order", "Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. \"A B A\"). If true, GPU instancing will be disabled at all materials and MaterialPropertyBlocks are assigned at each material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect draw order (e.g. \"A1 B A2\" changed to \"A1A2 B\"). You can disable this parameter when everything is drawn correctly to save the additional performance cost. Note: the GPU instancing setting will remain disabled at affected material assets after exiting play mode, you have to enable it manually if you accidentally enabled this parameter.");
|
||||
|
||||
skeletonDataAsset = serializedObject.FindProperty("skeletonDataAsset");
|
||||
initialSkinName = serializedObject.FindProperty("initialSkinName");
|
||||
initialFlipX = serializedObject.FindProperty("initialFlipX");
|
||||
initialFlipY = serializedObject.FindProperty("initialFlipY");
|
||||
|
||||
clearStateOnDisable = serializedObject.FindProperty("clearStateOnDisable");
|
||||
updateWhenInvisible = serializedObject.FindProperty("updateWhenInvisible");
|
||||
fixDrawOrder = serializedObject.FindProperty("fixDrawOrder");
|
||||
|
||||
meshSettings = serializedObject.FindProperty("meshSettings");
|
||||
meshSettings.isExpanded = SkeletonRendererInspector.advancedFoldout;
|
||||
|
||||
useClipping = meshSettings.FindPropertyRelative("useClipping");
|
||||
zSpacing = meshSettings.FindPropertyRelative("zSpacing");
|
||||
tintBlack = meshSettings.FindPropertyRelative("tintBlack");
|
||||
canvasGroupCompatible = meshSettings.FindPropertyRelative("canvasGroupCompatible");
|
||||
pmaVertexColors = meshSettings.FindPropertyRelative("pmaVertexColors");
|
||||
addNormals = meshSettings.FindPropertyRelative("addNormals");
|
||||
calculateTangents = meshSettings.FindPropertyRelative("calculateTangents");
|
||||
immutableTriangles = meshSettings.FindPropertyRelative("immutableTriangles");
|
||||
|
||||
threadedMeshGeneration = serializedObject.FindProperty("threadedMeshGeneration");
|
||||
separatorSlotNames = serializedObject.FindProperty("separatorSlotNames");
|
||||
separatorSlotNames.isExpanded = true;
|
||||
enableSeparatorSlots = serializedObject.FindProperty("enableSeparatorSlots");
|
||||
|
||||
physicsPositionInheritanceFactor = serializedObject.FindProperty("physicsPositionInheritanceFactor");
|
||||
physicsRotationInheritanceFactor = serializedObject.FindProperty("physicsRotationInheritanceFactor");
|
||||
physicsPositionInheritanceLimit = serializedObject.FindProperty("physicsPositionInheritanceLimit");
|
||||
physicsRotationInheritanceLimit = serializedObject.FindProperty("physicsRotationInheritanceLimit");
|
||||
physicsMovementRelativeTo = serializedObject.FindProperty("physicsMovementRelativeTo");
|
||||
}
|
||||
|
||||
public virtual void OnSceneGUI () {
|
||||
var skeletonRenderer = (ISkeletonRenderer)target;
|
||||
if (loadingFailed)
|
||||
return;
|
||||
|
||||
var skeleton = skeletonRenderer.Skeleton;
|
||||
if (skeleton == null) {
|
||||
loadingFailed = true;
|
||||
return;
|
||||
}
|
||||
var transform = skeletonRenderer.Component.transform;
|
||||
if (skeleton == null) return;
|
||||
|
||||
SpineHandles.DrawBones(transform, skeleton, skeletonRenderer.MeshScale, skeletonRenderer.MeshOffset);
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
bool multi = serializedObject.isEditingMultipleObjects;
|
||||
DrawInspectorGUI(multi);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void InspectorDrawPreparation () { }
|
||||
protected virtual void FirstPropertyFields () { }
|
||||
protected virtual void MaterialWarningsBox () { }
|
||||
protected virtual void AdditionalSeparatorSlotProperties () { }
|
||||
protected virtual void VertexDataProperties () { }
|
||||
protected virtual void AfterAdvancedPropertyFields () { }
|
||||
|
||||
protected virtual void RendererProperties () {
|
||||
using (new SpineInspectorUtility.LabelWidthScope()) {
|
||||
// Optimization options
|
||||
if (updateWhenInvisible != null) EditorGUILayout.PropertyField(updateWhenInvisible, UpdateWhenInvisibleLabel);
|
||||
|
||||
#if PER_MATERIAL_PROPERTY_BLOCKS
|
||||
if (fixDrawOrder != null) EditorGUILayout.PropertyField(fixDrawOrder, FixDrawOrderLabel);
|
||||
#endif
|
||||
if (immutableTriangles != null) EditorGUILayout.PropertyField(immutableTriangles, ImmutableTrianglesLabel);
|
||||
EditorGUILayout.PropertyField(clearStateOnDisable, ClearStateOnDisableLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
SeparatorSlotProperties(separatorSlotNames, enableSeparatorSlots);
|
||||
AdditionalSeparatorSlotProperties();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Render options
|
||||
EditorGUILayout.PropertyField(useClipping, UseClippingLabel);
|
||||
const float MinZSpacing = -0.1f;
|
||||
const float MaxZSpacing = 0f;
|
||||
EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing, ZSpacingLabel);
|
||||
}
|
||||
|
||||
protected virtual void PhysicsProperties () {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.LabelField(PhysicsPositionInheritanceFactorLabel, GUILayout.Width(EditorGUIUtility.labelWidth));
|
||||
int savedIndentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
EditorGUILayout.PropertyField(physicsPositionInheritanceFactor, GUIContent.none, GUILayout.MinWidth(60));
|
||||
EditorGUI.indentLevel = savedIndentLevel;
|
||||
}
|
||||
DrawOptionalLimitVector2(physicsPositionInheritanceLimit, PhysicsPositionInheritanceLimitLabel, new Vector2(10f, 10f),
|
||||
EditorGUI.indentLevel + 1);
|
||||
|
||||
EditorGUILayout.PropertyField(physicsRotationInheritanceFactor, PhysicsRotationInheritanceFactorLabel);
|
||||
DrawOptionalLimitFloat(physicsRotationInheritanceLimit, PhysicsRotationInheritanceLimitLabel, 10f,
|
||||
EditorGUI.indentLevel + 1);
|
||||
EditorGUILayout.PropertyField(physicsMovementRelativeTo, PhysicsMovementRelativeToLabel);
|
||||
}
|
||||
|
||||
static readonly GUIContent UnlimitedLabel = new GUIContent("Unlimited");
|
||||
static readonly GUIContent LimitToggleLabel = new GUIContent("",
|
||||
"Enable to set a maximum value. When disabled, no limit is applied.");
|
||||
|
||||
static void DrawOptionalLimitVector2 (SerializedProperty prop, GUIContent label, Vector2 enableDefault, int labelIndentLevel) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
int savedIndentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = labelIndentLevel;
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth));
|
||||
EditorGUI.indentLevel = 0;
|
||||
Vector2 currentValue = prop.vector2Value;
|
||||
bool isLimited = !(float.IsPositiveInfinity(currentValue.x) && float.IsPositiveInfinity(currentValue.y));
|
||||
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool newIsLimited = EditorGUILayout.Toggle(LimitToggleLabel, isLimited, GUILayout.Width(15));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
prop.vector2Value = newIsLimited ? enableDefault : Vector2.positiveInfinity;
|
||||
isLimited = newIsLimited;
|
||||
}
|
||||
if (isLimited) {
|
||||
EditorGUILayout.PropertyField(prop, GUIContent.none, GUILayout.MinWidth(60));
|
||||
} else {
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
EditorGUILayout.LabelField(UnlimitedLabel);
|
||||
}
|
||||
EditorGUI.indentLevel = savedIndentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawOptionalLimitFloat (SerializedProperty prop, GUIContent label, float enableDefault, int labelIndentLevel) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
int savedIndentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = labelIndentLevel;
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth));
|
||||
EditorGUI.indentLevel = 0;
|
||||
float currentValue = prop.floatValue;
|
||||
bool isLimited = currentValue < float.MaxValue && !float.IsPositiveInfinity(currentValue);
|
||||
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool newIsLimited = EditorGUILayout.Toggle(LimitToggleLabel, isLimited, GUILayout.Width(15));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
prop.floatValue = newIsLimited ? enableDefault : float.MaxValue;
|
||||
isLimited = newIsLimited;
|
||||
}
|
||||
if (isLimited) {
|
||||
EditorGUILayout.PropertyField(prop, GUIContent.none, GUILayout.MinWidth(60));
|
||||
} else {
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
EditorGUILayout.LabelField(UnlimitedLabel);
|
||||
}
|
||||
EditorGUI.indentLevel = savedIndentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AdvancedPropertyFields () {
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Renderer Settings", EditorStyles.boldLabel);
|
||||
RendererProperties();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (threadedMeshGeneration != null) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Threaded Mesh Generation", SpineEditorUtilities.Icons.subMeshRenderer), EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(threadedMeshGeneration, ThreadedMeshGenerationLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.LabelWidthScope()) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Vertex Data", SpineInspectorUtility.UnityIcon<MeshFilter>()), EditorStyles.boldLabel);
|
||||
VertexDataProperties();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
using (new SpineInspectorUtility.LabelWidthScope()) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Physics Inheritance", SpineEditorUtilities.Icons.constraintPhysics), EditorStyles.boldLabel);
|
||||
PhysicsProperties();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DrawInspectorGUI (bool multi) {
|
||||
// Initialize.
|
||||
if (Event.current.type == EventType.Layout) {
|
||||
if (forceReloadQueued) {
|
||||
forceReloadQueued = false;
|
||||
foreach (var c in targets) {
|
||||
SpineEditorUtilities.ReloadSkeletonDataAssetAndComponent(c as ISkeletonRenderer);
|
||||
}
|
||||
} else {
|
||||
foreach (var c in targets) {
|
||||
var component = c as ISkeletonRenderer;
|
||||
if (!component.IsValid) {
|
||||
SpineEditorUtilities.ReinitializeComponent(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InspectorDrawPreparation();
|
||||
|
||||
#if NO_PREFAB_MESH
|
||||
if (isInspectingPrefab) {
|
||||
foreach (var c in targets) {
|
||||
var component = c as SkeletonRenderer;
|
||||
if (component != null) {
|
||||
MeshFilter meshFilter = component.GetComponent<MeshFilter>();
|
||||
if (meshFilter != null && meshFilter.sharedMesh != null)
|
||||
meshFilter.sharedMesh = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool valid = TargetIsValid;
|
||||
|
||||
// Fields.
|
||||
bool skeletonAssetValid = CommonSkeletonAssetProperties(multi);
|
||||
if (!skeletonAssetValid || !valid)
|
||||
return;
|
||||
|
||||
EditorGUILayout.PropertyField(initialSkinName, SpineInspectorUtility.TempContent("Initial Skin"));
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
SpineInspectorUtility.ToggleLeftLayout(initialFlipX);
|
||||
SpineInspectorUtility.ToggleLeftLayout(initialFlipY);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
FirstPropertyFields();
|
||||
|
||||
MaterialWarningsBox();
|
||||
|
||||
// More Render Options...
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
|
||||
advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced");
|
||||
if (advancedFoldout) {
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button("Debug", EditorStyles.miniButton, GUILayout.Width(65f)))
|
||||
SkeletonDebugWindow.Init();
|
||||
} else {
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (advancedFoldout) {
|
||||
|
||||
using (new SpineInspectorUtility.IndentScope()) {
|
||||
|
||||
AdvancedPropertyFields();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (valid && !isInspectingPrefab) {
|
||||
if (multi) {
|
||||
// Support multi-edit SkeletonUtility button.
|
||||
// EditorGUILayout.Space();
|
||||
// bool addSkeletonUtility = GUILayout.Button(buttonContent, GUILayout.Height(30));
|
||||
// foreach (var t in targets) {
|
||||
// var component = t as Component;
|
||||
// if (addSkeletonUtility && component.GetComponent<SkeletonUtility>() == null)
|
||||
// component.gameObject.AddComponent<SkeletonUtility>();
|
||||
// }
|
||||
} else {
|
||||
var component = (Component)target;
|
||||
if (component.GetComponent<SkeletonUtility>() == null) {
|
||||
if (SpineInspectorUtility.CenteredButton(SkeletonUtilityButtonContent, 21, true, 200f))
|
||||
component.gameObject.AddComponent<SkeletonUtility>();
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AfterAdvancedPropertyFields();
|
||||
}
|
||||
|
||||
/// <returns>True when the SkeletonDataAsset is valid, false otherwise.</returns>
|
||||
protected bool CommonSkeletonAssetProperties (bool multi) {
|
||||
if (multi) {
|
||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
|
||||
SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
|
||||
if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth))
|
||||
forceReloadQueued = true;
|
||||
}
|
||||
} else {
|
||||
var component = (ISkeletonRenderer)target;
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
|
||||
SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
|
||||
if (component.IsValid) {
|
||||
if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth))
|
||||
forceReloadQueued = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (component.SkeletonDataAsset == null) {
|
||||
EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.SkeletonDataAsset)) {
|
||||
EditorGUILayout.HelpBox("Skeleton Data Asset error. Please check Skeleton Data Asset.", MessageType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void SetSeparatorSlotNames (SkeletonRenderer skeletonRenderer, string[] newSlotNames) {
|
||||
var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName);
|
||||
field.SetValue(skeletonRenderer, newSlotNames);
|
||||
}
|
||||
|
||||
public static string[] GetSeparatorSlotNames (SkeletonRenderer skeletonRenderer) {
|
||||
var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName);
|
||||
return field.GetValue(skeletonRenderer) as string[];
|
||||
}
|
||||
|
||||
public static string TerminalSlotWarningString (SerializedProperty separatorSlotNames) {
|
||||
bool multi = separatorSlotNames.serializedObject.isEditingMultipleObjects;
|
||||
bool hasTerminalSlot = false;
|
||||
if (!multi) {
|
||||
var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent;
|
||||
var skeleton = sr.Skeleton;
|
||||
int lastSlot = skeleton.Slots.Count - 1;
|
||||
if (skeleton != null) {
|
||||
for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) {
|
||||
string slotName = separatorSlotNames.GetArrayElementAtIndex(i).stringValue;
|
||||
SlotData slot = skeleton.Data.FindSlot(slotName);
|
||||
int index = slot != null ? slot.Index : -1;
|
||||
if (index == 0 || index == lastSlot) {
|
||||
hasTerminalSlot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasTerminalSlot ? " (!)" : "";
|
||||
}
|
||||
|
||||
public static void SeparatorSlotProperties (SerializedProperty separatorSlotNames,
|
||||
SerializedProperty enableSeparatorSlots) {
|
||||
|
||||
string terminalSlotWarning = TerminalSlotWarningString(separatorSlotNames);
|
||||
const string SeparatorsDescription = "Stored names of slots where the Skeleton's render will be split into different batches. This is used by separate components that split the render into different MeshRenderers or GameObjects.";
|
||||
if (separatorSlotNames.isExpanded) {
|
||||
EditorGUILayout.PropertyField(separatorSlotNames, SpineInspectorUtility.TempContent(separatorSlotNames.displayName + terminalSlotWarning, Icons.slotRoot, SeparatorsDescription), true);
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("+", GUILayout.MaxWidth(28f), GUILayout.MaxHeight(15f))) {
|
||||
separatorSlotNames.arraySize++;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
} else
|
||||
EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + string.Format("{0} [{1}]", terminalSlotWarning, separatorSlotNames.arraySize), SeparatorsDescription), true);
|
||||
|
||||
if (EnableSeparatorSlotsLabel == null)
|
||||
EnableSeparatorSlotsLabel = new GUIContent("Enable Separation", "Whether to enable separation at the above separator slots.");
|
||||
|
||||
EditorGUILayout.PropertyField(enableSeparatorSlots, EnableSeparatorSlotsLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04d6404d1e6803b48974da739a5fefcc
|
||||
timeCreated: 1626445114
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,187 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
using Editor = UnityEditor.Editor;
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(PointFollower)), CanEditMultipleObjects]
|
||||
public class PointFollowerInspector : Editor {
|
||||
SerializedProperty slotName, pointAttachmentName, skeletonRenderer, followZPosition, followBoneRotation, followSkeletonFlip;
|
||||
PointFollower targetPointFollower;
|
||||
bool needsReset;
|
||||
|
||||
#region Context Menu Item
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add PointFollower GameObject")]
|
||||
static void AddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
GameObject go = EditorInstantiation.NewGameObject("PointFollower", true);
|
||||
Transform t = go.transform;
|
||||
t.SetParent(skeletonRenderer.transform);
|
||||
t.localPosition = Vector3.zero;
|
||||
|
||||
PointFollower f = go.AddComponent<PointFollower>();
|
||||
f.skeletonRenderer = skeletonRenderer;
|
||||
|
||||
EditorGUIUtility.PingObject(t);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add PointFollower");
|
||||
}
|
||||
|
||||
// Validate
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add PointFollower GameObject", true)]
|
||||
static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
return skeletonRenderer.valid;
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
slotName = serializedObject.FindProperty("slotName");
|
||||
pointAttachmentName = serializedObject.FindProperty("pointAttachmentName");
|
||||
|
||||
targetPointFollower = (PointFollower)target;
|
||||
if (targetPointFollower.skeletonRenderer != null)
|
||||
targetPointFollower.skeletonRenderer.Initialize(false);
|
||||
|
||||
if (!targetPointFollower.IsValid || needsReset) {
|
||||
targetPointFollower.Initialize();
|
||||
targetPointFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI () {
|
||||
PointFollower tbf = target as PointFollower;
|
||||
SkeletonRenderer skeletonRendererComponent = tbf.skeletonRenderer;
|
||||
if (skeletonRendererComponent == null)
|
||||
return;
|
||||
|
||||
Skeleton skeleton = skeletonRendererComponent.skeleton;
|
||||
Transform skeletonTransform = skeletonRendererComponent.transform;
|
||||
|
||||
if (string.IsNullOrEmpty(pointAttachmentName.stringValue)) {
|
||||
// Draw all active PointAttachments in the current skin
|
||||
Skin currentSkin = skeleton.Skin;
|
||||
if (currentSkin != skeleton.Data.DefaultSkin) DrawPointsInSkin(skeleton.Data.DefaultSkin, skeleton, skeletonTransform);
|
||||
if (currentSkin != null) DrawPointsInSkin(currentSkin, skeleton, skeletonTransform);
|
||||
} else {
|
||||
Slot slot = skeleton.FindSlot(slotName.stringValue);
|
||||
if (slot != null) {
|
||||
int slotIndex = slot.Data.Index;
|
||||
PointAttachment point = skeleton.GetAttachment(slotIndex, pointAttachmentName.stringValue) as PointAttachment;
|
||||
if (point != null) {
|
||||
DrawPointAttachmentWithLabel(point, slot.Bone, skeletonTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawPointsInSkin (Skin skin, Skeleton skeleton, Transform transform) {
|
||||
foreach (Skin.SkinEntry skinEntry in skin.Attachments) {
|
||||
PointAttachment attachment = skinEntry.Attachment as PointAttachment;
|
||||
if (attachment != null) {
|
||||
Slot slot = skeleton.Slots.Items[skinEntry.SlotIndex];
|
||||
DrawPointAttachmentWithLabel(attachment, slot.Bone, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawPointAttachmentWithLabel (PointAttachment point, Bone bone, Transform transform) {
|
||||
Vector3 labelOffset = new Vector3(0f, -0.2f, 0f);
|
||||
SpineHandles.DrawPointAttachment(bone, point, transform);
|
||||
Handles.Label(labelOffset + point.GetWorldPosition(bone, transform), point.Name, SpineHandles.PointNameStyle);
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
if (needsReset) {
|
||||
needsReset = false;
|
||||
foreach (Object o in targets) {
|
||||
BoneFollower bf = (BoneFollower)o;
|
||||
bf.Initialize();
|
||||
bf.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsReset && Event.current.type == EventType.Layout) {
|
||||
targetPointFollower.Initialize();
|
||||
targetPointFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
serializedObject.Update();
|
||||
|
||||
DrawDefaultInspector();
|
||||
|
||||
// Find Renderer
|
||||
if (skeletonRenderer.objectReferenceValue == null) {
|
||||
SkeletonRenderer parentRenderer = targetPointFollower.GetComponentInParent<SkeletonRenderer>();
|
||||
if (parentRenderer != null && parentRenderer.gameObject != targetPointFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = parentRenderer;
|
||||
Debug.Log("Inspector automatically assigned PointFollower.SkeletonRenderer");
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonRenderer skeletonRendererReference = skeletonRenderer.objectReferenceValue as SkeletonRenderer;
|
||||
if (skeletonRendererReference != null) {
|
||||
if (skeletonRendererReference.gameObject == targetPointFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = null;
|
||||
EditorUtility.DisplayDialog("Invalid assignment.", "PointFollower can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your PointFollower, or choose a SkeletonRenderer from a different GameObject.", "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetPointFollower.IsValid) {
|
||||
needsReset = true;
|
||||
}
|
||||
|
||||
Event current = Event.current;
|
||||
bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed");
|
||||
if (wasUndo)
|
||||
targetPointFollower.Initialize();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c7e838a8ec295a4e9c53602f690f42f
|
||||
timeCreated: 1518163038
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonAnimation))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonAnimationInspector : UnityEditor.Editor {
|
||||
|
||||
protected SerializedProperty updateTiming, animationName, loop, timeScale, unscaledTime, autoReset, threadedAnimation;
|
||||
readonly GUIContent UpdateTimingLabel = new GUIContent("Animation Update",
|
||||
"Whether to update the animation in normal Update (the default), " +
|
||||
"physics step FixedUpdate, or manually via a user call.");
|
||||
readonly GUIContent LoopLabel = new GUIContent("Loop",
|
||||
"Whether or not .AnimationName should loop. This only applies to the initial " +
|
||||
"animation specified in the inspector, or any subsequent Animations played through .AnimationName. " +
|
||||
"Animations set through state.SetAnimation are unaffected.");
|
||||
readonly GUIContent TimeScaleLabel = new GUIContent("Time Scale",
|
||||
"The rate at which animations progress over time. 1 means normal speed. 0.5 means 50% speed.");
|
||||
readonly GUIContent UnscaledTimeLabel = new GUIContent("Unscaled Time",
|
||||
"When enabled, AnimationState uses unscaled game time (Time.unscaledDeltaTime), " +
|
||||
"running animations independent of e.g. game pause (Time.timeScale). " +
|
||||
"Instance SkeletonAnimation.timeScale will still be applied.");
|
||||
readonly GUIContent ThreadedAnimationLabel = new GUIContent("Use Threading",
|
||||
"When enabled, animations are processed on multiple threads in parallel.");
|
||||
|
||||
protected bool TargetIsValid {
|
||||
get {
|
||||
foreach (UnityEngine.Object o in targets) {
|
||||
ISkeletonAnimation component = (ISkeletonAnimation)o;
|
||||
if (!component.IsValid)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnEnable () {
|
||||
animationName = serializedObject.FindProperty("animationName");
|
||||
loop = serializedObject.FindProperty("loop");
|
||||
timeScale = serializedObject.FindProperty("timeScale");
|
||||
unscaledTime = serializedObject.FindProperty("unscaledTime");
|
||||
updateTiming = serializedObject.FindProperty("updateTiming");
|
||||
threadedAnimation = serializedObject.FindProperty("threadedAnimation");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
DrawInspectorGUI();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void DrawInspectorGUI () {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
ISkeletonAnimation component = c as ISkeletonAnimation;
|
||||
if (!component.IsValid) {
|
||||
SpineEditorUtilities.ReinitializeComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
|
||||
EditorGUILayout.Space();
|
||||
if (!sameData) {
|
||||
EditorGUILayout.DelayedTextField(animationName);
|
||||
} else {
|
||||
EditorGUILayout.PropertyField(animationName);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(loop, LoopLabel);
|
||||
EditorGUILayout.PropertyField(timeScale, TimeScaleLabel);
|
||||
foreach (UnityEngine.Object o in targets) {
|
||||
SkeletonAnimation component = o as SkeletonAnimation;
|
||||
component.timeScale = Mathf.Max(component.timeScale, 0);
|
||||
}
|
||||
EditorGUILayout.PropertyField(unscaledTime, UnscaledTimeLabel);
|
||||
EditorGUILayout.PropertyField(updateTiming, UpdateTimingLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (threadedAnimation != null) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Threaded Animation", SpineEditorUtilities.Icons.subMeshRenderer), EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(threadedAnimation, ThreadedAnimationLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
SkeletonRootMotionParameter();
|
||||
}
|
||||
|
||||
protected void SkeletonRootMotionParameter () {
|
||||
SkeletonRootMotionParameter(targets);
|
||||
}
|
||||
|
||||
public static void SkeletonRootMotionParameter (Object[] targets) {
|
||||
int rootMotionComponentCount = 0;
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
if (component.GetComponent<SkeletonRootMotion>() != null) {
|
||||
++rootMotionComponentCount;
|
||||
}
|
||||
}
|
||||
bool allHaveRootMotion = rootMotionComponentCount == targets.Length;
|
||||
bool anyHaveRootMotion = rootMotionComponentCount > 0;
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PrefixLabel("Root Motion");
|
||||
if (!allHaveRootMotion) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Component", Icons.constraintTransform), GUILayout.MaxWidth(130), GUILayout.Height(18))) {
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
if (component.GetComponent<SkeletonRootMotion>() == null) {
|
||||
component.gameObject.AddComponent<SkeletonRootMotion>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anyHaveRootMotion) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Remove Component", Icons.constraintTransform), GUILayout.MaxWidth(140), GUILayout.Height(18))) {
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
SkeletonRootMotion rootMotionComponent = component.GetComponent<SkeletonRootMotion>();
|
||||
if (rootMotionComponent != null) {
|
||||
DestroyImmediate(rootMotionComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39fbfef61034ca045b5aa80088e1e8a4
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,183 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine.Unity.Examples;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
// This script is not intended for use with code. See spine-unity documentation page for additional information.
|
||||
[CustomEditor(typeof(SkeletonGraphicCustomMaterials))]
|
||||
public class SkeletonGraphicCustomMaterialsInspector : UnityEditor.Editor {
|
||||
List<SkeletonGraphicCustomMaterials.SlotMaterialOverride> componentCustomSlotMaterials, _customSlotMaterialsPrev;
|
||||
List<SkeletonGraphicCustomMaterials.AtlasMaterialOverride> componentCustomMaterialOverrides, _customMaterialOverridesPrev;
|
||||
List<SkeletonGraphicCustomMaterials.AtlasTextureOverride> componentCustomTextureOverrides, _customTextureOverridesPrev;
|
||||
SkeletonGraphicCustomMaterials component;
|
||||
|
||||
const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
MethodInfo RemoveCustomMaterialOverrides, RemoveCustomTextureOverrides, RemoveCustomSlotMaterials,
|
||||
SetCustomMaterialOverrides, SetCustomTextureOverrides, SetCustomSlotMaterials;
|
||||
|
||||
#region SkeletonGraphic context menu
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add Basic Serialized Custom Materials")]
|
||||
static void AddSkeletonGraphicCustomMaterials (MenuCommand menuCommand) {
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)menuCommand.context;
|
||||
SkeletonGraphicCustomMaterials newComponent = skeletonGraphic.gameObject.AddComponent<SkeletonGraphicCustomMaterials>();
|
||||
Undo.RegisterCreatedObjectUndo(newComponent, "Add Basic Serialized Custom Materials");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add Basic Serialized Custom Materials", true)]
|
||||
static bool AddSkeletonGraphicCustomMaterials_Validate (MenuCommand menuCommand) {
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)menuCommand.context;
|
||||
return (skeletonGraphic.GetComponent<SkeletonGraphicCustomMaterials>() == null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
Type cm = typeof(SkeletonGraphicCustomMaterials);
|
||||
RemoveCustomMaterialOverrides = cm.GetMethod("RemoveCustomMaterialOverrides", PrivateInstance);
|
||||
RemoveCustomTextureOverrides = cm.GetMethod("RemoveCustomTextureOverrides", PrivateInstance);
|
||||
RemoveCustomSlotMaterials = cm.GetMethod("RemoveCustomSlotMaterials", PrivateInstance);
|
||||
SetCustomMaterialOverrides = cm.GetMethod("SetCustomMaterialOverrides", PrivateInstance);
|
||||
SetCustomTextureOverrides = cm.GetMethod("SetCustomTextureOverrides", PrivateInstance);
|
||||
SetCustomSlotMaterials = cm.GetMethod("SetCustomSlotMaterials", PrivateInstance);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
component = (SkeletonGraphicCustomMaterials)target;
|
||||
SkeletonGraphic skeletonGraphic = component.skeletonGraphic;
|
||||
|
||||
// Draw the default inspector
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (serializedObject.isEditingMultipleObjects)
|
||||
return;
|
||||
|
||||
if (componentCustomMaterialOverrides == null) {
|
||||
Type cm = typeof(SkeletonGraphicCustomMaterials);
|
||||
componentCustomMaterialOverrides = cm.GetField("customMaterialOverrides", PrivateInstance).GetValue(component) as List<SkeletonGraphicCustomMaterials.AtlasMaterialOverride>;
|
||||
componentCustomTextureOverrides = cm.GetField("customTextureOverrides", PrivateInstance).GetValue(component) as List<SkeletonGraphicCustomMaterials.AtlasTextureOverride>;
|
||||
componentCustomSlotMaterials = cm.GetField("customSlotMaterials", PrivateInstance).GetValue(component) as List<SkeletonGraphicCustomMaterials.SlotMaterialOverride>;
|
||||
if (componentCustomMaterialOverrides == null || componentCustomTextureOverrides == null ||
|
||||
componentCustomSlotMaterials == null) {
|
||||
Debug.Log("Reflection failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill with current values at start
|
||||
if (_customMaterialOverridesPrev == null || _customTextureOverridesPrev == null
|
||||
|| _customSlotMaterialsPrev == null) {
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customTextureOverridesPrev = CopyList(componentCustomTextureOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
}
|
||||
|
||||
// Compare new values with saved. If change is detected:
|
||||
// store new values, restore old values, remove overrides, restore new values, restore overrides.
|
||||
|
||||
// 1. Store new values
|
||||
var customMaterialOverridesNew = CopyList(componentCustomMaterialOverrides);
|
||||
var customTextureOverridesNew = CopyList(componentCustomTextureOverrides);
|
||||
var customSlotMaterialsNew = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
// Detect changes
|
||||
if (!_customMaterialOverridesPrev.SequenceEqual(customMaterialOverridesNew) ||
|
||||
!_customTextureOverridesPrev.SequenceEqual(customTextureOverridesNew) ||
|
||||
!_customSlotMaterialsPrev.SequenceEqual(customSlotMaterialsNew)) {
|
||||
// 2. Restore old values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomTextureOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(_customMaterialOverridesPrev);
|
||||
componentCustomTextureOverrides.AddRange(_customTextureOverridesPrev);
|
||||
componentCustomSlotMaterials.AddRange(_customSlotMaterialsPrev);
|
||||
|
||||
// 3. Remove overrides
|
||||
RemoveCustomMaterials();
|
||||
|
||||
// 4. Restore new values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomTextureOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(customMaterialOverridesNew);
|
||||
componentCustomTextureOverrides.AddRange(customTextureOverridesNew);
|
||||
componentCustomSlotMaterials.AddRange(customSlotMaterialsNew);
|
||||
|
||||
// 5. Restore overrides
|
||||
SetCustomMaterials();
|
||||
|
||||
if (skeletonGraphic != null)
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customTextureOverridesPrev = CopyList(componentCustomTextureOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
if (componentCustomSlotMaterials.Count > 0 && !skeletonGraphic.allowMultipleCanvasRenderers &&
|
||||
componentCustomSlotMaterials.Any(entry => entry.overrideEnabled)) {
|
||||
EditorGUILayout.HelpBox("Please enable 'Advanced - Multiple CanvasRenderers' at the SkeletonGraphic " +
|
||||
"component when using Custom Slot Materials.", MessageType.Warning, true);
|
||||
}
|
||||
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Clear and Reapply Changes", tooltip: "Removes all non-serialized overrides in the SkeletonGraphic and reapplies the overrides on this component."))) {
|
||||
if (skeletonGraphic != null) {
|
||||
skeletonGraphic.CustomMaterialOverride.Clear();
|
||||
skeletonGraphic.CustomTextureOverride.Clear();
|
||||
skeletonGraphic.CustomSlotMaterials.Clear();
|
||||
RemoveCustomMaterials();
|
||||
SetCustomMaterials();
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomMaterials () {
|
||||
RemoveCustomMaterialOverrides.Invoke(component, null);
|
||||
RemoveCustomTextureOverrides.Invoke(component, null);
|
||||
RemoveCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
void SetCustomMaterials () {
|
||||
SetCustomMaterialOverrides.Invoke(component, null);
|
||||
SetCustomTextureOverrides.Invoke(component, null);
|
||||
SetCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
static List<T> CopyList<T> (List<T> list) {
|
||||
return list.GetRange(0, list.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 349bf125947e3aa4bb78690fec69ea17
|
||||
timeCreated: 1588789940
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,389 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
#define HAS_CULL_TRANSPARENT_MESH
|
||||
#endif
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
#define NEWPLAYMODECALLBACKS
|
||||
#endif
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonGraphic))]
|
||||
[CanEditMultipleObjects]
|
||||
|
||||
public class SkeletonGraphicInspector : ISkeletonRendererInspector {
|
||||
|
||||
protected SerializedProperty material, color;
|
||||
protected SerializedProperty additiveMaterial, multiplyMaterial, screenMaterial, forceAdditiveMaterial;
|
||||
protected SerializedProperty freeze;
|
||||
protected SerializedProperty allowMultipleCanvasRenderers,
|
||||
updateSeparatorPartLocation, updateSeparatorPartScale;
|
||||
protected SerializedProperty raycastTarget, maskable;
|
||||
protected SerializedProperty layoutScaleMode, editReferenceRect;
|
||||
|
||||
protected GUIContent allowMultipleCanvasRenderersLabel, updateSeparatorPartLocationLabel,
|
||||
updateSeparatorPartScaleLabel;
|
||||
|
||||
protected SkeletonGraphic thisSkeletonGraphic;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
|
||||
// Labels
|
||||
allowMultipleCanvasRenderersLabel = new GUIContent("Multiple CanvasRenderers",
|
||||
"When set to true, SkeletonGraphic no longer uses a single CanvasRenderer" +
|
||||
"but automatically creates the required number of child CanvasRenderer" +
|
||||
"GameObjects for each required draw call (submesh).");
|
||||
updateSeparatorPartLocationLabel = new GUIContent("Update Part Location",
|
||||
"Update separator part GameObject location to match the position of the SkeletonGraphic. " +
|
||||
"This can be helpful when re-parenting parts to a different GameObject.");
|
||||
updateSeparatorPartScaleLabel = new GUIContent("Update Part Scale",
|
||||
"Update separator part GameObject scale to match the scale (lossyScale) of the SkeletonGraphic. " +
|
||||
"This can be helpful when re-parenting parts to a different GameObject.");
|
||||
|
||||
// Properties
|
||||
thisSkeletonGraphic = target as SkeletonGraphic;
|
||||
|
||||
// MaskableGraphic
|
||||
material = serializedObject.FindProperty("m_Material");
|
||||
color = serializedObject.FindProperty("m_SkeletonColor");
|
||||
raycastTarget = serializedObject.FindProperty("m_RaycastTarget");
|
||||
maskable = serializedObject.FindProperty("m_Maskable");
|
||||
|
||||
// SkeletonGraphic
|
||||
additiveMaterial = serializedObject.FindProperty("additiveMaterial");
|
||||
multiplyMaterial = serializedObject.FindProperty("multiplyMaterial");
|
||||
screenMaterial = serializedObject.FindProperty("screenMaterial");
|
||||
forceAdditiveMaterial = serializedObject.FindProperty("forceAdditiveMaterial");
|
||||
freeze = serializedObject.FindProperty("freeze");
|
||||
allowMultipleCanvasRenderers = serializedObject.FindProperty("allowMultipleCanvasRenderers");
|
||||
updateSeparatorPartLocation = serializedObject.FindProperty("updateSeparatorPartLocation");
|
||||
updateSeparatorPartScale = serializedObject.FindProperty("updateSeparatorPartScale");
|
||||
layoutScaleMode = serializedObject.FindProperty("layoutScaleMode");
|
||||
editReferenceRect = serializedObject.FindProperty("editReferenceRect");
|
||||
|
||||
#if NEWPLAYMODECALLBACKS
|
||||
EditorApplication.playModeStateChanged += OnPlaymodeChanged;
|
||||
#else
|
||||
EditorApplication.playmodeStateChanged += OnPlaymodeChanged;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
#if NEWPLAYMODECALLBACKS
|
||||
EditorApplication.playModeStateChanged -= OnPlaymodeChanged;
|
||||
#else
|
||||
EditorApplication.playmodeStateChanged -= OnPlaymodeChanged;
|
||||
#endif
|
||||
DisableEditReferenceRectMode();
|
||||
}
|
||||
|
||||
#if NEWPLAYMODECALLBACKS
|
||||
void OnPlaymodeChanged (PlayModeStateChange mode) {
|
||||
#else
|
||||
void OnPlaymodeChanged () {
|
||||
#endif
|
||||
DisableEditReferenceRectMode();
|
||||
}
|
||||
|
||||
void DisableEditReferenceRectMode () {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
SkeletonGraphic component = c as SkeletonGraphic;
|
||||
if (component == null) continue;
|
||||
component.EditReferenceRect = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void FirstPropertyFields () {
|
||||
using (new SpineInspectorUtility.LabelWidthScope(100)) {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(material);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(67f))) {
|
||||
Undo.RecordObjects(targets, "Detect Material");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectMaterial(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorGUILayout.PropertyField(color);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void MaterialWarningsBox () {
|
||||
string errorMessage = null;
|
||||
if (SpineEditorUtilities.Preferences.componentMaterialWarning &&
|
||||
MaterialChecks.IsMaterialSetupProblematic(thisSkeletonGraphic, ref errorMessage)) {
|
||||
EditorGUILayout.HelpBox(errorMessage, MessageType.Error, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void VertexDataProperties () {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(tintBlack, TintBlackLabel);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
Undo.RecordObjects(targets, "Detect Tint Black");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectTintBlack(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(canvasGroupCompatible, CanvasGroupCompatibleLabel);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
Undo.RecordObjects(targets, "Detect CanvasGroup Compatible");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectCanvasGroupCompatible(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(pmaVertexColors, PMAVertexColorsLabel);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
Undo.RecordObjects(targets, "Detect PMA Vertex Colors");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectPMAVertexColors(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Detect Settings", EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
Undo.RecordObjects(targets, "Detect Settings");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectTintBlack(skeletonGraphic);
|
||||
SkeletonGraphicUtility.DetectCanvasGroupCompatible(skeletonGraphic);
|
||||
SkeletonGraphicUtility.DetectPMAVertexColors(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("Detect Material", EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
Undo.RecordObjects(targets, "Detect Material");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectMaterial(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(addNormals, AddNormalsLabel);
|
||||
EditorGUILayout.PropertyField(calculateTangents, CalculateTangentsLabel);
|
||||
EditorGUILayout.PropertyField(immutableTriangles, ImmutableTrianglesLabel);
|
||||
}
|
||||
|
||||
protected override void RendererProperties () {
|
||||
|
||||
bool isSingleRendererOnly = (!allowMultipleCanvasRenderers.hasMultipleDifferentValues && allowMultipleCanvasRenderers.boolValue == false);
|
||||
bool isSeparationEnabledButNotMultipleRenderers =
|
||||
isSingleRendererOnly && (!enableSeparatorSlots.hasMultipleDifferentValues && enableSeparatorSlots.boolValue == true);
|
||||
bool meshRendersIncorrectlyWithSingleRenderer =
|
||||
isSingleRendererOnly && SkeletonHasMultipleSubmeshes();
|
||||
|
||||
if (isSeparationEnabledButNotMultipleRenderers || meshRendersIncorrectlyWithSingleRenderer)
|
||||
advancedFoldout = true;
|
||||
|
||||
base.RendererProperties();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(allowMultipleCanvasRenderers, allowMultipleCanvasRenderersLabel);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Trim Renderers", "Remove currently unused CanvasRenderer GameObjects. These will be regenerated whenever needed."),
|
||||
EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
|
||||
Undo.RecordObjects(targets, "Trim Renderers");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
skeletonGraphic.TrimRenderers();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
BlendModeMaterials blendModeMaterials = thisSkeletonGraphic.skeletonDataAsset.blendModeMaterials;
|
||||
if (allowMultipleCanvasRenderers.boolValue == true && blendModeMaterials.RequiresBlendModeMaterials) {
|
||||
using (new SpineInspectorUtility.IndentScope()) {
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Blend Mode Materials", EditorStyles.boldLabel);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Detect", "Auto-Assign Blend Mode Materials according to Vertex Data and Texture settings."),
|
||||
EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
|
||||
Undo.RecordObjects(targets, "Detect Blend Mode Materials");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectBlendModeMaterials(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
bool usesAdditiveMaterial = blendModeMaterials.applyAdditiveMaterial;
|
||||
bool pmaVertexColors = thisSkeletonGraphic.MeshSettings.pmaVertexColors;
|
||||
bool forceAdditiveEnabled = thisSkeletonGraphic.forceAdditiveMaterial;
|
||||
if (pmaVertexColors) {
|
||||
EditorGUILayout.PropertyField(forceAdditiveMaterial, SpineInspectorUtility.TempContent("Force Additive Material", null, "Still use 'Additive' material regardless of enabled 'PMA Vertex Colors'."));
|
||||
if (forceAdditiveEnabled)
|
||||
EditorGUILayout.PropertyField(additiveMaterial, SpineInspectorUtility.TempContent("Additive Material", null, "SkeletonGraphic Material for 'Additive' blend mode slots. Unused when 'PMA Vertex Colors' is enabled."));
|
||||
else
|
||||
using (new EditorGUI.DisabledGroupScope(true)) {
|
||||
EditorGUILayout.LabelField("Additive Material - Unused with PMA Vertex Colors", EditorStyles.label);
|
||||
}
|
||||
} else if (usesAdditiveMaterial) {
|
||||
EditorGUILayout.PropertyField(additiveMaterial, SpineInspectorUtility.TempContent("Additive Material", null, "SkeletonGraphic Material for 'Additive' blend mode slots. Unused when 'PMA Vertex Colors' is enabled."));
|
||||
} else {
|
||||
using (new EditorGUI.DisabledGroupScope(true)) {
|
||||
EditorGUILayout.LabelField("No Additive Mat - 'Apply Additive Material' disabled at SkeletonDataAsset", EditorStyles.label);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.PropertyField(multiplyMaterial, SpineInspectorUtility.TempContent("Multiply Material", null, "SkeletonGraphic Material for 'Multiply' blend mode slots."));
|
||||
EditorGUILayout.PropertyField(screenMaterial, SpineInspectorUtility.TempContent("Screen Material", null, "SkeletonGraphic Material for 'Screen' blend mode slots."));
|
||||
}
|
||||
}
|
||||
|
||||
// warning box
|
||||
if (isSeparationEnabledButNotMultipleRenderers) {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
meshSettings.isExpanded = true;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("'Multiple Canvas Renderers' must be enabled\nwhen 'Enable Separation' is enabled.", Icons.warning), GUILayout.Height(42), GUILayout.Width(340));
|
||||
}
|
||||
} else if (meshRendersIncorrectlyWithSingleRenderer) {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
meshSettings.isExpanded = true;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("This mesh uses multiple atlas pages or blend modes.\n" +
|
||||
"You need to enable 'Multiple Canvas Renderers'\n" +
|
||||
"for correct rendering. Consider packing\n" +
|
||||
"attachments to a single atlas page if possible.", Icons.warning), GUILayout.Height(60), GUILayout.Width(340));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AfterAdvancedPropertyFields () {
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(freeze);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("UI", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(raycastTarget);
|
||||
if (maskable != null) EditorGUILayout.PropertyField(maskable);
|
||||
|
||||
EditorGUILayout.PropertyField(layoutScaleMode);
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(layoutScaleMode.intValue == 0)) {
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
|
||||
EditorGUILayout.PrefixLabel("Edit Layout Bounds");
|
||||
editReferenceRect.boolValue = GUILayout.Toggle(editReferenceRect.boolValue,
|
||||
EditorGUIUtility.IconContent("EditCollider"), EditorStyles.miniButton, GUILayout.Width(40f));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
if (layoutScaleMode.intValue == 0) {
|
||||
editReferenceRect.boolValue = false;
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(editReferenceRect.boolValue == false && layoutScaleMode.intValue != 0)) {
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
|
||||
EditorGUILayout.PrefixLabel("Match RectTransform with Mesh");
|
||||
if (GUILayout.Button("Match", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
MatchRectTransformWithBounds(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
protected bool SkeletonHasMultipleSubmeshes () {
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
if (skeletonGraphic.HasMultipleSubmeshInstructions())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void AdditionalSeparatorSlotProperties () {
|
||||
EditorGUILayout.PropertyField(updateSeparatorPartLocation, updateSeparatorPartLocationLabel);
|
||||
EditorGUILayout.PropertyField(updateSeparatorPartScale, updateSeparatorPartScaleLabel);
|
||||
}
|
||||
|
||||
public override void OnSceneGUI () {
|
||||
base.OnSceneGUI();
|
||||
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)target;
|
||||
|
||||
if (skeletonGraphic.layoutScaleMode != SkeletonGraphic.LayoutMode.None) {
|
||||
if (skeletonGraphic.EditReferenceRect) {
|
||||
SpineHandles.DrawRectTransformRect(skeletonGraphic, Color.gray);
|
||||
SpineHandles.DrawReferenceRect(skeletonGraphic, Color.green);
|
||||
} else {
|
||||
SpineHandles.DrawReferenceRect(skeletonGraphic, Color.blue);
|
||||
}
|
||||
}
|
||||
SpineHandles.DrawPivotOffsetHandle(skeletonGraphic, Color.green);
|
||||
}
|
||||
|
||||
#region Menus
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")]
|
||||
static void MatchRectTransformWithBounds (MenuCommand command) {
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)command.context;
|
||||
MatchRectTransformWithBounds(skeletonGraphic);
|
||||
}
|
||||
|
||||
static void MatchRectTransformWithBounds (SkeletonGraphic skeletonGraphic) {
|
||||
if (!skeletonGraphic.MatchRectTransformWithBounds())
|
||||
Debug.Log("Mesh was not previously generated.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d81cc76b52fcdf499b2db252a317726
|
||||
timeCreated: 1455570945
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,237 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Contributed by: Mitch Thompson
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(SkeletonMecanim))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonMecanimInspector : UnityEditor.Editor {
|
||||
public static bool mecanimSettingsFoldout;
|
||||
public static bool enableScenePreview;
|
||||
|
||||
protected SerializedProperty updateTiming, autoReset, useCustomMixMode, layerMixModes, threadedAnimation;
|
||||
|
||||
readonly GUIContent UpdateTimingLabel = new GUIContent("Animation Update",
|
||||
"Whether to update the animation in normal Update (the default), " +
|
||||
"physics step FixedUpdate, or manually via a user call.");
|
||||
readonly GUIContent AutoResetLabel = new GUIContent("Auto Reset",
|
||||
"When set to true, the skeleton state is mixed out to setup-" +
|
||||
"pose when an animation finishes, according to the " +
|
||||
"animation's keyed items.");
|
||||
readonly GUIContent UseCustomMixModeLabel = new GUIContent("Custom MixMode",
|
||||
"When disabled, the recommended MixMode is used according to the layer blend mode. " +
|
||||
"Enable to specify a custom MixMode for each Mecanim layer.");
|
||||
readonly GUIContent ThreadedAnimationLabel = new GUIContent("Use Threading",
|
||||
"When enabled, animations are processed on multiple threads in parallel.");
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
SerializedProperty mecanimTranslator = serializedObject.FindProperty("translator");
|
||||
|
||||
autoReset = mecanimTranslator.FindPropertyRelative("autoReset");
|
||||
useCustomMixMode = mecanimTranslator.FindPropertyRelative("useCustomMixMode");
|
||||
layerMixModes = mecanimTranslator.FindPropertyRelative("layerMixModes");
|
||||
|
||||
updateTiming = serializedObject.FindProperty("updateTiming");
|
||||
threadedAnimation = serializedObject.FindProperty("threadedAnimation");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
DrawInspectorGUI();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void DrawInspectorGUI () {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
ISkeletonAnimation component = c as ISkeletonAnimation;
|
||||
if (!component.IsValid) {
|
||||
SpineEditorUtilities.ReinitializeComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
AddRootMotionComponentIfEnabled();
|
||||
|
||||
EditorGUILayout.PropertyField(updateTiming, UpdateTimingLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (threadedAnimation != null) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Threaded Animation", SpineEditorUtilities.Icons.subMeshRenderer), EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(threadedAnimation, ThreadedAnimationLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
mecanimSettingsFoldout = EditorGUILayout.Foldout(mecanimSettingsFoldout, "Mecanim Translator");
|
||||
if (mecanimSettingsFoldout) {
|
||||
EditorGUILayout.PropertyField(autoReset, AutoResetLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(useCustomMixMode, UseCustomMixModeLabel);
|
||||
|
||||
if (useCustomMixMode.hasMultipleDifferentValues || useCustomMixMode.boolValue == true) {
|
||||
DrawLayerSettings();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
enableScenePreview = EditorGUILayout.Toggle(new GUIContent("Scene Preview",
|
||||
"Preview the Animation Clip selected in the Animation window. Lock this SkeletonMecanim Inspector " +
|
||||
"window, open the Animation window and select the Animation Clip. Then in the Animation window " +
|
||||
"scrub through the timeline."),
|
||||
enableScenePreview, GUILayout.MaxWidth(150f));
|
||||
bool wasScenePreviewChanged = EditorGUI.EndChangeCheck();
|
||||
if (enableScenePreview)
|
||||
HandleAnimationPreview();
|
||||
else if (wasScenePreviewChanged) // just disabled, back to setup pose
|
||||
PreviewAnimationInScene(null, 0.0f);
|
||||
}
|
||||
|
||||
protected void AddRootMotionComponentIfEnabled () {
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
Animator animator = component.GetComponent<Animator>();
|
||||
if (animator != null && animator.applyRootMotion) {
|
||||
if (component.GetComponent<SkeletonMecanimRootMotion>() == null) {
|
||||
component.gameObject.AddComponent<SkeletonMecanimRootMotion>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void HandleAnimationPreview () {
|
||||
UnityEngine.Object animationWindow = AnimationWindowPreview.GetOpenAnimationWindow();
|
||||
|
||||
AnimationClip selectedClip = null;
|
||||
if (animationWindow != null) {
|
||||
selectedClip = AnimationWindowPreview.GetAnimationClip(animationWindow);
|
||||
}
|
||||
|
||||
if (selectedClip != null) {
|
||||
float time = AnimationWindowPreview.GetAnimationTime(animationWindow);
|
||||
PreviewAnimationInScene(selectedClip, time);
|
||||
} else // back to setup pose
|
||||
PreviewAnimationInScene(null, 0.0f);
|
||||
}
|
||||
|
||||
protected void PreviewAnimationInScene (AnimationClip clip, float time) {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
SkeletonRenderer skeletonRenderer = ((SkeletonMecanim)c).Renderer as SkeletonRenderer;
|
||||
if (skeletonRenderer == null) continue;
|
||||
Skeleton skeleton = skeletonRenderer.Skeleton;
|
||||
SkeletonData skeletonData = skeleton.Data;
|
||||
|
||||
skeleton.SetupPose();
|
||||
if (clip != null) {
|
||||
Spine.Animation animation = skeletonData.FindAnimation(clip.name);
|
||||
animation.Apply(skeleton, 0, time, false, null, 1.0f, true, false, false, false);
|
||||
}
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
protected void DrawLayerSettings () {
|
||||
string[] layerNames = GetLayerNames();
|
||||
float widthLayerColumn = 140;
|
||||
float widthMixColumn = 84;
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
Rect rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight);
|
||||
rect.width = widthLayerColumn;
|
||||
EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mecanim Layer"), EditorStyles.boldLabel);
|
||||
|
||||
int savedIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
rect.position += new Vector2(rect.width, 0);
|
||||
rect.width = widthMixColumn;
|
||||
EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mix Mode"), EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.indentLevel = savedIndent;
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.IndentScope()) {
|
||||
int layerCount = layerMixModes.arraySize;
|
||||
for (int i = 0; i < layerCount; ++i) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
string layerName = i < layerNames.Length ? layerNames[i] : ("Layer " + i);
|
||||
|
||||
Rect rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight);
|
||||
rect.width = widthLayerColumn;
|
||||
EditorGUI.PrefixLabel(rect, SpineInspectorUtility.TempContent(layerName));
|
||||
|
||||
int savedIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
SerializedProperty mixMode = layerMixModes.GetArrayElementAtIndex(i);
|
||||
rect.position += new Vector2(rect.width, 0);
|
||||
rect.width = widthMixColumn;
|
||||
EditorGUI.PropertyField(rect, mixMode, GUIContent.none);
|
||||
|
||||
EditorGUI.indentLevel = savedIndent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected string[] GetLayerNames () {
|
||||
int maxLayerCount = 0;
|
||||
int maxIndex = 0;
|
||||
for (int i = 0; i < targets.Length; ++i) {
|
||||
SkeletonMecanim skeletonMecanim = ((SkeletonMecanim)targets[i]);
|
||||
|
||||
Animator animator = skeletonMecanim.Translator.Animator;
|
||||
if (!Application.isPlaying) {
|
||||
if (animator != null && animator.isInitialized &&
|
||||
animator.isActiveAndEnabled && animator.runtimeAnimatorController != null) {
|
||||
// Note: Rebind is required to prevent warning "Animator is not playing an AnimatorController"
|
||||
// when saving and perhaps also with prefabs.
|
||||
animator.Rebind();
|
||||
}
|
||||
}
|
||||
|
||||
int count = skeletonMecanim.Translator.MecanimLayerCount;
|
||||
if (count > maxLayerCount) {
|
||||
maxLayerCount = count;
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
if (maxLayerCount == 0)
|
||||
return new string[0];
|
||||
SkeletonMecanim skeletonMecanimMaxLayers = ((SkeletonMecanim)targets[maxIndex]);
|
||||
return skeletonMecanimMaxLayers.Translator.MecanimLayerNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a9ca5213a3a4614c9a9f2e60909bc33
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonMecanimRootMotion))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonMecanimRootMotionInspector : SkeletonRootMotionBaseInspector {
|
||||
protected SerializedProperty mecanimLayerFlags;
|
||||
|
||||
protected GUIContent mecanimLayersLabel;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
mecanimLayerFlags = serializedObject.FindProperty("mecanimLayerFlags");
|
||||
|
||||
mecanimLayersLabel = new UnityEngine.GUIContent("Mecanim Layers", "Mecanim layers to apply root motion at. Defaults to the first Mecanim layer.");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
|
||||
base.MainPropertyFields();
|
||||
MecanimLayerMaskPropertyField();
|
||||
|
||||
base.OptionalPropertyFields();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected string[] GetLayerNames () {
|
||||
int maxLayerCount = 0;
|
||||
int maxIndex = 0;
|
||||
for (int i = 0; i < targets.Length; ++i) {
|
||||
SkeletonMecanim skeletonMecanim = ((SkeletonMecanimRootMotion)targets[i]).SkeletonMecanim;
|
||||
int count = skeletonMecanim.Translator.MecanimLayerCount;
|
||||
if (count > maxLayerCount) {
|
||||
maxLayerCount = count;
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
if (maxLayerCount == 0)
|
||||
return new string[0];
|
||||
SkeletonMecanim skeletonMecanimMaxLayers = ((SkeletonMecanimRootMotion)targets[maxIndex]).SkeletonMecanim;
|
||||
return skeletonMecanimMaxLayers.Translator.MecanimLayerNames;
|
||||
}
|
||||
|
||||
protected void MecanimLayerMaskPropertyField () {
|
||||
string[] layerNames = GetLayerNames();
|
||||
if (layerNames.Length > 0)
|
||||
mecanimLayerFlags.intValue = EditorGUILayout.MaskField(
|
||||
mecanimLayersLabel, mecanimLayerFlags.intValue, layerNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4613924c50d66cf458f0db803776dd2f
|
||||
timeCreated: 1593175106
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Contributed by: Lost Polygon
|
||||
|
||||
using Spine.Unity.Examples;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
// This script is not intended for use with code. See the readme.txt file in SkeletonRendererCustomMaterials folder to learn more.
|
||||
[CustomEditor(typeof(SkeletonRendererCustomMaterials))]
|
||||
public class SkeletonRendererCustomMaterialsInspector : UnityEditor.Editor {
|
||||
List<SkeletonRendererCustomMaterials.AtlasMaterialOverride> componentCustomMaterialOverrides, _customMaterialOverridesPrev;
|
||||
List<SkeletonRendererCustomMaterials.SlotMaterialOverride> componentCustomSlotMaterials, _customSlotMaterialsPrev;
|
||||
SkeletonRendererCustomMaterials component;
|
||||
|
||||
const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
MethodInfo RemoveCustomMaterialOverrides, RemoveCustomSlotMaterials, SetCustomMaterialOverrides, SetCustomSlotMaterials;
|
||||
|
||||
#region SkeletonRenderer context menu
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials")]
|
||||
static void AddSkeletonRendererCustomMaterials (MenuCommand menuCommand) {
|
||||
SkeletonRenderer skeletonRenderer = (SkeletonRenderer)menuCommand.context;
|
||||
SkeletonRendererCustomMaterials newComponent = skeletonRenderer.gameObject.AddComponent<SkeletonRendererCustomMaterials>();
|
||||
Undo.RegisterCreatedObjectUndo(newComponent, "Add Basic Serialized Custom Materials");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials", true)]
|
||||
static bool AddSkeletonRendererCustomMaterials_Validate (MenuCommand menuCommand) {
|
||||
SkeletonRenderer skeletonRenderer = (SkeletonRenderer)menuCommand.context;
|
||||
return (skeletonRenderer.GetComponent<SkeletonRendererCustomMaterials>() == null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
Type cm = typeof(SkeletonRendererCustomMaterials);
|
||||
RemoveCustomMaterialOverrides = cm.GetMethod("RemoveCustomMaterialOverrides", PrivateInstance);
|
||||
RemoveCustomSlotMaterials = cm.GetMethod("RemoveCustomSlotMaterials", PrivateInstance);
|
||||
SetCustomMaterialOverrides = cm.GetMethod("SetCustomMaterialOverrides", PrivateInstance);
|
||||
SetCustomSlotMaterials = cm.GetMethod("SetCustomSlotMaterials", PrivateInstance);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
component = (SkeletonRendererCustomMaterials)target;
|
||||
SkeletonRenderer skeletonRenderer = component.skeletonRenderer;
|
||||
|
||||
// Draw the default inspector
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (serializedObject.isEditingMultipleObjects)
|
||||
return;
|
||||
|
||||
if (componentCustomMaterialOverrides == null) {
|
||||
Type cm = typeof(SkeletonRendererCustomMaterials);
|
||||
componentCustomMaterialOverrides = cm.GetField("customMaterialOverrides", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.AtlasMaterialOverride>;
|
||||
componentCustomSlotMaterials = cm.GetField("customSlotMaterials", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.SlotMaterialOverride>;
|
||||
if (componentCustomMaterialOverrides == null || componentCustomSlotMaterials == null) {
|
||||
Debug.Log("Reflection failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill with current values at start
|
||||
if (_customMaterialOverridesPrev == null || _customSlotMaterialsPrev == null) {
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
}
|
||||
|
||||
// Compare new values with saved. If change is detected:
|
||||
// store new values, restore old values, remove overrides, restore new values, restore overrides.
|
||||
|
||||
// 1. Store new values
|
||||
var customMaterialOverridesNew = CopyList(componentCustomMaterialOverrides);
|
||||
var customSlotMaterialsNew = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
// Detect changes
|
||||
if (!_customMaterialOverridesPrev.SequenceEqual(customMaterialOverridesNew) ||
|
||||
!_customSlotMaterialsPrev.SequenceEqual(customSlotMaterialsNew)) {
|
||||
// 2. Restore old values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(_customMaterialOverridesPrev);
|
||||
componentCustomSlotMaterials.AddRange(_customSlotMaterialsPrev);
|
||||
|
||||
// 3. Remove overrides
|
||||
RemoveCustomMaterials();
|
||||
|
||||
// 4. Restore new values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(customMaterialOverridesNew);
|
||||
componentCustomSlotMaterials.AddRange(customSlotMaterialsNew);
|
||||
|
||||
// 5. Restore overrides
|
||||
SetCustomMaterials();
|
||||
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Clear and Reapply Changes", tooltip: "Removes all non-serialized overrides in the SkeletonRenderer and reapplies the overrides on this component."))) {
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.CustomMaterialOverride.Clear();
|
||||
skeletonRenderer.CustomSlotMaterials.Clear();
|
||||
RemoveCustomMaterials();
|
||||
SetCustomMaterials();
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomMaterials () {
|
||||
RemoveCustomMaterialOverrides.Invoke(component, null);
|
||||
RemoveCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
void SetCustomMaterials () {
|
||||
SetCustomMaterialOverrides.Invoke(component, null);
|
||||
SetCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
static List<T> CopyList<T> (List<T> list) {
|
||||
return list.GetRange(0, list.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e70f7f2a241d6d34aafd6a4a52a368d0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,129 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#else
|
||||
#define NO_PREFAB_MESH
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#define PER_MATERIAL_PROPERTY_BLOCKS
|
||||
#endif
|
||||
|
||||
#if UNITY_2017_1_OR_NEWER
|
||||
#define BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endif
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
#define HAS_ON_POSTPROCESS_PREFAB
|
||||
#endif
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonRenderer))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonRendererInspector : ISkeletonRendererInspector {
|
||||
|
||||
protected SerializedProperty singleSubmesh;
|
||||
protected SerializedProperty fixPrefabOverrideViaMeshFilter;
|
||||
protected SerializedProperty maskInteraction;
|
||||
protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
|
||||
|
||||
|
||||
protected GUIContent SingleSubmeshLabel;
|
||||
protected GUIContent FixPrefabOverrideViaMeshFilterLabel;
|
||||
protected GUIContent MaskInteractionLabel;
|
||||
|
||||
const string ReloadButtonString = "Reload";
|
||||
static GUILayoutOption reloadButtonWidth;
|
||||
static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } }
|
||||
static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } }
|
||||
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
|
||||
// Labels
|
||||
SingleSubmeshLabel = new GUIContent("Use Single Submesh", "Simplifies submesh generation by assuming you are only using one Material and need only one submesh. This is will disable multiple materials, render separation, and custom slot materials.");
|
||||
FixPrefabOverrideViaMeshFilterLabel = new GUIContent("Fix Prefab Overr. MeshFilter", "Fixes the prefab always being marked as changed (sets the MeshFilter's hide flags to DontSaveInEditor), but at the cost of references to the MeshFilter by other components being lost. For global settings see Edit - Preferences - Spine.");
|
||||
MaskInteractionLabel = new GUIContent("Mask Interaction", "SkeletonRenderer's interaction with a Sprite Mask.");
|
||||
|
||||
// Properties
|
||||
singleSubmesh = serializedObject.FindProperty("singleSubmesh");
|
||||
fixPrefabOverrideViaMeshFilter = serializedObject.FindProperty("fixPrefabOverrideViaMeshFilter");
|
||||
maskInteraction = serializedObject.FindProperty("maskInteraction");
|
||||
|
||||
SerializedObject renderersSerializedObject = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject); // Allows proper multi-edit behavior.
|
||||
sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(renderersSerializedObject);
|
||||
}
|
||||
|
||||
protected override void InspectorDrawPreparation () {
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
foreach (UnityEngine.Object t in targets)
|
||||
SpineMaskUtilities.EditorSetupSpriteMaskMaterials((SkeletonRenderer)t);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void FirstPropertyFields () {
|
||||
EditorGUILayout.Space();
|
||||
|
||||
SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
|
||||
if (maskInteraction != null) EditorGUILayout.PropertyField(maskInteraction, MaskInteractionLabel);
|
||||
}
|
||||
|
||||
protected override void VertexDataProperties () {
|
||||
EditorGUILayout.PropertyField(pmaVertexColors, PMAVertexColorsLabel);
|
||||
EditorGUILayout.PropertyField(tintBlack, TintBlackLabel);
|
||||
EditorGUILayout.PropertyField(addNormals, AddNormalsLabel);
|
||||
EditorGUILayout.PropertyField(calculateTangents, CalculateTangentsLabel);
|
||||
}
|
||||
|
||||
protected override void RendererProperties () {
|
||||
base.RendererProperties();
|
||||
|
||||
if (singleSubmesh != null) EditorGUILayout.PropertyField(singleSubmesh, SingleSubmeshLabel);
|
||||
|
||||
#if HAS_ON_POSTPROCESS_PREFAB
|
||||
if (fixPrefabOverrideViaMeshFilter != null) EditorGUILayout.PropertyField(fixPrefabOverrideViaMeshFilter, FixPrefabOverrideViaMeshFilterLabel);
|
||||
EditorGUILayout.Space();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void MaterialWarningsBox () {
|
||||
string errorMessage = null;
|
||||
if (SpineEditorUtilities.Preferences.componentMaterialWarning &&
|
||||
MaterialChecks.IsMaterialSetupProblematic((SkeletonRenderer)this.target, ref errorMessage)) {
|
||||
EditorGUILayout.HelpBox(errorMessage, MessageType.Error, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0fc5db9788bce4418ad3252d43faa8a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,148 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonRootMotionBase))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonRootMotionBaseInspector : UnityEditor.Editor {
|
||||
protected SerializedProperty rootMotionBoneName;
|
||||
protected SerializedProperty transformPositionX;
|
||||
protected SerializedProperty transformPositionY;
|
||||
protected SerializedProperty transformRotation;
|
||||
protected SerializedProperty rootMotionScaleX;
|
||||
protected SerializedProperty rootMotionScaleY;
|
||||
protected SerializedProperty rootMotionScaleRotation;
|
||||
protected SerializedProperty rootMotionTranslateXPerY;
|
||||
protected SerializedProperty rootMotionTranslateYPerX;
|
||||
protected SerializedProperty rigidBody2D;
|
||||
protected SerializedProperty applyRigidbody2DGravity;
|
||||
protected SerializedProperty rigidBody;
|
||||
|
||||
protected GUIContent rootMotionBoneNameLabel;
|
||||
protected GUIContent transformPositionXLabel;
|
||||
protected GUIContent transformPositionYLabel;
|
||||
protected GUIContent transformRotationLabel;
|
||||
protected GUIContent rootMotionScaleXLabel;
|
||||
protected GUIContent rootMotionScaleYLabel;
|
||||
protected GUIContent rootMotionScaleRotationLabel;
|
||||
protected GUIContent rootMotionTranslateXPerYLabel;
|
||||
protected GUIContent rootMotionTranslateYPerXLabel;
|
||||
protected GUIContent rigidBody2DLabel;
|
||||
protected GUIContent applyRigidbody2DGravityLabel;
|
||||
protected GUIContent rigidBodyLabel;
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
|
||||
rootMotionBoneName = serializedObject.FindProperty("rootMotionBoneName");
|
||||
transformPositionX = serializedObject.FindProperty("transformPositionX");
|
||||
transformPositionY = serializedObject.FindProperty("transformPositionY");
|
||||
transformRotation = serializedObject.FindProperty("transformRotation");
|
||||
rootMotionScaleX = serializedObject.FindProperty("rootMotionScaleX");
|
||||
rootMotionScaleY = serializedObject.FindProperty("rootMotionScaleY");
|
||||
rootMotionScaleRotation = serializedObject.FindProperty("rootMotionScaleRotation");
|
||||
rootMotionTranslateXPerY = serializedObject.FindProperty("rootMotionTranslateXPerY");
|
||||
rootMotionTranslateYPerX = serializedObject.FindProperty("rootMotionTranslateYPerX");
|
||||
rigidBody2D = serializedObject.FindProperty("rigidBody2D");
|
||||
applyRigidbody2DGravity = serializedObject.FindProperty("applyRigidbody2DGravity");
|
||||
rigidBody = serializedObject.FindProperty("rigidBody");
|
||||
|
||||
rootMotionBoneNameLabel = new UnityEngine.GUIContent("Root Motion Bone", "The bone to take the motion from.");
|
||||
transformPositionXLabel = new UnityEngine.GUIContent("X", "Root transform position (X)");
|
||||
transformPositionYLabel = new UnityEngine.GUIContent("Y", "Use the Y-movement of the bone.");
|
||||
transformRotationLabel = new UnityEngine.GUIContent("Rotation", "Use the rotation of the bone.");
|
||||
rootMotionScaleXLabel = new UnityEngine.GUIContent("Root Motion Scale (X)", "Scale applied to the horizontal root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
|
||||
rootMotionScaleYLabel = new UnityEngine.GUIContent("Root Motion Scale (Y)", "Scale applied to the vertical root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
|
||||
rootMotionScaleRotationLabel = new UnityEngine.GUIContent("Root Motion Scale (Rotation)", "Scale applied to the rotational root motion delta. Can be used for delta compensation to e.g. adjust an angled jump landing to the desired platform angle.");
|
||||
rootMotionTranslateXPerYLabel = new UnityEngine.GUIContent("Root Motion Translate (X)", "Added X translation per root motion Y delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal.");
|
||||
rootMotionTranslateYPerXLabel = new UnityEngine.GUIContent("Root Motion Translate (Y)", "Added Y translation per root motion X delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal.");
|
||||
rigidBody2DLabel = new UnityEngine.GUIContent("Rigidbody2D",
|
||||
"Optional Rigidbody2D: Assign a Rigidbody2D here if you want " +
|
||||
" to apply the root motion to the rigidbody instead of the Transform." +
|
||||
"\n\n" +
|
||||
"Note that animation and physics updates are not always in sync." +
|
||||
"Some jitter may result at certain framerates.");
|
||||
applyRigidbody2DGravityLabel = new UnityEngine.GUIContent("Apply Gravity",
|
||||
"Apply Rigidbody2D Gravity");
|
||||
rigidBodyLabel = new UnityEngine.GUIContent("Rigidbody",
|
||||
"Optional Rigidbody: Assign a Rigidbody here if you want " +
|
||||
" to apply the root motion to the rigidbody instead of the Transform." +
|
||||
"\n\n" +
|
||||
"Note that animation and physics updates are not always in sync." +
|
||||
"Some jitter may result at certain framerates.");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
MainPropertyFields();
|
||||
OptionalPropertyFields();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void MainPropertyFields () {
|
||||
EditorGUILayout.PropertyField(rootMotionBoneName, rootMotionBoneNameLabel);
|
||||
EditorGUILayout.PropertyField(transformPositionX, transformPositionXLabel);
|
||||
EditorGUILayout.PropertyField(transformPositionY, transformPositionYLabel);
|
||||
EditorGUILayout.PropertyField(transformRotation, transformRotationLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(rootMotionScaleX, rootMotionScaleXLabel);
|
||||
EditorGUILayout.PropertyField(rootMotionScaleY, rootMotionScaleYLabel);
|
||||
EditorGUILayout.PropertyField(rootMotionScaleRotation, rootMotionScaleRotationLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(rootMotionTranslateXPerY, rootMotionTranslateXPerYLabel);
|
||||
EditorGUILayout.PropertyField(rootMotionTranslateYPerX, rootMotionTranslateYPerXLabel);
|
||||
}
|
||||
|
||||
protected virtual void OptionalPropertyFields () {
|
||||
EditorGUILayout.PropertyField(rigidBody2D, rigidBody2DLabel);
|
||||
|
||||
if (rigidBody2D.objectReferenceValue != null || rigidBody2D.hasMultipleDifferentValues) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(applyRigidbody2DGravity, applyRigidbody2DGravityLabel);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(rigidBody, rigidBodyLabel);
|
||||
DisplayWarnings();
|
||||
}
|
||||
|
||||
protected void DisplayWarnings () {
|
||||
bool usesRigidbodyPhysics = rigidBody.objectReferenceValue != null || rigidBody2D.objectReferenceValue != null;
|
||||
if (usesRigidbodyPhysics) {
|
||||
SkeletonRootMotionBase rootMotionComponent = (SkeletonRootMotionBase)serializedObject.targetObject;
|
||||
ISkeletonAnimation skeletonComponent = rootMotionComponent ? rootMotionComponent.TargetSkeletonAnimationComponent : null;
|
||||
if (skeletonComponent != null && skeletonComponent.UpdateTiming == UpdateTiming.InUpdate) {
|
||||
string warningMessage = "Skeleton component uses 'Advanced - Animation Update' mode 'In Update'.\n" +
|
||||
"When using a Rigidbody, 'In FixedUpdate' is recommended instead.";
|
||||
EditorGUILayout.HelpBox(warningMessage, MessageType.Warning, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2cba83baf6afdf44a996e40017c6325
|
||||
timeCreated: 1593175106
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonRootMotion))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonRootMotionInspector : SkeletonRootMotionBaseInspector {
|
||||
protected SerializedProperty animationTrackFlags;
|
||||
protected GUIContent animationTrackFlagsLabel;
|
||||
|
||||
string[] TrackNames;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
|
||||
animationTrackFlags = serializedObject.FindProperty("animationTrackFlags");
|
||||
animationTrackFlagsLabel = new UnityEngine.GUIContent("Animation Tracks",
|
||||
"Animation tracks to apply root motion at. Defaults to the first" +
|
||||
" animation track (index 0).");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
|
||||
base.MainPropertyFields();
|
||||
AnimationTracksPropertyField();
|
||||
|
||||
base.OptionalPropertyFields();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected void AnimationTracksPropertyField () {
|
||||
|
||||
if (TrackNames == null) {
|
||||
InitTrackNames();
|
||||
|
||||
}
|
||||
|
||||
animationTrackFlags.intValue = EditorGUILayout.MaskField(
|
||||
animationTrackFlagsLabel, animationTrackFlags.intValue, TrackNames);
|
||||
}
|
||||
|
||||
protected void InitTrackNames () {
|
||||
int numEntries = 32;
|
||||
TrackNames = new string[numEntries];
|
||||
for (int i = 0; i < numEntries; ++i) {
|
||||
TrackNames[i] = string.Format("Track {0}", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4836100aed984c4a9af11d39c63cb6b
|
||||
timeCreated: 1593183609
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonSubmeshGraphic))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonGraphicSubmeshInspector : UnityEditor.Editor {
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
EditorGUILayout.HelpBox("This component is manged by the parent SkeletonGraphic component.", MessageType.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bc35530b2335ef4da1dafa6214b6ccd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,606 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Contributed by: Mitch Thompson
|
||||
|
||||
#if UNITY_2019_2_OR_NEWER
|
||||
#define HINGE_JOINT_NEW_BEHAVIOUR
|
||||
#endif
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
#define USE_RIGIDBODY_BODY_TYPE
|
||||
#endif
|
||||
|
||||
using Spine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonUtilityBone)), CanEditMultipleObjects]
|
||||
public class SkeletonUtilityBoneInspector : UnityEditor.Editor {
|
||||
SerializedProperty mode, boneName, zPosition, position, rotation, scale, overrideAlpha, hierarchy, parentReference;
|
||||
GUIContent hierarchyLabel;
|
||||
|
||||
//multi selected flags
|
||||
bool containsFollows, containsOverrides, multiObject;
|
||||
|
||||
//single selected helpers
|
||||
SkeletonUtilityBone utilityBone;
|
||||
SkeletonUtility skeletonUtility;
|
||||
bool canCreateHingeChain = false;
|
||||
|
||||
Dictionary<Slot, List<BoundingBoxAttachment>> boundingBoxTable = new Dictionary<Slot, List<BoundingBoxAttachment>>();
|
||||
|
||||
void OnEnable () {
|
||||
mode = this.serializedObject.FindProperty("boneMode");
|
||||
boneName = this.serializedObject.FindProperty("boneName");
|
||||
zPosition = this.serializedObject.FindProperty("zPosition");
|
||||
position = this.serializedObject.FindProperty("position");
|
||||
rotation = this.serializedObject.FindProperty("rotation");
|
||||
scale = this.serializedObject.FindProperty("scale");
|
||||
overrideAlpha = this.serializedObject.FindProperty("overrideAlpha");
|
||||
hierarchy = this.serializedObject.FindProperty("hierarchy");
|
||||
hierarchyLabel = new GUIContent("Skeleton Utility Parent");
|
||||
parentReference = this.serializedObject.FindProperty("parentReference");
|
||||
|
||||
utilityBone = (SkeletonUtilityBone)target;
|
||||
skeletonUtility = utilityBone.hierarchy;
|
||||
EvaluateFlags();
|
||||
|
||||
if (!utilityBone.valid && skeletonUtility != null) {
|
||||
if (skeletonUtility.skeletonRenderer != null)
|
||||
skeletonUtility.skeletonRenderer.Initialize(false);
|
||||
if (skeletonUtility.skeletonGraphic != null)
|
||||
skeletonUtility.skeletonGraphic.Initialize(false);
|
||||
}
|
||||
|
||||
canCreateHingeChain = CanCreateHingeChain();
|
||||
boundingBoxTable.Clear();
|
||||
|
||||
if (multiObject) return;
|
||||
if (utilityBone.bone == null) return;
|
||||
|
||||
Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton;
|
||||
int slotCount = skeleton.Slots.Count;
|
||||
Skin skin = skeleton.Skin;
|
||||
if (skeleton.Skin == null)
|
||||
skin = skeleton.Data.DefaultSkin;
|
||||
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
Slot slot = skeletonUtility.Skeleton.Slots.Items[i];
|
||||
if (slot.Bone == utilityBone.bone) {
|
||||
List<Skin.SkinEntry> slotAttachments = new List<Skin.SkinEntry>();
|
||||
int slotIndex = skeleton.Data.FindSlot(slot.Data.Name).Index;
|
||||
skin.GetAttachments(slotIndex, slotAttachments);
|
||||
|
||||
List<BoundingBoxAttachment> boundingBoxes = new List<BoundingBoxAttachment>();
|
||||
foreach (Skin.SkinEntry entry in slotAttachments) {
|
||||
BoundingBoxAttachment boundingBoxAttachment = entry.Attachment as BoundingBoxAttachment;
|
||||
if (boundingBoxAttachment != null)
|
||||
boundingBoxes.Add(boundingBoxAttachment);
|
||||
}
|
||||
|
||||
if (boundingBoxes.Count > 0)
|
||||
boundingBoxTable.Add(slot, boundingBoxes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EvaluateFlags () {
|
||||
if (Selection.objects.Length == 1) {
|
||||
containsFollows = utilityBone.mode == SkeletonUtilityBone.Mode.Follow;
|
||||
containsOverrides = utilityBone.mode == SkeletonUtilityBone.Mode.Override;
|
||||
} else {
|
||||
int boneCount = 0;
|
||||
foreach (Object o in Selection.objects) {
|
||||
GameObject go = o as GameObject;
|
||||
if (go != null) {
|
||||
SkeletonUtilityBone sub = go.GetComponent<SkeletonUtilityBone>();
|
||||
if (sub != null) {
|
||||
boneCount++;
|
||||
containsFollows |= (sub.mode == SkeletonUtilityBone.Mode.Follow);
|
||||
containsOverrides |= (sub.mode == SkeletonUtilityBone.Mode.Override);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
multiObject |= (boneCount > 1);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(mode);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
containsOverrides = mode.enumValueIndex == 1;
|
||||
containsFollows = mode.enumValueIndex == 0;
|
||||
if (skeletonUtility != null)
|
||||
skeletonUtility.OnUtilityBoneChanged();
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject)) {
|
||||
string str = boneName.stringValue;
|
||||
if (str == "")
|
||||
str = "<None>";
|
||||
if (multiObject)
|
||||
str = "<Multiple>";
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PrefixLabel("Bone");
|
||||
if (GUILayout.Button(str, EditorStyles.popup)) {
|
||||
BoneSelectorContextMenu(str, ((SkeletonUtilityBone)target).hierarchy.Skeleton.Bones, "<None>", TargetBoneSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isOverrideMode = mode.enumValueIndex == 1;
|
||||
using (new EditorGUI.DisabledGroupScope(isOverrideMode))
|
||||
EditorGUILayout.PropertyField(zPosition);
|
||||
EditorGUILayout.PropertyField(position, new GUIContent("XY Position"));
|
||||
EditorGUILayout.PropertyField(rotation);
|
||||
EditorGUILayout.PropertyField(scale);
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(containsFollows)) {
|
||||
EditorGUILayout.PropertyField(overrideAlpha);
|
||||
EditorGUILayout.PropertyField(parentReference);
|
||||
EditorGUILayout.PropertyField(hierarchy, hierarchyLabel);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.Space();
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || utilityBone.bone.Children.Count == 0)) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Child Bone", Icons.bone), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
BoneSelectorContextMenu("", utilityBone.bone.Children, "<Recursively>", SpawnChildBoneSelected);
|
||||
}
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || containsOverrides)) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Override", Icons.poseBones), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
SpawnOverride();
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.Space();
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || !canCreateHingeChain)) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Create 3D Hinge Chain", Icons.hingeChain), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
CreateHingeChain();
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Create 2D Hinge Chain", Icons.hingeChain), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
CreateHingeChain2D();
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || boundingBoxTable.Count == 0)) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Bounding Boxes", Icons.boundingBox), EditorStyles.boldLabel);
|
||||
|
||||
foreach (KeyValuePair<Slot, List<BoundingBoxAttachment>> entry in boundingBoxTable) {
|
||||
Slot slot = entry.Key;
|
||||
List<BoundingBoxAttachment> boundingBoxes = entry.Value;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField(slot.Data.Name);
|
||||
EditorGUI.indentLevel++;
|
||||
{
|
||||
Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton;
|
||||
foreach (BoundingBoxAttachment box in boundingBoxes) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
GUILayout.Space(30);
|
||||
string buttonLabel = box.IsWeighted() ? box.Name + " (!)" : box.Name;
|
||||
if (GUILayout.Button(buttonLabel, GUILayout.Width(200))) {
|
||||
skeleton.UpdateWorldTransform(Physics.Update);
|
||||
Transform bbTransform = utilityBone.transform.Find("[BoundingBox]" + box.Name); // Use FindChild in older versions of Unity.
|
||||
if (bbTransform != null) {
|
||||
PolygonCollider2D originalCollider = bbTransform.GetComponent<PolygonCollider2D>();
|
||||
if (originalCollider != null)
|
||||
SkeletonUtility.SetColliderPointsLocal(originalCollider, skeleton, slot, box);
|
||||
else
|
||||
SkeletonUtility.AddBoundingBoxAsComponent(box, skeleton, slot, bbTransform.gameObject);
|
||||
} else {
|
||||
PolygonCollider2D newPolygonCollider = SkeletonUtility.AddBoundingBoxGameObject(null, box, skeleton, slot, utilityBone.transform);
|
||||
bbTransform = newPolygonCollider.transform;
|
||||
}
|
||||
EditorGUIUtility.PingObject(bbTransform);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
BoneFollowerInspector.RecommendRigidbodyButton(utilityBone);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
static void BoneSelectorContextMenu (string current, ExposedList<Bone> bones, string topValue, GenericMenu.MenuFunction2 callback) {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
if (topValue != "")
|
||||
menu.AddItem(new GUIContent(topValue), current == topValue, callback, null);
|
||||
|
||||
for (int i = 0; i < bones.Count; i++)
|
||||
menu.AddItem(new GUIContent(bones.Items[i].Data.Name), bones.Items[i].Data.Name == current, callback, bones.Items[i]);
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
void TargetBoneSelected (object obj) {
|
||||
if (obj == null) {
|
||||
boneName.stringValue = "";
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
} else {
|
||||
Bone bone = (Bone)obj;
|
||||
boneName.stringValue = bone.Data.Name;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
utilityBone.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnChildBoneSelected (object obj) {
|
||||
if (obj == null) {
|
||||
// Add recursively
|
||||
foreach (Bone bone in utilityBone.bone.Children) {
|
||||
GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
|
||||
SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>();
|
||||
foreach (SkeletonUtilityBone utilBone in newUtilityBones)
|
||||
SkeletonUtilityInspector.AttachIcon(utilBone);
|
||||
}
|
||||
} else {
|
||||
Bone bone = (Bone)obj;
|
||||
GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
|
||||
SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnOverride () {
|
||||
GameObject go = skeletonUtility.SpawnBone(utilityBone.bone, utilityBone.transform.parent, SkeletonUtilityBone.Mode.Override, utilityBone.position, utilityBone.rotation, utilityBone.scale);
|
||||
go.name = go.name + " [Override]";
|
||||
SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
}
|
||||
|
||||
bool CanCreateHingeChain () {
|
||||
if (utilityBone == null)
|
||||
return false;
|
||||
if (utilityBone.GetComponent<Rigidbody>() != null || utilityBone.GetComponent<Rigidbody2D>() != null)
|
||||
return false;
|
||||
if (utilityBone.bone != null && utilityBone.bone.Children.Count == 0)
|
||||
return false;
|
||||
|
||||
Rigidbody[] rigidbodies = utilityBone.GetComponentsInChildren<Rigidbody>();
|
||||
Rigidbody2D[] rigidbodies2D = utilityBone.GetComponentsInChildren<Rigidbody2D>();
|
||||
return rigidbodies.Length <= 0 && rigidbodies2D.Length <= 0;
|
||||
}
|
||||
|
||||
void CreateHingeChain2D () {
|
||||
SkeletonUtilityBone kinematicParentUtilityBone = utilityBone.transform.parent.GetComponent<SkeletonUtilityBone>();
|
||||
if (kinematicParentUtilityBone == null) {
|
||||
UnityEditor.EditorUtility.DisplayDialog("No parent SkeletonUtilityBone found!", "Please select the first physically moving chain node, having a parent GameObject with a SkeletonUtilityBone component attached.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
Undo.SetCurrentGroupName("Create 2D Hinge Chain");
|
||||
|
||||
float mass = 10;
|
||||
const float rotationLimit = 20.0f;
|
||||
|
||||
SetSkeletonUtilityToFlipByRotation();
|
||||
|
||||
Undo.RecordObject(kinematicParentUtilityBone, "Create 2D Hinge Chain");
|
||||
kinematicParentUtilityBone.mode = SkeletonUtilityBone.Mode.Follow;
|
||||
kinematicParentUtilityBone.position = kinematicParentUtilityBone.rotation = kinematicParentUtilityBone.scale = kinematicParentUtilityBone.zPosition = true;
|
||||
|
||||
GameObject commonParentObject = new GameObject(skeletonUtility.name + " HingeChain Parent " + utilityBone.name);
|
||||
Undo.RegisterCreatedObjectUndo(commonParentObject, "Create 2D Hinge Chain");
|
||||
ActivateBasedOnFlipDirection commonParentActivateOnFlip = commonParentObject.AddComponent<ActivateBasedOnFlipDirection>();
|
||||
commonParentActivateOnFlip.skeletonRenderer = skeletonUtility.skeletonRenderer;
|
||||
commonParentActivateOnFlip.skeletonGraphic = skeletonUtility.skeletonGraphic;
|
||||
|
||||
// HingeChain Parent
|
||||
// Needs to be on top hierarchy level (not attached to the moving skeleton at least) for physics to apply proper momentum.
|
||||
GameObject normalChainParentObject = new GameObject("HingeChain");
|
||||
normalChainParentObject.transform.SetParent(commonParentObject.transform);
|
||||
commonParentActivateOnFlip.activeOnNormalX = normalChainParentObject;
|
||||
|
||||
//FollowSkeletonUtilityRootRotation followRotationComponent = normalChainParentObject.AddComponent<FollowSkeletonUtilityRootRotation>();
|
||||
//followRotationComponent.reference = skeletonUtility.boneRoot;
|
||||
|
||||
// Follower Kinematic Rigidbody
|
||||
GameObject rootFollowerKinematic = new GameObject(kinematicParentUtilityBone.name + " Follower");
|
||||
rootFollowerKinematic.transform.parent = normalChainParentObject.transform;
|
||||
Rigidbody2D followerRigidbody = rootFollowerKinematic.AddComponent<Rigidbody2D>();
|
||||
followerRigidbody.mass = mass;
|
||||
#if USE_RIGIDBODY_BODY_TYPE
|
||||
followerRigidbody.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
followerRigidbody.isKinematic = true;
|
||||
#endif
|
||||
rootFollowerKinematic.AddComponent<FollowLocationRigidbody2D>().reference = kinematicParentUtilityBone.transform;
|
||||
rootFollowerKinematic.transform.position = kinematicParentUtilityBone.transform.position;
|
||||
rootFollowerKinematic.transform.rotation = kinematicParentUtilityBone.transform.rotation;
|
||||
|
||||
CreateHingeChain2D(utilityBone, mass, rotationLimit, normalChainParentObject.transform,
|
||||
rootFollowerKinematic.transform, kinematicParentUtilityBone.transform);
|
||||
|
||||
Duplicate2DHierarchyForFlippedChains(normalChainParentObject, commonParentActivateOnFlip, skeletonUtility.transform, rotationLimit);
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
UnityEditor.Selection.activeGameObject = commonParentObject;
|
||||
}
|
||||
|
||||
void CreateHingeChain2D (SkeletonUtilityBone bone, float mass, float rotationLimit, Transform groupObject,
|
||||
Transform jointParent, Transform utilityParent) {
|
||||
|
||||
mass *= 0.75f;
|
||||
Undo.RecordObject(bone, "Create 2D Hinge Chain");
|
||||
bone.parentReference = utilityParent;
|
||||
// Note: we need a flat hierarchy of all Joint objects in Unity.
|
||||
Undo.SetTransformParent(bone.transform, groupObject, "Create 2D Hinge Chain");
|
||||
AttachRigidbodyAndCollider2D(bone);
|
||||
bone.mode = SkeletonUtilityBone.Mode.Override;
|
||||
bone.scale = bone.position = bone.zPosition = false;
|
||||
|
||||
HingeJoint2D joint = Undo.AddComponent<HingeJoint2D>(bone.gameObject);
|
||||
joint.connectedBody = jointParent.GetComponent<Rigidbody2D>();
|
||||
joint.useLimits = true;
|
||||
ApplyJoint2DAngleLimits(joint, rotationLimit, jointParent, bone.transform);
|
||||
bone.GetComponent<Rigidbody2D>().mass = mass;
|
||||
|
||||
Transform parent = bone.transform;
|
||||
List<SkeletonUtilityBone> children = new List<SkeletonUtilityBone>();
|
||||
int utilityChildCount = 0;
|
||||
for (int i = 0; i < parent.childCount; ++i) {
|
||||
var childUtilityBone = parent.GetChild(i).GetComponent<SkeletonUtilityBone>();
|
||||
if (childUtilityBone != null)
|
||||
children.Add(childUtilityBone);
|
||||
}
|
||||
mass /= Mathf.Max(1.0f, utilityChildCount);
|
||||
|
||||
for (int i = 0; i < children.Count; ++i) {
|
||||
SkeletonUtilityBone childBone = children[i];
|
||||
if (childBone == null) continue;
|
||||
CreateHingeChain2D(childBone, mass, rotationLimit, groupObject, parent, parent);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyJoint2DAngleLimits (HingeJoint2D joint, float rotationLimit, Transform parentBone, Transform bone) {
|
||||
#if HINGE_JOINT_NEW_BEHAVIOUR
|
||||
float referenceAngle = (parentBone.eulerAngles.z - bone.eulerAngles.z + 360f) % 360f;
|
||||
float minAngle = referenceAngle - rotationLimit;
|
||||
float maxAngle = referenceAngle + rotationLimit;
|
||||
if (maxAngle > 270f) {
|
||||
minAngle -= 360f;
|
||||
maxAngle -= 360f;
|
||||
}
|
||||
if (minAngle < -90f) {
|
||||
minAngle += 360f;
|
||||
maxAngle += 360f;
|
||||
}
|
||||
#else
|
||||
float minAngle = -rotationLimit;
|
||||
float maxAngle = rotationLimit;
|
||||
#endif
|
||||
joint.limits = new JointAngleLimits2D {
|
||||
min = minAngle,
|
||||
max = maxAngle
|
||||
};
|
||||
}
|
||||
|
||||
void Duplicate2DHierarchyForFlippedChains (GameObject normalChainParentObject, ActivateBasedOnFlipDirection commonParentActivateOnFlip,
|
||||
Transform skeletonUtilityRoot, float rotationLimit) {
|
||||
|
||||
GameObject mirroredChain = GameObject.Instantiate(normalChainParentObject, normalChainParentObject.transform.position,
|
||||
normalChainParentObject.transform.rotation, commonParentActivateOnFlip.transform);
|
||||
mirroredChain.name = normalChainParentObject.name + " FlippedX";
|
||||
|
||||
commonParentActivateOnFlip.activeOnFlippedX = mirroredChain;
|
||||
|
||||
FollowLocationRigidbody2D followerKinematicObject = mirroredChain.GetComponentInChildren<FollowLocationRigidbody2D>();
|
||||
followerKinematicObject.followFlippedX = true;
|
||||
FlipBone2DHorizontal(followerKinematicObject.transform, skeletonUtilityRoot);
|
||||
|
||||
HingeJoint2D[] childBoneJoints = mirroredChain.GetComponentsInChildren<HingeJoint2D>();
|
||||
Transform prevRotatedChild = null;
|
||||
Transform parentTransformForAngles = followerKinematicObject.transform;
|
||||
for (int i = 0; i < childBoneJoints.Length; ++i) {
|
||||
HingeJoint2D joint = childBoneJoints[i];
|
||||
FlipBone2DHorizontal(joint.transform, skeletonUtilityRoot);
|
||||
ApplyJoint2DAngleLimits(joint, rotationLimit, parentTransformForAngles, joint.transform);
|
||||
|
||||
GameObject rotatedChild = GameObject.Instantiate(joint.gameObject, joint.transform, true);
|
||||
rotatedChild.name = joint.name + " rotated";
|
||||
Vector3 rotationEulerAngles = rotatedChild.transform.localEulerAngles;
|
||||
rotationEulerAngles.x = 180;
|
||||
rotatedChild.transform.localEulerAngles = rotationEulerAngles;
|
||||
DestroyImmediate(rotatedChild.GetComponent<HingeJoint2D>());
|
||||
DestroyImmediate(rotatedChild.GetComponent<BoxCollider2D>());
|
||||
DestroyImmediate(rotatedChild.GetComponent<Rigidbody2D>());
|
||||
|
||||
DestroyImmediate(joint.gameObject.GetComponent<SkeletonUtilityBone>());
|
||||
|
||||
if (i > 0) {
|
||||
SkeletonUtilityBone utilityBone = rotatedChild.GetComponent<SkeletonUtilityBone>();
|
||||
utilityBone.parentReference = prevRotatedChild;
|
||||
}
|
||||
prevRotatedChild = rotatedChild.transform;
|
||||
parentTransformForAngles = joint.transform;
|
||||
}
|
||||
|
||||
mirroredChain.SetActive(false);
|
||||
}
|
||||
|
||||
void FlipBone2DHorizontal (Transform bone, Transform mirrorPosition) {
|
||||
Vector3 position = bone.position;
|
||||
position.x = 2 * mirrorPosition.position.x - position.x; // = mirrorPosition + (mirrorPosition - bone.position)
|
||||
bone.position = position;
|
||||
|
||||
Vector3 boneZ = bone.forward;
|
||||
Vector3 boneX = bone.right;
|
||||
boneX.x *= -1;
|
||||
|
||||
bone.rotation = Quaternion.LookRotation(boneZ, Vector3.Cross(boneZ, boneX));
|
||||
}
|
||||
|
||||
void CreateHingeChain () {
|
||||
SkeletonUtilityBone kinematicParentUtilityBone = utilityBone.transform.parent.GetComponent<SkeletonUtilityBone>();
|
||||
if (kinematicParentUtilityBone == null) {
|
||||
UnityEditor.EditorUtility.DisplayDialog("No parent SkeletonUtilityBone found!", "Please select the first physically moving chain node, having a parent GameObject with a SkeletonUtilityBone component attached.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
Undo.SetCurrentGroupName("Create 3D Hinge Chain");
|
||||
|
||||
float mass = 10;
|
||||
const float rotationLimit = 20.0f;
|
||||
|
||||
SetSkeletonUtilityToFlipByRotation();
|
||||
|
||||
Undo.RecordObject(kinematicParentUtilityBone, "Create 3D Hinge Chain");
|
||||
kinematicParentUtilityBone.mode = SkeletonUtilityBone.Mode.Follow;
|
||||
kinematicParentUtilityBone.position = kinematicParentUtilityBone.rotation = kinematicParentUtilityBone.scale = kinematicParentUtilityBone.zPosition = true;
|
||||
|
||||
// HingeChain Parent
|
||||
// Needs to be on top hierarchy level (not attached to the moving skeleton at least) for physics to apply proper momentum.
|
||||
GameObject chainParentObject = new GameObject(skeletonUtility.name + " HingeChain Parent " + utilityBone.name);
|
||||
Undo.RegisterCreatedObjectUndo(chainParentObject, "Create 3D Hinge Chain");
|
||||
FollowSkeletonUtilityRootRotation followRotationComponent = chainParentObject.AddComponent<FollowSkeletonUtilityRootRotation>();
|
||||
followRotationComponent.reference = skeletonUtility.boneRoot;
|
||||
|
||||
// Follower Kinematic Rigidbody
|
||||
GameObject rootFollowerKinematic = new GameObject(kinematicParentUtilityBone.name + " Follower");
|
||||
rootFollowerKinematic.transform.parent = chainParentObject.transform;
|
||||
Rigidbody followerRigidbody = rootFollowerKinematic.AddComponent<Rigidbody>();
|
||||
followerRigidbody.mass = mass;
|
||||
followerRigidbody.isKinematic = true;
|
||||
rootFollowerKinematic.AddComponent<FollowLocationRigidbody>().reference = kinematicParentUtilityBone.transform;
|
||||
rootFollowerKinematic.transform.position = kinematicParentUtilityBone.transform.position;
|
||||
rootFollowerKinematic.transform.rotation = kinematicParentUtilityBone.transform.rotation;
|
||||
|
||||
CreateHingeChain(utilityBone, mass, rotationLimit, chainParentObject.transform, rootFollowerKinematic.transform);
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
UnityEditor.Selection.activeGameObject = chainParentObject;
|
||||
}
|
||||
|
||||
void CreateHingeChain (SkeletonUtilityBone bone, float mass, float rotationLimit, Transform groupObject,
|
||||
Transform jointParent) {
|
||||
|
||||
mass *= 0.75f;
|
||||
|
||||
Undo.RecordObject(bone, "Create 3D Hinge Chain");
|
||||
bone.parentReference = jointParent;
|
||||
// Note: we need a flat hierarchy of all Joint objects in Unity.
|
||||
Undo.SetTransformParent(bone.transform, groupObject.transform, "Create 3D Hinge Chain");
|
||||
AttachRigidbodyAndCollider(bone);
|
||||
bone.mode = SkeletonUtilityBone.Mode.Override;
|
||||
|
||||
HingeJoint joint = Undo.AddComponent<HingeJoint>(bone.gameObject);
|
||||
joint.axis = Vector3.forward;
|
||||
joint.connectedBody = jointParent.GetComponent<Rigidbody>();
|
||||
joint.useLimits = true;
|
||||
joint.limits = new JointLimits {
|
||||
min = -rotationLimit,
|
||||
max = rotationLimit
|
||||
};
|
||||
bone.GetComponent<Rigidbody>().mass = mass;
|
||||
|
||||
Transform parent = bone.transform;
|
||||
List<SkeletonUtilityBone> children = new List<SkeletonUtilityBone>();
|
||||
int utilityChildCount = 0;
|
||||
for (int i = 0; i < parent.childCount; ++i) {
|
||||
var childUtilityBone = parent.GetChild(i).GetComponent<SkeletonUtilityBone>();
|
||||
if (childUtilityBone != null)
|
||||
children.Add(childUtilityBone);
|
||||
}
|
||||
mass /= Mathf.Max(1.0f, utilityChildCount);
|
||||
|
||||
for (int i = 0; i < children.Count; ++i) {
|
||||
SkeletonUtilityBone childBone = children[i];
|
||||
if (childBone == null) continue;
|
||||
CreateHingeChain(childBone, mass, rotationLimit, groupObject, parent);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSkeletonUtilityToFlipByRotation () {
|
||||
if (!skeletonUtility.flipBy180DegreeRotation) {
|
||||
Undo.RecordObject(skeletonUtility, "Create Hinge Chain");
|
||||
skeletonUtility.flipBy180DegreeRotation = true;
|
||||
Debug.Log("Set SkeletonUtility " + skeletonUtility.name + " to flip by rotation instead of negative scale (required).", skeletonUtility);
|
||||
}
|
||||
}
|
||||
|
||||
static void AttachRigidbodyAndCollider (SkeletonUtilityBone utilBone, bool enableCollider = false) {
|
||||
if (utilBone.GetComponent<Collider>() == null) {
|
||||
if (utilBone.bone.Data.Length == 0) {
|
||||
SphereCollider sphere = Undo.AddComponent<SphereCollider>(utilBone.gameObject);
|
||||
sphere.radius = 0.1f;
|
||||
sphere.enabled = enableCollider;
|
||||
} else {
|
||||
float length = utilBone.bone.Data.Length;
|
||||
BoxCollider box = Undo.AddComponent<BoxCollider>(utilBone.gameObject);
|
||||
box.size = new Vector3(length, length / 3f, 0.2f);
|
||||
box.center = new Vector3(length / 2f, 0, 0);
|
||||
box.enabled = enableCollider;
|
||||
}
|
||||
}
|
||||
Undo.AddComponent<Rigidbody>(utilBone.gameObject);
|
||||
}
|
||||
|
||||
static void AttachRigidbodyAndCollider2D (SkeletonUtilityBone utilBone, bool enableCollider = false) {
|
||||
if (utilBone.GetComponent<Collider2D>() == null) {
|
||||
if (utilBone.bone.Data.Length == 0) {
|
||||
CircleCollider2D sphere = Undo.AddComponent<CircleCollider2D>(utilBone.gameObject);
|
||||
sphere.radius = 0.1f;
|
||||
sphere.enabled = enableCollider;
|
||||
} else {
|
||||
float length = utilBone.bone.Data.Length;
|
||||
BoxCollider2D box = Undo.AddComponent<BoxCollider2D>(utilBone.gameObject);
|
||||
box.size = new Vector3(length, length / 3f, 0.2f);
|
||||
box.offset = new Vector3(length / 2f, 0, 0);
|
||||
box.enabled = enableCollider;
|
||||
}
|
||||
}
|
||||
Undo.AddComponent<Rigidbody2D>(utilBone.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3ae20b4bcc31f645afd6f5b64f82473
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,197 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#define PUBLIC_SET_ICON_FOR_OBJECT
|
||||
#endif
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonUtility))]
|
||||
public class SkeletonUtilityInspector : UnityEditor.Editor {
|
||||
|
||||
SkeletonUtility skeletonUtility;
|
||||
Skeleton skeleton;
|
||||
SkeletonRenderer skeletonRenderer;
|
||||
SkeletonGraphic skeletonGraphic;
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
bool isPrefab;
|
||||
#endif
|
||||
|
||||
readonly GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton);
|
||||
|
||||
void OnEnable () {
|
||||
skeletonUtility = (SkeletonUtility)target;
|
||||
skeletonRenderer = skeletonUtility.skeletonRenderer;
|
||||
skeletonGraphic = skeletonUtility.skeletonGraphic;
|
||||
skeleton = skeletonUtility.Skeleton;
|
||||
|
||||
if (skeleton == null) {
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.Initialize(false);
|
||||
skeletonRenderer.LateUpdate();
|
||||
} else if (skeletonGraphic != null) {
|
||||
skeletonGraphic.Initialize(false);
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
skeleton = skeletonUtility.Skeleton;
|
||||
}
|
||||
|
||||
if ((skeletonRenderer != null && !skeletonRenderer.valid) ||
|
||||
(skeletonGraphic != null && !skeletonGraphic.IsValid)) return;
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
isPrefab |= PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
if (isPrefab) {
|
||||
GUILayout.Label(new GUIContent("Cannot edit Prefabs", Icons.warning));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
if ((skeletonRenderer != null && !skeletonRenderer.valid) ||
|
||||
(skeletonGraphic != null && !skeletonGraphic.IsValid)) {
|
||||
GUILayout.Label(new GUIContent("Spine Component invalid. Check SkeletonData asset.", Icons.warning));
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("flipBy180DegreeRotation"), SpineInspectorUtility.TempContent("Flip by Rotation", null,
|
||||
"If true, Skeleton.ScaleX and Skeleton.ScaleY are followed " +
|
||||
"by 180 degree rotation. If false, negative Transform scale is used. " +
|
||||
"Note that using negative scale is consistent with previous behaviour (hence the default), " +
|
||||
"however causes serious problems with rigidbodies and physics. Therefore, it is recommended to " +
|
||||
"enable this parameter where possible. When creating hinge chains for a chain of skeleton bones " +
|
||||
"via SkeletonUtilityBone, it is mandatory to have this parameter enabled."));
|
||||
|
||||
bool hasRootBone = skeletonUtility.boneRoot != null;
|
||||
|
||||
if (!hasRootBone)
|
||||
EditorGUILayout.HelpBox("No hierarchy found. Use Spawn Hierarchy to generate GameObjects for bones.", MessageType.Info);
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(hasRootBone)) {
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpawnHierarchyButtonLabel))
|
||||
SpawnHierarchyContextMenu();
|
||||
}
|
||||
|
||||
if (hasRootBone) {
|
||||
if (SpineInspectorUtility.CenteredButton(new GUIContent("Remove Hierarchy"))) {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Remove Hierarchy");
|
||||
Undo.DestroyObjectImmediate(skeletonUtility.boneRoot.gameObject);
|
||||
skeletonUtility.boneRoot = null;
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void SpawnHierarchyContextMenu () {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(new GUIContent("Follow all bones"), false, SpawnFollowHierarchy);
|
||||
menu.AddItem(new GUIContent("Follow (Root Only)"), false, SpawnFollowHierarchyRootOnly);
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(new GUIContent("Override all bones"), false, SpawnOverrideHierarchy);
|
||||
menu.AddItem(new GUIContent("Override (Root Only)"), false, SpawnOverrideHierarchyRootOnly);
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
public static void AttachIcon (SkeletonUtilityBone boneComponent) {
|
||||
Skeleton skeleton = boneComponent.hierarchy.Skeleton;
|
||||
Texture2D icon = boneComponent.bone.Data.Length == 0 ? Icons.nullBone : Icons.boneNib;
|
||||
|
||||
var ikConstraints = skeleton.Constraints.OfType<IkConstraint>();
|
||||
foreach (IkConstraint c in ikConstraints)
|
||||
if (c.Target == boneComponent.bone) {
|
||||
icon = Icons.constraintNib;
|
||||
break;
|
||||
}
|
||||
#if PUBLIC_SET_ICON_FOR_OBJECT
|
||||
EditorGUIUtility.SetIconForObject(boneComponent.gameObject, icon);
|
||||
#else
|
||||
typeof(EditorGUIUtility).InvokeMember("SetIconForObject", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new object[2] {
|
||||
boneComponent.gameObject,
|
||||
icon
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
static void AttachIconsToChildren (Transform root) {
|
||||
if (root != null) {
|
||||
SkeletonUtilityBone[] utilityBones = root.GetComponentsInChildren<SkeletonUtilityBone>();
|
||||
foreach (SkeletonUtilityBone utilBone in utilityBones)
|
||||
AttachIcon(utilBone);
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnFollowHierarchy () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Hierarchy");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
|
||||
void SpawnFollowHierarchyRootOnly () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Root");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Follow, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
|
||||
void SpawnOverrideHierarchy () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Hierarchy");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Override, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
|
||||
void SpawnOverrideHierarchyRootOnly () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Root");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Override, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5b90df955eb8c2429ac67c8b2de6c5c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfaea6b7e7f52bc46b8d1c3cb5e9eaa1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 529 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fc714a0dc1cf6b4b959e073fff2844e
|
||||
timeCreated: 1508165143
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 507 B |
@@ -0,0 +1,46 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68defdbc95b30a74a9ad396bfc9a2277
|
||||
TextureImporter:
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 581 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52b12ec801461494185a4d3dc66f3d1d
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 552 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d1be4ea889f3a14b864352fe49a1bde
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 289 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04ae56b3698d3e844844cfcef2f009e7
|
||||
timeCreated: 1494928093
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 386 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8322793223a533a4ca8be6f430256dfc
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 411 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97a43f11e00735147a9dc3dff6d68191
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 225 B |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 955aed20030d0504b8a9c6934a5cb47a
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 482 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5fff1b5caee03642ab77c9984b4bb6a
|
||||
timeCreated: 1497479335
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 540 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02822eb69e09dd947b434ab81e3d938f
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 752 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de1a4f5ad4bdf1a4ea072c4d59ba87d8
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 502 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1aae98dd56b14c4b8c25360000b7e9e
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,124 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10e534174824cb04e8a7ec21825f2827
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 471 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4709175437c21f64bab9b061f98a49fc
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 587 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed0736a1eb519ef42b4892d1db2426b3
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 359 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d226a80acc775714aa78b85e16a00e9b
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 596 B |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c2c6d283dcf3654baf40001c982891c
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 387 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b3a6f35bbaa8414eb51a344743ee641
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 397 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a309a2e14638a204091b915126910f45
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -1
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 213 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1de1604dfe4cb64c9d31246a8e43c78
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 432 B |
@@ -0,0 +1,59 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbc817a6c9e9c5747b7f6261bf5d1d09
|
||||
timeCreated: 1482240904
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 7
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
allowsAlphaSplitting: 0
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 505 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7a76922e4dd9fa429da15c018ff127f
|
||||
timeCreated: 1524196821
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da6f6d414e43aac46a57cc5a87208db4
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 458 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2216037084d99d4481810cb521ed96f
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||