/// /// MIT License - Copyright(c) 2019 Ugo Belfiore /// using System.Collections; using System.Collections.Generic; using UnityEngine; using static UnityEditor.EditorGUILayout; #if UNITY_EDITOR using UnityEditor; #endif namespace ExtUnityComponents { /// /// Move, rotate & scale a parent, whild keeping children still /// public class LockChildren { /// /// Save state of a child /// public struct SaveChildPosition { public Transform Child; public Transform ChildTmp; public Vector3 PreviousPosition; public Quaternion PreviousRotation; public Vector3 PreviousScale; } private struct SaveStateParent { public Vector3 InitialPosition; public Quaternion InitialRotation; public Vector3 InitialScale; public SaveStateParent(Vector3 position, Quaternion rotation, Vector3 scale) { InitialPosition = position; InitialRotation = rotation; InitialScale = scale; } } //all child saved private List _allChilds = new List(); private Vector3 _parentPosition; private Quaternion _parentRotation; private Vector3 _parentScale; private SaveStateParent _initialStateParent; private GameObject _parentTmpCreated = null; private List _allChildsTmps = new List(); private bool _lockChildrenPosition = false; //keep this value outside of the inspected gameObject private PivotMode _cachedPivotMode; private bool _hasBeenInternallyInit = false; private Transform _currentTarget = null; private TinyEditorWindowSceneView _tinyLockChildrenWindow; private readonly string KEY_EDITOR_PREF_LOCK_CHILDREN_WINDOW = "KEY_EDITOR_PREF_LOCK_CHILDREN_WINDOW"; private Editor _currentEditor; public void Init(Transform parent, Editor current) { _currentTarget = parent; _currentEditor = current; _lockChildrenPosition = false; _hasBeenInternallyInit = false; } public void InitOnFirstOnSceneGUI() { InitTinyEditorWindow(); } private void InitTinyEditorWindow() { _tinyLockChildrenWindow = new TinyEditorWindowSceneView(); _tinyLockChildrenWindow.TinyInit(KEY_EDITOR_PREF_LOCK_CHILDREN_WINDOW, "Move Parent Tool", TinyEditorWindowSceneView.DEFAULT_POSITION.MIDDLE_RIGHT); } /// /// display Lock children button /// public void CustomOnInspectorGUI() { using (VerticalScope verticalScope = new VerticalScope(EditorStyles.helpBox)) { using (HorizontalScope horizontalScope = new HorizontalScope()) { GUILayout.Label("Lock Children positions:"); if (DisplayToggle(_currentTarget)) { LockStateChanged(); } } if (_lockChildrenPosition && IsParentAPrefabs(_currentTarget)) { GUI.color = Color.yellow; GUILayout.Label("/!\\ Can't lock scaling on a prefab"); } } } private bool DisplayToggle(Transform target) { bool disable = false; string messageButton = (_lockChildrenPosition) ? "Unlock [L]" : "Lock [L]"; bool previousState = _lockChildrenPosition; if (!IsTargetHaveChild(target)) { disable = true; messageButton = "no child to lock"; _lockChildrenPosition = previousState = false; } EditorGUI.BeginDisabledGroup(disable); { _lockChildrenPosition = GUILayout.Toggle(_lockChildrenPosition, messageButton, EditorStyles.miniButton); } EditorGUI.EndDisabledGroup(); return (previousState != _lockChildrenPosition); } private bool IsTargetHaveChild(Transform target) { return (target.childCount != 0); } public bool IsParentAPrefabs(Transform parent) { return (PrefabUtility.IsPartOfAnyPrefab(parent.gameObject)); } /// /// called when the lock state changed /// private void LockStateChanged() { if (_lockChildrenPosition) { _tinyLockChildrenWindow.IsClosed = false; if (!_hasBeenInternallyInit) { InitLock(); } } else { CustomDisable(); } } /// /// called for initializing the parent state and all his children state /// /// private void InitLock() { _cachedPivotMode = Tools.pivotMode; Tools.pivotMode = PivotMode.Pivot; InitTmpGameObjects(_currentTarget); SaveChildsPosition(_currentTarget); _parentPosition = _currentTarget.position; _initialStateParent = new SaveStateParent(_currentTarget.position, _currentTarget.rotation, _currentTarget.localScale); //ExtReflexion.SetSearch(_currentTarget.name); } private void InitTmpGameObjects(Transform parent) { if (_parentTmpCreated != null) { return; } _parentTmpCreated = ExtGameObject.CreateLocalGameObject("tmp Parent", parent.parent, parent.localPosition, parent.localRotation, parent.localScale, null); _parentTmpCreated.hideFlags = HideFlags.HideInHierarchy; InitTmpChilds(parent); } private void InitTmpChilds(Transform parent) { _allChildsTmps.Clear(); for (int i = 0; i < parent.childCount; i++) { GameObject child = ExtGameObject.CreateLocalGameObject("tmp child " + i, _parentTmpCreated.transform, parent.GetChild(i).localPosition, parent.GetChild(i).localRotation, parent.GetChild(i).localScale, null); child.hideFlags = HideFlags.HideInHierarchy; _allChildsTmps.Add(child); } } /// /// save all childrens in an array, and store there position /// /// parent of all childrens public void SaveChildsPosition(Transform parent) { _allChilds.Clear(); for (int i = 0; i < parent.childCount; i++) { SaveChildPosition newChild = new SaveChildPosition() { Child = parent.GetChild(i), ChildTmp = _allChildsTmps[i].transform, PreviousPosition = parent.GetChild(i).position, PreviousRotation = parent.GetChild(i).rotation, PreviousScale = parent.GetChild(i).localScale//GetGlobalToLocalScaleFactor(parent.GetChild(i)) }; _allChilds.Add(newChild); } } /// /// clear childs and remap original pivot tool /// public void CustomDisable() { if (_parentTmpCreated != null) { GameObject.DestroyImmediate(_parentTmpCreated); } if (_allChildsTmps.Count > 0) { for (int i = _allChildsTmps.Count - 1; i >= 0; i--) { if (_allChildsTmps[i] != null) { GameObject.DestroyImmediate(_allChildsTmps[i]); } } } Tools.pivotMode = _cachedPivotMode; _hasBeenInternallyInit = false; //ExtReflexion.SetSearch(""); } public void CustomOnSceneGUI() { if (_parentTmpCreated != null) { for (int i = 0; i < _allChildsTmps.Count; i++) { ExtDrawGuizmos.DebugWireSphere(_allChildsTmps[i].transform.position, Color.red, 0.1f); ExtDrawGuizmos.DebugCross(_allChildsTmps[i].transform); } } if (IsPressingL(Event.current)) { _lockChildrenPosition = !_lockChildrenPosition; LockStateChanged(); _currentEditor.Repaint(); } if (_lockChildrenPosition) { ExtDrawGuizmos.DebugCross(_currentTarget.position, _currentTarget.rotation, 1); ShowTinyEditor(); } } /// /// return true if we press on J /// /// /// private bool IsPressingL(Event current) { if ((current.keyCode == KeyCode.L && current.type == EventType.KeyUp)) { return (true); } return (false); } private void ShowTinyEditor() { _tinyLockChildrenWindow.ShowEditorWindow(DisplayLockChildrenWindow, SceneView.currentDrawingSceneView, Event.current); if (_tinyLockChildrenWindow.IsClosed) { _lockChildrenPosition = false; EditorUtility.SetDirty(_currentTarget); LockStateChanged(); } } private void DisplayLockChildrenWindow() { if (GUILayout.Button("Mean position")) { ExtUndo.Record(_currentTarget, "Tool: lock children move"); Vector3 mean = ExtVector3.GetMeanOfXPoints(_currentTarget.GetAllChild().ToArray(), true); _currentTarget.position = mean; } if (GUILayout.Button("Mean Rotation")) { Quaternion mean = ExtQuaternion.AverageQuaternion(ExtQuaternion.GetAllRotation(_currentTarget.GetAllChild().ToArray())); _currentTarget.rotation = mean; } if (GUILayout.Button("Reset")) { _currentTarget.position = _initialStateParent.InitialPosition; _currentTarget.rotation = _initialStateParent.InitialRotation; _currentTarget.localScale = _initialStateParent.InitialScale; } } /// /// called every update of the editor /// /// manange only one target public void OnEditorUpdate() { if (_currentTarget == null || !_lockChildrenPosition) { return; } if (IsParentChanged(_currentTarget)) { MoveEveryChildToTherePreviousPosition(_currentTarget); } SaveCurrentParentPosition(_currentTarget); SaveChildsPosition(_currentTarget); } /// /// Say if the parent is moved, in position, rotation or scale /// /// parent to check /// true if the parent moved, rotate or scaled public bool IsParentChanged(Transform parent) { return (_parentPosition != parent.position || _parentRotation != parent.rotation || _parentScale != parent.localScale); } /// /// here move every children to there original state /// /// public void MoveEveryChildToTherePreviousPosition(Transform parent) { _parentTmpCreated.transform.position = parent.transform.position; _parentTmpCreated.transform.rotation = parent.transform.rotation; for (int i = 0; i < _allChilds.Count; i++) { #if UNITY_EDITOR if (Application.isPlaying) { MoveChildNotInsidePrefabs(i, parent); } else { if (IsParentAPrefabs(parent)) { MoveChildInsidePrefabs(i, parent); } else { MoveChildNotInsidePrefabs(i, parent); } } #else MoveChildNotInsidePrefabs(i, parent); #endif } } private void MoveChildNotInsidePrefabs(int i, Transform parent) { ExtUndo.Record(_allChilds[i].Child, "Lock tool child lock"); _allChilds[i].ChildTmp.position = _allChilds[i].PreviousPosition; _allChilds[i].ChildTmp.rotation = _allChilds[i].PreviousRotation; _allChilds[i].Child.SetParent(_parentTmpCreated.transform); _allChilds[i].Child.position = _allChilds[i].PreviousPosition; _allChilds[i].Child.rotation = _allChilds[i].PreviousRotation; _allChilds[i].Child.localScale = _allChilds[i].ChildTmp.localScale; _allChilds[i].Child.SetParent(parent); } /// /// move object inside the prefabs, we can't manage the scale here /// /// /// private void MoveChildInsidePrefabs(int i, Transform parent) { _allChilds[i].ChildTmp.position = _allChilds[i].PreviousPosition; _allChilds[i].ChildTmp.rotation = _allChilds[i].PreviousRotation; _allChilds[i].Child.position = _allChilds[i].PreviousPosition; _allChilds[i].Child.rotation = _allChilds[i].PreviousRotation; _allChilds[i].Child.localScale = _allChilds[i].PreviousScale; } /// /// save parent state for later /// /// public void SaveCurrentParentPosition(Transform parent) { _parentPosition = parent.position; _parentRotation = parent.rotation; _parentScale = parent.localScale; } } }