找回密码
 立即注册
查看: 456|回复: 0

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

[复制链接]
发表于 2022-3-1 11:11 | 显示全部楼层 |阅读模式
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标记为[ExecuteInEditMode]
    这个方案,NavMesh确实可以显示出来,但是重新烘焙后,它引用的资源还是旧的,没有更新。
    需要一个烘焙结束的事件来刷新NavMeshData,这个方案才真正可用。我翻了一遍相关的Editor代码,没找到合适的。
  • 将2变种一下,加按钮让美术自己点显示、隐藏
    这个方案没实际做,因为都要手动点按钮,直接烘焙更简单、安全,美术也不必关心额外的限制规则。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-17 00:30 , Processed in 0.091033 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表