天天看點

如何使用A*插件實作移動以及網格的動态生成

在學習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。

如何使用A*插件實作移動以及網格的動态生成

再往下,我們在Collision testing中選擇一些層作為障礙物,比如Wall等。因為A*是通過層來差別障礙物的。下面是表示高度的一個 參考平面,用來做高度檢測,有時候我們需要多層的網格。(類似于海拔的基準平面吧,不知道是不是,隻是感覺),我們這裡選擇Ground。這時候我們可以不用去管其他的了,直接拉到最底下點選Scan按鈕,或者是按快捷鍵Ctrl+Alt+S鍵生成網格(也是更新網格),我們可以看到平面上覆寫了一層網格了,如果看不到,把AstarPath下面的Show Graphs勾選上。

如何使用A*插件實作移動以及網格的動态生成

好啦,我們初始工作已經做好了,接下來開始我們的正事,讓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函數不做解釋,也是參考了别人的一些做法。下一篇博文我将把這個尋路與行為樹結合起來,實作一個簡單的巡邏任務。

繼續閱讀