如何在Unity3D中使用GameObject.Find(“”)查找非活动对象?

时间:2013-10-25 07:49:42

标签: c# unity3d

我需要使用C#在Unity3D中查找非活动对象。

我有64个对象,每当我单击一个按钮,它就会在运行时激活/取消激活相应按钮的对象。如何在此时找到非活动对象?

6 个答案:

答案 0 :(得分:11)

好吧,使用GameObject.Find(...)永远不会返回任何非活动对象。 As the documentation states

  

此功能仅返回活动的游戏对象。

即使你可以,你也希望将这些代价高昂的电话保持在最低限度。

找到非活动的游戏对象有“技巧”,例如使用Resources.FindObjectsOfTypeAll(Type type)调用(尽管应该非常谨慎地使用)。

但最好的办法是编写自己的管理代码。这可以是一个简单的类,其中包含您可能希望在某些时候查找和使用的对象列表。您可以在首次加载时将对象放入其中。或者在成为活动或非活动时添加/删除它们。无论您的特定场景需要什么。

答案 1 :(得分:2)

首先

通常应避免使用Find或其变体。

实际上,它们实际上并不是真正需要的,而只是用来覆盖实现“懒惰”的“修补程序”。

通常,从一开始就存储和传递所需的引用总是更好的方法。

特别是在您的情况下,您似乎有固定数量的对象,因此您可能已经在某个“管理器”组件中全部引用了它们并将它们存储在列表或数组中(它们可以通过索引获得引用)或甚至是Dictionary<string, GameObject>(然后您也可以通过名称获得相应的引用-您可以在下面找到示例)。


解决方法

还有其他解决方案(FindObjectsWithTagFindObjectsOfType),但是它总是很昂贵的(尽管大多数Find变体都非常昂贵)。

您可以例如还可以Scene.GetRootGameObjects

“手动”遍历场景中的所有对象

返回场景中的所有根游戏对象。

然后搜索它们,直到找到您的对象。这样一来,您也会获得不活跃的GameObject。

public static GameObject Find(string search)
{
    var scene = SceneManager.GetActiveScene();
    var sceneRoots = scene.GetRootGameObjects();

    GameObject result = null;
    foreach(var root in sceneRoots)
    {
        if(root.name.Equals(search)) return root;

        result = FindRecursive(root, search);

        if(result) break;
    }

    return result;
}

private static GameObject FindRecursive(GameObject obj, string search)
{
    GameObject result = null;
    foreach(Transform child in obj.transform)
    {
        if(child.name.Equals(search) return child;

        result = FindRecursive (child, search);

        if(result) break;
    }

    return result;
}

但是,当然应该避免这样做,并且将这种深度搜索的使用减少到最低!


我会做什么

另一种方法-在我看来这是最好的方法-可以是将某个组件附加到您的所有对象上,并实际上将所有引用一次存储在如字典之类的字典中,例如

public class FindAble : MonoBehaviour
{
    private static readonly Dictionary<string, GameObject> _findAbles = new Dictionary<string, GameObject>();

    public static GameObject Find(string search)
    {
        if(!_findAbles.ContainsKey(search)) return null;

        return _findAbles[search];
    }

    private IEnumerator Start()
    {
        // Wait one frame
        // This makes it possible to spawn this object and 
        // assign it a different name before it registers
        // itself in the dictionary
        yield return null;

        if(_findAbles.ContainsKey(name))
        {
            Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
            yield break;
        }

        _findAbles.Add(name, gameObject);
    }

    private void OnDestroy ()
    {
        if(_findAbles.ContainsKey(name))
        {
            _findAbles.Remove(name);
        }

        // Optionally clean up and remove entries that are invalid
        _findAbles = _findAbles.Where(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
    }
}

然后像

一样使用它
var obj = FindAble.Find("SomeName");
if(obj)
{
    // ...
}

为此,该组件也需要至少启用一次,以便调用Start

同样可以选择

public void Initialize(string newName)
{
    if(_findAbles.ContainsKey(name))
    {
        Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
        return;
    }

    name = newName;

    _findAbles.Add(name, gameObject);
}

,您也可以在例如生成不活动的对象。

答案 2 :(得分:1)

如果您有父对象(只是扮演文件夹角色的空对象),您可以找到这样的活动和非活动对象:

this.playButton = MainMenuItems.transform.Find("PlayButton").gameObject;

MainMenuItems - 是您的父对象。

请注意,Find()是一种慢速方法,因此请考虑使用对象的引用或使用游戏对象组织字典集合,这些对象需要经常访问

祝你好运!

答案 3 :(得分:1)

您可以使用谓词。 只需获取gameObjects并使用谓词进行检查,如下所示:

public List<GameObject> FindInactiveGameObjects()
{
    GameObject[] all = GameObject.FindObjectsOfType<GameObject> ();//Get all of them in the scene
    List<GameObject> objs = new List<GameObject> ();
    foreach(GameObject obj in all) //Create a list 
    {
        objs.Add(obj);
    }
    Predicate inactiveFinder = new Predicate((GameObject go) => {return !go.activeInHierarchy;});//Create the Finder
    List<GameObject> results = objs.FindAll (inactiveFinder);//And find inactive ones
    return results;
}

并且不要忘记使用系统; 使用System.Collections.Generic;

答案 4 :(得分:0)

虽然这不是正确答案,但这就是我在我的案例中所做的 1)将脚本附加到(非活动)游戏对象,而不是设置然后不活动,使其保持活动状态 2)将它们放在某处的场景之外 3)在脚本中设置一个标志为无效的标志 4)在Update()中检查此非活动标志,如果为false则跳过函数调用 5)当需要对象时,将其放置在适当的位置并将标志设置为活动状态。

这将是一个性能问题,但这是迄今为止我能想到的唯一解决方法。

答案 5 :(得分:0)

自从提出这个问题以来的几年里,Unity 准确地提供了您需要的东西。至少,正是我需要的东西。在这里为未来的人们发帖。

要查找特定类型的对象,无论它是在活动的还是非活动的 GameObject 上,您都可以使用 FindObjectsOfType<T>(true): https://docs.unity3d.com/ScriptReference/Object.FindObjectsOfType.html

<块引用>

附加到非活动游戏对象的对象仅在 inactiveObjects 设置为 true 时才包含。

因此,只需像平常一样使用它,同时传入 true

以下代码需要 System.Linq

List<SpriteRenderer> onlyActive = GameObject.FindObjectsOfType<SpriteRenderer>().ToList();

List<SpriteRenderer> activeAndInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true).ToList();

List<SpriteRenderer> onlyInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true).ToList()
    .FindAll(sr => !sr.gameObject.activeInHierarchy);

第一个列表仅包含活动 GameObject 上的 SpriteRenderer,第二个列表包含活动和非活动 GameObject 上的 SpriteRenderer,第三个列表使用 System.Linq 仅包含非活动 GameObject 上的 SpriteRenderer。