├── LICENSE ├── README.md └── RespectRectTransform ├── README.md └── SkeletonGraphicRespectRectTransform.cs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 한승훈(Han Seung-hun) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Here? 2 | Very Simple Unity Spine runtime support from ProvisGames 3 | 4 | ![image](https://user-images.githubusercontent.com/14087406/81164192-ebc5dc80-8fca-11ea-9f8a-e0ef44938523.png) 5 | 6 | 7 | - - - 8 | # Contents 9 | 1. [RespectRectTransform](https://github.com/ggzerosum/SpineUtility/tree/master/RespectRectTransform) 10 | Keep SkeletonGraphic tracking RectTranform's rect bounds. 11 | 12 | 13 | # ProvisGames? 14 | Indie Game Team from South Korea. 15 | 프로비스 게임즈는 대한민국 부산에서 꿈을 키우고있는 인디게임개발사입니다. 16 | -------------------------------------------------------------------------------- /RespectRectTransform/README.md: -------------------------------------------------------------------------------- 1 | # What is this? 2 | With this Component, your skeletongraphic will fit into RectTransform. 3 | Like this.. 4 | ![SpineUtility-RespectRectTransform](https://user-images.githubusercontent.com/14087406/81152100-97b3fb80-8fbc-11ea-8358-7d4827965724.gif) 5 | 6 | 7 | # How to Use? 8 | Just Attach component with skeletonGraphic. 9 | If skeletonGraphic already attached, this component will detect it. 10 | 11 | If not, or need to reset, Click reset button of context menu. 12 | ![image](https://user-images.githubusercontent.com/14087406/88248419-1bd47000-ccdc-11ea-89d5-52ca5847d138.png) 13 | 14 | 15 | # Description! 16 | ![image](https://user-images.githubusercontent.com/14087406/88248452-3a3a6b80-ccdc-11ea-97a7-7deb6e9e206b.png) 17 | 18 | ### skeletonGraphic 19 | find & bind skeletonGraphic you want to respect RectTransform 20 | 21 | ### PreserveAspectRatio 22 | Alway keep aspect ratio of cached size. 23 | when 1)attach or 2)reset this component or 3)press Fit&Cache Button mesh bound of spine automatically cached. 24 | 25 | ### SetToSetupPoseWhenScaling 26 | By default, SetToSetupPose not called when scalling. 27 | Related Issue #2. 28 | 29 | ### Fit & Cache Button 30 | Cache Current Mesh bound of current spine animation and rescale spine skeleton. 31 | You should be careful! Spine doesn't have fixed mesh bound. 32 | 33 | ### Resize Button 34 | You can call resize function manually. You can use this button in playmode. 35 | 36 | ### Stop Editor Update 37 | By Default, this component always executed in editor. If you have uncomfortable feeling to this behaviour, you can stop 38 | this by pressed button. 39 | 40 | After stop update, you should press Resize Button manually to visualize spine rescaling. 41 | 42 | # Caustion! 43 | ### This Script control Skeleton's Scale 44 | so, if you have certain script that control spine skeleton's scale, it can not perform properly. 45 | 46 | ### Spine doesn't have fixed mesh bound!! 47 | You should select animation that has mesh bound that you want to make as standard. 48 | 49 | # Test 50 | Tested in Unity 2019.3.0f 51 | Spine runtime 3.8 (Updated 2020-05-05) 52 | -------------------------------------------------------------------------------- /RespectRectTransform/SkeletonGraphicRespectRectTransform.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using Spine.Unity; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | #if UNITY_EDITOR 8 | using UnityEditor; 9 | #endif 10 | 11 | namespace ProvisGames.Core.SpineSupport 12 | { 13 | /// 14 | /// Make SkeletonGraphic fit into RectTransform 15 | /// 16 | [ExecuteAlways] 17 | [AddComponentMenu("Spine/RespectRectTranform")] 18 | public class SkeletonGraphicRespectRectTransform : MonoBehaviour 19 | { 20 | [SerializeField] private SkeletonGraphic skeletonGraphic; 21 | [SerializeField] private bool m_preserveAspectRatio = true; 22 | [Header("Call skeletonGraphic.SetToSetupPose when scale changed")] 23 | [SerializeField] private bool m_SetToSetupPoseWhenScaling = false; 24 | [HideInInspector] public bool m_isEditorUpdateStopped = false; 25 | 26 | // Cache initial bound & skeleton scale for standard of size change 27 | [HideInInspector] [SerializeField] private Rect m_initialRectWithBounds; 28 | [HideInInspector] [SerializeField] private Vector2 m_initialSpineSkeletonScale; 29 | 30 | private bool m_isInitialized = false; 31 | 32 | void Reset() 33 | { 34 | skeletonGraphic = this.GetComponent(); 35 | SetScale(1.0f, 1.0f); 36 | CacheRectTransformWithBounds(); 37 | } 38 | void Start() 39 | { 40 | this.Initialize(false); 41 | } 42 | void Update() 43 | { 44 | if (Application.isEditor && m_isEditorUpdateStopped) 45 | return; 46 | 47 | RespectRectTranform(); 48 | } 49 | 50 | void Initialize(bool overwrite) 51 | { 52 | if (m_isInitialized && !overwrite) 53 | return; 54 | 55 | skeletonGraphic.Initialize(false); 56 | 57 | m_isInitialized = true; 58 | } 59 | 60 | public bool RespectRectTranform() 61 | { 62 | Rect currentRect = this.skeletonGraphic.GetPixelAdjustedRect(); 63 | if (currentRect.size.sqrMagnitude > 0.0f && m_initialRectWithBounds.size.sqrMagnitude > 0.0f && m_initialSpineSkeletonScale.sqrMagnitude > 0.0f) 64 | { 65 | if (m_preserveAspectRatio) 66 | { 67 | Vector2 size = PreserveAspectRatio(currentRect, m_initialRectWithBounds); 68 | currentRect.width = size.x; 69 | currentRect.height = size.y; 70 | } 71 | 72 | float xDiff = currentRect.width / m_initialRectWithBounds.width; 73 | float yDiff = currentRect.height / m_initialRectWithBounds.height; 74 | 75 | SetScale(m_initialSpineSkeletonScale.x * xDiff, m_initialSpineSkeletonScale.y * yDiff); 76 | 77 | return true; 78 | } 79 | 80 | return false; 81 | } 82 | private Vector2 PreserveAspectRatio(Rect rect, Rect spine) 83 | { 84 | float spineAspectRatio = spine.width / spine.height; 85 | float rectAspectRatio = rect.width / rect.height; 86 | 87 | if (spineAspectRatio > rectAspectRatio) 88 | { 89 | float adjustedHeight = rect.width * (1.0f / spineAspectRatio); 90 | return new Vector2(rect.width, adjustedHeight); 91 | } 92 | else 93 | { 94 | float adjustedWidth = rect.height * spineAspectRatio; 95 | return new Vector2(adjustedWidth, rect.height); 96 | } 97 | } 98 | 99 | private void SetScale(float x, float y) 100 | { 101 | this.skeletonGraphic.Skeleton.ScaleX = x; 102 | this.skeletonGraphic.Skeleton.ScaleY = y; 103 | 104 | if (m_SetToSetupPoseWhenScaling) 105 | this.skeletonGraphic.Skeleton.SetToSetupPose(); 106 | 107 | this.skeletonGraphic.Skeleton.UpdateWorldTransform(); 108 | } 109 | 110 | public bool CacheRectTransformWithBounds() 111 | { 112 | bool result = MatchRectTransformWithBounds_lowVersionCompatablity(this.skeletonGraphic); 113 | if (result) CacheSize(); 114 | 115 | return result; 116 | } 117 | private void CacheSize() 118 | { 119 | m_initialRectWithBounds = this.skeletonGraphic.GetPixelAdjustedRect(); 120 | m_initialSpineSkeletonScale = new Vector2(this.skeletonGraphic.Skeleton.ScaleX, this.skeletonGraphic.Skeleton.ScaleY); 121 | } 122 | 123 | private bool MatchRectTransformWithBounds_lowVersionCompatablity(SkeletonGraphic skeletonGraphic) 124 | { 125 | skeletonGraphic.UpdateMesh(); 126 | 127 | Mesh mesh = skeletonGraphic.GetLastMesh(); 128 | if (mesh == null) 129 | { 130 | return false; 131 | } 132 | 133 | if (mesh.vertexCount == 0) 134 | { 135 | skeletonGraphic.rectTransform.sizeDelta = new Vector2(50f, 50f); 136 | skeletonGraphic.rectTransform.pivot = new Vector2(0.5f, 0.5f); 137 | return false; 138 | } 139 | 140 | mesh.RecalculateBounds(); 141 | var bounds = mesh.bounds; 142 | var size = bounds.size; 143 | var center = bounds.center; 144 | var p = new Vector2( 145 | 0.5f - (center.x / size.x), 146 | 0.5f - (center.y / size.y) 147 | ); 148 | 149 | skeletonGraphic.rectTransform.sizeDelta = size; 150 | skeletonGraphic.rectTransform.pivot = p; 151 | return true; 152 | } 153 | } 154 | 155 | #if UNITY_EDITOR 156 | [CustomEditor(typeof(SkeletonGraphicRespectRectTransform))] 157 | [CanEditMultipleObjects] 158 | internal class SkeletonGraphicRespectRectTransformDrawer : UnityEditor.Editor 159 | { 160 | private SerializedProperty m_isEditorUpdateStopped; 161 | 162 | protected virtual void OnDisable() 163 | { 164 | 165 | } 166 | protected virtual void OnEnable() 167 | { 168 | m_isEditorUpdateStopped = this.serializedObject.FindProperty("m_isEditorUpdateStopped"); 169 | } 170 | protected virtual void OnValidate() 171 | { 172 | 173 | } 174 | 175 | public sealed override void OnInspectorGUI() 176 | { 177 | EditorGUI.BeginChangeCheck(); 178 | this.serializedObject.UpdateIfRequiredOrScript(); 179 | 180 | DrawSerializedProperty(this.serializedObject); 181 | DrawInspector(); 182 | 183 | if (EditorGUI.EndChangeCheck()) 184 | { 185 | this.serializedObject.ApplyModifiedProperties(); 186 | } 187 | } 188 | 189 | protected void DrawInspector() 190 | { 191 | EditorGUILayout.Space(); 192 | 193 | GUIContent matchButtonLabel = new GUIContent("Make current mesh bound as standard of spine size change"); 194 | Vector2 matchButtonSize = GUI.skin.label.CalcSize(matchButtonLabel); 195 | 196 | GUIContent resizeButtonLabel = new GUIContent("Can Resize Spine with RectTranform Manually"); 197 | Vector2 resizeButtonSize = GUI.skin.label.CalcSize(resizeButtonLabel); 198 | 199 | float maxButtonSize = Mathf.Max(matchButtonSize.x, resizeButtonSize.x); 200 | 201 | EditorGUILayout.BeginHorizontal(); 202 | EditorGUILayout.LabelField(matchButtonLabel, GUILayout.Width(maxButtonSize)); 203 | if (GUILayout.Button("Fit & Cache", EditorStyles.miniButton, GUILayout.Width(120f))) 204 | { 205 | foreach (Object target in targets) 206 | { 207 | if (target is SkeletonGraphicRespectRectTransform instance) 208 | { 209 | instance.CacheRectTransformWithBounds(); 210 | } 211 | } 212 | } 213 | EditorGUILayout.EndHorizontal(); 214 | 215 | 216 | bool guiPrevState = GUI.enabled; 217 | GUI.enabled = Application.isPlaying; // Allow Manual Resize when playmode 218 | EditorGUILayout.BeginHorizontal(); 219 | EditorGUILayout.LabelField(resizeButtonLabel, GUILayout.Width(maxButtonSize)); 220 | if (GUILayout.Button("Resize", EditorStyles.miniButton, GUILayout.Width(120f))) 221 | { 222 | foreach (Object target in targets) 223 | { 224 | if (target is SkeletonGraphicRespectRectTransform instance) 225 | { 226 | instance.RespectRectTranform(); 227 | } 228 | } 229 | } 230 | GUI.enabled = guiPrevState; 231 | EditorGUILayout.EndHorizontal(); 232 | 233 | 234 | EditorGUILayout.Space(); 235 | bool prev = GUI.enabled; 236 | GUI.enabled = false; 237 | EditorGUILayout.PropertyField(m_isEditorUpdateStopped); 238 | GUI.enabled = prev; 239 | EditorGUILayout.BeginHorizontal(); 240 | 241 | string buttonName = string.Empty; 242 | Color prevColor = GUI.color; 243 | if (m_isEditorUpdateStopped.boolValue) 244 | { 245 | buttonName = "Play Editor Update"; 246 | GUI.color = new Color(0.45f, 0.63f, 0.76f); 247 | } 248 | else 249 | { 250 | buttonName = "Stop Editor Update"; 251 | GUI.color = new Color(0.83f, 0.13f, 0.18f); 252 | } 253 | if (GUILayout.Button(buttonName)) 254 | { 255 | m_isEditorUpdateStopped.boolValue = !m_isEditorUpdateStopped.boolValue; 256 | } 257 | GUI.color = prevColor; 258 | EditorGUILayout.EndHorizontal(); 259 | } 260 | 261 | protected void DrawSerializedProperty(SerializedObject obj, bool isEnterChildren = true) 262 | { 263 | SerializedProperty iterator = obj.GetIterator(); 264 | 265 | while (iterator.NextVisible(isEnterChildren)) 266 | { 267 | using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) 268 | EditorGUILayout.PropertyField(iterator, true); 269 | } 270 | } 271 | } 272 | #endif 273 | } 274 | --------------------------------------------------------------------------------