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)烘培漏麦。
- 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)擊后會在同一文件夾下生成。
爬坑指南:
點(diǎn)擊Bake按鈕生成數(shù)據(jù)成功后讶隐,打開Navigation面板會在場景中顯示已經(jīng)烘焙好的導(dǎo)航網(wǎng)格起胰。
失敗和成功的情況下,場景中的顯示但很多時候由于某些操作不正確或者工具本身問題巫延,無法正常生成數(shù)據(jù)效五。如果生成后場景中沒有顯示導(dǎo)航網(wǎng)格,那么需要檢查下是否有以下情況:
- NavMeshSurface組件的節(jié)點(diǎn)是否是在場景中炉峰。預(yù)制體中烘培的數(shù)據(jù)會生成在Assets根目錄下畏妖,且如果場景中引用了預(yù)制體也無法生效。并且因?yàn)閳鼍爸泻皖A(yù)制體中都可以烘焙數(shù)據(jù)疼阔,且數(shù)據(jù)在不同目錄戒劫,如果開發(fā)者不熟悉,管理不妥婆廊,則非逞赶福可能造成數(shù)據(jù)冗余;
- clear按鈕偶爾會無法刪除掉舊的烘焙數(shù)據(jù)淘邻,再次生成時會生成了重復(fù)的副本茵典,所以需要檢查不要生成過多重復(fù)導(dǎo)航數(shù)據(jù),或者自行修改保存邏輯宾舅,強(qiáng)制刪除備份统阿;
- 在沒有以上問題的前提下,如果出現(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 的使用破镰,其主要邏輯分為:
- 掛載NavMeshSourceTag組件的節(jié)點(diǎn)初始化時會把該節(jié)點(diǎn)記錄在NavMeshSourceTags中餐曼;
void OnEnable()
{
_meshFilter = GetComponent<MeshFilter>();
if (_meshFilter != null)
{
NavMeshSourceTags.Add(this);
}
}
- 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ù);
- 手動調(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ù),則需要檢查以下部分:
- 上面步驟3的代碼中养渴,bounds范圍是否包含需要動態(tài)更新的節(jié)點(diǎn)雷绢;
- 異步更新不會立即更新,需要一定的時間處理理卑;