-
Notifications
You must be signed in to change notification settings - Fork 450
/
Copy pathTransformEditor.cs
436 lines (369 loc) · 20.4 KB
/
TransformEditor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
// source https://gist.github.com/unitycoder/e5e6384f087639c0d9edc93aa3820468
using UnityEngine;
using UnityEditor;
namespace OddTales.Framework.Core.EditorExtension
{
/// <summary>
/// Custom inspector for Transform component. Using only DrawDefaultInspector would give different display.
/// Script based on Unity wiki implementation : https://wiki.unity3d.com/index.php/TransformInspector
/// Buttons to reset, copy, paste Transform values.
/// Context menu to round/truncate values, hide/show tools.
/// </summary>
[CanEditMultipleObjects, CustomEditor(typeof(Transform))]
public class TransformEditor : Editor
{
private const float FIELD_WIDTH = 212.0f;
private const bool WIDE_MODE = true;
private const float POSITION_MAX = 100000.0f;
private static GUIContent positionGUIContent = new GUIContent(LocalString("Position"));
private static GUIContent rotationGUIContent = new GUIContent(LocalString("Rotation"));
private static GUIContent scaleGUIContent = new GUIContent(LocalString("Scale"));
private static string positionWarningText = LocalString("Due to floating-point precision limitations, it is recommended to bring the world coordinates of the GameObject within a smaller range.");
private SerializedProperty positionProperty, rotationProperty, scaleProperty;
private static Vector3? positionClipboard = null;
private static Quaternion? rotationClipboard = null;
private static Vector3? scaleClipboard = null;
private const string SHOW_TOOLS_KEY = "TransformEditor_ShowTools";
private const string SHOW_RESET_TOOLS_KEY = "TransformEditor_ShowResetTools";
private const string SHOW_PASTE_TOOLS_KEY = "TransformEditor_ShowPasteTools";
private const string SHOW_ADVANCED_PASTE_TOOLS_KEY = "TransformEditor_ShowAdvancedPasteTools";
private const string SHOW_CLIPBOARD_INFORMATIONS_KEY = "TransformEditor_ShowClipboardInformations";
private const string SHOW_SHORTCUTS_KEY = "TransformEditor_ShowHelpbox";
#if UNITY_2017_3_OR_NEWER
private static System.Reflection.MethodInfo getLocalizedStringMethod;
#endif
/// <summary> Get translated Transform label </summary>
private static string LocalString(string text)
{
#if UNITY_2017_3_OR_NEWER
// Since Unity 2017.3, static class LocalizationDatabase is no longer public. Need to use reflection to access it.
if (getLocalizedStringMethod == null)
{
System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
System.Type localizationDatabaseType = assembly.GetType("UnityEditor.LocalizationDatabase");
getLocalizedStringMethod = localizationDatabaseType.GetMethod("GetLocalizedString");
}
return (string)getLocalizedStringMethod.Invoke(null, new object[] { text });
#else
return LocalizationDatabase.GetLocalizedString(text);
#endif
}
public void OnEnable()
{
positionProperty = serializedObject.FindProperty("m_LocalPosition");
rotationProperty = serializedObject.FindProperty("m_LocalRotation");
scaleProperty = serializedObject.FindProperty("m_LocalScale");
// Init options
if (!EditorPrefs.HasKey(SHOW_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_TOOLS_KEY, true);
if (!EditorPrefs.HasKey(SHOW_RESET_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_RESET_TOOLS_KEY, true);
if (!EditorPrefs.HasKey(SHOW_PASTE_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_PASTE_TOOLS_KEY, true);
if (!EditorPrefs.HasKey(SHOW_ADVANCED_PASTE_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY, true);
if (!EditorPrefs.HasKey(SHOW_CLIPBOARD_INFORMATIONS_KEY)) EditorPrefs.SetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY, true);
if (!EditorPrefs.HasKey(SHOW_SHORTCUTS_KEY)) EditorPrefs.SetBool(SHOW_SHORTCUTS_KEY, true);
}
public override void OnInspectorGUI()
{
Rect beginRect = GUILayoutUtility.GetRect(0, 0);
EditorGUIUtility.wideMode = TransformEditor.WIDE_MODE;
EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth - TransformEditor.FIELD_WIDTH; // align field to right of inspector
serializedObject.Update();
EditorGUIUtility.labelWidth = 60; // To allow float fields to expand when inspector width is increased
// Position GUI
EditorGUILayout.BeginHorizontal();
PositionPropertyField(positionProperty, positionGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead
if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY))
{
if (GUILayout.Button("Reset", GUILayout.Width(50)))
{
Undo.RecordObjects(targets, "Reset Positions");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localPosition = Vector3.zero;
}
GUI.FocusControl(null);
}
}
EditorGUILayout.EndHorizontal();
// Rotation GUI
EditorGUILayout.BeginHorizontal();
RotationPropertyField(rotationProperty, rotationGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead
if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY))
{
if (GUILayout.Button("Reset", GUILayout.Width(50)))
{
Undo.RecordObjects(targets, "Reset Rotations");
for (int i = 0; i < targets.Length; i++)
{
TransformUtils.SetInspectorRotation(((Transform)targets[i]), Vector3.zero);
}
GUI.FocusControl(null);
}
}
EditorGUILayout.EndHorizontal();
// Scale GUI
EditorGUILayout.BeginHorizontal();
ScalePropertyField(scaleProperty, scaleGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead
if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY))
{
if (GUILayout.Button("Reset", GUILayout.Width(50)))
{
Undo.RecordObjects(targets, "Reset Scales");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localScale = Vector3.one;
}
GUI.FocusControl(null);
}
}
EditorGUILayout.EndHorizontal();
if (!ValidatePosition(((Transform)target).position)) EditorGUILayout.HelpBox(positionWarningText, MessageType.Warning); // Display floating-point warning message if values are too high
if (EditorPrefs.GetBool(SHOW_TOOLS_KEY))
{
// Paste Tools GUI
if (EditorPrefs.GetBool(SHOW_PASTE_TOOLS_KEY))
{
GUILayout.BeginHorizontal();
if (GUILayout.Button("Copy"))
{
positionClipboard = ((Transform)target).localPosition;
rotationClipboard = ((Transform)target).localRotation;
scaleClipboard = ((Transform)target).localScale;
}
if (!positionClipboard.HasValue) EditorGUI.BeginDisabledGroup(true);
if (GUILayout.Button("Paste"))
{
Undo.RecordObjects(targets, "Paste Clipboard Values");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localPosition = positionClipboard.Value;
((Transform)targets[i]).localRotation = rotationClipboard.Value;
((Transform)targets[i]).localScale = scaleClipboard.Value;
}
GUI.FocusControl(null);
}
if (!positionClipboard.HasValue) EditorGUI.EndDisabledGroup();
GUILayout.EndHorizontal();
}
// Advanced Paste Tools GUI
if (EditorPrefs.GetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY))
{
GUILayout.BeginHorizontal();
if (!positionClipboard.HasValue) EditorGUI.BeginDisabledGroup(true);
if (GUILayout.Button("Paste position"))
{
Undo.RecordObjects(targets, "Paste Position Clipboard Value");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localPosition = positionClipboard.Value;
}
GUI.FocusControl(null);
}
if (GUILayout.Button("Paste rotation"))
{
Undo.RecordObjects(targets, "Paste Rotation Clipboard Value");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).rotation = rotationClipboard.Value;
}
GUI.FocusControl(null);
}
if (GUILayout.Button("Paste scale"))
{
Undo.RecordObjects(targets, "Paste Scale Clipboard Value");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localScale = scaleClipboard.Value;
}
GUI.FocusControl(null);
}
if (!positionClipboard.HasValue) EditorGUI.EndDisabledGroup();
GUILayout.EndHorizontal();
}
// Clipboard GUI
if (EditorPrefs.GetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY))
{
if (positionClipboard.HasValue && rotationClipboard.HasValue && scaleClipboard.HasValue)
{
GUIStyle helpboxStyle = new GUIStyle(EditorStyles.helpBox);
helpboxStyle.richText = true;
EditorGUILayout.TextArea("Clipboard values :\n" +
"Position : " + positionClipboard.Value.ToString("f2") + "\n" +
"Rotation : " + rotationClipboard.Value.ToString("f2") + "\n" +
"Scale : " + scaleClipboard.Value.ToString("f2"), helpboxStyle);
}
}
// Shortcuts GUI - Related to InspectorShortcuts.cs https://github.com/VoxelBoy/Useful-Unity-Scripts/blob/master/InspectorShortcuts.cs
if (EditorPrefs.GetBool(SHOW_SHORTCUTS_KEY))
{
EditorGUILayout.HelpBox("Inspector shortcuts :\n" +
"Toggle inspector lock : Ctrl + Shift + L\n" +
"Toggle inspector mode : Ctrl + Shift + D", MessageType.None);
}
}
Rect endRect = GUILayoutUtility.GetLastRect();
endRect.y += endRect.height;
#region Context Menu
Rect componentRect = new Rect(beginRect.x, beginRect.y, beginRect.width, endRect.y - beginRect.y);
//EditorGUI.DrawRect(componentRect, Color.green); // Debug : display GenericMenu zone
Event currentEvent = Event.current;
if (currentEvent.type == EventType.ContextClick)
{
if (componentRect.Contains(currentEvent.mousePosition))
{
GUI.FocusControl(null);
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("Display/Tools"), EditorPrefs.GetBool(SHOW_TOOLS_KEY), ToggleOption, SHOW_TOOLS_KEY);
menu.AddSeparator("Display/");
menu.AddItem(new GUIContent("Display/Reset Tools"), EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY), ToggleOption, SHOW_RESET_TOOLS_KEY);
menu.AddItem(new GUIContent("Display/Paste Tools"), EditorPrefs.GetBool(SHOW_PASTE_TOOLS_KEY), ToggleOption, SHOW_PASTE_TOOLS_KEY);
menu.AddItem(new GUIContent("Display/Advanced Paste Tools"), EditorPrefs.GetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY), ToggleOption, SHOW_ADVANCED_PASTE_TOOLS_KEY);
menu.AddItem(new GUIContent("Display/Clipboard informations"), EditorPrefs.GetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY), ToggleOption, SHOW_CLIPBOARD_INFORMATIONS_KEY);
menu.AddItem(new GUIContent("Display/Shortcuts informations"), EditorPrefs.GetBool(SHOW_SHORTCUTS_KEY), ToggleOption, SHOW_SHORTCUTS_KEY);
// Round menu
menu.AddItem(new GUIContent("Round/Three Decimals"), false, Round, 3);
menu.AddItem(new GUIContent("Round/Two Decimals"), false, Round, 2);
menu.AddItem(new GUIContent("Round/One Decimal"), false, Round, 1);
menu.AddItem(new GUIContent("Round/Integer"), false, Round, 0);
// Truncate menu
menu.AddItem(new GUIContent("Truncate/Three Decimals"), false, Truncate, 3);
menu.AddItem(new GUIContent("Truncate/Two Decimals"), false, Truncate, 2);
menu.AddItem(new GUIContent("Truncate/One Decimal"), false, Truncate, 1);
menu.AddItem(new GUIContent("Truncate/Integer"), false, Truncate, 0);
menu.ShowAsContext();
currentEvent.Use();
}
}
#endregion
serializedObject.ApplyModifiedProperties();
}
private bool ValidatePosition(Vector3 position)
{
if (Mathf.Abs(position.x) > POSITION_MAX) return false;
if (Mathf.Abs(position.y) > POSITION_MAX) return false;
if (Mathf.Abs(position.z) > POSITION_MAX) return false;
return true;
}
private void PositionPropertyField(SerializedProperty positionProperty, GUIContent content)
{
Transform transform = (Transform)targets[0];
Vector3 localPosition = transform.localPosition;
for (int i = 0; i < targets.Length; i++)
{
if (!localPosition.Equals(((Transform)targets[i]).localPosition))
{
EditorGUI.showMixedValue = true;
break;
}
}
EditorGUI.BeginChangeCheck();
Vector3 newLocalPosition = EditorGUILayout.Vector3Field(content, localPosition);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, "Position Changed");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localPosition = newLocalPosition;
}
positionProperty.serializedObject.SetIsDifferentCacheDirty();
}
EditorGUI.showMixedValue = false;
}
private void RotationPropertyField(SerializedProperty rotationProperty, GUIContent content)
{
Transform transform = (Transform)targets[0];
Vector3 localRotation = TransformUtils.GetInspectorRotation(transform);
for (int i = 0; i < targets.Length; i++)
{
if (!localRotation.Equals(TransformUtils.GetInspectorRotation((Transform)targets[i])))
{
EditorGUI.showMixedValue = true;
break;
}
}
EditorGUI.BeginChangeCheck();
Vector3 eulerAngles = EditorGUILayout.Vector3Field(content, localRotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, "Rotation Changed");
for (int i = 0; i < targets.Length; i++)
{
//((Transform)targets[i]).localEulerAngles = eulerAngles;
TransformUtils.SetInspectorRotation(((Transform)targets[i]), eulerAngles);
}
rotationProperty.serializedObject.SetIsDifferentCacheDirty();
}
EditorGUI.showMixedValue = false;
}
private void ScalePropertyField(SerializedProperty scaleProperty, GUIContent content)
{
Transform transform = (Transform)targets[0];
Vector3 localScale = transform.localScale;
for (int i = 0; i < targets.Length; i++)
{
if (!localScale.Equals(((Transform)targets[i]).localScale))
{
EditorGUI.showMixedValue = true;
break;
}
}
EditorGUI.BeginChangeCheck();
Vector3 newLocalScale = EditorGUILayout.Vector3Field(content, localScale);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, "Scale Changed");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localScale = newLocalScale;
}
scaleProperty.serializedObject.SetIsDifferentCacheDirty();
}
EditorGUI.showMixedValue = false;
}
#region Generic Menu Callbacks
private void ToggleOption(object obj)
{
EditorPrefs.SetBool(obj.ToString(), !EditorPrefs.GetBool(obj.ToString()));
}
/// <summary> Round all values of the Transform to a given number of decimals </summary>
private void Round(object objNumberOfDecimals)
{
int numberOfDecimals = (int)objNumberOfDecimals;
Undo.RecordObjects(targets, "Round to " + numberOfDecimals + " decimals");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localPosition = RoundVector(((Transform)targets[i]).localPosition, numberOfDecimals);
((Transform)targets[i]).localEulerAngles = RoundVector(((Transform)targets[i]).localEulerAngles, numberOfDecimals);
((Transform)targets[i]).localScale = RoundVector(((Transform)targets[i]).localScale, numberOfDecimals);
}
}
/// <summary> Round all components of a Vector3 </summary>
private Vector3 RoundVector(Vector3 vector, int numberOfDecimals)
{
vector.x = Mathf.Round(vector.x * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
vector.y = Mathf.Round(vector.y * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
vector.z = Mathf.Round(vector.z * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
return vector;
}
/// <summary> Truncate all values of the Transform to a given number of decimals </summary>
private void Truncate(object objNumberOfDecimals)
{
int numberOfDecimals = (int)objNumberOfDecimals;
Undo.RecordObjects(targets, "Truncate to " + numberOfDecimals + " decimals");
for (int i = 0; i < targets.Length; i++)
{
((Transform)targets[i]).localPosition = TruncateVector(((Transform)targets[i]).localPosition, numberOfDecimals);
((Transform)targets[i]).localEulerAngles = TruncateVector(((Transform)targets[i]).localEulerAngles, numberOfDecimals);
((Transform)targets[i]).localScale = TruncateVector(((Transform)targets[i]).localScale, numberOfDecimals);
}
}
/// <summary> Truncate all components of a Vector3 </summary>
private Vector3 TruncateVector(Vector3 vector, int numberOfDecimals)
{
vector.x = Mathf.Floor(vector.x * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
vector.y = Mathf.Floor(vector.y * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
vector.z = Mathf.Floor(vector.z * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
return vector;
}
#endregion
}
}