2020-05-09 13:31:21 +08:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Spine Runtimes Software License v2 . 5
*
* Copyright ( c ) 2013 - 2016 , Esoteric Software
* All rights reserved .
*
* You are granted a perpetual , non - exclusive , non - sublicensable , and
* non - transferable license to use , install , execute , and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use . Without the written permission of Esoteric Software ( see Section 2 of
* the Spine Software License Agreement ) , you may not ( a ) modify , translate ,
* adapt , or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or ( b ) remove ,
* delete , alter , or obscure any trademarks or any copyright , trademark , patent ,
* or other intellectual property or proprietary rights notices on or in the
* Software , including any copy thereof . Redistributions in binary or source
* form must include this license and terms .
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO
* EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO ,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES , BUSINESS INTERRUPTION , OR LOSS OF
* USE , DATA , OR PROFITS ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER
* IN CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
#pragma warning disable 0219
// Original contribution by: Mitch Thompson
#define SPINE_SKELETONANIMATOR
using UnityEngine ;
using UnityEditor ;
using System.Collections.Generic ;
using System.IO ;
using System.Text ;
using System.Linq ;
using System.Reflection ;
using Spine ;
namespace Spine.Unity.Editor {
using EventType = UnityEngine . EventType ;
// Analysis disable once ConvertToStaticType
[InitializeOnLoad]
public class SpineEditorUtilities : AssetPostprocessor {
public static class Icons {
public static Texture2D skeleton ;
public static Texture2D nullBone ;
public static Texture2D bone ;
public static Texture2D poseBones ;
public static Texture2D boneNib ;
public static Texture2D slot ;
public static Texture2D slotRoot ;
public static Texture2D skinPlaceholder ;
public static Texture2D image ;
public static Texture2D genericAttachment ;
public static Texture2D boundingBox ;
public static Texture2D point ;
public static Texture2D mesh ;
public static Texture2D weights ;
public static Texture2D path ;
public static Texture2D clipping ;
public static Texture2D skin ;
public static Texture2D skinsRoot ;
public static Texture2D animation ;
public static Texture2D animationRoot ;
public static Texture2D spine ;
public static Texture2D userEvent ;
public static Texture2D constraintNib ;
public static Texture2D constraintRoot ;
public static Texture2D constraintTransform ;
public static Texture2D constraintPath ;
public static Texture2D constraintIK ;
public static Texture2D warning ;
public static Texture2D skeletonUtility ;
public static Texture2D hingeChain ;
public static Texture2D subMeshRenderer ;
public static Texture2D skeletonDataAssetIcon ;
public static Texture2D info ;
public static Texture2D unity ;
// public static Texture2D controllerIcon;
static Texture2D LoadIcon ( string filename ) {
return ( Texture2D ) AssetDatabase . LoadMainAssetAtPath ( SpineEditorUtilities . editorGUIPath + "/" + filename ) ;
}
public static void Initialize ( ) {
skeleton = LoadIcon ( "icon-skeleton.png" ) ;
nullBone = LoadIcon ( "icon-null.png" ) ;
bone = LoadIcon ( "icon-bone.png" ) ;
poseBones = LoadIcon ( "icon-poseBones.png" ) ;
boneNib = LoadIcon ( "icon-boneNib.png" ) ;
slot = LoadIcon ( "icon-slot.png" ) ;
slotRoot = LoadIcon ( "icon-slotRoot.png" ) ;
skinPlaceholder = LoadIcon ( "icon-skinPlaceholder.png" ) ;
genericAttachment = LoadIcon ( "icon-attachment.png" ) ;
image = LoadIcon ( "icon-image.png" ) ;
boundingBox = LoadIcon ( "icon-boundingBox.png" ) ;
point = LoadIcon ( "icon-point.png" ) ;
mesh = LoadIcon ( "icon-mesh.png" ) ;
weights = LoadIcon ( "icon-weights.png" ) ;
path = LoadIcon ( "icon-path.png" ) ;
clipping = LoadIcon ( "icon-clipping.png" ) ;
skin = LoadIcon ( "icon-skin.png" ) ;
skinsRoot = LoadIcon ( "icon-skinsRoot.png" ) ;
animation = LoadIcon ( "icon-animation.png" ) ;
animationRoot = LoadIcon ( "icon-animationRoot.png" ) ;
spine = LoadIcon ( "icon-spine.png" ) ;
userEvent = LoadIcon ( "icon-event.png" ) ;
constraintNib = LoadIcon ( "icon-constraintNib.png" ) ;
constraintRoot = LoadIcon ( "icon-constraints.png" ) ;
constraintTransform = LoadIcon ( "icon-constraintTransform.png" ) ;
constraintPath = LoadIcon ( "icon-constraintPath.png" ) ;
constraintIK = LoadIcon ( "icon-constraintIK.png" ) ;
warning = LoadIcon ( "icon-warning.png" ) ;
skeletonUtility = LoadIcon ( "icon-skeletonUtility.png" ) ;
hingeChain = LoadIcon ( "icon-hingeChain.png" ) ;
subMeshRenderer = LoadIcon ( "icon-subMeshRenderer.png" ) ;
skeletonDataAssetIcon = LoadIcon ( "SkeletonDataAsset Icon.png" ) ;
info = EditorGUIUtility . FindTexture ( "console.infoicon.sml" ) ;
unity = EditorGUIUtility . FindTexture ( "SceneAsset Icon" ) ;
// controllerIcon = EditorGUIUtility.FindTexture("AnimatorController Icon");
}
public static Texture2D GetAttachmentIcon ( Attachment attachment ) {
// Analysis disable once CanBeReplacedWithTryCastAndCheckForNull
if ( attachment is RegionAttachment )
return Icons . image ;
else if ( attachment is MeshAttachment )
return ( ( MeshAttachment ) attachment ) . IsWeighted ( ) ? Icons . weights : Icons . mesh ;
else if ( attachment is BoundingBoxAttachment )
return Icons . boundingBox ;
else if ( attachment is PointAttachment )
return Icons . point ;
else if ( attachment is PathAttachment )
return Icons . path ;
else if ( attachment is ClippingAttachment )
return Icons . clipping ;
else
return Icons . warning ;
}
}
public static string editorPath = "" ;
public static string editorGUIPath = "" ;
public static bool initialized ;
/// HACK: This list keeps the asset reference temporarily during importing.
///
/// In cases of very large projects/sufficient RAM pressure, when AssetDatabase.SaveAssets is called,
/// Unity can mistakenly unload assets whose references are only on the stack.
/// This leads to MissingReferenceException and other errors.
static readonly List < ScriptableObject > protectFromStackGarbageCollection = new List < ScriptableObject > ( ) ;
static HashSet < string > assetsImportedInWrongState = new HashSet < string > ( ) ;
#if SPINE_TK2D
const float DEFAULT_DEFAULT_SCALE = 1f ;
# else
const float DEFAULT_DEFAULT_SCALE = 0.01f ;
# endif
const string DEFAULT_SCALE_KEY = "SPINE_DEFAULT_SCALE" ;
public static float defaultScale = DEFAULT_DEFAULT_SCALE ;
const float DEFAULT_DEFAULT_MIX = 0.2f ;
const string DEFAULT_MIX_KEY = "SPINE_DEFAULT_MIX" ;
public static float defaultMix = DEFAULT_DEFAULT_MIX ;
const string DEFAULT_DEFAULT_SHADER = "Spine/Skeleton" ;
const string DEFAULT_SHADER_KEY = "SPINE_DEFAULT_SHADER" ;
public static string defaultShader = DEFAULT_DEFAULT_SHADER ;
const float DEFAULT_DEFAULT_ZSPACING = 0f ;
const string DEFAULT_ZSPACING_KEY = "SPINE_DEFAULT_ZSPACING" ;
public static float defaultZSpacing = DEFAULT_DEFAULT_ZSPACING ;
const bool DEFAULT_SHOW_HIERARCHY_ICONS = true ;
const string SHOW_HIERARCHY_ICONS_KEY = "SPINE_SHOW_HIERARCHY_ICONS" ;
public static bool showHierarchyIcons = DEFAULT_SHOW_HIERARCHY_ICONS ;
const bool DEFAULT_SET_TEXTUREIMPORTER_SETTINGS = true ;
const string SET_TEXTUREIMPORTER_SETTINGS_KEY = "SPINE_SET_TEXTUREIMPORTER_SETTINGS" ;
public static bool setTextureImporterSettings = DEFAULT_SET_TEXTUREIMPORTER_SETTINGS ;
internal const float DEFAULT_MIPMAPBIAS = - 0.5f ;
public const float DEFAULT_SCENE_ICONS_SCALE = 1f ;
public const string SCENE_ICONS_SCALE_KEY = "SPINE_SCENE_ICONS_SCALE" ;
#region Initialization
static SpineEditorUtilities ( ) {
Initialize ( ) ;
}
static void LoadPreferences ( ) {
defaultMix = EditorPrefs . GetFloat ( DEFAULT_MIX_KEY , DEFAULT_DEFAULT_MIX ) ;
defaultScale = EditorPrefs . GetFloat ( DEFAULT_SCALE_KEY , DEFAULT_DEFAULT_SCALE ) ;
defaultZSpacing = EditorPrefs . GetFloat ( DEFAULT_ZSPACING_KEY , DEFAULT_DEFAULT_ZSPACING ) ;
defaultShader = EditorPrefs . GetString ( DEFAULT_SHADER_KEY , DEFAULT_DEFAULT_SHADER ) ;
showHierarchyIcons = EditorPrefs . GetBool ( SHOW_HIERARCHY_ICONS_KEY , DEFAULT_SHOW_HIERARCHY_ICONS ) ;
setTextureImporterSettings = EditorPrefs . GetBool ( SET_TEXTUREIMPORTER_SETTINGS_KEY , DEFAULT_SET_TEXTUREIMPORTER_SETTINGS ) ;
SpineHandles . handleScale = EditorPrefs . GetFloat ( SCENE_ICONS_SCALE_KEY , DEFAULT_SCENE_ICONS_SCALE ) ;
preferencesLoaded = true ;
}
static void Initialize ( ) {
LoadPreferences ( ) ;
DirectoryInfo rootDir = new DirectoryInfo ( Application . dataPath ) ;
FileInfo [ ] files = rootDir . GetFiles ( "SpineEditorUtilities.cs" , SearchOption . AllDirectories ) ;
editorPath = Path . GetDirectoryName ( files [ 0 ] . FullName . Replace ( "\\" , "/" ) . Replace ( Application . dataPath , "Assets" ) ) ;
editorGUIPath = editorPath + "/GUI" ;
Icons . Initialize ( ) ;
// Drag and Drop
SceneView . onSceneGUIDelegate - = SceneViewDragAndDrop ;
SceneView . onSceneGUIDelegate + = SceneViewDragAndDrop ;
EditorApplication . hierarchyWindowItemOnGUI - = SpineEditorHierarchyHandler . HierarchyDragAndDrop ;
EditorApplication . hierarchyWindowItemOnGUI + = SpineEditorHierarchyHandler . HierarchyDragAndDrop ;
// Hierarchy Icons
#if UNITY_2017_2_OR_NEWER
EditorApplication . playModeStateChanged - = SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ;
EditorApplication . playModeStateChanged + = SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ;
SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ( PlayModeStateChange . EnteredEditMode ) ;
# else
EditorApplication . playmodeStateChanged - = SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ;
EditorApplication . playmodeStateChanged + = SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ;
SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ( ) ;
# endif
initialized = true ;
}
public static void ConfirmInitialization ( ) {
if ( ! initialized | | Icons . skeleton = = null )
Initialize ( ) ;
}
# endregion
#region Spine Preferences and Defaults
static bool preferencesLoaded = false ;
[PreferenceItem("Spine")]
static void PreferencesGUI ( ) {
if ( ! preferencesLoaded )
LoadPreferences ( ) ;
EditorGUI . BeginChangeCheck ( ) ;
showHierarchyIcons = EditorGUILayout . Toggle ( new GUIContent ( "Show Hierarchy Icons" , "Show relevant icons on GameObjects with Spine Components on them. Disable this if you have large, complex scenes." ) , showHierarchyIcons ) ;
if ( EditorGUI . EndChangeCheck ( ) ) {
EditorPrefs . SetBool ( SHOW_HIERARCHY_ICONS_KEY , showHierarchyIcons ) ;
#if UNITY_2017_2_OR_NEWER
SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ( PlayModeStateChange . EnteredEditMode ) ;
# else
SpineEditorHierarchyHandler . HierarchyIconsOnPlaymodeStateChanged ( ) ;
# endif
}
EditorGUILayout . Separator ( ) ;
EditorGUILayout . LabelField ( "Auto-Import Settings" , EditorStyles . boldLabel ) ;
EditorGUI . BeginChangeCheck ( ) ;
defaultMix = EditorGUILayout . FloatField ( "Default Mix" , defaultMix ) ;
if ( EditorGUI . EndChangeCheck ( ) )
EditorPrefs . SetFloat ( DEFAULT_MIX_KEY , defaultMix ) ;
EditorGUI . BeginChangeCheck ( ) ;
defaultScale = EditorGUILayout . FloatField ( "Default SkeletonData Scale" , defaultScale ) ;
if ( EditorGUI . EndChangeCheck ( ) )
EditorPrefs . SetFloat ( DEFAULT_SCALE_KEY , defaultScale ) ;
EditorGUI . BeginChangeCheck ( ) ;
var shader = ( EditorGUILayout . ObjectField ( "Default Shader" , Shader . Find ( defaultShader ) , typeof ( Shader ) , false ) as Shader ) ;
defaultShader = shader ! = null ? shader . name : DEFAULT_DEFAULT_SHADER ;
if ( EditorGUI . EndChangeCheck ( ) )
EditorPrefs . SetString ( DEFAULT_SHADER_KEY , defaultShader ) ;
EditorGUI . BeginChangeCheck ( ) ;
setTextureImporterSettings = EditorGUILayout . Toggle ( new GUIContent ( "Apply Atlas Texture Settings" , "Apply the recommended settings for Texture Importers." ) , showHierarchyIcons ) ;
if ( EditorGUI . EndChangeCheck ( ) ) {
EditorPrefs . SetBool ( SET_TEXTUREIMPORTER_SETTINGS_KEY , showHierarchyIcons ) ;
}
EditorGUILayout . Space ( ) ;
EditorGUILayout . LabelField ( "Editor Instantiation" , EditorStyles . boldLabel ) ;
EditorGUI . BeginChangeCheck ( ) ;
defaultZSpacing = EditorGUILayout . Slider ( "Default Slot Z-Spacing" , defaultZSpacing , - 0.1f , 0f ) ;
if ( EditorGUI . EndChangeCheck ( ) )
EditorPrefs . SetFloat ( DEFAULT_ZSPACING_KEY , defaultZSpacing ) ;
EditorGUILayout . Space ( ) ;
EditorGUILayout . LabelField ( "Handles and Gizmos" , EditorStyles . boldLabel ) ;
EditorGUI . BeginChangeCheck ( ) ;
SpineHandles . handleScale = EditorGUILayout . Slider ( "Editor Bone Scale" , SpineHandles . handleScale , 0.01f , 2f ) ;
SpineHandles . handleScale = Mathf . Max ( 0.01f , SpineHandles . handleScale ) ;
if ( EditorGUI . EndChangeCheck ( ) ) {
EditorPrefs . SetFloat ( SCENE_ICONS_SCALE_KEY , SpineHandles . handleScale ) ;
SceneView . RepaintAll ( ) ;
}
GUILayout . Space ( 20 ) ;
EditorGUILayout . LabelField ( "3rd Party Settings" , EditorStyles . boldLabel ) ;
using ( new GUILayout . HorizontalScope ( ) ) {
EditorGUILayout . PrefixLabel ( "Define TK2D" ) ;
if ( GUILayout . Button ( "Enable" , GUILayout . Width ( 64 ) ) )
SpineTK2DEditorUtility . EnableTK2D ( ) ;
if ( GUILayout . Button ( "Disable" , GUILayout . Width ( 64 ) ) )
SpineTK2DEditorUtility . DisableTK2D ( ) ;
}
}
# endregion
#region Drag and Drop Instantiation
public delegate Component InstantiateDelegate ( SkeletonDataAsset skeletonDataAsset ) ;
public struct SpawnMenuData {
public Vector3 spawnPoint ;
public SkeletonDataAsset skeletonDataAsset ;
public InstantiateDelegate instantiateDelegate ;
public bool isUI ;
}
public class SkeletonComponentSpawnType {
public string menuLabel ;
public InstantiateDelegate instantiateDelegate ;
public bool isUI ;
}
internal static readonly List < SkeletonComponentSpawnType > additionalSpawnTypes = new List < SkeletonComponentSpawnType > ( ) ;
static void SceneViewDragAndDrop ( SceneView sceneview ) {
var current = UnityEngine . Event . current ;
var references = DragAndDrop . objectReferences ;
if ( current . type = = EventType . Layout ) return ;
// Allow drag and drop of one SkeletonDataAsset.
if ( references . Length = = 1 ) {
var skeletonDataAsset = references [ 0 ] as SkeletonDataAsset ;
if ( skeletonDataAsset ! = null ) {
var mousePos = current . mousePosition ;
bool invalidSkeletonData = skeletonDataAsset . GetSkeletonData ( true ) = = null ;
if ( invalidSkeletonData ) {
DragAndDrop . visualMode = DragAndDropVisualMode . Rejected ;
Handles . BeginGUI ( ) ;
GUI . Label ( new Rect ( mousePos + new Vector2 ( 20f , 20f ) , new Vector2 ( 400f , 40f ) ) , new GUIContent ( string . Format ( "{0} is invalid.\nCannot create new Spine GameObject." , skeletonDataAsset . name ) , SpineEditorUtilities . Icons . warning ) ) ;
Handles . EndGUI ( ) ;
return ;
} else {
DragAndDrop . visualMode = DragAndDropVisualMode . Copy ;
Handles . BeginGUI ( ) ;
GUI . Label ( new Rect ( mousePos + new Vector2 ( 20f , 20f ) , new Vector2 ( 400f , 20f ) ) , new GUIContent ( string . Format ( "Create Spine GameObject ({0})" , skeletonDataAsset . skeletonJSON . name ) , SpineEditorUtilities . Icons . skeletonDataAssetIcon ) ) ;
Handles . EndGUI ( ) ;
if ( current . type = = EventType . DragPerform ) {
RectTransform rectTransform = ( Selection . activeGameObject = = null ) ? null : Selection . activeGameObject . GetComponent < RectTransform > ( ) ;
Plane plane = ( rectTransform = = null ) ? new Plane ( Vector3 . back , Vector3 . zero ) : new Plane ( - rectTransform . forward , rectTransform . position ) ;
Vector3 spawnPoint = MousePointToWorldPoint2D ( mousePos , sceneview . camera , plane ) ;
ShowInstantiateContextMenu ( skeletonDataAsset , spawnPoint ) ;
DragAndDrop . AcceptDrag ( ) ;
current . Use ( ) ;
}
}
}
}
}
public static void ShowInstantiateContextMenu ( SkeletonDataAsset skeletonDataAsset , Vector3 spawnPoint ) {
var menu = new GenericMenu ( ) ;
// SkeletonAnimation
menu . AddItem ( new GUIContent ( "SkeletonAnimation" ) , false , HandleSkeletonComponentDrop , new SpawnMenuData {
skeletonDataAsset = skeletonDataAsset ,
spawnPoint = spawnPoint ,
instantiateDelegate = ( data ) = > InstantiateSkeletonAnimation ( data ) ,
isUI = false
} ) ;
// SkeletonGraphic
var skeletonGraphicInspectorType = System . Type . GetType ( "Spine.Unity.Editor.SkeletonGraphicInspector" ) ;
if ( skeletonGraphicInspectorType ! = null ) {
var graphicInstantiateDelegate = skeletonGraphicInspectorType . GetMethod ( "SpawnSkeletonGraphicFromDrop" , BindingFlags . Static | BindingFlags . Public ) ;
if ( graphicInstantiateDelegate ! = null )
menu . AddItem ( new GUIContent ( "SkeletonGraphic (UI)" ) , false , HandleSkeletonComponentDrop , new SpawnMenuData {
skeletonDataAsset = skeletonDataAsset ,
spawnPoint = spawnPoint ,
instantiateDelegate = System . Delegate . CreateDelegate ( typeof ( InstantiateDelegate ) , graphicInstantiateDelegate ) as InstantiateDelegate ,
isUI = true
} ) ;
}
#if SPINE_SKELETONANIMATOR
menu . AddSeparator ( "" ) ;
// SkeletonAnimator
menu . AddItem ( new GUIContent ( "SkeletonAnimator" ) , false , HandleSkeletonComponentDrop , new SpawnMenuData {
skeletonDataAsset = skeletonDataAsset ,
spawnPoint = spawnPoint ,
instantiateDelegate = ( data ) = > InstantiateSkeletonAnimator ( data )
} ) ;
# endif
menu . ShowAsContext ( ) ;
}
public static void HandleSkeletonComponentDrop ( object spawnMenuData ) {
var data = ( SpawnMenuData ) spawnMenuData ;
if ( data . skeletonDataAsset . GetSkeletonData ( true ) = = null ) {
EditorUtility . DisplayDialog ( "Invalid SkeletonDataAsset" , "Unable to create Spine GameObject.\n\nPlease check your SkeletonDataAsset." , "Ok" ) ;
return ;
}
bool isUI = data . isUI ;
Component newSkeletonComponent = data . instantiateDelegate . Invoke ( data . skeletonDataAsset ) ;
GameObject newGameObject = newSkeletonComponent . gameObject ;
Transform newTransform = newGameObject . transform ;
var activeGameObject = Selection . activeGameObject ;
if ( isUI & & activeGameObject ! = null )
newTransform . SetParent ( activeGameObject . transform , false ) ;
newTransform . position = isUI ? data . spawnPoint : RoundVector ( data . spawnPoint , 2 ) ;
if ( isUI & & ( activeGameObject = = null | | activeGameObject . GetComponent < RectTransform > ( ) = = null ) )
Debug . Log ( "Created a UI Skeleton GameObject not under a RectTransform. It may not be visible until you parent it to a canvas." ) ;
if ( ! isUI & & activeGameObject ! = null & & activeGameObject . transform . localScale ! = Vector3 . one )
Debug . Log ( "New Spine GameObject was parented to a scaled Transform. It may not be the intended size." ) ;
Selection . activeGameObject = newGameObject ;
//EditorGUIUtility.PingObject(newGameObject); // Doesn't work when setting activeGameObject.
Undo . RegisterCreatedObjectUndo ( newGameObject , "Create Spine GameObject" ) ;
}
/// <summary>
/// Rounds off vector components to a number of decimal digits.
/// </summary>
public static Vector3 RoundVector ( Vector3 vector , int digits ) {
vector . x = ( float ) System . Math . Round ( vector . x , digits ) ;
vector . y = ( float ) System . Math . Round ( vector . y , digits ) ;
vector . z = ( float ) System . Math . Round ( vector . z , digits ) ;
return vector ;
}
/// <summary>
/// Converts a mouse point to a world point on a plane.
/// </summary>
static Vector3 MousePointToWorldPoint2D ( Vector2 mousePosition , Camera camera , Plane plane ) {
var screenPos = new Vector3 ( mousePosition . x , camera . pixelHeight - mousePosition . y , 0f ) ;
var ray = camera . ScreenPointToRay ( screenPos ) ;
float distance ;
bool hit = plane . Raycast ( ray , out distance ) ;
return ray . GetPoint ( distance ) ;
}
# endregion
#region Hierarchy
static class SpineEditorHierarchyHandler {
static Dictionary < int , GameObject > skeletonRendererTable = new Dictionary < int , GameObject > ( ) ;
static Dictionary < int , SkeletonUtilityBone > skeletonUtilityBoneTable = new Dictionary < int , SkeletonUtilityBone > ( ) ;
static Dictionary < int , BoundingBoxFollower > boundingBoxFollowerTable = new Dictionary < int , BoundingBoxFollower > ( ) ;
#if UNITY_2017_2_OR_NEWER
internal static void HierarchyIconsOnPlaymodeStateChanged ( PlayModeStateChange stateChange ) {
# else
internal static void HierarchyIconsOnPlaymodeStateChanged ( ) {
# endif
skeletonRendererTable . Clear ( ) ;
skeletonUtilityBoneTable . Clear ( ) ;
boundingBoxFollowerTable . Clear ( ) ;
#if UNITY_2018
EditorApplication . hierarchyChanged - = HierarchyIconsOnChanged ;
# else
EditorApplication . hierarchyWindowChanged - = HierarchyIconsOnChanged ;
# endif
EditorApplication . hierarchyWindowItemOnGUI - = HierarchyIconsOnGUI ;
if ( ! Application . isPlaying & & showHierarchyIcons ) {
#if UNITY_2018
EditorApplication . hierarchyChanged + = HierarchyIconsOnChanged ;
# else
EditorApplication . hierarchyWindowChanged + = HierarchyIconsOnChanged ;
# endif
EditorApplication . hierarchyWindowItemOnGUI + = HierarchyIconsOnGUI ;
HierarchyIconsOnChanged ( ) ;
}
}
internal static void HierarchyIconsOnChanged ( ) {
skeletonRendererTable . Clear ( ) ;
skeletonUtilityBoneTable . Clear ( ) ;
boundingBoxFollowerTable . Clear ( ) ;
SkeletonRenderer [ ] arr = Object . FindObjectsOfType < SkeletonRenderer > ( ) ;
foreach ( SkeletonRenderer r in arr )
skeletonRendererTable [ r . gameObject . GetInstanceID ( ) ] = r . gameObject ;
SkeletonUtilityBone [ ] boneArr = Object . FindObjectsOfType < SkeletonUtilityBone > ( ) ;
foreach ( SkeletonUtilityBone b in boneArr )
skeletonUtilityBoneTable [ b . gameObject . GetInstanceID ( ) ] = b ;
BoundingBoxFollower [ ] bbfArr = Object . FindObjectsOfType < BoundingBoxFollower > ( ) ;
foreach ( BoundingBoxFollower bbf in bbfArr )
boundingBoxFollowerTable [ bbf . gameObject . GetInstanceID ( ) ] = bbf ;
}
internal static void HierarchyIconsOnGUI ( int instanceId , Rect selectionRect ) {
Rect r = new Rect ( selectionRect ) ;
if ( skeletonRendererTable . ContainsKey ( instanceId ) ) {
r . x = r . width - 15 ;
r . width = 15 ;
GUI . Label ( r , Icons . spine ) ;
} else if ( skeletonUtilityBoneTable . ContainsKey ( instanceId ) ) {
r . x - = 26 ;
if ( skeletonUtilityBoneTable [ instanceId ] ! = null ) {
if ( skeletonUtilityBoneTable [ instanceId ] . transform . childCount = = 0 )
r . x + = 13 ;
r . y + = 2 ;
r . width = 13 ;
r . height = 13 ;
if ( skeletonUtilityBoneTable [ instanceId ] . mode = = SkeletonUtilityBone . Mode . Follow )
GUI . DrawTexture ( r , Icons . bone ) ;
else
GUI . DrawTexture ( r , Icons . poseBones ) ;
}
} else if ( boundingBoxFollowerTable . ContainsKey ( instanceId ) ) {
r . x - = 26 ;
if ( boundingBoxFollowerTable [ instanceId ] ! = null ) {
if ( boundingBoxFollowerTable [ instanceId ] . transform . childCount = = 0 )
r . x + = 13 ;
r . y + = 2 ;
r . width = 13 ;
r . height = 13 ;
GUI . DrawTexture ( r , Icons . boundingBox ) ;
}
}
}
internal static void HierarchyDragAndDrop ( int instanceId , Rect selectionRect ) {
// HACK: Uses EditorApplication.hierarchyWindowItemOnGUI.
// Only works when there is at least one item in the scene.
var current = UnityEngine . Event . current ;
var eventType = current . type ;
bool isDraggingEvent = eventType = = EventType . DragUpdated ;
bool isDropEvent = eventType = = EventType . DragPerform ;
if ( isDraggingEvent | | isDropEvent ) {
var mouseOverWindow = EditorWindow . mouseOverWindow ;
if ( mouseOverWindow ! = null ) {
// One, existing, valid SkeletonDataAsset
var references = DragAndDrop . objectReferences ;
if ( references . Length = = 1 ) {
var skeletonDataAsset = references [ 0 ] as SkeletonDataAsset ;
if ( skeletonDataAsset ! = null & & skeletonDataAsset . GetSkeletonData ( true ) ! = null ) {
// Allow drag-and-dropping anywhere in the Hierarchy Window.
// HACK: string-compare because we can't get its type via reflection.
const string HierarchyWindow = "UnityEditor.SceneHierarchyWindow" ;
if ( HierarchyWindow . Equals ( mouseOverWindow . GetType ( ) . ToString ( ) , System . StringComparison . Ordinal ) ) {
if ( isDraggingEvent ) {
DragAndDrop . visualMode = DragAndDropVisualMode . Copy ;
current . Use ( ) ;
} else if ( isDropEvent ) {
ShowInstantiateContextMenu ( skeletonDataAsset , Vector3 . zero ) ;
DragAndDrop . AcceptDrag ( ) ;
current . Use ( ) ;
return ;
}
}
}
}
}
}
}
}
# endregion
#region Auto-Import Entry Point
static void OnPostprocessAllAssets ( string [ ] imported , string [ ] deleted , string [ ] moved , string [ ] movedFromAssetPaths ) {
if ( imported . Length = = 0 )
return ;
// In case user used "Assets -> Reimport All", during the import process,
// asset database is not initialized until some point. During that period,
// all attempts to load any assets using API (i.e. AssetDatabase.LoadAssetAtPath)
// will return null, and as result, assets won't be loaded even if they actually exists,
// which may lead to numerous importing errors.
// This situation also happens if Library folder is deleted from the project, which is a pretty
// common case, since when using version control systems, the Library folder must be excluded.
//
// So to avoid this, in case asset database is not available, we delay loading the assets
// until next time.
/ /
// Unity *always* reimports some internal assets after the process is done, so this method
// is always called once again in a state when asset database is available.
/ /
// Checking whether AssetDatabase is initialized is done by attempting to load
// a known "marker" asset that should always be available. Failing to load this asset
// means that AssetDatabase is not initialized.
assetsImportedInWrongState . UnionWith ( imported ) ;
if ( AssetDatabaseAvailabilityDetector . IsAssetDatabaseAvailable ( ) ) {
string [ ] combinedAssets = assetsImportedInWrongState . ToArray ( ) ;
assetsImportedInWrongState . Clear ( ) ;
ImportSpineContent ( combinedAssets ) ;
}
}
public static void ImportSpineContent ( string [ ] imported , bool reimport = false ) {
var atlasPaths = new List < string > ( ) ;
var imagePaths = new List < string > ( ) ;
var skeletonPaths = new List < string > ( ) ;
foreach ( string str in imported ) {
string extension = Path . GetExtension ( str ) . ToLower ( ) ;
switch ( extension ) {
case ".txt" :
if ( str . EndsWith ( ".atlas.txt" , System . StringComparison . Ordinal ) )
atlasPaths . Add ( str ) ;
break ;
case ".png" :
case ".jpg" :
imagePaths . Add ( str ) ;
break ;
case ".json" :
var jsonAsset = ( TextAsset ) AssetDatabase . LoadAssetAtPath ( str , typeof ( TextAsset ) ) ;
if ( jsonAsset ! = null & & SkeletonDataFileValidator . IsSpineData ( jsonAsset ) )
skeletonPaths . Add ( str ) ;
break ;
case ".bytes" :
if ( str . ToLower ( ) . EndsWith ( ".skel.bytes" , System . StringComparison . Ordinal ) ) {
if ( SkeletonDataFileValidator . IsSpineData ( ( TextAsset ) AssetDatabase . LoadAssetAtPath ( str , typeof ( TextAsset ) ) ) )
skeletonPaths . Add ( str ) ;
}
break ;
}
}
// Import atlases first.
var atlases = new List < AtlasAsset > ( ) ;
foreach ( string ap in atlasPaths ) {
TextAsset atlasText = ( TextAsset ) AssetDatabase . LoadAssetAtPath ( ap , typeof ( TextAsset ) ) ;
AtlasAsset atlas = IngestSpineAtlas ( atlasText ) ;
atlases . Add ( atlas ) ;
}
// Import skeletons and match them with atlases.
bool abortSkeletonImport = false ;
foreach ( string sp in skeletonPaths ) {
if ( ! reimport & & SkeletonDataFileValidator . CheckForValidSkeletonData ( sp ) ) {
ReloadSkeletonData ( sp ) ;
continue ;
}
string dir = Path . GetDirectoryName ( sp ) ;
#if SPINE_TK2D
IngestSpineProject ( AssetDatabase . LoadAssetAtPath ( sp , typeof ( TextAsset ) ) as TextAsset , null ) ;
# else
var localAtlases = FindAtlasesAtPath ( dir ) ;
var requiredPaths = GetRequiredAtlasRegions ( sp ) ;
var atlasMatch = GetMatchingAtlas ( requiredPaths , localAtlases ) ;
if ( atlasMatch ! = null | | requiredPaths . Count = = 0 ) {
IngestSpineProject ( AssetDatabase . LoadAssetAtPath ( sp , typeof ( TextAsset ) ) as TextAsset , atlasMatch ) ;
} else {
bool resolved = false ;
while ( ! resolved ) {
string filename = Path . GetFileNameWithoutExtension ( sp ) ;
int result = EditorUtility . DisplayDialogComplex (
string . Format ( "AtlasAsset for \"{0}\"" , filename ) ,
string . Format ( "Could not automatically set the AtlasAsset for \"{0}\". You may set it manually." , filename ) ,
"Choose AtlasAssets..." , "Skip this" , "Stop importing all"
) ;
switch ( result ) {
case - 1 :
//Debug.Log("Select Atlas");
AtlasAsset selectedAtlas = GetAtlasDialog ( Path . GetDirectoryName ( sp ) ) ;
if ( selectedAtlas ! = null ) {
localAtlases . Clear ( ) ;
localAtlases . Add ( selectedAtlas ) ;
atlasMatch = GetMatchingAtlas ( requiredPaths , localAtlases ) ;
if ( atlasMatch ! = null ) {
resolved = true ;
IngestSpineProject ( AssetDatabase . LoadAssetAtPath ( sp , typeof ( TextAsset ) ) as TextAsset , atlasMatch ) ;
}
}
break ;
case 0 : // Choose AtlasAssets...
var atlasList = MultiAtlasDialog ( requiredPaths , Path . GetDirectoryName ( sp ) , Path . GetFileNameWithoutExtension ( sp ) ) ;
if ( atlasList ! = null )
IngestSpineProject ( AssetDatabase . LoadAssetAtPath ( sp , typeof ( TextAsset ) ) as TextAsset , atlasList . ToArray ( ) ) ;
resolved = true ;
break ;
case 1 : // Skip
Debug . Log ( "Skipped importing: " + Path . GetFileName ( sp ) ) ;
resolved = true ;
break ;
case 2 : // Stop importing all
abortSkeletonImport = true ;
resolved = true ;
break ;
}
}
}
if ( abortSkeletonImport )
break ;
# endif
}
// Any post processing of images
}
static void ReloadSkeletonData ( string skeletonJSONPath ) {
string dir = Path . GetDirectoryName ( skeletonJSONPath ) ;
TextAsset textAsset = ( TextAsset ) AssetDatabase . LoadAssetAtPath ( skeletonJSONPath , typeof ( TextAsset ) ) ;
DirectoryInfo dirInfo = new DirectoryInfo ( dir ) ;
FileInfo [ ] files = dirInfo . GetFiles ( "*.asset" ) ;
foreach ( var f in files ) {
string localPath = dir + "/" + f . Name ;
var obj = AssetDatabase . LoadAssetAtPath ( localPath , typeof ( Object ) ) ;
var skeletonDataAsset = obj as SkeletonDataAsset ;
if ( skeletonDataAsset ! = null ) {
if ( skeletonDataAsset . skeletonJSON = = textAsset ) {
if ( Selection . activeObject = = skeletonDataAsset )
Selection . activeObject = null ;
Debug . LogFormat ( "Changes to '{0}' detected. Clearing SkeletonDataAsset: {1}" , skeletonJSONPath , localPath ) ;
skeletonDataAsset . Clear ( ) ;
string guid = AssetDatabase . AssetPathToGUID ( AssetDatabase . GetAssetPath ( skeletonDataAsset ) ) ;
string lastHash = EditorPrefs . GetString ( guid + "_hash" ) ;
// For some weird reason sometimes Unity loses the internal Object pointer,
// and as a result, all comparisons with null returns true.
// But the C# wrapper is still alive, so we can "restore" the object
// by reloading it from its Instance ID.
AtlasAsset [ ] skeletonDataAtlasAssets = skeletonDataAsset . atlasAssets ;
if ( skeletonDataAtlasAssets ! = null ) {
for ( int i = 0 ; i < skeletonDataAtlasAssets . Length ; i + + ) {
if ( ! ReferenceEquals ( null , skeletonDataAtlasAssets [ i ] ) & &
skeletonDataAtlasAssets [ i ] . Equals ( null ) & &
skeletonDataAtlasAssets [ i ] . GetInstanceID ( ) ! = 0
) {
skeletonDataAtlasAssets [ i ] = EditorUtility . InstanceIDToObject ( skeletonDataAtlasAssets [ i ] . GetInstanceID ( ) ) as AtlasAsset ;
}
}
}
SkeletonData skeletonData = skeletonDataAsset . GetSkeletonData ( true ) ;
string currentHash = skeletonData ! = null ? skeletonData . Hash : null ;
#if SPINE_SKELETONANIMATOR
if ( currentHash = = null | | lastHash ! = currentHash )
UpdateMecanimClips ( skeletonDataAsset ) ;
# endif
// if (currentHash == null || lastHash != currentHash)
// Do any upkeep on synchronized assets
if ( currentHash ! = null )
EditorPrefs . SetString ( guid + "_hash" , currentHash ) ;
}
}
}
}
# endregion
#region Match SkeletonData with Atlases
static readonly AttachmentType [ ] AtlasTypes = { AttachmentType . Region , AttachmentType . Linkedmesh , AttachmentType . Mesh } ;
static List < AtlasAsset > MultiAtlasDialog ( List < string > requiredPaths , string initialDirectory , string filename = "" ) {
List < AtlasAsset > atlasAssets = new List < AtlasAsset > ( ) ;
bool resolved = false ;
string lastAtlasPath = initialDirectory ;
while ( ! resolved ) {
// Build dialog box message.
var missingRegions = new List < string > ( requiredPaths ) ;
var dialogText = new StringBuilder ( ) ;
{
dialogText . AppendLine ( string . Format ( "SkeletonDataAsset for \"{0}\"" , filename ) ) ;
dialogText . AppendLine ( "has missing regions." ) ;
dialogText . AppendLine ( ) ;
dialogText . AppendLine ( "Current Atlases:" ) ;
if ( atlasAssets . Count = = 0 )
dialogText . AppendLine ( "\t--none--" ) ;
for ( int i = 0 ; i < atlasAssets . Count ; i + + )
dialogText . AppendLine ( "\t" + atlasAssets [ i ] . name ) ;
dialogText . AppendLine ( ) ;
dialogText . AppendLine ( "Missing Regions:" ) ;
foreach ( var atlasAsset in atlasAssets ) {
var atlas = atlasAsset . GetAtlas ( ) ;
for ( int i = 0 ; i < missingRegions . Count ; i + + ) {
if ( atlas . FindRegion ( missingRegions [ i ] ) ! = null ) {
missingRegions . RemoveAt ( i ) ;
i - - ;
}
}
}
int n = missingRegions . Count ;
if ( n = = 0 ) break ;
const int MaxListLength = 15 ;
for ( int i = 0 ; ( i < n & & i < MaxListLength ) ; i + + )
dialogText . AppendLine ( "\t" + missingRegions [ i ] ) ;
if ( n > MaxListLength ) dialogText . AppendLine ( string . Format ( "\t... {0} more..." , n - MaxListLength ) ) ;
}
// Show dialog box.
int result = EditorUtility . DisplayDialogComplex (
"SkeletonDataAsset has missing Atlas." ,
dialogText . ToString ( ) ,
"Browse..." , "Import anyway" , "Cancel"
) ;
switch ( result ) {
case 0 : // Browse...
AtlasAsset selectedAtlasAsset = GetAtlasDialog ( lastAtlasPath ) ;
if ( selectedAtlasAsset ! = null ) {
var atlas = selectedAtlasAsset . GetAtlas ( ) ;
bool hasValidRegion = false ;
foreach ( string str in missingRegions ) {
if ( atlas . FindRegion ( str ) ! = null ) {
hasValidRegion = true ;
break ;
}
}
atlasAssets . Add ( selectedAtlasAsset ) ;
}
break ;
case 1 : // Import anyway
resolved = true ;
break ;
case 2 : // Cancel
atlasAssets = null ;
resolved = true ;
break ;
}
}
return atlasAssets ;
}
static AtlasAsset GetAtlasDialog ( string dirPath ) {
string path = EditorUtility . OpenFilePanel ( "Select AtlasAsset..." , dirPath , "asset" ) ;
if ( path = = "" ) return null ; // Canceled or closed by user.
int subLen = Application . dataPath . Length - 6 ;
string assetRelativePath = path . Substring ( subLen , path . Length - subLen ) . Replace ( "\\" , "/" ) ;
Object obj = AssetDatabase . LoadAssetAtPath ( assetRelativePath , typeof ( AtlasAsset ) ) ;
if ( obj = = null | | obj . GetType ( ) ! = typeof ( AtlasAsset ) )
return null ;
return ( AtlasAsset ) obj ;
}
static void AddRequiredAtlasRegionsFromBinary ( string skeletonDataPath , List < string > requiredPaths ) {
SkeletonBinary binary = new SkeletonBinary ( new AtlasRequirementLoader ( requiredPaths ) ) ;
TextAsset data = ( TextAsset ) AssetDatabase . LoadAssetAtPath ( skeletonDataPath , typeof ( TextAsset ) ) ;
MemoryStream input = new MemoryStream ( data . bytes ) ;
binary . ReadSkeletonData ( input ) ;
binary = null ;
}
public static List < string > GetRequiredAtlasRegions ( string skeletonDataPath ) {
List < string > requiredPaths = new List < string > ( ) ;
if ( skeletonDataPath . Contains ( ".skel" ) ) {
AddRequiredAtlasRegionsFromBinary ( skeletonDataPath , requiredPaths ) ;
return requiredPaths ;
}
TextAsset spineJson = ( TextAsset ) AssetDatabase . LoadAssetAtPath ( skeletonDataPath , typeof ( TextAsset ) ) ;
StringReader reader = new StringReader ( spineJson . text ) ;
var root = Json . Deserialize ( reader ) as Dictionary < string , object > ;
if ( ! root . ContainsKey ( "skins" ) )
return requiredPaths ;
foreach ( KeyValuePair < string , object > entry in ( Dictionary < string , object > ) root [ "skins" ] ) {
foreach ( KeyValuePair < string , object > slotEntry in ( Dictionary < string , object > ) entry . Value ) {
foreach ( KeyValuePair < string , object > attachmentEntry in ( ( Dictionary < string , object > ) slotEntry . Value ) ) {
var data = ( ( Dictionary < string , object > ) attachmentEntry . Value ) ;
// Ignore non-atlas-requiring types.
if ( data . ContainsKey ( "type" ) ) {
AttachmentType attachmentType ;
string typeString = ( string ) data [ "type" ] ;
try {
attachmentType = ( AttachmentType ) System . Enum . Parse ( typeof ( AttachmentType ) , typeString , true ) ;
} catch ( System . ArgumentException e ) {
2022-02-24 21:26:09 +08:00
// For more info, visit: http://esoteric_hx_soft_hx_ware.com/forum/Spine-editor-and-runtime-version-management-6534
2020-05-09 13:31:21 +08:00
Debug . LogWarning ( string . Format ( "Unidentified Attachment type: \"{0}\". Skeleton may have been exported from an incompatible Spine version." , typeString ) ) ;
throw e ;
}
if ( ! AtlasTypes . Contains ( attachmentType ) )
continue ;
}
if ( data . ContainsKey ( "path" ) )
requiredPaths . Add ( ( string ) data [ "path" ] ) ;
else if ( data . ContainsKey ( "name" ) )
requiredPaths . Add ( ( string ) data [ "name" ] ) ;
else
requiredPaths . Add ( attachmentEntry . Key ) ;
}
}
}
return requiredPaths ;
}
static AtlasAsset GetMatchingAtlas ( List < string > requiredPaths , List < AtlasAsset > atlasAssets ) {
AtlasAsset atlasAssetMatch = null ;
foreach ( AtlasAsset a in atlasAssets ) {
Atlas atlas = a . GetAtlas ( ) ;
bool failed = false ;
foreach ( string regionPath in requiredPaths ) {
if ( atlas . FindRegion ( regionPath ) = = null ) {
failed = true ;
break ;
}
}
if ( ! failed ) {
atlasAssetMatch = a ;
break ;
}
}
return atlasAssetMatch ;
}
public class AtlasRequirementLoader : AttachmentLoader {
List < string > requirementList ;
public AtlasRequirementLoader ( List < string > requirementList ) {
this . requirementList = requirementList ;
}
public RegionAttachment NewRegionAttachment ( Skin skin , string name , string path ) {
requirementList . Add ( path ) ;
return new RegionAttachment ( name ) ;
}
public MeshAttachment NewMeshAttachment ( Skin skin , string name , string path ) {
requirementList . Add ( path ) ;
return new MeshAttachment ( name ) ;
}
public BoundingBoxAttachment NewBoundingBoxAttachment ( Skin skin , string name ) {
return new BoundingBoxAttachment ( name ) ;
}
public PathAttachment NewPathAttachment ( Skin skin , string name ) {
return new PathAttachment ( name ) ;
}
public PointAttachment NewPointAttachment ( Skin skin , string name ) {
return new PointAttachment ( name ) ;
}
public ClippingAttachment NewClippingAttachment ( Skin skin , string name ) {
return new ClippingAttachment ( name ) ;
}
}
# endregion
#region Import Atlases
static List < AtlasAsset > FindAtlasesAtPath ( string path ) {
List < AtlasAsset > arr = new List < AtlasAsset > ( ) ;
DirectoryInfo dir = new DirectoryInfo ( path ) ;
FileInfo [ ] assetInfoArr = dir . GetFiles ( "*.asset" ) ;
int subLen = Application . dataPath . Length - 6 ;
foreach ( var f in assetInfoArr ) {
string assetRelativePath = f . FullName . Substring ( subLen , f . FullName . Length - subLen ) . Replace ( "\\" , "/" ) ;
Object obj = AssetDatabase . LoadAssetAtPath ( assetRelativePath , typeof ( AtlasAsset ) ) ;
if ( obj ! = null )
arr . Add ( obj as AtlasAsset ) ;
}
return arr ;
}
static AtlasAsset IngestSpineAtlas ( TextAsset atlasText ) {
if ( atlasText = = null ) {
Debug . LogWarning ( "Atlas source cannot be null!" ) ;
return null ;
}
string primaryName = Path . GetFileNameWithoutExtension ( atlasText . name ) . Replace ( ".atlas" , "" ) ;
string assetPath = Path . GetDirectoryName ( AssetDatabase . GetAssetPath ( atlasText ) ) ;
string atlasPath = assetPath + "/" + primaryName + "_Atlas.asset" ;
AtlasAsset atlasAsset = ( AtlasAsset ) AssetDatabase . LoadAssetAtPath ( atlasPath , typeof ( AtlasAsset ) ) ;
List < Material > vestigialMaterials = new List < Material > ( ) ;
if ( atlasAsset = = null )
atlasAsset = AtlasAsset . CreateInstance < AtlasAsset > ( ) ;
else {
foreach ( Material m in atlasAsset . materials )
vestigialMaterials . Add ( m ) ;
}
protectFromStackGarbageCollection . Add ( atlasAsset ) ;
atlasAsset . atlasFile = atlasText ;
//strip CR
string atlasStr = atlasText . text ;
atlasStr = atlasStr . Replace ( "\r" , "" ) ;
string [ ] atlasLines = atlasStr . Split ( '\n' ) ;
List < string > pageFiles = new List < string > ( ) ;
for ( int i = 0 ; i < atlasLines . Length - 1 ; i + + ) {
if ( atlasLines [ i ] . Trim ( ) . Length = = 0 )
pageFiles . Add ( atlasLines [ i + 1 ] . Trim ( ) ) ;
}
var populatingMaterials = new List < Material > ( pageFiles . Count ) ; //atlasAsset.materials = new Material[pageFiles.Count];
for ( int i = 0 ; i < pageFiles . Count ; i + + ) {
string texturePath = assetPath + "/" + pageFiles [ i ] ;
Texture2D texture = ( Texture2D ) AssetDatabase . LoadAssetAtPath ( texturePath , typeof ( Texture2D ) ) ;
if ( setTextureImporterSettings ) {
TextureImporter texImporter = ( TextureImporter ) TextureImporter . GetAtPath ( texturePath ) ;
if ( texImporter = = null ) {
Debug . LogWarning ( string . Format ( "{0} ::: Texture asset \"{1}\" not found. Skipping. Please check your atlas file for renamed files." , atlasAsset . name , texturePath ) ) ;
continue ;
}
texImporter . textureCompression = TextureImporterCompression . Uncompressed ;
texImporter . alphaSource = TextureImporterAlphaSource . FromInput ;
texImporter . mipmapEnabled = false ;
texImporter . alphaIsTransparency = false ; // Prevent the texture importer from applying bleed to the transparent parts for PMA.
texImporter . spriteImportMode = SpriteImportMode . None ;
texImporter . maxTextureSize = 2048 ;
EditorUtility . SetDirty ( texImporter ) ;
AssetDatabase . ImportAsset ( texturePath ) ;
AssetDatabase . SaveAssets ( ) ;
}
string pageName = Path . GetFileNameWithoutExtension ( pageFiles [ i ] ) ;
//because this looks silly
if ( pageName = = primaryName & & pageFiles . Count = = 1 )
pageName = "Material" ;
string materialPath = assetPath + "/" + primaryName + "_" + pageName + ".mat" ;
Material mat = ( Material ) AssetDatabase . LoadAssetAtPath ( materialPath , typeof ( Material ) ) ;
if ( mat = = null ) {
mat = new Material ( Shader . Find ( defaultShader ) ) ;
AssetDatabase . CreateAsset ( mat , materialPath ) ;
} else {
vestigialMaterials . Remove ( mat ) ;
}
mat . mainTexture = texture ;
EditorUtility . SetDirty ( mat ) ;
AssetDatabase . SaveAssets ( ) ;
populatingMaterials . Add ( mat ) ; //atlasAsset.materials[i] = mat;
}
atlasAsset . materials = populatingMaterials . ToArray ( ) ;
for ( int i = 0 ; i < vestigialMaterials . Count ; i + + )
AssetDatabase . DeleteAsset ( AssetDatabase . GetAssetPath ( vestigialMaterials [ i ] ) ) ;
if ( AssetDatabase . GetAssetPath ( atlasAsset ) = = "" )
AssetDatabase . CreateAsset ( atlasAsset , atlasPath ) ;
else
atlasAsset . Clear ( ) ;
EditorUtility . SetDirty ( atlasAsset ) ;
AssetDatabase . SaveAssets ( ) ;
if ( pageFiles . Count ! = atlasAsset . materials . Length )
Debug . LogWarning ( string . Format ( "{0} ::: Not all atlas pages were imported. If you rename your image files, please make sure you also edit the filenames specified in the atlas file." , atlasAsset . name ) ) ;
else
Debug . Log ( string . Format ( "{0} ::: Imported with {1} material" , atlasAsset . name , atlasAsset . materials . Length ) ) ;
// Iterate regions and bake marked.
Atlas atlas = atlasAsset . GetAtlas ( ) ;
FieldInfo field = typeof ( Atlas ) . GetField ( "regions" , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . NonPublic ) ;
List < AtlasRegion > regions = ( List < AtlasRegion > ) field . GetValue ( atlas ) ;
string atlasAssetPath = AssetDatabase . GetAssetPath ( atlasAsset ) ;
string atlasAssetDirPath = Path . GetDirectoryName ( atlasAssetPath ) ;
string bakedDirPath = Path . Combine ( atlasAssetDirPath , atlasAsset . name ) ;
bool hasBakedRegions = false ;
for ( int i = 0 ; i < regions . Count ; i + + ) {
AtlasRegion region = regions [ i ] ;
string bakedPrefabPath = Path . Combine ( bakedDirPath , SpineEditorUtilities . GetPathSafeName ( region . name ) + ".prefab" ) . Replace ( "\\" , "/" ) ;
GameObject prefab = ( GameObject ) AssetDatabase . LoadAssetAtPath ( bakedPrefabPath , typeof ( GameObject ) ) ;
if ( prefab ! = null ) {
BakeRegion ( atlasAsset , region , false ) ;
hasBakedRegions = true ;
}
}
if ( hasBakedRegions ) {
AssetDatabase . SaveAssets ( ) ;
AssetDatabase . Refresh ( ) ;
}
protectFromStackGarbageCollection . Remove ( atlasAsset ) ;
return ( AtlasAsset ) AssetDatabase . LoadAssetAtPath ( atlasPath , typeof ( AtlasAsset ) ) ;
}
# endregion
#region Bake Atlas Region
public static GameObject BakeRegion ( AtlasAsset atlasAsset , AtlasRegion region , bool autoSave = true ) {
Atlas atlas = atlasAsset . GetAtlas ( ) ;
string atlasAssetPath = AssetDatabase . GetAssetPath ( atlasAsset ) ;
string atlasAssetDirPath = Path . GetDirectoryName ( atlasAssetPath ) ;
string bakedDirPath = Path . Combine ( atlasAssetDirPath , atlasAsset . name ) ;
string bakedPrefabPath = Path . Combine ( bakedDirPath , GetPathSafeName ( region . name ) + ".prefab" ) . Replace ( "\\" , "/" ) ;
GameObject prefab = ( GameObject ) AssetDatabase . LoadAssetAtPath ( bakedPrefabPath , typeof ( GameObject ) ) ;
GameObject root ;
Mesh mesh ;
bool isNewPrefab = false ;
if ( ! Directory . Exists ( bakedDirPath ) )
Directory . CreateDirectory ( bakedDirPath ) ;
if ( prefab = = null ) {
root = new GameObject ( "temp" , typeof ( MeshFilter ) , typeof ( MeshRenderer ) ) ;
prefab = PrefabUtility . CreatePrefab ( bakedPrefabPath , root ) ;
isNewPrefab = true ;
Object . DestroyImmediate ( root ) ;
}
mesh = ( Mesh ) AssetDatabase . LoadAssetAtPath ( bakedPrefabPath , typeof ( Mesh ) ) ;
Material mat = null ;
mesh = atlasAsset . GenerateMesh ( region . name , mesh , out mat ) ;
if ( isNewPrefab ) {
AssetDatabase . AddObjectToAsset ( mesh , prefab ) ;
prefab . GetComponent < MeshFilter > ( ) . sharedMesh = mesh ;
}
EditorUtility . SetDirty ( mesh ) ;
EditorUtility . SetDirty ( prefab ) ;
if ( autoSave ) {
AssetDatabase . SaveAssets ( ) ;
AssetDatabase . Refresh ( ) ;
}
prefab . GetComponent < MeshRenderer > ( ) . sharedMaterial = mat ;
return prefab ;
}
# endregion
#region Import SkeletonData (json or binary)
public const string SkeletonDataSuffix = "_SkeletonData" ;
static SkeletonDataAsset IngestSpineProject ( TextAsset spineJson , params AtlasAsset [ ] atlasAssets ) {
string primaryName = Path . GetFileNameWithoutExtension ( spineJson . name ) ;
string assetPath = Path . GetDirectoryName ( AssetDatabase . GetAssetPath ( spineJson ) ) ;
string filePath = assetPath + "/" + primaryName + SkeletonDataSuffix + ".asset" ;
#if SPINE_TK2D
if ( spineJson ! = null ) {
SkeletonDataAsset skeletonDataAsset = ( SkeletonDataAsset ) AssetDatabase . LoadAssetAtPath ( filePath , typeof ( SkeletonDataAsset ) ) ;
if ( skeletonDataAsset = = null ) {
skeletonDataAsset = SkeletonDataAsset . CreateInstance < SkeletonDataAsset > ( ) ;
skeletonDataAsset . skeletonJSON = spineJson ;
skeletonDataAsset . fromAnimation = new string [ 0 ] ;
skeletonDataAsset . toAnimation = new string [ 0 ] ;
skeletonDataAsset . duration = new float [ 0 ] ;
skeletonDataAsset . defaultMix = defaultMix ;
skeletonDataAsset . scale = defaultScale ;
AssetDatabase . CreateAsset ( skeletonDataAsset , filePath ) ;
AssetDatabase . SaveAssets ( ) ;
} else {
skeletonDataAsset . Clear ( ) ;
skeletonDataAsset . GetSkeletonData ( true ) ;
}
return skeletonDataAsset ;
} else {
EditorUtility . DisplayDialog ( "Error!" , "Tried to ingest null Spine data." , "OK" ) ;
return null ;
}
# else
if ( spineJson ! = null & & atlasAssets ! = null ) {
SkeletonDataAsset skeletonDataAsset = ( SkeletonDataAsset ) AssetDatabase . LoadAssetAtPath ( filePath , typeof ( SkeletonDataAsset ) ) ;
if ( skeletonDataAsset = = null ) {
skeletonDataAsset = ScriptableObject . CreateInstance < SkeletonDataAsset > ( ) ; {
skeletonDataAsset . atlasAssets = atlasAssets ;
skeletonDataAsset . skeletonJSON = spineJson ;
skeletonDataAsset . defaultMix = defaultMix ;
skeletonDataAsset . scale = defaultScale ;
}
AssetDatabase . CreateAsset ( skeletonDataAsset , filePath ) ;
AssetDatabase . SaveAssets ( ) ;
} else {
skeletonDataAsset . atlasAssets = atlasAssets ;
skeletonDataAsset . Clear ( ) ;
skeletonDataAsset . GetSkeletonData ( true ) ;
}
return skeletonDataAsset ;
} else {
EditorUtility . DisplayDialog ( "Error!" , "Must specify both Spine JSON and AtlasAsset array" , "OK" ) ;
return null ;
}
# endif
}
# endregion
#region SkeletonDataFileValidator
internal static class SkeletonDataFileValidator {
static int [ ] [ ] compatibleBinaryVersions = { new [ ] { 3 , 6 , 0 } , new [ ] { 3 , 5 , 0 } } ;
static int [ ] [ ] compatibleJsonVersions = { new [ ] { 3 , 6 , 0 } , new [ ] { 3 , 7 , 0 } , new [ ] { 3 , 5 , 0 } } ;
//static bool isFixVersionRequired = false;
public static bool CheckForValidSkeletonData ( string skeletonJSONPath ) {
string dir = Path . GetDirectoryName ( skeletonJSONPath ) ;
TextAsset textAsset = ( TextAsset ) AssetDatabase . LoadAssetAtPath ( skeletonJSONPath , typeof ( TextAsset ) ) ;
DirectoryInfo dirInfo = new DirectoryInfo ( dir ) ;
FileInfo [ ] files = dirInfo . GetFiles ( "*.asset" ) ;
foreach ( var path in files ) {
string localPath = dir + "/" + path . Name ;
var obj = AssetDatabase . LoadAssetAtPath ( localPath , typeof ( Object ) ) ;
var skeletonDataAsset = obj as SkeletonDataAsset ;
if ( skeletonDataAsset ! = null & & skeletonDataAsset . skeletonJSON = = textAsset )
return true ;
}
return false ;
}
public static bool IsSpineData ( TextAsset asset ) {
if ( asset = = null )
return false ;
bool isSpineData = false ;
string rawVersion = null ;
int [ ] [ ] compatibleVersions ;
if ( asset . name . Contains ( ".skel" ) ) {
try {
rawVersion = SkeletonBinary . GetVersionString ( new MemoryStream ( asset . bytes ) ) ;
isSpineData = ! ( string . IsNullOrEmpty ( rawVersion ) ) ;
compatibleVersions = compatibleBinaryVersions ;
} catch ( System . Exception e ) {
Debug . LogErrorFormat ( "Failed to read '{0}'. It is likely not a binary Spine SkeletonData file.\n{1}" , asset . name , e ) ;
return false ;
}
} else {
object obj = Json . Deserialize ( new StringReader ( asset . text ) ) ;
if ( obj = = null ) {
Debug . LogErrorFormat ( "'{0}' is not valid JSON." , asset . name ) ;
return false ;
}
var root = obj as Dictionary < string , object > ;
if ( root = = null ) {
Debug . LogError ( "Parser returned an incorrect type." ) ;
return false ;
}
isSpineData = root . ContainsKey ( "skeleton" ) ;
if ( isSpineData ) {
var skeletonInfo = ( Dictionary < string , object > ) root [ "skeleton" ] ;
object jv ;
skeletonInfo . TryGetValue ( "spine" , out jv ) ;
rawVersion = jv as string ;
}
compatibleVersions = compatibleJsonVersions ;
}
// Version warning
if ( isSpineData ) {
string primaryRuntimeVersionDebugString = compatibleVersions [ 0 ] [ 0 ] + "." + compatibleVersions [ 0 ] [ 1 ] ;
if ( string . IsNullOrEmpty ( rawVersion ) ) {
Debug . LogWarningFormat ( "Skeleton '{0}' has no version information. It may be incompatible with your runtime version: spine-unity v{1}" , asset . name , primaryRuntimeVersionDebugString ) ;
} else {
string [ ] versionSplit = rawVersion . Split ( '.' ) ;
bool match = false ;
foreach ( var version in compatibleVersions ) {
bool primaryMatch = version [ 0 ] = = int . Parse ( versionSplit [ 0 ] ) ;
bool secondaryMatch = version [ 1 ] = = int . Parse ( versionSplit [ 1 ] ) ;
// if (isFixVersionRequired) secondaryMatch &= version[2] <= int.Parse(jsonVersionSplit[2]);
if ( primaryMatch & & secondaryMatch ) {
match = true ;
break ;
}
}
if ( ! match )
Debug . LogWarningFormat ( "Skeleton '{0}' (exported with Spine {1}) may be incompatible with your runtime version: spine-unity v{2}" , asset . name , rawVersion , primaryRuntimeVersionDebugString ) ;
}
}
return isSpineData ;
}
}
# endregion
#region SkeletonAnimation Menu
public static void IngestAdvancedRenderSettings ( SkeletonRenderer skeletonRenderer ) {
const string PMAShaderQuery = "Spine/Skeleton" ;
const string TintBlackShaderQuery = "Tint Black" ;
if ( skeletonRenderer = = null ) return ;
var skeletonDataAsset = skeletonRenderer . skeletonDataAsset ;
if ( skeletonDataAsset = = null ) return ;
bool pmaVertexColors = false ;
bool tintBlack = false ;
foreach ( AtlasAsset atlasAsset in skeletonDataAsset . atlasAssets ) {
if ( ! pmaVertexColors ) {
foreach ( Material m in atlasAsset . materials ) {
if ( m . shader . name . Contains ( PMAShaderQuery ) ) {
pmaVertexColors = true ;
break ;
}
}
}
if ( ! tintBlack ) {
foreach ( Material m in atlasAsset . materials ) {
if ( m . shader . name . Contains ( TintBlackShaderQuery ) ) {
tintBlack = true ;
break ;
}
}
}
}
skeletonRenderer . pmaVertexColors = pmaVertexColors ;
skeletonRenderer . tintBlack = tintBlack ;
}
public static SkeletonAnimation InstantiateSkeletonAnimation ( SkeletonDataAsset skeletonDataAsset , string skinName , bool destroyInvalid = true ) {
var skeletonData = skeletonDataAsset . GetSkeletonData ( true ) ;
var skin = skeletonData ! = null ? skeletonData . FindSkin ( skinName ) : null ;
return InstantiateSkeletonAnimation ( skeletonDataAsset , skin , destroyInvalid ) ;
}
public static SkeletonAnimation InstantiateSkeletonAnimation ( SkeletonDataAsset skeletonDataAsset , Skin skin = null , bool destroyInvalid = true ) {
SkeletonData data = skeletonDataAsset . GetSkeletonData ( true ) ;
if ( data = = null ) {
for ( int i = 0 ; i < skeletonDataAsset . atlasAssets . Length ; i + + ) {
string reloadAtlasPath = AssetDatabase . GetAssetPath ( skeletonDataAsset . atlasAssets [ i ] ) ;
skeletonDataAsset . atlasAssets [ i ] = ( AtlasAsset ) AssetDatabase . LoadAssetAtPath ( reloadAtlasPath , typeof ( AtlasAsset ) ) ;
}
data = skeletonDataAsset . GetSkeletonData ( false ) ;
}
if ( data = = null ) {
Debug . LogWarning ( "InstantiateSkeletonAnimation tried to instantiate a skeleton from an invalid SkeletonDataAsset." ) ;
return null ;
}
string spineGameObjectName = string . Format ( "Spine GameObject ({0})" , skeletonDataAsset . name . Replace ( "_SkeletonData" , "" ) ) ;
GameObject go = new GameObject ( spineGameObjectName , typeof ( MeshFilter ) , typeof ( MeshRenderer ) , typeof ( SkeletonAnimation ) ) ;
SkeletonAnimation newSkeletonAnimation = go . GetComponent < SkeletonAnimation > ( ) ;
newSkeletonAnimation . skeletonDataAsset = skeletonDataAsset ;
IngestAdvancedRenderSettings ( newSkeletonAnimation ) ;
try {
newSkeletonAnimation . Initialize ( false ) ;
} catch ( System . Exception e ) {
if ( destroyInvalid ) {
Debug . LogWarning ( "Editor-instantiated SkeletonAnimation threw an Exception. Destroying GameObject to prevent orphaned GameObject." ) ;
GameObject . DestroyImmediate ( go ) ;
}
throw e ;
}
// Set Defaults
bool noSkins = data . DefaultSkin = = null & & ( data . Skins = = null | | data . Skins . Count = = 0 ) ; // Support attachmentless/skinless SkeletonData.
skin = skin ? ? data . DefaultSkin ? ? ( noSkins ? null : data . Skins . Items [ 0 ] ) ;
if ( skin ! = null ) {
newSkeletonAnimation . initialSkinName = skin . Name ;
newSkeletonAnimation . skeleton . SetSkin ( skin ) ;
}
newSkeletonAnimation . zSpacing = defaultZSpacing ;
newSkeletonAnimation . skeleton . Update ( 0 ) ;
newSkeletonAnimation . state . Update ( 0 ) ;
newSkeletonAnimation . state . Apply ( newSkeletonAnimation . skeleton ) ;
newSkeletonAnimation . skeleton . UpdateWorldTransform ( ) ;
return newSkeletonAnimation ;
}
# endregion
#region SkeletonAnimator
#if SPINE_SKELETONANIMATOR
static void UpdateMecanimClips ( SkeletonDataAsset skeletonDataAsset ) {
if ( skeletonDataAsset . controller = = null )
return ;
SkeletonBaker . GenerateMecanimAnimationClips ( skeletonDataAsset ) ;
}
public static SkeletonAnimator InstantiateSkeletonAnimator ( SkeletonDataAsset skeletonDataAsset , string skinName ) {
return InstantiateSkeletonAnimator ( skeletonDataAsset , skeletonDataAsset . GetSkeletonData ( true ) . FindSkin ( skinName ) ) ;
}
public static SkeletonAnimator InstantiateSkeletonAnimator ( SkeletonDataAsset skeletonDataAsset , Skin skin = null ) {
string spineGameObjectName = string . Format ( "Spine Mecanim GameObject ({0})" , skeletonDataAsset . name . Replace ( "_SkeletonData" , "" ) ) ;
GameObject go = new GameObject ( spineGameObjectName , typeof ( MeshFilter ) , typeof ( MeshRenderer ) , typeof ( Animator ) , typeof ( SkeletonAnimator ) ) ;
if ( skeletonDataAsset . controller = = null ) {
SkeletonBaker . GenerateMecanimAnimationClips ( skeletonDataAsset ) ;
Debug . Log ( string . Format ( "Mecanim controller was automatically generated and assigned for {0}" , skeletonDataAsset . name ) ) ;
}
go . GetComponent < Animator > ( ) . runtimeAnimatorController = skeletonDataAsset . controller ;
SkeletonAnimator anim = go . GetComponent < SkeletonAnimator > ( ) ;
anim . skeletonDataAsset = skeletonDataAsset ;
IngestAdvancedRenderSettings ( anim ) ;
SkeletonData data = skeletonDataAsset . GetSkeletonData ( true ) ;
if ( data = = null ) {
for ( int i = 0 ; i < skeletonDataAsset . atlasAssets . Length ; i + + ) {
string reloadAtlasPath = AssetDatabase . GetAssetPath ( skeletonDataAsset . atlasAssets [ i ] ) ;
skeletonDataAsset . atlasAssets [ i ] = ( AtlasAsset ) AssetDatabase . LoadAssetAtPath ( reloadAtlasPath , typeof ( AtlasAsset ) ) ;
}
data = skeletonDataAsset . GetSkeletonData ( true ) ;
}
// Set defaults
skin = skin ? ? data . DefaultSkin ? ? data . Skins . Items [ 0 ] ;
anim . zSpacing = defaultZSpacing ;
anim . Initialize ( false ) ;
anim . skeleton . SetSkin ( skin ) ;
anim . initialSkinName = skin . Name ;
anim . skeleton . Update ( 0 ) ;
anim . skeleton . UpdateWorldTransform ( ) ;
anim . LateUpdate ( ) ;
return anim ;
}
# endif
# endregion
#region SpineTK2DEditorUtility
internal static class SpineTK2DEditorUtility {
const string SPINE_TK2D_DEFINE = "SPINE_TK2D" ;
static bool IsInvalidGroup ( BuildTargetGroup group ) {
int gi = ( int ) group ;
return
gi = = 15 | | gi = = 16
| |
group = = BuildTargetGroup . Unknown ;
}
internal static void EnableTK2D ( ) {
bool added = false ;
foreach ( BuildTargetGroup group in System . Enum . GetValues ( typeof ( BuildTargetGroup ) ) ) {
if ( IsInvalidGroup ( group ) )
continue ;
string defines = PlayerSettings . GetScriptingDefineSymbolsForGroup ( group ) ;
if ( ! defines . Contains ( SPINE_TK2D_DEFINE ) ) {
added = true ;
if ( defines . EndsWith ( ";" , System . StringComparison . Ordinal ) )
defines = defines + SPINE_TK2D_DEFINE ;
else
defines = defines + ";" + SPINE_TK2D_DEFINE ;
PlayerSettings . SetScriptingDefineSymbolsForGroup ( group , defines ) ;
}
}
if ( added ) {
Debug . LogWarning ( "Setting Scripting Define Symbol " + SPINE_TK2D_DEFINE ) ;
} else {
Debug . LogWarning ( "Already Set Scripting Define Symbol " + SPINE_TK2D_DEFINE ) ;
}
}
internal static void DisableTK2D ( ) {
bool removed = false ;
foreach ( BuildTargetGroup group in System . Enum . GetValues ( typeof ( BuildTargetGroup ) ) ) {
if ( IsInvalidGroup ( group ) )
continue ;
string defines = PlayerSettings . GetScriptingDefineSymbolsForGroup ( group ) ;
if ( defines . Contains ( SPINE_TK2D_DEFINE ) ) {
removed = true ;
if ( defines . Contains ( SPINE_TK2D_DEFINE + ";" ) )
defines = defines . Replace ( SPINE_TK2D_DEFINE + ";" , "" ) ;
else
defines = defines . Replace ( SPINE_TK2D_DEFINE , "" ) ;
PlayerSettings . SetScriptingDefineSymbolsForGroup ( group , defines ) ;
}
}
if ( removed ) {
Debug . LogWarning ( "Removing Scripting Define Symbol " + SPINE_TK2D_DEFINE ) ;
} else {
Debug . LogWarning ( "Already Removed Scripting Define Symbol " + SPINE_TK2D_DEFINE ) ;
}
}
}
# endregion
public static string GetPathSafeName ( string name ) {
foreach ( char c in System . IO . Path . GetInvalidFileNameChars ( ) ) { // Doesn't handle more obscure file name limitations.
name = name . Replace ( c , '_' ) ;
}
return name ;
}
}
public static class SpineHandles {
internal static float handleScale = 1f ;
public static Color BoneColor { get { return new Color ( 0.8f , 0.8f , 0.8f , 0.4f ) ; } }
public static Color PathColor { get { return new Color ( 254 / 255f , 127 / 255f , 0 ) ; } }
public static Color TransformContraintColor { get { return new Color ( 170 / 255f , 226 / 255f , 35 / 255f ) ; } }
public static Color IkColor { get { return new Color ( 228 / 255f , 90 / 255f , 43 / 255f ) ; } }
public static Color PointColor { get { return new Color ( 1f , 1f , 0f , 1f ) ; } }
static Vector3 [ ] _boneMeshVerts = {
new Vector3 ( 0 , 0 , 0 ) ,
new Vector3 ( 0.1f , 0.1f , 0 ) ,
new Vector3 ( 1 , 0 , 0 ) ,
new Vector3 ( 0.1f , - 0.1f , 0 )
} ;
static Mesh _boneMesh ;
public static Mesh BoneMesh {
get {
if ( _boneMesh = = null ) {
_boneMesh = new Mesh {
vertices = _boneMeshVerts ,
uv = new Vector2 [ 4 ] ,
triangles = new [ ] { 0 , 1 , 2 , 2 , 3 , 0 }
} ;
_boneMesh . RecalculateBounds ( ) ;
_boneMesh . RecalculateNormals ( ) ;
}
return _boneMesh ;
}
}
static Mesh _arrowheadMesh ;
public static Mesh ArrowheadMesh {
get {
if ( _arrowheadMesh = = null ) {
_arrowheadMesh = new Mesh {
vertices = new [ ] {
new Vector3 ( 0 , 0 ) ,
new Vector3 ( - 0.1f , 0.05f ) ,
new Vector3 ( - 0.1f , - 0.05f )
} ,
uv = new Vector2 [ 3 ] ,
triangles = new [ ] { 0 , 1 , 2 }
} ;
_arrowheadMesh . RecalculateBounds ( ) ;
_arrowheadMesh . RecalculateNormals ( ) ;
}
return _arrowheadMesh ;
}
}
static Material _boneMaterial ;
static Material BoneMaterial {
get {
if ( _boneMaterial = = null ) {
_boneMaterial = new Material ( Shader . Find ( "Hidden/Spine/Bones" ) ) ;
_boneMaterial . SetColor ( "_Color" , SpineHandles . BoneColor ) ;
}
return _boneMaterial ;
}
}
public static Material GetBoneMaterial ( ) {
BoneMaterial . SetColor ( "_Color" , SpineHandles . BoneColor ) ;
return BoneMaterial ;
}
public static Material GetBoneMaterial ( Color color ) {
BoneMaterial . SetColor ( "_Color" , color ) ;
return BoneMaterial ;
}
static Material _ikMaterial ;
public static Material IKMaterial {
get {
if ( _ikMaterial = = null ) {
_ikMaterial = new Material ( Shader . Find ( "Hidden/Spine/Bones" ) ) ;
_ikMaterial . SetColor ( "_Color" , SpineHandles . IkColor ) ;
}
return _ikMaterial ;
}
}
static GUIStyle _boneNameStyle ;
public static GUIStyle BoneNameStyle {
get {
if ( _boneNameStyle = = null ) {
_boneNameStyle = new GUIStyle ( EditorStyles . whiteMiniLabel ) {
alignment = TextAnchor . MiddleCenter ,
stretchWidth = true ,
padding = new RectOffset ( 0 , 0 , 0 , 0 ) ,
contentOffset = new Vector2 ( - 5f , 0f )
} ;
}
return _boneNameStyle ;
}
}
static GUIStyle _pathNameStyle ;
public static GUIStyle PathNameStyle {
get {
if ( _pathNameStyle = = null ) {
_pathNameStyle = new GUIStyle ( SpineHandles . BoneNameStyle ) ;
_pathNameStyle . normal . textColor = SpineHandles . PathColor ;
}
return _pathNameStyle ;
}
}
static GUIStyle _pointNameStyle ;
public static GUIStyle PointNameStyle {
get {
if ( _pointNameStyle = = null ) {
_pointNameStyle = new GUIStyle ( SpineHandles . BoneNameStyle ) ;
_pointNameStyle . normal . textColor = SpineHandles . PointColor ;
}
return _pointNameStyle ;
}
}
public static void DrawBoneNames ( Transform transform , Skeleton skeleton , float positionScale = 1f ) {
GUIStyle style = BoneNameStyle ;
foreach ( Bone b in skeleton . Bones ) {
var pos = new Vector3 ( b . WorldX * positionScale , b . WorldY * positionScale , 0 ) + ( new Vector3 ( b . A , b . C ) * ( b . Data . Length * 0.5f ) ) ;
pos = transform . TransformPoint ( pos ) ;
Handles . Label ( pos , b . Data . Name , style ) ;
}
}
public static void DrawBones ( Transform transform , Skeleton skeleton , float positionScale = 1f ) {
float boneScale = 1.8f ; // Draw the root bone largest;
DrawCrosshairs2D ( skeleton . Bones . Items [ 0 ] . GetWorldPosition ( transform ) , 0.08f , positionScale ) ;
foreach ( Bone b in skeleton . Bones ) {
DrawBone ( transform , b , boneScale , positionScale ) ;
boneScale = 1f ;
}
}
static Vector3 [ ] _boneWireBuffer = new Vector3 [ 5 ] ;
static Vector3 [ ] GetBoneWireBuffer ( Matrix4x4 m ) {
for ( int i = 0 , n = _boneMeshVerts . Length ; i < n ; i + + )
_boneWireBuffer [ i ] = m . MultiplyPoint ( _boneMeshVerts [ i ] ) ;
_boneWireBuffer [ 4 ] = _boneWireBuffer [ 0 ] ; // closed polygon.
return _boneWireBuffer ;
}
public static void DrawBoneWireframe ( Transform transform , Bone b , Color color , float skeletonRenderScale = 1f ) {
Handles . color = color ;
var pos = new Vector3 ( b . WorldX * skeletonRenderScale , b . WorldY * skeletonRenderScale , 0 ) ;
float length = b . Data . Length ;
if ( length > 0 ) {
Quaternion rot = Quaternion . Euler ( 0 , 0 , b . WorldRotationX ) ;
Vector3 scale = Vector3 . one * length * b . WorldScaleX * skeletonRenderScale ;
const float my = 1.5f ;
scale . y * = ( SpineHandles . handleScale + 1 ) * 0.5f ;
scale . y = Mathf . Clamp ( scale . x , - my * skeletonRenderScale , my * skeletonRenderScale ) ;
Handles . DrawPolyLine ( GetBoneWireBuffer ( transform . localToWorldMatrix * Matrix4x4 . TRS ( pos , rot , scale ) ) ) ;
var wp = transform . TransformPoint ( pos ) ;
DrawBoneCircle ( wp , color , transform . forward , skeletonRenderScale ) ;
} else {
var wp = transform . TransformPoint ( pos ) ;
DrawBoneCircle ( wp , color , transform . forward , skeletonRenderScale ) ;
}
}
public static void DrawBone ( Transform transform , Bone b , float boneScale , float skeletonRenderScale = 1f ) {
var pos = new Vector3 ( b . WorldX * skeletonRenderScale , b . WorldY * skeletonRenderScale , 0 ) ;
float length = b . Data . Length ;
if ( length > 0 ) {
Quaternion rot = Quaternion . Euler ( 0 , 0 , b . WorldRotationX ) ;
Vector3 scale = Vector3 . one * length * b . WorldScaleX * skeletonRenderScale ;
const float my = 1.5f ;
scale . y * = ( SpineHandles . handleScale + 1f ) * 0.5f ;
scale . y = Mathf . Clamp ( scale . x , - my * skeletonRenderScale , my * skeletonRenderScale ) ;
SpineHandles . GetBoneMaterial ( ) . SetPass ( 0 ) ;
Graphics . DrawMeshNow ( SpineHandles . BoneMesh , transform . localToWorldMatrix * Matrix4x4 . TRS ( pos , rot , scale ) ) ;
} else {
var wp = transform . TransformPoint ( pos ) ;
DrawBoneCircle ( wp , SpineHandles . BoneColor , transform . forward , boneScale * skeletonRenderScale ) ;
}
}
public static void DrawBone ( Transform transform , Bone b , float boneScale , Color color , float skeletonRenderScale = 1f ) {
var pos = new Vector3 ( b . WorldX * skeletonRenderScale , b . WorldY * skeletonRenderScale , 0 ) ;
float length = b . Data . Length ;
if ( length > 0 ) {
Quaternion rot = Quaternion . Euler ( 0 , 0 , b . WorldRotationX ) ;
Vector3 scale = Vector3 . one * length * b . WorldScaleX ;
const float my = 1.5f ;
scale . y * = ( SpineHandles . handleScale + 1f ) * 0.5f ;
scale . y = Mathf . Clamp ( scale . x , - my , my ) ;
SpineHandles . GetBoneMaterial ( color ) . SetPass ( 0 ) ;
Graphics . DrawMeshNow ( SpineHandles . BoneMesh , transform . localToWorldMatrix * Matrix4x4 . TRS ( pos , rot , scale ) ) ;
} else {
var wp = transform . TransformPoint ( pos ) ;
DrawBoneCircle ( wp , color , transform . forward , boneScale * skeletonRenderScale ) ;
}
}
public static void DrawPaths ( Transform transform , Skeleton skeleton ) {
foreach ( Slot s in skeleton . DrawOrder ) {
var p = s . Attachment as PathAttachment ;
if ( p ! = null ) SpineHandles . DrawPath ( s , p , transform , true ) ;
}
}
static float [ ] pathVertexBuffer ;
public static void DrawPath ( Slot s , PathAttachment p , Transform t , bool includeName ) {
int worldVerticesLength = p . WorldVerticesLength ;
if ( pathVertexBuffer = = null | | pathVertexBuffer . Length < worldVerticesLength )
pathVertexBuffer = new float [ worldVerticesLength ] ;
float [ ] pv = pathVertexBuffer ;
p . ComputeWorldVertices ( s , pv ) ;
var ocolor = Handles . color ;
Handles . color = SpineHandles . PathColor ;
Matrix4x4 m = t . localToWorldMatrix ;
const int step = 6 ;
int n = worldVerticesLength - step ;
Vector3 p0 , p1 , p2 , p3 ;
for ( int i = 2 ; i < n ; i + = step ) {
p0 = m . MultiplyPoint ( new Vector3 ( pv [ i ] , pv [ i + 1 ] ) ) ;
p1 = m . MultiplyPoint ( new Vector3 ( pv [ i + 2 ] , pv [ i + 3 ] ) ) ;
p2 = m . MultiplyPoint ( new Vector3 ( pv [ i + 4 ] , pv [ i + 5 ] ) ) ;
p3 = m . MultiplyPoint ( new Vector3 ( pv [ i + 6 ] , pv [ i + 7 ] ) ) ;
DrawCubicBezier ( p0 , p1 , p2 , p3 ) ;
}
n + = step ;
if ( p . Closed ) {
p0 = m . MultiplyPoint ( new Vector3 ( pv [ n - 4 ] , pv [ n - 3 ] ) ) ;
p1 = m . MultiplyPoint ( new Vector3 ( pv [ n - 2 ] , pv [ n - 1 ] ) ) ;
p2 = m . MultiplyPoint ( new Vector3 ( pv [ 0 ] , pv [ 1 ] ) ) ;
p3 = m . MultiplyPoint ( new Vector3 ( pv [ 2 ] , pv [ 3 ] ) ) ;
DrawCubicBezier ( p0 , p1 , p2 , p3 ) ;
}
const float endCapSize = 0.05f ;
Vector3 firstPoint = m . MultiplyPoint ( new Vector3 ( pv [ 2 ] , pv [ 3 ] ) ) ;
SpineHandles . DrawDot ( firstPoint , endCapSize ) ;
//if (!p.Closed) SpineHandles.DrawDot(m.MultiplyPoint(new Vector3(pv[n - 4], pv[n - 3])), endCapSize);
if ( includeName ) Handles . Label ( firstPoint + new Vector3 ( 0 , 0.1f ) , p . Name , PathNameStyle ) ;
Handles . color = ocolor ;
}
public static void DrawDot ( Vector3 position , float size ) {
Handles . DotHandleCap ( 0 , position , Quaternion . identity , size * HandleUtility . GetHandleSize ( position ) , EventType . Ignore ) ; //Handles.DotCap(0, position, Quaternion.identity, size * HandleUtility.GetHandleSize(position));
}
public static void DrawBoundingBoxes ( Transform transform , Skeleton skeleton ) {
foreach ( var slot in skeleton . Slots ) {
var bba = slot . Attachment as BoundingBoxAttachment ;
if ( bba ! = null ) SpineHandles . DrawBoundingBox ( slot , bba , transform ) ;
}
}
public static void DrawBoundingBox ( Slot slot , BoundingBoxAttachment box , Transform t ) {
if ( box . Vertices . Length < = 2 ) return ; // Handle cases where user creates a BoundingBoxAttachment but doesn't actually define it.
var worldVerts = new float [ box . WorldVerticesLength ] ;
box . ComputeWorldVertices ( slot , worldVerts ) ;
Handles . color = Color . green ;
Vector3 lastVert = Vector3 . zero ;
Vector3 vert = Vector3 . zero ;
Vector3 firstVert = t . TransformPoint ( new Vector3 ( worldVerts [ 0 ] , worldVerts [ 1 ] , 0 ) ) ;
for ( int i = 0 ; i < worldVerts . Length ; i + = 2 ) {
vert . x = worldVerts [ i ] ;
vert . y = worldVerts [ i + 1 ] ;
vert . z = 0 ;
vert = t . TransformPoint ( vert ) ;
if ( i > 0 )
Handles . DrawLine ( lastVert , vert ) ;
lastVert = vert ;
}
Handles . DrawLine ( lastVert , firstVert ) ;
}
public static void DrawPointAttachment ( Bone bone , PointAttachment pointAttachment , Transform skeletonTransform ) {
if ( bone = = null ) return ;
if ( pointAttachment = = null ) return ;
Vector2 localPos ;
pointAttachment . ComputeWorldPosition ( bone , out localPos . x , out localPos . y ) ;
float localRotation = pointAttachment . ComputeWorldRotation ( bone ) ;
Matrix4x4 m = Matrix4x4 . TRS ( localPos , Quaternion . Euler ( 0 , 0 , localRotation ) , Vector3 . one ) * Matrix4x4 . TRS ( Vector3 . right * 0.25f , Quaternion . identity , Vector3 . one ) ;
DrawBoneCircle ( skeletonTransform . TransformPoint ( localPos ) , SpineHandles . PointColor , Vector3 . back , 1.3f ) ;
DrawArrowhead ( skeletonTransform . localToWorldMatrix * m ) ;
}
public static void DrawConstraints ( Transform transform , Skeleton skeleton , float skeletonRenderScale = 1f ) {
Vector3 targetPos ;
Vector3 pos ;
bool active ;
Color handleColor ;
const float Thickness = 4f ;
Vector3 normal = transform . forward ;
// Transform Constraints
handleColor = SpineHandles . TransformContraintColor ;
foreach ( var tc in skeleton . TransformConstraints ) {
var targetBone = tc . Target ;
targetPos = targetBone . GetWorldPosition ( transform , skeletonRenderScale ) ;
if ( tc . TranslateMix > 0 ) {
if ( tc . TranslateMix ! = 1f ) {
Handles . color = handleColor ;
foreach ( var b in tc . Bones ) {
pos = b . GetWorldPosition ( transform , skeletonRenderScale ) ;
Handles . DrawDottedLine ( targetPos , pos , Thickness ) ;
}
}
SpineHandles . DrawBoneCircle ( targetPos , handleColor , normal , 1.3f * skeletonRenderScale ) ;
Handles . color = handleColor ;
SpineHandles . DrawCrosshairs ( targetPos , 0.2f , targetBone . A , targetBone . B , targetBone . C , targetBone . D , transform , skeletonRenderScale ) ;
}
}
// IK Constraints
handleColor = SpineHandles . IkColor ;
foreach ( var ikc in skeleton . IkConstraints ) {
Bone targetBone = ikc . Target ;
targetPos = targetBone . GetWorldPosition ( transform , skeletonRenderScale ) ;
var bones = ikc . Bones ;
active = ikc . Mix > 0 ;
if ( active ) {
pos = bones . Items [ 0 ] . GetWorldPosition ( transform , skeletonRenderScale ) ;
switch ( bones . Count ) {
case 1 : {
Handles . color = handleColor ;
Handles . DrawLine ( targetPos , pos ) ;
SpineHandles . DrawBoneCircle ( targetPos , handleColor , normal ) ;
var m = bones . Items [ 0 ] . GetMatrix4x4 ( ) ;
m . m03 = targetBone . WorldX * skeletonRenderScale ;
m . m13 = targetBone . WorldY * skeletonRenderScale ;
SpineHandles . DrawArrowhead ( transform . localToWorldMatrix * m ) ;
break ;
}
case 2 : {
Bone childBone = bones . Items [ 1 ] ;
Vector3 child = childBone . GetWorldPosition ( transform , skeletonRenderScale ) ;
Handles . color = handleColor ;
Handles . DrawLine ( child , pos ) ;
Handles . DrawLine ( targetPos , child ) ;
SpineHandles . DrawBoneCircle ( pos , handleColor , normal , 0.5f ) ;
SpineHandles . DrawBoneCircle ( child , handleColor , normal , 0.5f ) ;
SpineHandles . DrawBoneCircle ( targetPos , handleColor , normal ) ;
var m = childBone . GetMatrix4x4 ( ) ;
m . m03 = targetBone . WorldX * skeletonRenderScale ;
m . m13 = targetBone . WorldY * skeletonRenderScale ;
SpineHandles . DrawArrowhead ( transform . localToWorldMatrix * m ) ;
break ;
}
}
}
//Handles.Label(targetPos, ikc.Data.Name, SpineHandles.BoneNameStyle);
}
// Path Constraints
handleColor = SpineHandles . PathColor ;
foreach ( var pc in skeleton . PathConstraints ) {
active = pc . TranslateMix > 0 ;
if ( active )
foreach ( var b in pc . Bones )
SpineHandles . DrawBoneCircle ( b . GetWorldPosition ( transform , skeletonRenderScale ) , handleColor , normal , 1f * skeletonRenderScale ) ;
}
}
static void DrawCrosshairs2D ( Vector3 position , float scale , float skeletonRenderScale = 1f ) {
scale * = SpineHandles . handleScale * skeletonRenderScale ;
Handles . DrawLine ( position + new Vector3 ( - scale , 0 ) , position + new Vector3 ( scale , 0 ) ) ;
Handles . DrawLine ( position + new Vector3 ( 0 , - scale ) , position + new Vector3 ( 0 , scale ) ) ;
}
static void DrawCrosshairs ( Vector3 position , float scale , float a , float b , float c , float d , Transform transform , float skeletonRenderScale = 1f ) {
scale * = SpineHandles . handleScale * skeletonRenderScale ;
var xOffset = ( Vector3 ) ( new Vector2 ( a , c ) . normalized * scale ) ;
var yOffset = ( Vector3 ) ( new Vector2 ( b , d ) . normalized * scale ) ;
xOffset = transform . TransformDirection ( xOffset ) ;
yOffset = transform . TransformDirection ( yOffset ) ;
Handles . DrawLine ( position + xOffset , position - xOffset ) ;
Handles . DrawLine ( position + yOffset , position - yOffset ) ;
}
static void DrawArrowhead2D ( Vector3 pos , float localRotation , float scale = 1f ) {
scale * = SpineHandles . handleScale ;
SpineHandles . IKMaterial . SetPass ( 0 ) ;
Graphics . DrawMeshNow ( SpineHandles . ArrowheadMesh , Matrix4x4 . TRS ( pos , Quaternion . Euler ( 0 , 0 , localRotation ) , new Vector3 ( scale , scale , scale ) ) ) ;
}
static void DrawArrowhead ( Vector3 pos , Quaternion worldQuaternion ) {
Graphics . DrawMeshNow ( SpineHandles . ArrowheadMesh , pos , worldQuaternion , 0 ) ;
}
static void DrawArrowhead ( Matrix4x4 m ) {
float s = SpineHandles . handleScale ;
m . m00 * = s ;
m . m01 * = s ;
m . m02 * = s ;
m . m10 * = s ;
m . m11 * = s ;
m . m12 * = s ;
m . m20 * = s ;
m . m21 * = s ;
m . m22 * = s ;
SpineHandles . IKMaterial . SetPass ( 0 ) ;
Graphics . DrawMeshNow ( SpineHandles . ArrowheadMesh , m ) ;
}
static void DrawBoneCircle ( Vector3 pos , Color outlineColor , Vector3 normal , float scale = 1f ) {
scale * = SpineHandles . handleScale ;
Color o = Handles . color ;
Handles . color = outlineColor ;
float firstScale = 0.08f * scale ;
Handles . DrawSolidDisc ( pos , normal , firstScale ) ;
const float Thickness = 0.03f ;
float secondScale = firstScale - ( Thickness * SpineHandles . handleScale * scale ) ;
if ( secondScale > 0f ) {
Handles . color = new Color ( 0.3f , 0.3f , 0.3f , 0.5f ) ;
Handles . DrawSolidDisc ( pos , normal , secondScale ) ;
}
Handles . color = o ;
}
internal static void DrawCubicBezier ( Vector3 p0 , Vector3 p1 , Vector3 p2 , Vector3 p3 ) {
Handles . DrawBezier ( p0 , p3 , p1 , p2 , Handles . color , Texture2D . whiteTexture , 2f ) ;
// const float dotSize = 0.01f;
// Quaternion q = Quaternion.identity;
// Handles.DotCap(0, p0, q, dotSize);
// Handles.DotCap(0, p1, q, dotSize);
// Handles.DotCap(0, p2, q, dotSize);
// Handles.DotCap(0, p3, q, dotSize);
// Handles.DrawLine(p0, p1);
// Handles.DrawLine(p3, p2);
}
}
}