There is no way (that I am currently aware of) to have DDoL objects not be the root object. I believe this is because Unity actually copies them to another scene that is loaded additively. When you run your game, you will see that it has moved to a ‘scene’ called DontDestroyOnLoad
.
Not by default. I will share some code here, but take no responsibility for it
This first bit comes from Thomas Brush (with minor tweaks from me).
Make a folder called Editor
and make a script inside it called HierarchySectionHeader.cs
. Put this in there
/// <summary>
/// Courtesy of Thomas Brush (with minor tweaks)
/// </summary>
[InitializeOnLoad]
public static class HierarchySectionHeader
{
static Color HEADER_BACKGROUND_COLOR = new Color(80f / 255f, 80f / 255f, 80f / 255f);
static HierarchySectionHeader() => EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI;
static void HierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
{
var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
if (gameObject == null) return;
if (!gameObject.name.StartsWith("//")) return;
EditorGUI.DrawRect(selectionRect, HEADER_BACKGROUND_COLOR);
EditorGUI.DropShadowLabel(selectionRect, gameObject.name.Substring(2));
}
}
Now when you add an empty, you can start its name with //
to turn it into a header
Building on this, I have messed around a bit with it. It’s not pretty, but you could add the selected gizmo icon by just getting it and adding it to the header
static void HierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
{
var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
if (gameObject == null) return;
if (!gameObject.name.StartsWith("//")) return;
EditorGUI.DrawRect(selectionRect, HEADER_BACKGROUND_COLOR);
EditorGUI.DropShadowLabel(selectionRect, gameObject.name.Substring(2));
// Try to get the icon for this object and display it if it exists
var icon = EditorGUIUtility.GetIconForObject(gameObject);
if (icon is null) return;
var height = Mathf.Min(icon.height, selectionRect.height);
var imgRect = new Rect(selectionRect.x, selectionRect.y, height * 2f, height);
var guiColor = GUI.color;
try
{
GUI.color = Color.clear;
EditorGUI.DrawTextureTransparent(imgRect, icon, ScaleMode.ScaleToFit, (float)icon.width / (float)icon.height);
}
finally
{
GUI.color = guiColor;
}
}
Some of that code is a bit of messing around trying to get the icons to look good in the hierarchy
I don’t personally like the icons very much. What I have done previously was to add some sort of ‘tag’ that I could use to color the headers
static void HierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
{
var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
if (gameObject == null) return;
if (!gameObject.name.StartsWith("//")) return;
var objectName = gameObject.name[2..];
var color = HEADER_BACKGROUND_COLOR;
if (gameObject.name[2..].StartsWith("[c=#"))
{
var hexColor = gameObject.name[5..12];
if (ColorUtility.TryParseHtmlString(hexColor, out Color parsed))
{
color = parsed;
objectName = gameObject.name[13..];
}
}
EditorGUI.DrawRect(selectionRect, color);
EditorGUI.DropShadowLabel(selectionRect, objectName);
}
This lets me specify the color I want the ‘header’ to be by adding [c=#rrggbb]
after the //
and before the actual name
It is important to note what Hugo said, though: Make sure these ‘headers’ are at (0,0,0) or the children might behave funny.
These scripts (except the first one from Thomas) were slammed together very quickly and may very well break your editor, so use them at your own risk. There are things that could be better (like the way I parsed the color from the name) but it is what it is. It’s not like I’m going to get fired or anything…
Edit I have gone much further and created a MonoBehaviour with color properties and stuff that define the color for the header and removes the need for weird naming conventions, and had this script read those instead, but I haven’t actually used it in a project.
Late Edit
I cleaned up the color tag code a bit (if you want to consider regular expressions as ‘cleanup’). Also removed the ‘c=’ bit from the color tag because it’s not really necessary
static Regex _regex = new Regex("^(?<tag>//)?(?:\\[(?<color>#[0-9A-F]{6}){1}\\])?(?<title>.*)$", RegexOptions.IgnoreCase);
static void HierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
{
var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
if (gameObject == null) return;
var match = _regex.Match(gameObject.name);
// Did we match the regex? If not, ignore and treat like any other object
if (!match.Success) return;
// Does it have our tag? If not, ignore and treat like any other object
if (string.IsNullOrWhiteSpace(match.Groups["tag"].Value)) return;
// We have a 'header'. Try to parse color, if any
var color = HEADER_BACKGROUND_COLOR;
if (ColorUtility.TryParseHtmlString(match.Groups["color"].Value, out Color parsed))
color = parsed;
// Grab the title
var title = match.Groups["title"].Value;
// Draw the 'header'
EditorGUI.DrawRect(selectionRect, color);
EditorGUI.DropShadowLabel(selectionRect, title);
}
With some complimentary colors