Introduction
Managing GameObjects in Unity can be a complex task, especially when dealing with dynamic and interactive scenes. Detecting changes such as additions, deletions, renaming, and parent changes in the hierarchy is crucial for many applications. In this post, we will walk through a powerful script, HierarchyMonitor
, which helps in tracking these changes in real-time within the Unity Editor.
Overview of HierarchyMonitor
The HierarchyMonitor
script utilizes Unity's EditorApplication.hierarchyChanged
event to monitor changes in the hierarchy. It provides hooks for handling the addition, removal, renaming, and reparenting of GameObjects.
Here's a breakdown of what the script offers:
- GameObject Added: Triggered when a new GameObject is added to the hierarchy.
- GameObject Removed: Triggered when an existing GameObject is removed from the hierarchy.
- GameObject Renamed: Triggered when a GameObject is renamed.
- GameObject Parent Changed: Triggered when a GameObject's parent is changed.
The Script
Below is the complete script for the HierarchyMonitor
class:
using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using Object = UnityEngine.Object;
[InitializeOnLoad]
public static class HierarchyMonitor
{
private static readonly Dictionary<GameObject, string> _previousNames = new();
private static readonly Dictionary<GameObject, Transform> _previousParents = new();
public delegate void GameObjectEventHandler(GameObject gameObject);
public delegate void GameObjectRenamedEventHandler(GameObject editedObject);
public delegate void GameObjectParentChangedEventHandler(GameObject gameObject, Transform oldParent, Transform newParent);
public static event GameObjectEventHandler GameObjectAdded;
public static event GameObjectEventHandler GameObjectRemoved;
public static event GameObjectRenamedEventHandler GameObjectRenamed;
public static event GameObjectParentChangedEventHandler GameObjectParentChanged;
static HierarchyMonitor()
{
EditorApplication.hierarchyChanged += OnHierarchyChanged;
RefreshPreviousState();
}
private static void RefreshPreviousState()
{
_previousNames.Clear();
_previousParents.Clear();
GameObject[] gameObjects = Object.FindObjectsOfType<GameObject>();
foreach (GameObject gameObject in gameObjects)
{
if (gameObject.hideFlags != HideFlags.None ||
_previousNames.ContainsKey(gameObject))
{
continue;
}
_previousNames.Add(gameObject, gameObject.name);
_previousParents.Add(gameObject, gameObject.transform.parent);
}
}
private static void OnHierarchyChanged()
{
GameObject[] currentGameObjects = Object.FindObjectsOfType<GameObject>();
foreach (GameObject gameObject in currentGameObjects)
{
HandleGameObjectAdded(gameObject);
HandleGameObjectRenamed(gameObject);
HandleGameObjectParentChanged(gameObject);
}
ICollection<GameObject> keys = _previousNames.Keys.ToList();
foreach (GameObject previousGameObject in keys)
{
HandleGameObjectDeleted(currentGameObjects, previousGameObject);
}
RefreshPreviousState();
}
private static void HandleGameObjectDeleted(GameObject[] currentGameObjects, GameObject previousGameObject)
{
if (Array.Exists(currentGameObjects, obj => obj == previousGameObject))
{
return;
}
Debug.Log($"GameObject removed: {_previousNames[previousGameObject]}");
GameObjectRemoved?.Invoke(previousGameObject);
_previousNames.Remove(previousGameObject);
}
private static void HandleGameObjectRenamed(GameObject gameObject)
{
if (!_previousNames.ContainsKey(gameObject) ||
gameObject.name == _previousNames[gameObject])
{
return;
}
Debug.Log($"GameObject renamed from: {_previousNames[gameObject]} to: {gameObject.name}");
GameObjectRenamed?.Invoke(gameObject);
_previousNames[gameObject] = gameObject.name;
}
private static void HandleGameObjectAdded(GameObject gameObject)
{
if (_previousNames.ContainsKey(gameObject))
{
return;
}
Debug.Log($"GameObject added: {gameObject.name}");
GameObjectAdded?.Invoke(gameObject);
_previousNames.Add(gameObject, gameObject.name);
_previousParents.Add(gameObject, gameObject.transform.parent);
}
private static void HandleGameObjectParentChanged(GameObject gameObject)
{
Transform currentParent = gameObject.transform.parent;
if (!_previousParents.ContainsKey(gameObject) ||
currentParent == _previousParents[gameObject])
{
return;
}
Debug.Log($"GameObject parent changed: {gameObject.name}");
GameObjectParentChanged?.Invoke(gameObject, _previousParents[gameObject], currentParent);
_previousParents[gameObject] = currentParent;
}
}
How to Use the HierarchyMonitor
Create a New Script:
Create a new C# script in your Unity project and name itHierarchyMonitor.cs
.Copy the Script:
Copy the providedHierarchyMonitor
script into the new file.Place the Script in an Editor Folder:
Ensure the script is placed inside anEditor
folder in your Unity project. This is necessary as it usesUnityEditor
namespace functions which should only be included in the editor code.Compile the Script:
Unity will automatically compile the script. Once compiled, theHierarchyMonitor
will start monitoring changes in the hierarchy.Subscribe to Events:
You can now subscribe to theGameObjectAdded
,GameObjectRemoved
,GameObjectRenamed
, andGameObjectParentChanged
events from any other script to handle these changes as needed.
Example Usage
Hereโs an example of how you can subscribe to these events in another script:
using UnityEngine;
public class HierarchyMonitorExample : MonoBehaviour
{
private void OnEnable()
{
HierarchyMonitor.GameObjectAdded += OnGameObjectAdded;
HierarchyMonitor.GameObjectRemoved += OnGameObjectRemoved;
HierarchyMonitor.GameObjectRenamed += OnGameObjectRenamed;
HierarchyMonitor.GameObjectParentChanged += OnGameObjectParentChanged;
}
private void OnDisable()
{
HierarchyMonitor.GameObjectAdded -= OnGameObjectAdded;
HierarchyMonitor.GameObjectRemoved -= OnGameObjectRemoved;
HierarchyMonitor.GameObjectRenamed -= OnGameObjectRenamed;
HierarchyMonitor.GameObjectParentChanged -= OnGameObjectParentChanged;
}
private void OnGameObjectAdded(GameObject gameObject)
{
Debug.Log($"Detected GameObject added: {gameObject.name}");
}
private void OnGameObjectRemoved(GameObject gameObject)
{
Debug.Log($"Detected GameObject removed: {gameObject.name}");
}
private void OnGameObjectRenamed(GameObject gameObject)
{
Debug.Log($"Detected GameObject renamed: {gameObject.name}");
}
private void OnGameObjectParentChanged(GameObject gameObject, Transform oldParent, Transform newParent)
{
Debug.Log($"Detected GameObject parent change: {gameObject.name} from {oldParent?.name} to {newParent?.name}");
}
}
Conclusion
The HierarchyMonitor
script is a powerful tool for tracking and handling hierarchy changes in the Unity Editor. By utilizing this script, developers can easily detect and respond to changes in the scene, making it an invaluable asset for complex projects. Happy coding!
For the full source code, you can check out the Gist.
Top comments (0)