unity中NavMesh的靜態(tài)生成與動態(tài)加載,以及踩坑與爬坑

unity 導(dǎo)航數(shù)據(jù)的靜態(tài)生成與動態(tài)加載

本文主要描述了如何使用更加方便的 高級NavMesh構(gòu)建工具,用以靜態(tài)烘培&動態(tài)更新網(wǎng)格數(shù)據(jù),還包括其中遇到的一些坑與爬坑指南。不包含一些基礎(chǔ)知識描述,基礎(chǔ)知識請看下面官方文檔。

導(dǎo)航功能為unity內(nèi)置功能,基礎(chǔ)知識與具體各個組件功能與使用可見官方文檔:
https://docs.unity.cn/cn/2020.3/Manual/Navigation.html

高級NavMesh構(gòu)建工具未內(nèi)置在官方包內(nèi),需要從github導(dǎo)入使用。github倉庫地址:
https://github.com/Unity-Technologies/NavMeshComponents

1. 高級navmesh構(gòu)建工具

工具包內(nèi)主要包含這四個組件:

  • NavMeshSurface – 用于為一種代理類型構(gòu)建和啟用 NavMesh 表面。
  • NavMeshModifier – 根據(jù)變換層次影響 NavMesh 區(qū)域類型的數(shù)據(jù)生成。
  • NavMeshModifierVolume – 基于體積影響 NavMesh 區(qū)域類型的數(shù)據(jù)生成。
  • NavMeshLink – 為一種Agent連接相同或不同的 NavMesh 表面。

除了上面的組件外,工具包內(nèi)包含的Example工程內(nèi)也有一些腳本可以方便我們使用,例如后面會說到的navmesh動態(tài)更新使用到的 NavMeshSourceTag
另外由于該工具包是完全開源的,遇到任何問題都可以斷點(diǎn)debug和直接修改源碼來解決;

2. 靜態(tài)烘培navmesh數(shù)據(jù)

使用 NavMeshSurface 組件進(jìn)行靜態(tài)烘培。

NavmeshSerface.png

  • AgentType: 選擇代理類型;
  • CollectObjects: 可選擇通過體積生成或子節(jié)點(diǎn)生成;
  • IncludeLayers:包含的Layer;
  • UseGeometry:通過mesh還是collider去確認(rèn)生成的范圍;
  • DefaultArea:選擇默認(rèn)的區(qū)域類型;
  • OverrideVoxelSize(體素大小)和OverrideTileSize(區(qū)塊大小)算是導(dǎo)航組件中常有的屬性,后面會詳細(xì)介紹;

最下面的Clear按鈕用來清理已生成的navmesh數(shù)據(jù);Bake用來烘焙,點(diǎn)擊后會在同一文件夾下生成。


場景中的NavMeshSurface組件生成了導(dǎo)航數(shù)據(jù)

爬坑指南

點(diǎn)擊Bake按鈕生成數(shù)據(jù)成功后,打開Navigation面板會在場景中顯示已經(jīng)烘焙好的導(dǎo)航網(wǎng)格。


失敗和成功的情況下,場景中的顯示

但很多時候由于某些操作不正確或者工具本身問題,無法正常生成數(shù)據(jù)。如果生成后場景中沒有顯示導(dǎo)航網(wǎng)格,那么需要檢查下是否有以下情況:

  1. NavMeshSurface組件的節(jié)點(diǎn)是否是在場景中。預(yù)制體中烘培的數(shù)據(jù)會生成在Assets根目錄下,且如果場景中引用了預(yù)制體也無法生效。并且因?yàn)閳鼍爸泻皖A(yù)制體中都可以烘焙數(shù)據(jù),且數(shù)據(jù)在不同目錄,如果開發(fā)者不熟悉,管理不妥,則非常可能造成數(shù)據(jù)冗余;
  2. clear按鈕偶爾會無法刪除掉舊的烘焙數(shù)據(jù),再次生成時會生成了重復(fù)的副本,所以需要檢查不要生成過多重復(fù)導(dǎo)航數(shù)據(jù),或者自行修改保存邏輯,強(qiáng)制刪除備份;
  3. 在沒有以上問題的前提下,如果出現(xiàn)無法烘焙出數(shù)據(jù)的情況,則切換下CollectObjects選項(xiàng)就可以了。應(yīng)該是刷新機(jī)制有問題;

