///
/// 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;
}
}
}