如何在Unity3D中使用GameObject.Find(““)找到不活动的对象?

9rygscc1  于 2023-02-23  发布在  其他
关注(0)|答案(7)|浏览(477)

我需要使用C#在Unity3D中查找非活动对象。
我有64个对象,每当我点击一个按钮,它就会在运行时激活/去激活对应按钮的对象。我怎么才能找到不活动的对象呢?

ttygqcqt

ttygqcqt1#

自Unity 2020以来

在这个问题被问到的这些年里,Unity提供了你所需要的东西。至少,我所需要的东西。在这里为未来的人们发帖。
要找到某个类型的对象,无论它是在活动的还是非活动的游戏对象上,你可以使用FindObjectsOfType<T>(true)
仅当inactiveObjects设置为true时,才包括附加到非活动游戏对象的对象。
因此,只需像平常一样使用它,但还要传入true
以下代码需要System.Linq

SpriteRenderer[] onlyActive = GameObject.FindObjectsOfType<SpriteRenderer>();

SpriteRenderer[] activeAndInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true);

// requires "using System.Linq;"
SpriteRenderer[] onlyInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true).Where(sr => !sr.gameObject.activeInHierarchy).ToArray();

第一个数组只包含活动游戏对象上的SpriteRenderer,第二个数组同时包含活动和非活动游戏对象上的SpriteRenderer,第三个数组使用System.Linq只包含非活动游戏对象上的SpriteRenderer。

u7up0aaq

u7up0aaq2#

有关Unity 2020及更高版本,请参见this answers

Unity 2020之前

使用GameObject.Find(...)将不会返回任何非活动对象。As the documentation states
此函数仅返回活动的游戏对象。
即使你可以,你也会想把这些昂贵的电话费降到最低。
有一些“技巧”可以找到不活动的游戏对象,比如使用Resources.FindObjectsOfTypeAll(Type type)调用(尽管应该非常小心地使用)。
但是你最好的办法是编写你自己的管理代码。这可以是一个简单的类,包含你可能想在某个时候找到并使用的对象列表。你可以在第一次加载时将你的对象放入其中。或者在激活或不激活时添加/删除它们。无论你的特定场景需要什么。

m1m5dgzv

m1m5dgzv3#

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

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

MainMenuItems-是您的父对象。
请注意Find()是一个比较慢的方法,所以考虑使用对象的引用,或者用你经常需要访问的游戏对象来组织字典集合
祝你好运!

nlejzf6q

nlejzf6q4#

对于较新的Unity版本,this answer可能提供了更好的解决方案!
首先
一般而言,应避免使用Find或其变体。
实际上,它们从来都不是必需的,而只是用来弥补实现“惰性”的“热修复”。
通常从一开始就存储和传递所需的引用总是更好的方法。
特别是在您的情况下,您似乎有固定数量的对象,因此您可能已经在某个“管理器”组件中引用了它们,并将它们存储在列表或数组中(您可以通过索引获取引用),甚至Dictionary<string, GameObject>中(然后您也可以通过名称获取相应的引用-您可以在下面找到一个示例)。

变通方案

有替代的解决方案(FindObjectsWithTagFindObjectsOfType),但它总是相当昂贵(虽然大多数Find变种是昂贵的反正)。
例如,您还可以使用Scene.GetRootGameObjects“手动”迭代场景中的所有对象
返回场景中的所有根游戏对象。
然后在它们中搜索,直到你找到你的对象。这样你也得到了不活动的游戏对象。

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.gameObject;

        result = FindRecursive (child.gameObject, 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);
}

你也可以在例如生成一个非活动对象之后调用它。

a14dhokn

a14dhokn5#

你可以使用 predicate 。只要得到gameObject并使用 predicate 检查它们,如下所示:

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

不要忘记**使用系统;**使用系统.集合.泛型;

vshtjzan

vshtjzan6#

你可以在运行时将你的非活动的游戏对象放在一个活动的父对象下,如前所述;与前面提到的方法稍有不同,这是我用来激活/停用默认情况下不活动的菜单的方法:
canvas = GameObject.FindGameObjectWithTag("GameMenu").GetComponentInChildren<Canvas>().gameObject;
现在,您可以更改它的activeSelf,以便在您选择的方法/事件侦听器中切换它:
canvas.SetActive(!canvas.activeSelf);
即使它是不活动的,你仍然可以使用它的标签属性,并使用它作为一个过滤器,如果获得多个相同类型的组件。我还没有测试过使用GetComponentsInChildren,但你可能会使用一个'Single' linq查询,并通过标签名称获得对象,这将需要为每个游戏对象创建一个标签,你想这样做。

zy1mlcev

zy1mlcev7#

虽然这不是正确的答案,但这是我在我的情况下所做的。
1)附加一个脚本到(非活动的)游戏对象,而不是设置然后非活动保持它活动。
2)把他们安置在场景外的某个地方。
3)在脚本中设置一个标志,显示为非活动。
4)在Update()中检查此非活动标志,如果为假,则跳过函数调用。
5)当需要对象时,将其放置在适当的位置并将标记设置为活动.
这将是一个有点性能问题,但这是唯一的变通办法,我能想到的。

相关问题