目錄
基礎簡介
Unity的導航系統是需要先分析場景中的自由活動區域和障礙區域,簡單說就是先在編輯器模式下進行烘焙生成場景信息,然後再運行A*算法計算出路徑。
在Unity的Window選項中打開導航系統的面板
可以看到面板裏一共有四個選項卡
Object
Scene Filter是過濾器,可以根據不同的選擇在層次面板中隱藏部分物體
在層次面板中選中某個遊戲物體後,Object選項卡會多出幾個參數
首先我們可以看到選中物體的網格信息,下面就是三個屬性
Navigation Static:指明物體是靜態還是動態的,只有勾選了這個纔會在烘焙的時候被導航系統記錄
Generate OffMeshLinks:指明物體是否根據高度、可跳躍寬帶等信息自動生成鏈接
Navigation Area:指明物體對應於導航系統的區域:Walkable(可行走)、NotWalkable(不可行走)、Jump(可跳躍)
Bake
在這裏可以設置導航網格的相關信息進行烘焙。
如果遊戲裏的地形環境太複雜,不太容易進行烘焙。可以嘗試用一些簡單的3D模型搭建一個輔助形狀進行烘焙。
Areas
對應於Object選項卡中的Navigation Area屬性,用來設置路徑的層,Cost是指對應層的一個估值(類似於消耗的代價,當對於同一個目標的有多條路徑時系統會選擇Cost值較小的那一條)
Agents
腳本中使用導航系統
先獲取NavMeshAgent組件,再設定目標,NPC就自動開始追擊了。
NavMeshAgent類的常用:
SetDestination(Vector3 point):設置目標
destination = point:設置目標
RemainingDestance():獲取與目標的距離
Isstoped = true:停止導航
利用網格數據生成路徑
可以利用導航圖生成的網格數據獲取路徑。
NavMesh.CalculatePath(起始位置,目標位置,只計算某些層,獲取的路徑)
如果找到路徑該方法會返回true
public void GetPath(Vector3 startPos, Vector3 endPos)
{
//重置路徑數組
wayPoints = null;
//計算路徑
NavMeshPath navPath = new NavMeshPath();
bool hasFoundPath = NavMesh.CalculatePath(startPos, endPos, NavMesh.AllAreas, navPath);
if(!hasFoundPath)
{
return;
}
//生成路徑
int length = navPath.corners.Length;
wayPoints = new Vector3[length];
for(int i=0; i<length; i++)
{
wayPoints[i] = navPath.corners[i];
}
}
靜態網格
Unity自帶的導航系統是用來做自動尋路功能,下面以一個靜態的追擊實例來說明。
導航網格生成
首先搭建一個簡單的場景(地板、障礙物、玩家、敵人),並設置地板和障礙物的static選項
然後在Unity的Window選項中打開Navigation進行烘焙,這裏也可以設置一下烘焙信息。
烘焙完成後如下,藍色部分爲自由活動區域
製作追擊功能
這裏想實現的功能是敵人不斷追着玩家跑(在實際中一般是敵人進行巡邏,發現玩家後再進行追蹤)
給敵人添加NavMeshAgent組件,這個組件的作用是分析生成的網格信息並回避障礙物,計算到目標的最短距離並進行追擊。
組件的Stopping Distance屬性表示距目標多遠是停下,一般設置爲2。
對於追擊目標則需要在代碼中去設置,使用NavMeshAgent.destination屬性指定要追擊目標的位置。也可以用NavMeshAgent.SetDestination(Vector3 pos)函數進行設置。
注意引用using UnityEngine.AI;
public class EnemyCtrl : MonoBehaviour {
private Transform tr;
private Transform playerTr;
private NavMeshAgent navMeshAgent;
void Start () {
tr = this.transform;
playerTr = GameObject.FindGameObjectWithTag("Player").transform;
navMeshAgent = this.gameObject.GetComponent<NavMeshAgent>();
//設置目標位置後,馬上開始追擊
navMeshAgent.destination = playerTr.position;
}
void Update () {
}
}
這很簡單的實現了追擊功能,但是敵人只會追擊一開始的位置。可以改下邏輯給敵人設置一個巡查範圍。
public class EnemyCtrl : MonoBehaviour {
public float checkDis = 10;
private Transform tr;
private Transform playerTr;
private NavMeshAgent navMeshAgent;
void Start () {
tr = this.transform;
playerTr = GameObject.FindGameObjectWithTag("Player").transform;
navMeshAgent = this.gameObject.GetComponent<NavMeshAgent>();
//設置目標位置後,馬上開始追擊
//navMeshAgent.destination = playerTr.position;
}
void Update () {
float dis = Vector3.Distance(tr.position, playerTr.position);
//發現後才進行追擊,並可以實時更新目標位置
//由於之前在組件裏面設置了最小距離,這裏就不用再設置了
if(dis < checkDis)
{
navMeshAgent.destination = playerTr.position;
}
}
}
動態網格
Unity的導航系統不僅支持烘焙靜態的網格,還能動態烘焙。
在之前的場景中新加幾個方塊(添加了Rigidbody組件)作爲新的可移動障礙物,這幾個不用勾選Static選項。然後重新烘焙一下,可以發現新加入的障礙物在網格中還是可自由活動區域
然後給新加入的障礙物添加NavMeshObstacte組件,這裏類似於碰撞體可以去修改調整。
運行後可以發現敵人能識別這些在不斷改變位置的障礙物了,但在烘焙的網格中這些可移動的障礙物任然是藍色的。(猜想這可能是導航系統只是根據組件識別到障礙物並結合之前的烘焙圖尋找路徑,並沒有進行實際的烘焙)
在組件裏有個Carve選項,這個選項勾選後導航系統就會在遊戲運行時進行實時烘焙,但這回增加運算負荷,使用時要配合設置它的一些屬性來進行。這個選項是用來解決敵人在多個障礙物拍在一起是卡死的情況。
開啓Carve選項後運行,可以看到實時烘焙的圖
移動後
分離網格連接
Unity導航系統的導航網格是以模型的網格爲基礎生成的,若模型的網格是分離的那生成的導航網格也是分離的。敵人的追擊是不能跨網格進行,這是就需要將不同的網格連在一起。
不同網格之間生成的鏈接代表着npc在不同網格間移動的路徑。
網格鏈接
先在場景中模擬這種分離的情況,搭建一個平臺(Plane就可以)。然後就是實現它的烘焙和鏈接,先在層次圖中選中創建的物體,然後在導航視圖中勾選兩個選項(烘焙和連接)。
接下來切換包Bake選項卡,設置DropHeight屬性(生成的分離網格鏈接的最高值,Unity只會生成小於這個值的連接)
AgentRadius屬性是指生成的鏈接的間距
JumpDistance屬性是指相同高度時物體可以跳過的最大距離。
設置好後重新烘焙可以看到變化
自定義鏈接
除此之外還可以自定義生成分離網格鏈接,在烘焙之前去掉鏈接的勾選。
對比之前的烘焙圖就沒有了鏈接
接下來就是自己構造鏈接,先創建兩個空物體作爲平臺的子物體。這兩個空物體就代表着鏈接的兩頭。將其分別放在平臺上和地板上。注意Endpos的位置一定要被導航網格覆蓋
給平臺添加OffMeshLink組件,並將剛剛的兩個空物體放進組件裏
現在再來看就可以看到自定義的鏈接。注意這裏連接的箭頭是雙向的表示可以跳下來也可以跳上去,而之前那個單向的箭頭只能往下跳(按箭頭方向運動);