美文网首页
[RS] Unity多场景加载,NavMesh重叠问题解决

[RS] Unity多场景加载,NavMesh重叠问题解决

作者: _Walker__ | 来源:发表于2022-03-01 10:51 被阅读0次

    1、环境

    • Unity 2020.3.25f1
    • 烘焙方式的NavMesh做法

    2、问题描述

      项目中一个副本章节有多个关卡,每个关卡有一个独立的场景,玩家可以在场景中用摇杆行走(NavMesh烘路面)。策划希望关卡切换能营造出一个完整大地图的感觉(无缝切换),所以需要对章节中所有关卡的场景做预加载。
      我们采用Additive方式将多个Scene同时加载出来,然后将非当前关卡的RootGameObject隐藏,实现仅显示一个关卡的效果。这种方式多个Scene的NavMesh都被加载出来了,且同时生效,导致寻路错误,比如:看上去不能走的地方,走上去了;本该通行的路面,被卡住等。

    NavMesh重叠

      网上一通搜得知,烘培方式的NavMesh是和场景绑定的,场景加载出来NavMesh就一起带出来了,并且Unity并未提供简单的方式将这种绑定关系解除。

    3、解决方案

      翻Manual文档找禁用NavMesh接口时,发现有动态添加、移除NavMeshData的API,然后看了眼烘培生成的Asset恰好是NavMeshData。此时萌生了想法:

    1. 将NavMesh跟Scene断开关联,保证LoadScene的时候NavMesh不会被强制加载生效
    2. 场景里挂个脚本,Enable时加载NavMesh,Disable时移除NavMesh

    首先做个快速的验证测试,我手动将Scene序列化文件中对NavMeshData的引用删除

    // Scene引用的NavMeshData
    m_NavMeshData: {fileID: 23800000, guid: d82730c1770cac545a77d844d83bc62b, type: 2}
    // 改成
    m_NavMeshData: {fileID: 0}
    

    然后把第2项做了

    public class NavMeshHolder : MonoBehaviour
    {
        public NavMeshData NavData;
        private NavMeshDataInstance _navDataInst;
    
        public void OnEnable()
        {
            if (null != NavData)
            {
                _navDataInst = NavMesh.AddNavMeshData(NavData);
            }
        }
    
        public void OnDisable()
        {
            if (null != NavData)
            {
                NavMesh.RemoveNavMeshData(_navDataInst);
            }
        }
    }
    

      进游戏测试,问题解决!接下来是怎么优美的解决第1项,最粗暴的方案是用正则匹配替换。自己不喜欢用这种,不管文件结构的处理方式,所以还是先尝试找Unity的接口。两个方向:

    1)用SerializedObject修改场景序列化信息
    2)看Unity是否直接提供了相应的API。

      开始两个方向都没找到切入点,后来搜到一篇帖子(参考资料)给出了1)的做法,实现的过程中又发现了2)的接口。两种做法都记录下

    // 1) 用SerializedObject获取、修改NavMeshData
    public static NavMeshData CurSceneNavMeshData
    {
        get
        {
            SerializedObject so = new SerializedObject(NavMeshBuilder.navMeshSettingsObject);
            SerializedProperty sp = so.FindProperty("m_NavMeshData");
            NavMeshData data = (NavMeshData) sp.objectReferenceValue;
            return data;
        }
        set
        {
            SerializedObject so = new SerializedObject(NavMeshBuilder.navMeshSettingsObject);
            SerializedProperty sp = so.FindProperty("m_NavMeshData");
            sp.objectReferenceValue = value;
            so.ApplyModifiedPropertiesWithoutUndo();
        }
    }
    
    // 2) 直接用UnityAPI,这里需要反射,Unity有接口但没有public出来
    private static DynamicType _NavMeshBuilder = new DynamicType(typeof(NavMeshBuilder));
    
    public static NavMeshData CurSceneNavMeshData
    {
        get
        {
            NavMeshData data = (NavMeshData) _NavMeshBuilder.PrivateStaticProperty<Object>("sceneNavMeshData");
            return data;
        }
        set
        {
            BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;
            _NavMeshBuilder.SetProperty("sceneNavMeshData", flags, value);
        }
    }
    

      最后把试验的结果整合起来,完整实现这个解决方案!

    1. EditorSceneManager.sceneSaving的时候,获取当前场景的NavMeshData,并在场景中创建NavMeshHolder节点,引用NavMeshData对象。(本来这步处理也想放到Closing时一起做,但关闭时创建对象Unity会报错)
    2. EditorSceneManager.sceneClosing的时候,将NavMeshData从Scene中解绑。
    3. 运行时的东西都在NavMeshHolder中,不需要其他处理了

      这个方案有个弊端,美术同学下次打开场景是看不到NavMesh的,必须要手动烘焙一下。尝试过解决这个问题,没找到合适方案。最终跟美术同学确认,烘焙一次只需要几秒钟,这个就不管了。

      Unity自己有个实时烘焙的Package:NavMesh Components,应该可以更好的解决问题。我们临近出版本,先用快速方案撑一波。

    4、参考资料

    【unity】自动化流程之代码修改导航网格参数


      最后,记录下没成功的,还原场景NavMesh的方案

    1. NavMeshEditorHelpers.DrawBuildDebug
      看API介绍像是用来绘制NavMesh的,但没成功,可能因为我没有用NavMeshBuilder逐步构建。
    2. NavMeshHolder标记为[ExecuteInEditMode]
      这个方案,NavMesh确实可以显示出来,但是重新烘焙后,它引用的资源还是旧的,没有更新。
      需要一个烘焙结束的事件来刷新NavMeshData,这个方案才真正可用。我翻了一遍相关的Editor代码,没找到合适的。
    3. 将2变种一下,加按钮让美术自己点显示、隐藏
      这个方案没实际做,因为都要手动点按钮,直接烘焙更简单、安全,美术也不必关心额外的限制规则。

    相关文章

      网友评论

          本文标题:[RS] Unity多场景加载,NavMesh重叠问题解决

          本文链接:https://www.haomeiwen.com/subject/iokqrrtx.html