簡體   English   中英

如何確定運行時某個屬性所針對的 class 的名稱和引用?

[英]How do I determine the name and reference of a class being targeted by an attribute at runtime?

我正在嘗試為 Unity 創建一個自定義 class 屬性,以防止目標 MonoBehaviour 存在於場景中的多個 object 上。 我做了一些搜索,據說為了獲得屬性所針對的 class 的類型,我應該使用屬性的構造函數; 它不能使用反射來完成

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class DisallowMultipleInScene : Attribute
{
    public DisallowMultipleInScene(Type type)
    {
        // Do stuff with type here
    }
}

如果我沒記錯的話,這意味着在 class 上使用它,例如一個名為 ManagerManager 的,可以這樣實現:

[DisallowMultipleInScene(typeof(ManagerManager))]
public class ManagerManager : MonoBehaviour
{
    // Implementation
}

這似乎有點多余,並且還允許傳入不正確的 class 名稱。 為了禁止將多個組件(繼承自 MonoBehaviour 的類)放置在同一個 object 上,使用了[DisallowMultipleComponent]屬性。 這個屬性類似於我想要的。 您不需要傳遞它所應用的 class 的名稱,它似乎知道。

我查看了 UnityCsReference GitHub 中的源代碼,試圖了解它是如何在幕后工作的,但似乎沒有實現,只是一個定義,位於此處,摘錄如下:

[RequiredByNativeCode]
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class DisallowMultipleComponent : Attribute {}

因此,對於我自己的實現,要與[DisallowMultipleComponent]屬性類似地工作,我的屬性需要確定它所應用的 class,並獲取對 MonoBehaviour 腳本的引用,以便可以從 object 中刪除它剛剛添加到。

那么,首先, [DisallowMultipleComponent]屬性如何繞過將 class 類型作為屬性參數傳入的要求,我該怎么做?

其次,如何獲得對新創建的帶有屬性注釋的 class 實例的引用?

有四個步驟可以實現您想要的功能。

  1. 檢測具有適當屬性的所有類型。 您可以通過遍歷AppDomain.CurrentDomain中的每個Assembly來做到這一點。 每次重新加載腳本程序集時,您都需要緩存這些類型,您可以使用 static class 和編輯器中的InitializeOnLoad屬性進行檢查。 (如果你不需要,你肯定不想進行反思)。
  2. 檢測何時在場景層次結構中添加/修改對象。 這可以通過事件EditorApplication.hierarchyChanged來完成。
  3. 檢查是否有任何不應該存在的組件添加到場景中。 這可以使用UnityEditor.SceneManagement.EditorSceneManager class 通過循環場景中的所有根對象並跟蹤適當的信息來完成。
  4. 如果遇到多個相同的組件(銷毀、向用戶顯示消息等),請確定該怎么做。 這個取決於你,但我在下面提供了一個合乎邏輯的答案。

這可以通過以下屬性來實現

using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class DisallowMultipleComponentsInSceneAttribute : Attribute
{
    public DisallowMultipleComponentsInSceneAttribute()
    {

    }
}

和下面的編輯器腳本,它必須放在項目中的“編輯器”文件夾中。

using UnityEngine;
using UnityEngine.SceneManagement;

using UnityEditor;
using UnityEditor.SceneManagement;

using System;
using System.Reflection;
using System.Collections.Generic;

[InitializeOnLoad]
public static class SceneHierarchyMonitor 
{
    private class TrackingData
    {
        public Dictionary<Scene, Component> components = new Dictionary<Scene, Component>();
    }

    private static Dictionary<Type, TrackingData> __trackingData = new Dictionary<Type, TrackingData>();

    static SceneHierarchyMonitor()
    {
        EditorApplication.hierarchyChanged += OnHierarchyChanged;

        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (Type type in assembly.GetTypes())
            {
                if (type.GetCustomAttribute<DisallowMultipleComponentsInSceneAttribute>() != null)
                {
                    __trackingData.Add(type, new TrackingData());
                }
            }
        }

        for (int i = 0; i < EditorSceneManager.sceneCount; ++i)
        {
            MonitorScene(EditorSceneManager.GetSceneAt(i));
        }
    }

    private static void OnHierarchyChanged()
    {
        for (int i = 0; i < EditorSceneManager.sceneCount; ++i)
        {
            MonitorScene(EditorSceneManager.GetSceneAt(i));
        }
    }

    private static void MonitorScene(Scene scene)
    {
        foreach (KeyValuePair<Type, TrackingData> kvp in __trackingData)
        {
            // If the scene hasn't been tracked, initialize the component to a null value.
            bool isOpeningScene = false;
            if (!kvp.Value.components.ContainsKey(scene))
            {
                isOpeningScene = true;
                kvp.Value.components[scene] = null;
            }

            foreach (GameObject rootGameObject in scene.GetRootGameObjects())
            {
                Component[] components = rootGameObject.GetComponentsInChildren(kvp.Key, true);
                for (int i = 0; i < components.Length; ++i)
                {
                    Component component = components[i];

                    // If we haven't found a component of this type yet, set it to remember it. This will occur when either:
                    // 1. The component is added for the first time in a given scene.
                    // 2. The scene is being opened and we didn't have any tracking data previously.
                    if (kvp.Value.components[scene] == null)
                    {
                        kvp.Value.components[scene] = component;
                    }
                    else
                    {
                        // You can determine what to do with extra components. This makes sense to me, but you can change the
                        // behavior as you see fit.
                        if (kvp.Value.components[scene] != component)
                        {
                            GameObject gameObject = component.gameObject;
                            EditorGUIUtility.PingObject(gameObject);
                            if (!isOpeningScene)
                            {
                                Debug.LogError($"Destroying \"{component}\" because it has the attribute \"{typeof(DisallowMultipleComponentsInSceneAttribute).Name}\", " +
                                    $"and one of these components already exists in scene \"{scene.name}.\"", gameObject);
                                GameObject.DestroyImmediate(component);
                                EditorUtility.SetDirty(gameObject);
                            }
                            else
                            {
                                Debug.LogWarning($"Found multiple components of type {kvp.Key.Name} in scene {scene.name}. Please ensure there is exactly one " +
                                    $"instance of this type in the scene before continuing.", component.gameObject);
                            }
                        }
                    }
                }
            }
        }
    }
}

我已經在編輯器中對此進行了測試,效果非常好。

最后一點:

  • 此編輯器腳本在小型項目上運行良好,但在具有大場景和/或大量腳本文件的大型項目中,可能會遇到性能問題。 重新加載腳本程序集時,即使是幾分之一秒的延遲也是很明顯的,並且它會在項目的整個生命周期中累加。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM