在學習Unity Behavior插件和AStarPathFinding(即A*)插件的時候,遇到了一個小的問題。因為系統自帶的Nevigation元件地形需要事先烘培,沒辦法對随機生成的地圖和障礙進行動态Bake,在嘗試了很久之後,還是決定放棄自帶的,而把眼光瞄上了A*插件。從網上下載下傳了一個學習使用。在網上參考了很多大神的學習筆記,然後開始了自己的嘗試。
首先簡單講一下A*使用(網上已經很多資源,不多廢話):我們先打開項目,導入A*插件,在項目中加一個Plane,将位置reset為(0,0,0)不妨把Scale設定為10,1,10,順便可以為它設定一個layer,如Ground;再添加一個空物體,改名為A*,在Project欄搜尋AstarPath腳本,給這個空物體挂上。 點選Add New Graph,這裡我選擇了GridGraph,然後我們來修改網格資訊。如下:Width表示寬的這個方向有多少Node,Depth表示長的這個方向有多少個Node,Nodesize則表示每一個Node占了多大的面積,這裡剛才設定地闆Scale為10乘10,也就是長寬都是100米,是以我們網格大小設定為1,長寬都設定100個節點。往下把Center設定為(0,0,0),Connections這裡是設定網格之間怎麼連通的,可以選擇Four 或者Six 或者 Eight,這個牽扯到人物尋路可以怎麼從一個網格到另一個網格,我這裡選擇Eight。
再往下,我們在Collision testing中選擇一些層作為障礙物,比如Wall等。因為A*是通過層來差別障礙物的。下面是表示高度的一個 參考平面,用來做高度檢測,有時候我們需要多層的網格。(類似于海拔的基準平面吧,不知道是不是,隻是感覺),我們這裡選擇Ground。這時候我們可以不用去管其他的了,直接拉到最底下點選Scan按鈕,或者是按快捷鍵Ctrl+Alt+S鍵生成網格(也是更新網格),我們可以看到平面上覆寫了一層網格了,如果看不到,把AstarPath下面的Show Graphs勾選上。
好啦,我們初始工作已經做好了,接下來開始我們的正事,讓Player動起來。這裡先給Player加上Seeker元件和CharacterController元件(我是通過這個實作移動的),來編寫我們的第一段代碼吧!
建立一個C#腳本AStar.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class AStar : MonoBehaviour {
// public Transform target;
private Seeker seeker;
private CharacterController characterController;
public Path path;
private float speed = 100;
private float angularSpeed = 10;
private float nextWaypointDistance = 1.5f;
private int currentWaypoint = 0;
private AstarPath aStarPath;
void Start () {
aStarPath = GameObject.Find("A*").GetComponent<AstarPath>();
seeker = GetComponent<Seeker>();
characterController = GetComponent<CharacterController>();
seeker.pathCallback += OnPathComplete; //尋路的一個回調
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
if (!Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100))
{
return;
}
if (!hit.transform)
{
return;
}
seeker.StartPath(transform.position, hit.point, OnPathComplete); //尋路并移動到滑鼠點選位置
}
if (Input.GetMouseButtonDown(1)) //按滑鼠右鍵,更新網格,實作了動态障礙物網格生成 嘻嘻
{
aStarPath.Scan();
}
}
public void StarPath(Transform target)
{
seeker = GetComponent<Seeker>();
characterController = GetComponent<CharacterController>();
seeker.pathCallback += OnPathComplete; //尋路的一個回調
seeker.StartPath(transform.position, target.position, OnPathComplete);
}
private void FixedUpdate()
{
Move();
}
public void Move()
{
if (path == null) //如果路徑為空,直接傳回
{
return;
}
if (currentWaypoint >= path.vectorPath.Count)
{
return;
}
if (characterController != null && characterController.enabled == true)
{
Vector3 dir = (path.vectorPath[currentWaypoint] - transform.position).normalized;
dir *= speed * Time.deltaTime;
characterController.SimpleMove(dir);
Quaternion targetRotation = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.fixedDeltaTime * angularSpeed);
if (Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]) < nextWaypointDistance)
{
currentWaypoint++;
return;
}
}
}
public void OnPathComplete(Path p)
{
if (!p.error)
{
path = p; //如果不發生錯誤,把p傳給path
currentWaypoint = 0;
}
}
private void OnDisable()
{
seeker.pathCallback -= OnPathComplete;
}
//提供get set方法供其他腳本使用
#region GetSet
public float Speed
{
get { return speed; }
set { speed = value; }
}
public float AngularSpeed
{
get { return angularSpeed; }
set { angularSpeed = value; }
}
public float ArriveDistance
{
get { return nextWaypointDistance; }
set { nextWaypointDistance = value; }
}
#endregion
}
運作後點選地面,Player便會尋路,并移動到點選位置,這時候也可以加一些障礙物,按滑鼠右鍵可以在障礙物周圍設定障礙網格,通過Scan這個方法可以更新網格資訊,實作了網格動态更新。注意:
seeker.StartPath(transform.position, hit.point, OnPathComplete);
這個不能再UpDate中一直執行,否則不會移動。Move函數不做解釋,也是參考了别人的一些做法。下一篇博文我将把這個尋路與行為樹結合起來,實作一個簡單的巡邏任務。