DEV Community

Dutchskull
Dutchskull

Posted on

Monitoring GameObject Changes in the Unity editor hierarchy

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

How to Use the HierarchyMonitor

  1. Create a New Script:
    Create a new C# script in your Unity project and name it HierarchyMonitor.cs.

  2. Copy the Script:
    Copy the provided HierarchyMonitor script into the new file.

  3. Place the Script in an Editor Folder:
    Ensure the script is placed inside an Editor folder in your Unity project. This is necessary as it uses UnityEditor namespace functions which should only be included in the editor code.

  4. Compile the Script:
    Unity will automatically compile the script. Once compiled, the HierarchyMonitor will start monitoring changes in the hierarchy.

  5. Subscribe to Events:
    You can now subscribe to the GameObjectAdded, GameObjectRemoved, GameObjectRenamed, and GameObjectParentChanged 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}");
    }
}
Enter fullscreen mode Exit fullscreen mode

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)