【问题标题】:(Unity) Is there a way to get notified when referenced object instance gets deleted/missed? [With build support](统一)有没有办法在引用的对象实例被删除/丢失时得到通知? [有构建支持]
【发布时间】:2022-01-19 19:09:00
【问题描述】:

假设我们有一个带有 Transform 属性的 MonoBehaviour 类:

public class Class : MonoBehaviour
{
    public Transform Target; //An object is already referenced here.
}

问题很简单:如果该对象被(外部)删除,有没有办法获取事件或某种回调? Unity 会将其显示为“缺失”:

因为它只是一个转换,所以你不能在这里真正使用OnDestroy(),即使你可以,这也不是一个好的选择,因为有多少转换实例。

应该注意的是,我需要在构建时使用它,所以很遗憾,只有编辑器中的选项不能解决我的问题。

【问题讨论】:

  • 我不认为 Unity 可以支持这个确切的功能,但如果描述了该用例,可能会对底层用例提供有用的答案。见XY problem
  • 一个很好的起点:这将有助于解释为什么 Transform 被用作此处的类型 - 是任意选择 Transform 还是认为只有 Transform 是合适的类型在这里?
  • 描述我的部分案例 - NPC 的 AI 使用目标作为“考虑”的对象:要查看的目标或要拾取的对象。这个目标有可能被破坏(通过被拾取,或者如果它是可破坏的则被删除 - 任何使这个对象通过Destroy(gameObject) 方法消失的东西)但我无法检查在删除之前是否引用了这个对象,因为@ 987654331@ 是它们共同共享的唯一目标,为每个可能的目标对象创建脚本似乎过于零散。在删除 Target 时创建委托可以工作,但我不知道何时调用它。
  • 所以,我认为可能有一个解决方案可以满足您的期望if you are using DOTS 但我的 DOTS 经验太有限了。在不使用 DOTS 的情况下,我能想到的最佳解决方案是在转换的游戏对象被选为目标(如果该 g.o.还没有它,并且有一个触发事件的 OnDestroy,并将 npc sub/unsub 作为目标添加/删除,或者只是检查每一帧 target==null.
  • @Ruzihm 啊,我明白了.. 尽管它很粗体,我还是以某种方式过度阅读了那句话的最后一部分 ^^ 虽然at build 我不知何故认为它们实际上指的是建筑的时刻(多-场景被卸载等)

标签: c# unity3d


【解决方案1】:

这是一种方法。我认为这是一个部分解决方案,因为它目前在某些方面并不理想,包括在播放模式下无法编辑预制件,如果 AI 被破坏,则通知单一行为,不尊重编辑器中的撤消,可能还有其他。尽管如此,无论如何,这可能还是值得分享它的当前状态。

创建一个 MonoBehaviour,它将管理在删除拥有对象时调用的操作。我们有兴趣在编辑器和运行时完成这项工作,因此我为此使用了 UnityEvent api。

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();
        }
    }
}

然后,在您的 AI 脚本中,将您的转换转换为一个属性,该属性使用 DestructionNotifier 字段在目标更改时子/取消子到事件。使用自定义检查器允许在检查器中编辑属性:

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

【讨论】:

    猜你喜欢
    • 2022-01-19
    • 2021-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-18
    • 1970-01-01
    • 2010-10-27
    相关资源
    最近更新 更多