前言
本篇文章主要介紹 Unity 遊戲開發中使用 A* Pathfinding 插件實現【自動導航】和【尋路法】的應用教學,並額外提供了一些補充說明。希望能幫助大家更好地理解這些工具的使用方法,提升遊戲開發效率,也歡迎大家支持原文作者!
尋路法插件介紹
A* Pathfinding 是 Unity 上非常受歡迎且功能強大的尋路套件,由 Aron Granberg 開發。它使用 A* 演算法進行自動導航,適用於各種 2D 和 3D 遊戲環境。A* Pathfinding 能夠有效處理大量的單位移動需求,並提供靈活的 API,方便開發者根據遊戲需求自定義路徑尋找行為。
功能特色
- 多種尋路法和自動導航方式:支持網格圖 (Grid Graph)、導航網格 (Navmesh Graph) 和點圖 (Point Graph) 等多種路徑圖形結構,能滿足不同類型遊戲的需求。
- 靈活調整:使用者可以根據不同需求設置權重,如考慮地形難度、避開障礙物等,從而影響尋路結果,達到更加符合遊戲邏輯的效果。
- 本地避障:套件提供本地避障 (Local Avoidance) 功能,確保多個單位在移動過程中能避免相互碰撞。
- 高效性能:具有優化的計算方法,使其在大型地圖和大量單位移動的情境下也能保持良好的性能。
- 可視化工具:提供方便的可視化工具,能夠在 Unity 編輯器中直接查看路徑圖形、障礙物、單位行進的路徑等,便於調試和開發。
適用場景
- RTS 遊戲:適合用於即時戰略遊戲,幫助大量單位進行平滑的路徑移動。
- 角色扮演遊戲 (RPG):適合需要複雜地形和多層地圖的遊戲環境,提供靈活的導航網格。
- 塔防遊戲:在塔防遊戲中,可以用來讓敵人根據動態地形變化選擇合適的攻擊路徑。
尋路法最終效果展示
先來看看最終效果的展示。
尋路法插件導入步驟
首先將必要的AI插件導入。
導航路徑渲染與碰撞器配置
新建空物體,新增 PathFinder 元件,用在地圖導航。
渲染導航路徑,繪製出來的藍色部分則為可行走區域,非藍色區域是我配置的碰撞器區域,可自行修改,如果不滿意可以修改 Diameter 的值,控制碰撞器區域多大的範圍不可行走。
給敵人添加碰撞器,碰撞區域自行調整。
敵人 AI 導航與攻擊邏輯
為敵人添加 AIPath 組件,在我們的 2D 項目中,記得將 Orientation 設置為 YAxisForward(適用於 2D 遊戲)。如果你不希望敵人旋轉,可以取消勾選 Enable Rotation。
常用參數 | 解釋 |
---|---|
can move | 表示能否移動 |
max speed | 表示移動速度 |
rotation speed | 表示旋轉速度 |
slowdown distance | 表示減速距離 |
end reached distance | 表示停止距離,表示怪物距離玩家多遠時會停止移動。 |
再給敵人加入 AI Destination setter 組件,這個組件能幫助敵人自動導航到玩家的位置,利用 A* 尋路法計算最短路徑,實現智能化追擊。
上述設定都完成後,來看看運行效果。
程式碼實現敵人移動與攻擊控制
程式碼控制敵人移動,並發動攻擊
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class Enemy : MonoBehaviour
{
public float startHealth = 100; // 初始血量
public float health; // 當前血量
public bool isDead; // 是否死亡
public float damage = 10; // 敵人傷害
public float hitRate = 1.0f; // 攻速
private float _lastHit; // 計時器
public LayerMask whatToHit; // 可以攻擊的圖層
private float hitDistance = 2.0f; // 攻擊距離
[Header("AI 導航屬性")]
private AIPath aiPath;
private Transform target; // 目標
private void Start()
{
aiPath = GetComponent<AIPath>();
target = GameObject.FindGameObjectWithTag("Player")?.transform;
health = startHealth;
if (target == null)
{
Debug.LogError("Player not found. Ensure there's a GameObject tagged 'Player' in the scene.");
}
}
private void Update()
{
if (isDead || target == null) return;
aiPath.destination = target.position; // 敵人移動的目標位置
if (aiPath.reachedDestination) // 是否抵達目標位置
{
// 發起攻擊
if (Time.time > _lastHit + 1 / hitRate)
{
Hit();
_lastHit = Time.time;
}
}
}
// 攻擊
void Hit()
{
if (isDead) return;
// 怪物朝向
Vector3 targetDirection = (target.position - transform.position).normalized;
// 射線,aiPath.endReachedDistance 表示抵達終點的距離
RaycastHit2D hit2D = Physics2D.Raycast(transform.position, targetDirection, aiPath.endReachedDistance + hitDistance, whatToHit);
if (hit2D.collider != null)
{
PlayerController playerController = hit2D.collider.GetComponent<PlayerController>();
if (playerController != null)
{
playerController.TakeDamage(damage);
Debug.Log($"Damage dealt: {damage}");
}
else
{
Debug.LogWarning("Hit object does not have a PlayerController component.");
}
}
}
// 受到傷害的方法
public void TakeDamage(float damageAmount)
{
if (isDead) return;
health -= damageAmount;
if (health <= 0)
{
Die();
}
}
// 死亡方法
void Die()
{
isDead = true;
// Add additional logic for when the enemy dies, like playing an animation or disabling components
Debug.Log("Enemy died.");
}
}
更加複雜的程式碼控制
using UnityEngine;
using Pathfinding;
public class MyAIMove : MonoBehaviour
{
private Seeker mSeeker; // 尋路組件
private List<Vector3> mPathPointList; // 路徑點列表
private int mCurrentIndex = 0; // 當前路徑點索引
void Start()
{
mSeeker = GetComponent<Seeker>(); // 獲取 Seeker 組件
}
void Update()
{
// 當鼠標左鍵點擊時
if (Input.GetMouseButtonDown(0))
{
// 獲取鼠標點擊位置,並轉換為世界座標
Vector3 target = Camera.main.ScreenToWorldPoint(Input.mousePosition);
target.z = 0; // 保證 z 軸為 0(2D 場景)
// 創建路徑
CreatePath(target);
// 移動角色
Move();
}
}
private void Move()
{
// 如果路徑點列表為空或者當前索引超出範圍,直接返回
if (mPathPointList == null || mCurrentIndex >= mPathPointList.Count)
return;
// 如果當前位置與目標路徑點的距離大於 0.2f,則繼續移動
if (Vector2.Distance(transform.position, mPathPointList[mCurrentIndex]) > 0.2f)
{
// 計算移動方向
Vector3 dir = (mPathPointList[mCurrentIndex] - transform.position).normalized;
// 根據方向移動角色,這裡的 20 是移動速度,Time.deltaTime 是每幀的時間間隔
transform.position += dir * Time.deltaTime * 20;
}
else
{
// 如果已經到達當前路徑點,則切換到下一個路徑點
if (mCurrentIndex == mPathPointList.Count - 1)
return;
mCurrentIndex++;
}
}
private void CreatePath(Vector3 target)
{
mCurrentIndex = 0; // 重置當前路徑點索引為 0
// 開始尋路,從當前位置到目標位置
mSeeker.StartPath(transform.position, target, path =>
{
// 尋路完成後,將路徑點存入路徑點列表
mPathPointList = path.vectorPath;
});
}
}
目前人物的移動會非常生硬,在人物身上掛載一個 Simple Smooth 組件,不用做任何修改,我們的移動就變得順口了。
其他尋路法插件介紹
還有一個 Unity2d 自動尋路法的插件叫 NavMeshPlus,可以一併了解下。
A*Pathfinding 與 NavMeshPlus的差別
A*Pathfinding 插件和 NavMeshPlus 插件都是 Unity 中常見的【自動導航】與【尋路法】插件。它們之間的差異主要體現在以下幾個方面:
- 演算法原理:A* Pathfinding 插件使用的是 A* 演算法來搜尋最短路徑,而 NavMeshPlus 插件使用的是 Unity 自帶的導航網格系統來計算路徑。
- 功能特點:A* Pathfinding 插件提供了大量的路徑搜尋和尋路演算法,同時也提供了尋路障礙物躲避、尋路優化等功能;而 NavMeshPlus 插件則主要專注於優化Unity的導航網格系統,提供了更有效率更準確的表面剖分、NavMesh配置和障礙物遮蔽等功能。
- 使用成本:A* Pathfinding 插件需要進行額外的配置和調試,而 NavMeshPlus 插件則更加易用,直接在 Unity 中就能夠完成操作。
基於以上區別,建議的使用場景為:如果需要複雜的尋路演算法、路線最佳化或存在大量的尋路單位,建議使用 A*Pathfinding 外掛程式;如果您只需要簡單的尋路演算法,或需要最佳化現有導航網格的效能,建議使用 NavMeshPlus 插件。
總之,這兩個插件都是非常優秀的導航網格相關插件,具體的使用需根據實際需求進行選擇。
為什麼大量的尋路單位推薦使用 A*Pathfinding?
A* Pathfinding 插件採用了【基於圖的最短路徑】搜尋演算法,相較於 Unity 的導航網格系統來說,對於大量的尋路單位能夠更快速地搜尋到最短路徑,避免了因為單位數量增加而導致導航網格系統計算路徑的效率下降的問題。
A* Pathfinding 外掛程式也提供了許多【高階演算法】,如流形平滑、局部避障等演算法,能夠對路徑進行最佳化或避免行進時的碰撞,尤其適用於複雜的場景以及大量單位的遊戲中。
此外 A* Pathfinding 可以配置多個執行緒來計算尋路,能夠進一步提高效能。而 Unity 的導航網格是單執行緒計算,一旦出現大量的尋路單位,計算時間就會倍增。
因此對於需要處理大量自動導航單位的遊戲,特別是需要複雜尋路法的場景,建議使用 A* Pathfinding 插件。
尋路法插件下載點
Asset Store下載連結:A* Pathfinding (Google 下載連結:本載點文件僅供學術交流,請勿用於商業用途)
————————————————
更多好用插件:【Unity 好用插件推薦】持續更新,一起讓遊戲開發事半功倍!
本文原創(或整理)於亞洲電玩通,未經作者與本站同意不得隨意引用、轉載、改編或截錄。
特約作家簡介
支持贊助 / DONATE
亞洲電玩通只是很小的力量,但仍希望為復甦台灣遊戲研發貢獻一點動能,如果您喜歡亞洲電玩通的文章,或是覺得它們對您有幫助,歡迎給予一些支持鼓勵,不論是按讚追蹤或是贊助,讓亞洲電玩通持續產出,感謝。
BTC |
352Bw8r46rfXv6jno8qt9Bc3xx6ptTcPze |
ETH |
0x795442E321a953363a442C76d39f3fbf9b6bC666 |
TRON |
TCNcVmin18LbnXfdWZsY5pzcFvYe1MoD6f |