├── 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 | 
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 | 
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 | 
13 |
14 |
15 | # Description!
16 | 
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 |
--------------------------------------------------------------------------------