Home > Mobile >  How to detect/know when object have been deleted from the hierarchy in editor or runtime and update
How to detect/know when object have been deleted from the hierarchy in editor or runtime and update

Time:06-20

It's a bit long but everything is connected.

The goal what i'm trying to archive is to make objects in my hierarchy to be interactable items in the game.

I have editor script when i make right click on object i can chose in the context menu if i want to make this selected object to be interactable item. once i selected it to be interactable item it's changing the object tag to Interactable Item and also adding the object to a list in the editor.

The list is just to know and to be able to see what items are interactable instead searching for them all the time in all the hierarchy.

The problem is what to do if i deleted some object/s by accident or if i wanted to delete them how to update the list ?

Now if i will delete object it will show in the list missing.

The editor script :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

[CustomEditor(typeof(InteractableItems))]
public class InteractableItemsEditor : Editor
{
    private static List<GameObject> interactedobjects = new List<GameObject>();
    private static bool interacted = false;

    [MenuItem("GameObject/Make Object Interactable", false, 30)]
    public static void GeneratePickupItems()
    {
        if (Selection.gameObjects.Length > 0)
        {
            interactedobjects.Clear();

            for (int i = 0; i < Selection.gameObjects.Length; i  )
            {
                Selection.gameObjects[i].tag = "Interactable Item";

                interactedobjects.Add(Selection.gameObjects[i]);
            }

            interacted = true;
        }
    }

    private static Renderer TraverseHierarchy(Transform root, int childIndex)
    {
        Renderer renderer = new Renderer();

        if (root.childCount == 0 || root.childCount > 0)
        {
            var skinnedMesh = root.GetComponent<SkinnedMeshRenderer>();
            if (skinnedMesh) return skinnedMesh;

            var mesh = root.GetComponent<MeshRenderer>();
            if (mesh) return mesh;
        }

        if (root.childCount > 0)
        {
            foreach (Transform child in root)
            {
                // Deal with child
                if (child.GetComponent<SkinnedMeshRenderer>() != null)
                {
                    renderer = Selection.gameObjects[childIndex].GetComponentInChildren<SkinnedMeshRenderer>(true);
                }

                if (child.GetComponent<MeshRenderer>() != null)
                {
                    renderer = Selection.gameObjects[childIndex].GetComponentInChildren<MeshRenderer>(true);
                }
            }
        }

        return renderer;
    }

    private static void AddBoxCollider(Renderer renderer, int index)
    {
        Selection.gameObjects[index].AddComponent<BoxCollider>();

        var bounds = renderer.bounds;
        var size = bounds.size;
        var center = bounds.center;
        var boxCollider = Selection.gameObjects[index].GetComponent<BoxCollider>();
        size = boxCollider.transform.InverseTransformVector(size);
        center = boxCollider.transform.InverseTransformPoint(center);

        boxCollider.size = size;
        boxCollider.center = center;
    }

    InteractableItems _target;

    private ReorderableList _myList;

    public void OnEnable()
    {
        _myList = new ReorderableList(serializedObject, serializedObject.FindProperty("interactableItems"))
        {
            draggable = false,

            displayAdd = false,

            displayRemove = false,

            drawHeaderCallback = rect => EditorGUI.LabelField(rect, ""/*"My Reorderable List"*/, EditorStyles.boldLabel),

            drawElementCallback = (rect, index, isActive, isFocused) =>
            {
                if (index > _myList.serializedProperty.arraySize - 1) return;
                var element = _myList.serializedProperty.GetArrayElementAtIndex(index);

                var color = GUI.color;
                EditorGUI.BeginDisabledGroup(true);
                {
                    GUI.color = Color.green;
                    EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 20, EditorGUIUtility.singleLineHeight), element, GUIContent.none);
                }

                EditorGUI.EndDisabledGroup();
                GUI.color = color;
            }
        };
    }

    public override void OnInspectorGUI()
    {
        var list = serializedObject.FindProperty("interactableItems");

        serializedObject.Update();

        if (interacted)
        {
            interacted = false;
            foreach (var newEntry in interactedobjects)
            {
                // check if already contains this item
                bool alreadyPresent = false;
                for (var i = 0; i < list.arraySize; i  )
                {
                    if ((GameObject)list.GetArrayElementAtIndex(i).objectReferenceValue == newEntry)
                    {
                        alreadyPresent = true;
                        break;
                    }
                }

                if (alreadyPresent) continue;

                // Otherwise add via the serializedProperty
                list.arraySize  ;
                list.GetArrayElementAtIndex(list.arraySize - 1).objectReferenceValue = newEntry;
            }
            interactedobjects.Clear();
        }

        _myList.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
    }

    public object GetParent(SerializedProperty prop)
    {
        var path = prop.propertyPath.Replace(".Array.data[", "[");
        object obj = prop.serializedObject.targetObject;
        var elements = path.Split('.');
        foreach (var element in elements.Take(elements.Length - 1))
        {
            if (element.Contains("["))
            {
                var elementName = element.Substring(0, element.IndexOf("["));
                var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
                obj = GetValue(obj, elementName, index);
            }
            else
            {
                obj = GetValue(obj, element);
            }
        }
        return obj;
    }

    public object GetValue(object source, string name)
    {
        if (source == null)
            return null;
        var type = source.GetType();
        var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        if (f == null)
        {
            var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if (p == null)
                return null;
            return p.GetValue(source, null);
        }
        return f.GetValue(source);
    }

    public object GetValue(object source, string name, int index)
    {
        var enumerable = GetValue(source, name) as IEnumerable;
        var enm = enumerable.GetEnumerator();
        while (index-- >= 0)
            enm.MoveNext();
        return enm.Current;
    }
}

