在 Unity 中获取具有特定接口的所有组件

Que*_*n3r 8 c# unity-game-engine

GameObjects我场景中的一些实现了交互ISaveable。在我的脚本中,我想找到所有这些接口并存储它们。稍后我可以遍历它们并调用它们的实现方法SaveData()

我目前找到这些接口的解决方法:

    List<ISaveable> saveables = new List<ISaveable>();
    MonoBehaviour[] sceneObjects = FindObjectsOfType<MonoBehaviour>();
    
    for (int i = 0; i < sceneObjects.Length; i++)
    {
        MonoBehaviour currentObj = sceneObjects[i];
        ISaveable currentComponent = currentObj.GetComponent<ISaveable>();

        if (currentComponent != null)
        {
            saveables.Add(currentComponent);
        }
    }
Run Code Online (Sandbox Code Playgroud)

代码工作正常,但有更好的方法吗?我不想在场景中搜索每个 Monobehaviour 然后尝试获取其界面组件。

der*_*ugo 6

使用 Linq 的简化过滤器

您可以使用LinqOfType

ISaveable[] saveables = FindObjectsOfType<MonoBehaviour>().OfType<ISaveable>().ToArray();
Run Code Online (Sandbox Code Playgroud)

当然还是需要先找到所有的对象。

但请注意,它是FindObjectsOfType关于非活动游戏对象或禁用行为的一般限制的基础。


遍历层次结构

您可以扩展它以遍历场景的所有根级别对象。这是有效的,因为 afaikGetComponentsInChildren确实适用于接口!

var saveables = new List<ISaveable>();
var rootObjs = SceneManager.GetActiveScene().GetRootGameObjects();
foreach(var root in rootObjs)
{
    // Pass in "true" to include inactive and disabled children
    saveables.AddRange(root.GetComponentsInChildren<ISaveable>(true));
}
Run Code Online (Sandbox Code Playgroud)

如果它更有效 - 是的,不,也许,我不知道 - 但它也包括非活动和禁用的对象。

是的,可以使用以下方法扩展它以遍历多个加载的场景

var saveables = new List<ISaveable>();
for(var i = 0; i < SceneManager.sceneCount; i++)
{
    var rootObjs = SceneManager.GetSceneAt(i).GetRootGameObjects();
    foreach(var root in rootObjs)
    {
        saveables.AddRange(root.GetComponentsInChildren<ISaveable>(true));
    }
}
Run Code Online (Sandbox Code Playgroud)

首先不使用接口

这个替代方案有点类似于这个答案,但它有一个巨大的缺陷:你需要一个特定的接口实现才能使它工作,这使接口的整个想法无效。

所以评论中的一个大问题是:为什么要有一个界面?

如果它无论如何只用于MonoBehaviour你应该abstract class喜欢

public abstract class SaveableBehaviour : MonoBehaviour
{
    // Inheritors have to implement this (just like with an interface)
    public abstract void SaveData();
}
Run Code Online (Sandbox Code Playgroud)

这已经解决了FindObjectsOfType无论如何使用的整个问题,因为现在您可以简单地使用

SaveableBehaviour[] saveables = FindObjectsOfType<SaveableBehaviour>();
Run Code Online (Sandbox Code Playgroud)

但您仍然可以更进一步:为了更轻松、更有效地访问,您可以让它们完全注册自己,而无需管理器或单例模式!为什么类型不应该简单地处理它的实例本身?

public abstract class SaveableBehaviour : MonoBehaviour
{
    // Inheritors have to implement this (just like with an interface)
    public abstract void SaveData();

    private static readonly HashSet<SaveableBehaviour> instances = new HashSet<SaveableBehaviour>();

    // public read-only access to the instances by only providing a clone
    // of the HashSet so nobody can remove items from the outside
    public static HashSet<SaveableBehaviour> Instances => new HashSet<SaveableBehaviour>(instances);

    protected virtual void Awake()
    {
        // simply register yourself to the existing instances
        instances.Add(this);
    }

    protected virtual void OnDestroy()
    {
        // don't forget to also remove yourself at the end of your lifetime
        instances.Remove(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以你可以简单地继承

public class Example : SaveableBehaviour
{
    public override void SaveData()
    {
       // ...
    }

    protected override void Awake()
    {
        base.Awake(); // Make sure to always keep that line

        // do additional stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以通过访问该类型的所有实例

HashSet<SaveableBehaviour> saveables = SaveableBehaviour.Instances;
foreach(var saveable in saveables)
{
    saveable.SaveData();
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*ocy 1

拥有全局状态(也许是 的列表)ISaveable,并让每个可保存对象在 期间添加对其的引用怎么样start()?然后您可以简单地迭代数组,调用SaveData()每个ISaveable引用?