我需要使用 C# 在 Unity3D 中查找非活动对象。
我有 64 个对象,每当我单击一个按钮时,它都会在运行时激活/停用相应按钮的对象。此时如何找到不活动的对象?
好吧, usingGameObject.Find(...)
永远不会返回任何非活动对象。正如文档所述:
此函数仅返回活动的游戏对象。
即使可以,您也希望将这些代价高昂的调用降到最低。
找到不活动的游戏对象有一些“技巧”,例如使用Resources.FindObjectsOfTypeAll(Type type)
调用(尽管应该非常小心地使用)。
但最好的选择是编写自己的管理代码。这可以是一个简单的类,其中包含您可能希望在某些时候查找和使用的对象列表。您可以在第一次加载时将对象放入其中。或者也许在变得活跃或不活跃时添加/删除它们。无论您的特定场景需要什么。
自从提出这个问题以来的几年里,Unity 提供了您需要的确切内容。至少,正是我需要的东西。在这里张贴给未来的人。
要查找某种类型的对象,无论它是在活动的还是非活动的 GameObject 上,您可以使用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();
第一个数组仅包含活动游戏对象上的 SpriteRenderers,第二个包含活动和非活动游戏对象上的 SpriteRenderers,第三个用于System.Linq
仅包含非活动游戏对象上的那些。
如果您有父对象(只是扮演文件夹角色的空对象),您可以找到活动和非活动对象,如下所示:
this.playButton = MainMenuItems.transform.Find("PlayButton").gameObject;
MainMenuItems - 是您的父对象。
请注意 Find() 方法很慢,因此请考虑使用对对象的引用或使用您经常需要访问的游戏对象来组织字典集合
祝你好运!
对于较新的 Unity 版本,此答案可能提供了更好的解决方案!
一般来说,Find
应该避免任何使用或其变体。
实际上,它们从来都不是真正需要的,而只是用于覆盖实现“懒惰”的“热修复”。
通常从一开始就存储和传递所需的引用总是更好的方法。
特别是在您的情况下,您似乎有固定数量的对象,因此您可能已经在某个“管理器”组件中引用了它们,并将它们存储在列表或数组中(它们可以通过索引获得引用)甚至是Dictionary<string, GameObject>
(那么您也可以通过名称获得相应的参考 - 您可以在下面找到一个示例)。
有替代解决方案(FindObjectsWithTag
, FindObjectsOfType
),但它总是非常昂贵(尽管大多数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);
}
您也可以在生成非活动对象之后调用它。
您可以使用谓词。只需获取游戏对象并使用谓词检查它们,如下所示:
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;
虽然它不是正确的答案,但这是我在我的情况下所做的。
1)将脚本附加到(非活动)游戏对象,而不是设置然后非活动保持它处于活动状态。
2)将它们放置在场景之外的某个地方。
3)在脚本中设置一个标志,表示不活动。
4) 在 Update() 中检查此非活动标志,如果为 false,则跳过函数调用。
5)当需要对象时,将其放置在适当的位置并将标志设置为活动状态。
这将是一个性能问题,但这是迄今为止我能想到的唯一解决方法。