- Home /
Undo SetSiblingIndex for root objects?
I've created a script to naturally sort gameobjects by their name in the hierarchy, and I cannot seem to figure out how to use the built-in Undo system when root objects have SetSiblingIndex called on them.
Here's what I've got so far:
using UnityEditor;
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Zapdot
{
public static class EditorTools
{
[MenuItem("Tools/Utility/Sort Hierarchy #h", false)]
public static void SortObjects()
{
List<Transform> objsToSort = new List<Transform>();
Transform rootParent = null;
int minIndex = int.MaxValue;
if (Selection.transforms.Length > 1)
{
rootParent = Selection.transforms[0].parent;
foreach (Transform t in Selection.transforms)
{
minIndex = Mathf.Min(minIndex, t.GetSiblingIndex());
objsToSort.Add(t);
}
if (rootParent != null)
Undo.RegisterCompleteObjectUndo(rootParent, "Sort Hierarchy");
else
Undo.RegisterCompleteObjectUndo(objsToSort.ToArray(), "Sort Hierarchy");
}
else if (Selection.activeTransform.childCount > 0)
{
// since we're sorting all of the children, our minIndex would be 0.
minIndex = 0;
rootParent = Selection.activeTransform;
for (int i = 0; i < rootParent.childCount; ++i)
objsToSort.Add(rootParent.GetChild(i));
Undo.RegisterCompleteObjectUndo(rootParent, "Sort Hierarchy");
}
objsToSort.Sort(new NaturalNameComparer());
for (int i = 0; i < objsToSort.Count; ++i)
objsToSort[i].SetSiblingIndex(minIndex + i);
}
[MenuItem("Tools/Utility/Sort Hierarchy #h", true)]
public static bool SortObjectsValidate()
{
return Selection.transforms.Length > 1 || (Selection.transforms.Length == 1 && Selection.activeTransform.childCount > 1);
}
}
public class NaturalNameComparer : IComparer<Transform>
{
private static readonly Regex _re = new Regex(@"(?<=\D)(?=\d)|(?<=\d)(?=\D)");
public int Compare(Transform obj1, Transform obj2)
{
string x = obj1.name.ToLower();
string y = obj2.name.ToLower();
if (string.Compare(x, 0, y, 0, Mathf.Min(x.Length, y.Length)) == 0)
{
if (x.Length == y.Length)
return 0;
return x.Length < y.Length ? -1 : 1;
}
var a = _re.Split(x);
var b = _re.Split(y);
int i = 0;
while (true)
{
int r = PartCompare(a[i], b[i]);
if (r != 0)
return r;
++i;
}
}
private static int PartCompare(string x, string y)
{
int a, b;
if (int.TryParse(x, out a) && int.TryParse(y, out b))
return a.CompareTo(b);
return x.CompareTo(y);
}
}
}
To reproduce:
Add this editor script to an empty project
Create two gameobjects as root objects in a scene, "b" and "a"
Select them both
Run the function (shift h)
Try to undo -- the objects don't reorder themselves to their original position.
Answer by howong · Jan 22, 2018 at 04:00 AM
I am doing something similar (SetAsLastSibling, etc.), and I found a way that worked for me was for every Transform I was modifying the sibling index for, call Undo.SetTransformParent (t, t.parent, "Name of group operation") before making the change (Unity takes a current snapshot then diffs at the end of frame); Unity doc here: https://docs.unity3d.com/ScriptReference/Undo.SetTransformParent.html. I was able to undo 4 objects at once, from one Undo using this. I think Unity treats these sibling index changes as parent changes, which according to the Unity docs, isn't supported by the typical Undo "record"/"register" functions. I hope this severely late info helps someone. :)