3. 動態(tài)加載navmesh數(shù)據(jù)

游戲進(jìn)入時導(dǎo)航網(wǎng)格數(shù)據(jù)初始化很慢,在進(jìn)入游戲時加載較大的網(wǎng)格數(shù)據(jù)時,普通配置的手機(jī)上甚至?xí)霈F(xiàn)卡住十幾秒的情況。

跟著場景一次性加載較大的網(wǎng)格數(shù)據(jù),除了初始化慢與占用內(nèi)存外,有時候還不能很好滿足業(yè)務(wù)需求。所以將較大的網(wǎng)格按功能分割成小塊,在使用時再動態(tài)更新網(wǎng)格數(shù)據(jù),也是一個必不可少的處理方式。

動態(tài)更新navmesh數(shù)據(jù)可以參考高級構(gòu)建工具里的 NavMeshSourceTag 的使用,其主要邏輯分為:

  1. 掛載NavMeshSourceTag組件的節(jié)點(diǎn)初始化時會把該節(jié)點(diǎn)記錄在NavMeshSourceTags中;
    void OnEnable()
    {
        _meshFilter = GetComponent<MeshFilter>();
        if (_meshFilter != null)
        {
            NavMeshSourceTags.Add(this);
        }
    }
  1. NavMeshSourceTag.Collect() 將記錄的所有Tags處理為官方接口使用的NavMeshBuildSource類。
public static void Collect(ref List<NavMeshBuildSource> sources)
{
    sources.Clear();
    for (var i = 0; i < NavMeshSourceTags.Count; ++i)
    {
        var mf = NavMeshSourceTags[i];
        if (mf == null) continue;

        var m = mf._meshFilter.sharedMesh;
        if (m == null) continue;

        var s = new NavMeshBuildSource();
        s.shape = NavMeshBuildSourceShape.Mesh;
        s.sourceObject = m;
        s.transform = mf.transform.localToWorldMatrix;
        s.area = 0;
        sources.Add(s);
    }
}

在實(shí)際的項(xiàng)目中,我將上面代碼里的 s.area = 0; 替換為根據(jù)NavMeshSourceTag組件內(nèi)新加一個變量Area,這樣在組件節(jié)點(diǎn)上自定義,動態(tài)更新生成不同Areas的導(dǎo)航數(shù)據(jù);

給官方腳本添加選擇Area的功能

  1. 手動調(diào)用RefreshNavMesh(),具體邏輯見代碼與注釋
    // 根據(jù)第一步中收集的Tag生成List<NavMeshBuildSource>數(shù)據(jù)
    NavMeshSourceTag.Collect(ref _sources);
    // 填寫navmesh構(gòu)建設(shè)置參數(shù)
    NavMeshBuildSettings defaultBuildSettings = NavMesh.GetSettingsByID(0);
    defaultBuildSettings.agentRadius = 0.01f;
    defaultBuildSettings.voxelSize = 0.001f;
    defaultBuildSettings.overrideVoxelSize = true;
    // navmesh可構(gòu)建空間,空間內(nèi)包含的才可生成導(dǎo)航數(shù)據(jù)
    var bounds = QuantizedBounds();
    // 選擇異步或者同步更新navmesh
    if (isAsync)
    {
        return NavMeshBuilder.UpdateNavMeshDataAsync(_navMesh, defaultBuildSettings, _sources, bounds);
    }
    else
    {
        NavMeshBuilder.UpdateNavMeshData(_navMesh, defaultBuildSettings, _sources, bounds);
        return null;
    }

爬坑指南

游戲運(yùn)行中,如果動態(tài)更新發(fā)現(xiàn)場景中沒有生成導(dǎo)航數(shù)據(jù),則需要檢查以下部分:

  1. 上面步驟3的代碼中,bounds范圍是否包含需要動態(tài)更新的節(jié)點(diǎn);
  2. 異步更新不會立即更新,需要一定的時間處理;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容