標籤:
Unity4.6 後續版本都已經內建了尋路AI了,之前的文章有介紹
Unity3d 尋路功能 介紹及項目示範
然而兩年來項目中一直使用的是 A* Pathfinding 這個外掛程式的,所以抽時間來寫下這個外掛程式的簡單使用。
根據遊戲的類型,使用到的外掛程式功能可能會不一樣,我這裡只介紹最簡單的,也是使用的最多的簡單尋路。複雜的如跟隨、動態,都有對應的例子來學習。
我也一直都沒有去看……
轉自http://blog.csdn.net/huutu http://www.thisisgame.com.cn
下面是動態圖,藉助 A* 外掛程式,編寫很少的代碼就可以做到尋路。
1、建立情境
在情境中添加一些Cube 作為障礙物 Obstacles,添加一個 Capsule 作為Player,然後添加一個Plane 作為地面,再添加一個Plane,作為斜坡測試。
在建立一個GameObject,改名為 A* ,添加A Star Path (Path finder) 組件。
2、編輯情境,指定障礙物
A* 外掛程式中,是根據 Layer 來判斷障礙物的,所以我們要把 作為障礙物的 Cubes 都設定到 Obstacle 這一個Layer。
然後給我們的地板,設定Layer 為 Ground ,兩塊地板都是
轉自http://blog.csdn.net/huutu http://www.thisisgame.com.cn
3、產生尋路網格
選中 A* ,在Inspector 中,展開 。查看下面的面板。
中,
黑色箭頭所指是寬高,這裡的寬高,是指格子的數量。這裡用到的就是 A* 的格子尋路。
調整寬高,覆蓋整個Plane。
紅色箭頭所指,是左上、右上、左下、右下、中心 四個點,選中其中一個點,就可以調整這個點的位置。
選中中心,點擊藍色箭頭所指的 Snap Size,會根據中心的位置來自動對齊。
繼續設定。
紅框中的Collision Testing,是產生 禁止通過 格子的。
因為我們的 Cubes 是障礙物,所以在 Mask 中選擇 Cubes 所在的Layer - Obstacles。
黃色框中的Height Testing 是用來 讓尋路節點 與 Ground 進行檢測的,比如要爬坡的時候就需要檢測高度。
設定完成後,點擊Scan,就會產生尋路網格。
轉自http://blog.csdn.net/huutu http://www.thisisgame.com.cn
4、編寫尋路 AI 代碼
產生尋路網格之後,我們在代碼中就可以使用 A* 來進行尋路了。
首先在 Player 這個 Capsule 上添加Seeker 組件。
然後建立指令碼 AStarPlayer.cs 作為測試代碼。
在代碼中,首先我們從 螢幕發射射線,來定位目標位置。
然後使用 Seeker 來開始產生最短路徑。
Seeker產生路徑成功後,會把每一個節點的位置儲存在 List中。
我們按照順序讀取 List 中的位置,位移Player 到對應的位置,就完成了尋路。
下面是完整代碼:
using UnityEngine;using System.Collections;using Pathfinding;public class AStarPlayer : MonoBehaviour { //目標位置; Vector3 targetPosition; Seeker seeker; CharacterController characterController; //計算出來的路線; Path path; //移動速度; float playerMoveSpeed = 10f; //當前點 int currentWayPoint = 0; bool stopMove = true; //Player中心點; float playerCenterY = 1.0f;// Use this for initializationvoid Start () { seeker = GetComponent<Seeker>(); playerCenterY = transform.localPosition.y;} //尋路結束; public void OnPathComplete(Path p) { Debug.Log("OnPathComplete error = "+p.error); if (!p.error) { currentWayPoint = 0; path = p; stopMove = false; } for (int index = 0; index < path.vectorPath.Count; index++) { Debug.Log("path.vectorPath["+index+"]="+path.vectorPath[index]); } }// Update is called once per framevoid Update () { if (Input.GetMouseButtonDown(0)) { RaycastHit hit; if (!Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100)) { return; } if (!hit.transform) { return; } targetPosition = hit.point;// new Vector3(hit.point.x, transform.localPosition.y, hit.point.z); Debug.Log("targetPosition=" + targetPosition); seeker.StartPath(transform.position, targetPosition,OnPathComplete); }} void FixedUpdate() { if (path == null || stopMove) { return; } //根據Player當前位置和 下一個尋路點的位置,計算方向; Vector3 currentWayPointV = new Vector3(path.vectorPath[currentWayPoint].x, path.vectorPath[currentWayPoint].y + playerCenterY, path.vectorPath[currentWayPoint].z); Vector3 dir = (currentWayPointV - transform.position).normalized; //計算這一幀要朝著 dir方向 移動多少距離; dir *= playerMoveSpeed * Time.fixedDeltaTime; //計算加上這一幀的位移,是不是會超過下一個節點; float offset = Vector3.Distance(transform.localPosition, currentWayPointV); if (offset < 0.1f) { transform.localPosition = currentWayPointV; currentWayPoint++; if (currentWayPoint == path.vectorPath.Count) { stopMove = true; currentWayPoint = 0; path = null; } } else { if (dir.magnitude > offset) { Vector3 tmpV3 = dir * (offset / dir.magnitude); dir = tmpV3; currentWayPoint++; if (currentWayPoint == path.vectorPath.Count) { stopMove = true; currentWayPoint = 0; path = null; } } transform.localPosition += dir; } }}
至此簡單的尋路了。
在A* 的Example 中,有很多個例子。
最簡單的尋路指令碼寫法是 直接繼承 AIPath 。
下面建立一個 指令碼 PlayerAI.cs 繼承 AIPath 來作為測試
using UnityEngine;using System.Collections;using Pathfinding.RVO;namespace Pathfinding{ [RequireComponent(typeof(Seeker))] [RequireComponent(typeof(CharacterController))] public class PlayerAI : AIPath { /** Minimum velocity for moving */ public float sleepVelocity = 0.4F; /** Speed relative to velocity with which to play animations */ public float animationSpeed = 0.2F; /** Effect which will be instantiated when end of path is reached. * \see OnTargetReached */ public GameObject endOfPathEffect; public new void Start() { //Call Start in base script (AIPath) base.Start(); } /** Point for the last spawn of #endOfPathEffect */ protected Vector3 lastTarget; public override void OnTargetReached() { if (endOfPathEffect != null && Vector3.Distance(tr.position, lastTarget) > 1) { GameObject.Instantiate(endOfPathEffect, tr.position, tr.rotation); lastTarget = tr.position; } } public override Vector3 GetFeetPosition() { return tr.position; } protected new void Update() { if (Input.GetMouseButtonDown(0)) { RaycastHit hit; if (!Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100)) { return; } if (!hit.transform) { return; } target.localPosition = hit.point; } //Get velocity in world-space Vector3 velocity; if (canMove) { //Calculate desired velocity Vector3 dir = CalculateVelocity(GetFeetPosition()); //Rotate towards targetDirection (filled in by CalculateVelocity) RotateTowards(targetDirection); dir.y = 0; if (dir.sqrMagnitude > sleepVelocity * sleepVelocity) { //If the velocity is large enough, move } else { //Otherwise, just stand still (this ensures gravity is applied) dir = Vector3.zero; } if (this.rvoController != null) { rvoController.Move(dir); velocity = rvoController.velocity; } else if (navController != null) {#if FALSEnavController.SimpleMove (GetFeetPosition(), dir);#endif velocity = Vector3.zero; } else if (controller != null) { controller.SimpleMove(dir); velocity = controller.velocity; } else { Debug.LogWarning("No NavmeshController or CharacterController attached to GameObject"); velocity = Vector3.zero; } } else { velocity = Vector3.zero; } } }}
代碼量少,但是不如自己寫的直觀。
兩種不同的指令碼都可以實現尋路效果。
樣本項目打包下載:
http://pan.baidu.com/s/1hsm6YNi
A* Pathfinding Project (Unity A*尋路外掛程式) 使用教程