可能需要在运行时检测Prefab的场景
使用Unity进行开发时,为了降低脚本的挂载数量且让Monobehaviour的创建销毁由自己控制,所以采用了让脚本和Prefab分离的方法。因此很多物体都是运行时通过Prefab生成的。又因为Prefab上没有挂载任何游戏逻辑脚本,只是GameObject的集合。
所以此时,如果Prefab内部的GameObject有响应或操作,且需要Prefab本身进行一些逻辑操作,即在没有脚本且未知Prefab对应脚本类型时,子物体控制父物体(或更上层物体)。
那么,就需要使用到形如transform.parent.parent.parent.gameObject。比如:Prefab内部物体被射线打到时,又需要Prefab最上层物体对射线检测进行响应。
所以,我一开始想到的是能不能在运行时对Prefab进行检测。
无法在运行时检测的实际情况
Unity论坛上关于运行时Prefab的讨论
但实际上,这是根本不可执行的。很早的时候,在Unity的官方论坛就有人回复过运行时检测Prefab的问题。
if you have a game object,At runtime you can’t find it out any longer and its not needed cause you can not create or modify them anyway.
即:由于已经不需要更改这些Prefab了(因为如果要改,就应该在编辑器模式下提前改),所以检测它是不是一个prefab是一个没有意义的事。
废弃方案
所以以下函数在运行时是无法检测一个带有特性的Prefab的。
[Obsolete("Prefab在运行时不可检测,已废弃",true)]
public static GameObject GetClassByChild<T>(GameObject curObj) where T: PrefabBinding{
GameObject classObj=curObj;
//Prefab路径
string attrPath = typeof(T).GetCustomAttribute<PrefabPath>().path;
//Attribute.IsDefined(curObj.GetType(),PrefabPath)
string objPath = AssetDatabase.GetAssetPath(curObj);
/*如果不是PrefabClass节点,则往上递归
if(objPath!=null && attrPath == objPath){
return classObj;
}*/
//如果当前是根节点,则直接返回当前GameObject
if(curObj.transform.parent!=null){
classObj = GetClassByChild<T>(curObj.transform.parent.gameObject);
}else{
Debug.Log("已超出GameObject递归目录,当前节点为Global,将返回Null");
return null;
}
return classObj;
}
新的解决方案
样例场景
以射线检测为例,比如这种当射线打到一棵由Prefab动态生成出来的树上,该树上没有任何控制类脚本,Tree脚本和Tree物体在Unity中完全分离,需要通过hit.collider中的信息,来获取该物体在游戏场景中对应的Tree脚本实例。
...(略)
private void FindPrefab(RaycastHit2D hit) {
if (hit.collider.tag == "Tree") {
//Tree tree = GetClass<Tree>(hit.collider.transform.parent.parent.gameObject);
//通过合适的修改将上面的调用方式改为下面这种,通过碰撞体直接获取该prefab对应的脚本实例
Tree tree = GetClass<Tree>(hit.collider);
}
}
public T GetClass<T>(GameObject gameobject) where T : class {
Dictionary<GameObject, object> dic = data[typeof(T)];
if (dic.ContainsKey(gameobject)) {
return dic[gameobject] as T;
}
else {
Debug.LogError("未找到框架类" + typeof(T).Name);
return null;
}
}
...
解决方案1
在每个会产生多层级子物体控制最上层父物体操作的Prefab对应的脚本类中添加对应的响应处理函数。
- 优点:符合OOP,达到了解耦合的目的,即使Prefab嵌套依旧可以每个响应都有自己独立的函数互不干扰
- 缺点:每次Prefab变动,都需要去更改对应类中的响应函数,增大工作量。
//如果Prefab包含多个collider,则不使用接口
interface IColliderCheck{
GameObject SubControlMain();
}
//不同的类会写对应的不同的检测返回函数,再通过返回的GameObject来查找对应的脚本实例
public class Tree:PrefabClass,IColliderCheck{
...
GameObject SubControlMain(Collider2D col){
return col.transform.parent.parent.gameObject;
}
...
}
解决方案2
对单个Prefab的所有物体标上对应的tag,并利用一个静态函数和泛型
- 优点:所有的类都用一个静态函数来解决多层级子类控制父类,省去了大量响应函数的变动工作量
- 缺点:会产生很多tag,尤其是在嵌套Prefab中,tag不能重复命名,必须不同。
public T GetClass<T>(Collider2D col) where T : PrefabBinding{
Dictionary<GameObject, object> dic = data[typeof(T)];
GameObject gameobject = GetClassByChild(col.gameObject,col.tag);
if(dic.ContainsKey(gameobject)){
return dic[gameobject] as T;
}
else {
Debug.LogError("未找到PrefabBinding类:" + typeof(T).Name);
return null;
}
}
public static GameObject GetClassByChild(GameObject curObj,string tag){
GameObject classObj=curObj;
//Debug.Log(curObj.name+","+curObj.GetType());
if(curObj.transform.parent != null){
GameObject upperObj = curObj.transform.parent.gameObject;
//如果父节点与当前节点有相同的tag,则说明当前节点处于prefab内部,需要递归向上查找prefabClass主节点
if(upperObj.tag == tag){
classObj=GetClassByChild(upperObj,tag);
}
//tag不同,说明上层节点已经不是prefab,所以当前节点是prefabClass的主节点,此时返回当前节点
return classObj;
}
Log.Warning("无法递归找到PrefabClass类的GameObject,将返回Null");
return null;
}
参考链接
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!