可能需要在运行时检测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;
}

参考链接



Unity C# 游戏开发

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!