And the mono script :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InteractableItems : MonoBehaviour
{
    public List<GameObject> interactableItems = new List<GameObject>();
}

The interactable items empty gameobject with the mono script the list is empty now :

empty list

When i select one object or more in the hierarchy and make right click and select the Make Object Interactable :

Then i make right click on the cube and select to make the cube interactable item :

selecting to make the object to be interactable item

Now the cube tagged as Interactable Item :

Cube is interactable item

Now i'm deleting the cube from the hierarchy and this is what it's showing in the list :

Missing (Game Object)

missing

This is what i want to handle. a case where i deleted a interactable item for some reason how to update the list by removing this item from the list ?

I can loop nonstop on the list in the mono script but that's not a solution it will make a bad performance if the list a bit bigger. it's more like a work around then a solution.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Whilefun.FPEKit;

[ExecuteInEditMode]

public class InteractableObjects : MonoBehaviour
{
    public List<GameObject> interactableObjects = new List<GameObject>();

    private void Update()
    {
        for(int i = 0; i < interactableObjects.Count; i  )
        {
            if(interactableObjects[i] == null)
            {
                interactableObjects.RemoveAt(i);
            }
        }
    }
}

In the end i have this IK look at script that have a list of objects and i want to use this objects i make interactable items with this script list too. so i need to be able to update the list in this scripy sync with the list in the InteractableItems script :

using UnityEngine;
using System;
using System.Collections;

[RequireComponent(typeof(Animator))]
public class IKControl : MonoBehaviour
{
    public Transform[] lookObj = null;
    public float weightDamping = 1.5f;

    private Animator animator;
    private Transform lastPrimaryTarget;
    private float lerpEndDistance = 0.1f;
    private float finalLookWeight = 0;
    private bool transitionToNextTarget = false;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    // Callback for calculating IK
    void OnAnimatorIK()
    {
        if (lookObj != null)
        {
            Transform primaryTarget = null;
            float closestLookWeight = 0;

            // Here we find the target which is closest (by angle) to the players view line
            foreach (Transform target in lookObj)
            {
                Vector3 lookAt = target.position - transform.position;
                lookAt.y = 0f;
                float dotProduct = Vector3.Dot(new Vector3(transform.forward.x, 0f, transform.forward.z).normalized, lookAt.normalized);
                float lookWeight = Mathf.Clamp(dotProduct, 0f, 1f);
                if (lookWeight > closestLookWeight)
                {
                    closestLookWeight = lookWeight;
                    primaryTarget = target;
                }
            }

            if (primaryTarget != null)
            {
                if ((lastPrimaryTarget != null) && (lastPrimaryTarget != primaryTarget) && (finalLookWeight > 0f))
                {
                    // Here we start a new transition because the player looks already to a target but
                    // we have found another target the player should look at
                    transitionToNextTarget = true;
                }
            }

            // The player is in a neutral look position but has found a new target
            if ((primaryTarget != null) && !transitionToNextTarget)
            {
                lastPrimaryTarget = primaryTarget;
                finalLookWeight = Mathf.Lerp(finalLookWeight, closestLookWeight, Time.deltaTime * weightDamping);
                float bodyWeight = finalLookWeight * .75f;
                animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
                animator.SetLookAtPosition(primaryTarget.position);
            }

            // Let the player smoothly look away from the last target to the neutral look position
            if ((primaryTarget == null && lastPrimaryTarget != null) || transitionToNextTarget)
            {
                finalLookWeight = Mathf.Lerp(finalLookWeight, 0f, Time.deltaTime * weightDamping);
                float bodyWeight = finalLookWeight * .75f;
                animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
                animator.SetLookAtPosition(lastPrimaryTarget.position);
                if (finalLookWeight < lerpEndDistance)
                {
                    transitionToNextTarget = false;
                    finalLookWeight = 0f;
                    lastPrimaryTarget = null;
                }
            }

        }
    }
}

This way i can build a small interactable system.

CodePudding user response:

For the editor, you can try to handle EditorApplication.hierarchyChanged event.

But actually, in case I need such functionality, I will make a script and add it to every interactable object, that will register in the list in its Awake() and unregister in OnDestroy(). As for me, it is more obvious and will work in the runtime too.

  • Related