XGundam05 发表于 2022-3-1 11:11

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

1、环境

Unity 2020.3.25f1烘焙方式的NavMesh做法
2、问题描述


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

NavMesh重叠

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


翻Manual文档找禁用NavMesh接口时,发现有动态添加、移除NavMeshData的API,然后看了眼烘培生成的Asset恰好是NavMeshData。此时萌生了想法:
将NavMesh跟Scene断开关联,保证LoadScene的时候NavMesh不会被强制加载生效场景里挂个脚本,Enable时加载NavMesh,Disable时移除NavMesh

首先做个快速的验证测试,我手动将Scene序列化文件中对NavMeshData的引用删除
// Scene引用的NavMeshDatam_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获取、修改NavMeshDatapublic 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);    }}
最后把试验的结果整合起来,完整实现这个解决方案!
EditorSceneManager.sceneSaving的时候,获取当前场景的NavMeshData,并在场景中创建NavMeshHolder节点,引用NavMeshData对象。(本来这步处理也想放到Closing时一起做,但关闭时创建对象Unity会报错)EditorSceneManager.sceneClosing的时候,将NavMeshData从Scene中解绑。运行时的东西都在NavMeshHolder中,不需要其他处理了

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

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


【unity】自动化流程之代码修改导航网格参数
<hr>
最后,记录下没成功的,还原场景NavMesh的方案

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