【发布时间】:2013-10-25 07:49:41
【问题描述】:
我需要使用 C# 在 Unity3D 中查找非活动对象。
我有 64 个对象,每当我单击一个按钮时,它都会在运行时激活/停用相应按钮的对象。此时如何找到不活动的对象?
【问题讨论】:
我需要使用 C# 在 Unity3D 中查找非活动对象。
我有 64 个对象,每当我单击一个按钮时,它都会在运行时激活/停用相应按钮的对象。此时如何找到不活动的对象?
【问题讨论】:
好吧,使用GameObject.Find(...) 将永远不会返回任何非活动对象。 As the documentation states:
这个函数只返回活跃的游戏对象。
即使可以,您也希望将这些代价高昂的调用降至最低。
找到不活动的游戏对象有一些“技巧”,例如使用Resources.FindObjectsOfTypeAll(Type type) 调用(尽管使用时应格外小心)。
但最好的办法是编写自己的管理代码。这可以是一个简单的类,其中包含您可能希望在某些时候查找和使用的对象列表。您可以在第一次加载时将对象放入其中。或者也许在变得活跃或不活跃时添加/删除它们。无论您的特定场景需要什么。
【讨论】:
自从提出这个问题以来的几年里,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();
第一个数组仅包含活动 GameObjects 上的 SpriteRenderers,第二个包含活动和非活动 GameObjects 上的 SpriteRenderers,第三个使用 System.Linq 仅包含非活动 GameObjects 上的那些。
【讨论】:
static HashSet<T> 并让实例自行添加和删除(当然,仅适用于我们的自定义组件,不适用于 Unity 内置组件)
如果您有父对象(只是扮演文件夹角色的空对象),您可以像这样找到活动和非活动对象:
this.playButton = MainMenuItems.transform.Find("PlayButton").gameObject;
MainMenuItems - 是你的父对象。
请注意 Find() 方法很慢,因此请考虑使用对对象的引用或使用您经常需要访问的游戏对象来组织字典集合
祝你好运!
【讨论】:
transform.Find: does not perform a recursive descend down a Transform hierarchy. 所以它不适用于进一步的嵌套对象。
对于较新的 Unity 版本,this answer 可能提供了更好的解决方案!
一般应避免使用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;
}
别忘了using System; using System.Collections.Generic;
【讨论】:
FindObjectsOfType 也将搜索限制为启用对象的子对象。它不会在场景根级别找到禁用的对象
System.Linq你可以说List<GameObject> results = all.ToList().FindAll(go=>!go.activeInHierarchy);
虽然它不是正确的答案,但这就是我在我的案例中所做的。
1) 将脚本附加到(非活动)游戏对象,而不是设置然后非活动保持它处于活动状态。
2) 将它们放置在场景之外的某个地方。
3) 在脚本中设置一个标记为非活动状态。
4) 在 Update() 中检查此非活动标志,如果为 false,则跳过函数调用。
5) 当需要对象时,将其放置在适当的位置并将标志设置为活动状态。
这将是一个性能问题,但这是迄今为止我能想到的唯一解决方法。
【讨论】: