So let's say we have MonoBehaviour class with a Transform property:
public class Class : MonoBehaviour
{
public Transform Target; //An object is already referenced here.
}
Question is simple: is there a way to get an event or some sort of callback in case this object gets deleted (externally)? Unity will show this as "Missing":
Since it is just a transform, you cannot really use OnDestroy()
here, and even if you could this is not a good option because of how many transform instances there are.
It should be noted that I need this to work at build, so in-editor only options are unfortunately not solving my problem.
CodePudding user response:
Here's one way to do it. I'd consider this a partial solution since there are some ways it's currently not ideal including not being able to edit prefabs while in play mode, leaving notify monobehaviours behind if AIs are destroyed, not respecting undo in the editor, and likely others. Still, this is probably worth sharing in its current state anyway.
Make a MonoBehaviour that will manage actions to call in the event the owning object is deleted. We're interested in making this work in the editor and at runtime, so I used the UnityEvent
api for this.
using UnityEngine;
using UnityEditor;
using UnityEngine.Events;
[System.Serializable]
public class DestroyEvent : UnityEvent { };
[ExecuteInEditMode]
public class DestructionNotifier : MonoBehaviour
{
public DestroyEvent OnDestroyed;
private void Awake()
{
if (OnDestroyed == null)
{
OnDestroyed = new DestroyEvent();
}
}
public void Register(UnityAction act)
{
#if UNITY_EDITOR
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
// add non-persistent listener if in play mode
OnDestroyed.AddListener(act);
}
else
{
// add persistent listener if in edit mode
UnityEditor.Events.UnityEventTools.AddPersistentListener(
OnDestroyed, act);
OnDestroyed.SetPersistentListenerState(
OnDestroyed.GetPersistentEventCount() - 1,
UnityEventCallState.EditorAndRuntime);
}
#else
// add non-persistent listener
OnDestroyed.AddListener(act);
#endif
}
public void Deregister(UnityAction call)
{
// remove or disable matching persistent actions
for (int i = 0; i < OnDestroyed.GetPersistentEventCount(); i )
{
if ((Object)call.Target == OnDestroyed.GetPersistentTarget(i))
{
#if UNITY_EDITOR
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
OnDestroyed.SetPersistentListenerState(i,
UnityEventCallState.Off);
}
else
{
UnityEditor.Events.UnityEventTools
.RemovePersistentListener(OnDestroyed, i);
}
#else
OnDestroyed.SetPersistentListenerState(i,
UnityEventCallState.Off);
#endif
}
}
// remove matching non-persistent actions
OnDestroyed.RemoveListener(call);
// if in edit mode, remove self if no actions
#if UNITY_EDITOR
RemoveEmptyEvents();
if (!EditorApplication.isPlayingOrWillChangePlaymode
&& OnDestroyed.GetPersistentEventCount() == 0)
{
DestroyImmediate(this);
}
#endif
}
private void OnValidate()
{
RemoveEmptyEvents();
}
void RemoveEmptyEvents()
{
#if UNITY_EDITOR
for (int i = OnDestroyed.GetPersistentEventCount() - 1; i >= 0; i--)
{
if (OnDestroyed.GetPersistentTarget(i) == null)
{
UnityEditor.Events.UnityEventTools.RemovePersistentListener(
OnDestroyed, i);
}
}
#endif
}
void OnDestroy()
{
if (OnDestroyed != null)
{
OnDestroyed.Invoke();
}
}
}
Then, in your AI script, turn your transform into a DestructionNotifier
property that subs/unsubs to the event as the target is changed. Use a custom inspector to allow the property to be edited in the inspector:
using UnityEngine;
using UnityEditor;
public class Class : MonoBehaviour
{
void OnTargetDestroyed()
{
// Stuff to do when target is destroyed
Debug.Log($"{gameObject.name}: target destroyed.");
}
[SerializeField, HideInInspector] DestructionNotifier targetNotifier;
public Transform Target {
get { return targetNotifier == null ? null : targetNotifier.transform; }
set {
if (targetNotifier != null)
{
targetNotifier.Deregister(OnTargetDestroyed);
}
if (value == null)
{
targetNotifier = null;
return;
}
targetNotifier = value.GetComponent<DestructionNotifier>();
if (targetNotifier == null)
{
targetNotifier = value.gameObject
.AddComponent<DestructionNotifier>();
}
targetNotifier.Register(OnTargetDestroyed);
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(Class)), CanEditMultipleObjects]
public class AIInspector : Editor
{
public override void OnInspectorGUI()
{
Class targetAI = (Class)target;
EditorGUI.BeginChangeCheck();
Transform newTarget = EditorGUILayout.ObjectField("Target",
targetAI.Target, typeof(Transform), true) as Transform;
if (EditorGUI.EndChangeCheck())
{
foreach (Class curTarget in targets)
{
curTarget.Target = newTarget;
}
}
DrawDefaultInspector();
}
}
#endif