// ///////////////////////////////////////////////////////////////////////////////////////////////////////// // // Transform Utilities. // // This window contains four useful tools for asset placing and manipulation: Align, Copy, Randomize and Add noise. // // Put this into Assets/Editor and once compiled by Unity you find // the new functionality in Window -> TransformUtilities, or simply press Ctrl+t (Cmd+t for Mac users) // // Developed by Daniel Rodríguez (Seth Illgard) in January 2010 // http://www.silentkraken.com // e-mail: seth@silentkraken.com // // ///////////////////////////////////////////////////////////////////////////////////////////////////////// using UnityEngine; using UnityEditor; public class TransformUtilitiesWindow : EditorWindow { //Window control values public int toolbarOption = 0; public string[] toolbarTexts = {"Align", "Copy", "Randomize", "Add noise"}; private bool xCheckbox = true; private bool yCheckbox = true; private bool zCheckbox = true; private Transform source; private float randomRangeMin = 0f; private float randomRangeMax = 1f; private int alignSelectionOption = 0; private int alignSourceOption = 0; /// /// Window drawing operations /// void OnGUI () { toolbarOption = GUILayout.Toolbar(toolbarOption, toolbarTexts); switch (toolbarOption) { case 0: CreateAxisCheckboxes("Align"); CreateAlignTransformWindow(); break; case 1: CreateAxisCheckboxes("Copy"); CreateCopyTransformWindow(); break; case 2: CreateAxisCheckboxes("Randomize"); CreateRandomizeTransformWindow(); break; case 3: CreateAxisCheckboxes("Add noise"); CreateAddNoiseToTransformWindow(); break; } } /// /// Draws the 3 axis checkboxes (x y z) /// /// private void CreateAxisCheckboxes(string operationName) { GUILayout.Label(operationName + " on axis", EditorStyles.boldLabel); GUILayout.BeginHorizontal(); xCheckbox = GUILayout.Toggle(xCheckbox, "X"); yCheckbox = GUILayout.Toggle(yCheckbox, "Y"); zCheckbox = GUILayout.Toggle(zCheckbox, "Z"); GUILayout.EndHorizontal(); EditorGUILayout.Space(); } /// /// Draws the range min and max fields /// private void CreateRangeFields() { GUILayout.Label("Range", EditorStyles.boldLabel); GUILayout.BeginHorizontal(); randomRangeMin = EditorGUILayout.FloatField("Min:", randomRangeMin); randomRangeMax = EditorGUILayout.FloatField("Max:", randomRangeMax); GUILayout.EndHorizontal(); EditorGUILayout.Space(); } /// /// Creates the Align transform window /// private void CreateAlignTransformWindow() { //Source transform GUILayout.BeginHorizontal(); GUILayout.Label("Align to: \t"); source = EditorGUILayout.ObjectField(source, typeof(Transform)) as Transform; GUILayout.EndHorizontal(); string[] texts = new string[4] { "Min", "Max", "Center", "Pivot" }; //Display align options EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginVertical(); GUILayout.Label("Selection:", EditorStyles.boldLabel); alignSelectionOption = GUILayout.SelectionGrid(alignSelectionOption, texts, 1); EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical(); GUILayout.Label("Source:", EditorStyles.boldLabel); alignSourceOption = GUILayout.SelectionGrid(alignSourceOption, texts, 1); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); //Position if (GUILayout.Button("Align")) { if (source != null) { //Add a temporary box collider to the source if it doesn't have one Collider sourceCollider = source.collider; bool destroySourceCollider = false; if (sourceCollider == null) { sourceCollider = source.gameObject.AddComponent(); destroySourceCollider = true; } foreach (Transform t in Selection.transforms) { //Add a temporary box collider to the transform if it doesn't have one Collider transformCollider = t.collider; bool destroyTransformCollider = false; if (transformCollider == null) { transformCollider = t.gameObject.AddComponent(); destroyTransformCollider = true; } Vector3 sourceAlignData = new Vector3(); Vector3 transformAlignData = new Vector3(); //Transform switch (alignSelectionOption) { case 0: //Min transformAlignData = transformCollider.bounds.min; break; case 1: //Max transformAlignData = transformCollider.bounds.max; break; case 2: //Center transformAlignData = transformCollider.bounds.center; break; case 3: //Pivot transformAlignData = transformCollider.transform.position; break; } //Source switch (alignSourceOption) { case 0: //Min sourceAlignData = sourceCollider.bounds.min; break; case 1: //Max sourceAlignData = sourceCollider.bounds.max; break; case 2: //Center sourceAlignData = sourceCollider.bounds.center; break; case 3: //Pivot sourceAlignData = sourceCollider.transform.position; break; } Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? sourceAlignData.x - (transformAlignData.x - t.position.x) : t.position.x; tmp.y = yCheckbox ? sourceAlignData.y - (transformAlignData.y - t.position.y) : t.position.y; tmp.z = zCheckbox ? sourceAlignData.z - (transformAlignData.z - t.position.z) : t.position.z; //Register the Undo Undo.RegisterUndo(t, "Align " + t.gameObject.name + " to " + source.gameObject.name); t.position = tmp; //Ugly hack! //Unity needs to update the collider of the selection to it's new position //(it stores in cache the collider data) //We can force the update by a change in a public variable (shown in the inspector), //then a call SetDirty to update the collider (it won't work if all inspector variables are the same). //But we want to restore the changed property to what it was so we do it twice. transformCollider.isTrigger = !transformCollider.isTrigger; EditorUtility.SetDirty(transformCollider); transformCollider.isTrigger = !transformCollider.isTrigger; EditorUtility.SetDirty(transformCollider); //Destroy the collider we added if (destroyTransformCollider) { DestroyImmediate(transformCollider); } } //Destroy the collider we added if (destroySourceCollider) { DestroyImmediate(sourceCollider); } } else { EditorUtility.DisplayDialog("Error", "There is no source transform", "Ok"); EditorApplication.Beep(); } } } /// /// Creates the copy transform window /// private void CreateCopyTransformWindow() { //Source transform GUILayout.BeginHorizontal(); GUILayout.Label("Copy from: \t"); source = EditorGUILayout.ObjectField(source, typeof(Transform)) as Transform; GUILayout.EndHorizontal(); EditorGUILayout.Space(); //Position if (GUILayout.Button("Copy Position")) { if (source != null) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? source.position.x : t.position.x; tmp.y = yCheckbox ? source.position.y : t.position.y; tmp.z = zCheckbox ? source.position.z : t.position.z; Undo.RegisterUndo(t, "Copy position"); t.position = tmp; } } else { EditorUtility.DisplayDialog("Error", "There is no source transform", "Ok"); EditorApplication.Beep(); } } //Rotation if (GUILayout.Button("Copy Rotation")) { if (source != null) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? source.rotation.eulerAngles.x : t.rotation.eulerAngles.x; tmp.y = yCheckbox ? source.rotation.eulerAngles.y : t.rotation.eulerAngles.y; tmp.z = zCheckbox ? source.rotation.eulerAngles.z : t.rotation.eulerAngles.z; Quaternion tmp2 = t.rotation; tmp2.eulerAngles = tmp; Undo.RegisterUndo(t, "Copy rotation"); t.rotation = tmp2; } } else { EditorUtility.DisplayDialog("Error", "There is no source transform", "Ok"); EditorApplication.Beep(); } } //Local Scale if (GUILayout.Button("Copy Local Scale")) { if (source != null) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? source.localScale.x : t.localScale.x; tmp.y = yCheckbox ? source.localScale.y : t.localScale.y; tmp.z = zCheckbox ? source.localScale.z : t.localScale.z; Undo.RegisterUndo(t, "Copy local scale"); t.localScale = tmp; } } else { EditorUtility.DisplayDialog("Error", "There is no source transform", "Ok"); EditorApplication.Beep(); } } } /// /// Creates the Randomize transform window /// private void CreateRandomizeTransformWindow() { CreateRangeFields(); //Position if (GUILayout.Button("Randomize Position")) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.position.x; tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.position.y; tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.position.z; Undo.RegisterUndo(t, "Randomize position"); t.position = tmp; } } //Rotation if (GUILayout.Button("Randomize Rotation")) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.rotation.eulerAngles.x; tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.rotation.eulerAngles.y; tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.rotation.eulerAngles.z; Quaternion tmp2 = t.rotation; tmp2.eulerAngles = tmp; Undo.RegisterUndo(t, "Randomize rotation"); t.rotation = tmp2; } } //Local Scale if (GUILayout.Button("Randomize Local Scale")) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.localScale.x; tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.localScale.y; tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.localScale.z; Undo.RegisterUndo(t, "Randomize local scale"); t.localScale = tmp; } } } /// /// Creates the Add Noise To Transform window /// private void CreateAddNoiseToTransformWindow() { CreateRangeFields(); //Position if (GUILayout.Button("Add noise to Position")) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0; tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0; tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0; Undo.RegisterUndo(t, "Add noise to position"); t.position += tmp; } } //Rotation if (GUILayout.Button("Add noise to Rotation")) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? t.rotation.eulerAngles.x + Random.Range(randomRangeMin, randomRangeMax) : 0; tmp.y = yCheckbox ? t.rotation.eulerAngles.y + Random.Range(randomRangeMin, randomRangeMax) : 0; tmp.z = zCheckbox ? t.rotation.eulerAngles.z + Random.Range(randomRangeMin, randomRangeMax) : 0; Undo.RegisterUndo(t, "Add noise to rotation"); t.rotation = Quaternion.Euler(tmp); } } //Local Scale if (GUILayout.Button("Add noise to Local Scale")) { foreach (Transform t in Selection.transforms) { Vector3 tmp = new Vector3(); tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0; tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0; tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0; Undo.RegisterUndo(t, "Add noise to local scale"); t.localScale += tmp; } } } /// /// Retrives the TransformUtilities window or creates a new one /// [MenuItem("Window/TransformUtilities %t")] static void Init() { TransformUtilitiesWindow window = (TransformUtilitiesWindow)EditorWindow.GetWindow(typeof(TransformUtilitiesWindow)); window.Show(); } }