對於a星尋路算法最直白的理解:
從a點走到b點,首先把地圖畫成網格,讓障礙物在網格內
如圖,從s點要走到e點,把障礙物設成黑色,還要創建2個隊列,一個是尋找新的節點隊列(開啓隊列),一個是保存已走過的節點隊列(關閉隊列)。在尋找新的節點時,要判斷該節點距離,公式爲總距離=當前節點距離起點步數+當前節點距離終點步數。這裏注意的是即使有障礙物在計算當前節點距離起終點步數也要按照沒有障礙物來算。
拿該圖來說,從起點s走到終點e(假設可以斜着走且斜走距離爲1)。圖上所有open,close的標識是走完後的,所以先不看,從s點出發,把s點加入開啓隊列,進入第一次循環,條件是如果開啓隊列不爲空,找到開啓隊列中總距離最小點(只有一個點s),則找到s點旁邊可走的點(相鄰s點且不在open隊列,不在close隊列),可以找到3,5,8,9,10點,將這些點移入open隊列,計算每點總距離,將s點移除open隊列,s點移入close隊列,進入第二次循環,當open隊列不爲空,找到open隊列總距離最小點,可以看到總距離最小點有3個,8,9,10並且都是4(4=1+3這個1指的是該點距離起點距離3點是該點距離終點距離),假設第一個點是8,那麼找到8點的旁邊的可走點,由於5,9點在開啓隊列,4點在關閉隊列,所以找到6,7點移入開啓隊列並計算總距離,把8移入關閉隊列
此時,開啓隊列{3,10,9,5,6,7} 關閉隊列{4,8}
進入第三次循環,開啓隊列不爲空時,找到開啓隊列中總距離最小的點,這裏有兩個9,10總距離爲4,假設是9,找鄰點,所有鄰點要麼在開啓隊列要麼在關閉隊列,所以9移入關閉隊列,
進入第四次循環,找到10點,找鄰點2,11,14,此時開啓隊列{2,3,5,6,7,11,14} 關閉隊列{4,8,9,10}
第五次循環,找到5點,移入關閉,開啓隊列{2,3,6,7,11,14} 關閉隊列{4,5,8,9,10}
第六次開啓隊列{2,6,7,11,14} 關閉隊列{3,4,5,8,9,10}
第七次開啓隊列{2,6,11,14} 關閉隊列{3,4,5,7,8,9,10}
第八次開啓隊列{2,11,14} 關閉隊列{3,4,5,6,7,8,9,10}
不說啦,可能有點錯誤,你們明白意思就行
最後如何找到最佳路徑呢,每次在開啓隊列找到的點選取鄰點時候,都要將鄰點的父節點選爲該點,圖中用箭頭表示,這樣當第n次循環,當從開啓隊列找到的第一個點爲終點時,回溯父節點就找到了最短路徑。
貼出來一張c#代碼供參考:
- using UnityEngine;
- using System.Collections;
-
- public class AStar {
-
- public static PriorityQueue closedList, openList; //開啓關閉隊列
-
- private static float NodeCost(Node a, Node b) //已知a,b點,計算兩點距離
- {
- Vector3 vecCost = a.position - b.position;
- return vecCost.magnitude;
- }
-
- public static ArrayList FindPath(Node start, Node goal) //a星尋路算法
- {
- openList = new PriorityQueue();
- openList.Push(start); //首先將起點壓入隊列,計算該點距起點距離和該點距終點距離
- start.nodeTotalCost = 0.0f;
- start.estimatedCost = NodeCost(start, goal);
-
- closedList = new PriorityQueue();
-
- Node node = null;
-
- while (openList.Length != 0) //當開啓隊列不爲空,進入正式循環
- {
- node = openList.First(); //找到開啓隊列中總距離最小的點
- if (node.position == goal.position) //如果開啓隊列中總距離最小的點是終點就退出循環
- {
- return CalculatePath(node);
- }
-
- ArrayList neighbours = new ArrayList();
- GridManager.instance.GetNeighbours(node, neighbours); //尋找該點的所有鄰點用neighbours保存,這個函數是調用另一個腳本
- for (int i = 0; i < neighbours.Count; i++)
- {
- Node neighbourNode = (Node)neighbours[i];
- if(!closedList.Contains(neighbourNode)) //如果該鄰點不在關閉隊列中就計算該點總距離
- {
- float cost = NodeCost(node, neighbourNode);
- float totalCost = node.nodeTotalCost + cost;
-
- float neighbourNodeEstCost = NodeCost(neighbourNode, goal);
-
- neighbourNode.nodeTotalCost = totalCost;
- neighbourNode.estimatedCost = totalCost + neighbourNodeEstCost;
- neighbourNode.parent = node;
-
- if (!openList.Contains(neighbourNode)) //鄰點不在開啓隊列就壓入開啓隊列
- {
- openList.Push(neighbourNode);
- }
- }
- }
- closedList.Push(node); //將該點從開啓隊列移除,移入關閉隊列
- openList.Remove(node);
- }
-
- if (node.position != goal.position) //如果開啓隊列全部點都遍歷了沒找到目標點,報錯
- {
- Debug.LogError("Goal Not Found");
- return null;
- }
- return CalculatePath(node); //該方法返回最佳路徑
- }
-
- private static ArrayList CalculatePath(Node node) //找到從終點的所有父節點也即終點到起點路徑,將list倒過來
- {
- ArrayList list = new ArrayList();
- while (node != null)
- {
- list.Add(node);
- node = node.parent;
- }
- list.Reverse();
- return list;
- }
- }
避障算法:
對於一個物體要想通過障礙物需要避障算法,算法思想,從坦克前方做射線和障礙物的焦點處做法線,讓坦克的新方向爲(前方+法線方向*力的大小),這樣坦克向左走,當射線碰不到障礙物的時候就沒有法線,就不會轉向了
代碼中將障礙物的layer層射到第8層爲Obstacles層
參考代碼
- public class AvoidingObstacles : MonoBehaviour {
-
- public float speed = 20.0f;
- public float mass = 5.0f;
- public float force = 50.0f;
- public float minimunDistToAvoid = 20.0f;
-
- private float curSpeed;
- private Vector3 targetPoint;
-
- // Use this for initialization
- void Start () {
- targetPoint = Vector3.zero;
- }
-
- void OnGUI()
- {
- GUILayout.Label("Click anywhere to move the vehicle.");
- }
-
- // Update is called once per frame
- void Update () {
- RaycastHit hit;
- //找到目標點
- var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
-
- if (Input.GetMouseButton(0) && Physics.Raycast(ray, out hit, 100.0f)) //發射100單位長度射線
- {
- targetPoint = hit.point;
- }
- //找到坦克的目標移動方向,並得到方向向量
- Vector3 dir = targetPoint - transform.position;
- dir.Normalize();
-
- AvoidObstacles(ref dir); //執行避障算法
-
- if (Vector3.Distance(targetPoint, transform.position) < 3.0f)
- return;
-
- curSpeed = speed * Time.deltaTime;
- var rot = Quaternion.LookRotation(dir);
- //坦克一直朝着目標方向旋轉
- transform.rotation = Quaternion.Slerp(transform.rotation, rot, 5.0f * Time.deltaTime);
- transform.position += transform.forward * curSpeed;
- }
-
- private void AvoidObstacles(ref Vector3 dir) //在避障算法中傳入移動方向並修改移動方向
- {
- RaycastHit hit;
-
- // 0000 0000 0000 0000 0000 0001 0000 0000
- // 1
- int layerMask = 1 << 8; //因爲把障礙物層設成第8層所以這樣
- //從坦克點的前方發射一條長度爲minimunDistToAvoid=20單位長度的射線,與layer爲第8層的物體相交
- if (Physics.Raycast(transform.position, transform.forward,
- out hit, minimunDistToAvoid, layerMask))
- {
- Vector3 hitNormal = hit.normal;
- hitNormal.y = 0.0f;
- dir = transform.forward + hitNormal * force;
- }
- }
- }
Flocking算法:
用於羣體物體追蹤一個領頭物體,比如gta5警車追人,紅警派出一羣小兵去哪裏,如圖綠色的物體一直在追蹤紅色物體移動,把紅色物體的移動代碼寫好,在hierarchy中把綠色物體拖動到紅色物體下面,給綠色物體添加flock代碼即可
紅色物體代碼:比較簡單就不註釋了:
- public class FlockControl : MonoBehaviour {
-
- public float speed = 100.0f;
- public Vector3 bound;
-
- private Vector3 initialPosition;
- private Vector3 nextMovementPoint;
-
- // Use this for initialization
- void Start () {
- initialPosition = transform.position;
- CalculateNextMovementPoint();
- }
-
- private void CalculateNextMovementPoint()
- {
- float posX = Random.Range(-bound.x, bound.x);
- float posY = Random.Range(-bound.y, bound.y);
- float posZ = Random.Range(-bound.z, bound.z);
-
- nextMovementPoint = initialPosition + new Vector3(posX, posY, posZ);
- }
-
- // Update is called once per frame
- void Update () {
- transform.Translate(Vector3.forward * speed * Time.deltaTime);
- transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(nextMovementPoint - transform.position), 2.0f * Time.deltaTime);
- if (Vector3.Distance(nextMovementPoint,transform.position) <= 20.0f)
- CalculateNextMovementPoint();
- }
- }
綠色物體代碼,比較複雜,只管用就行,不用跟我客氣:
- public class Flock : MonoBehaviour
- {
- public float minSpeed = 100.0f; //movement speed of the flock
- public float turnSpeed = 20.0f; //rotation speed of the flock
- public float randomFreq = 20.0f;
-
- public float randomForce = 20.0f; //Force strength in the unit sphere
- public float toOriginForce = 20.0f;
- public float toOriginRange = 100.0f;
-
- public float gravity = 2.0f; //Gravity of the flock
-
- public float avoidanceRadius = 400.0f; //Minimum distance between flocks
- public float avoidanceForce = 20.0f;
-
- public float followVelocity = 4.0f;
- public float followRadius = 40.0f; //Minimum Follow distance to the leader
-
- private Transform origin; //Parent transform
- private Vector3 velocity; //Velocity of the flock
- private Vector3 normalizedVelocity;
- private Vector3 randomPush; //Random push value
- private Vector3 originPush;
- private Transform[] objects; //Flock objects in the group
- private Flock[] otherFlocks; //Unity Flocks in the group
- private Transform transformComponent; //My transform
-
- void Start ()
- {
- randomFreq = 1.0f / randomFreq;
-
- //Assign the parent as origin
- origin = transform.parent;
-
- //Flock transform
- transformComponent = transform;
-
- //Temporary components
- Component[] tempFlocks= null;
-
- //Get all the unity flock components from the parent transform in the group
- if (transform.parent)
- {
- tempFlocks = transform.parent.GetComponentsInChildren<Flock>();
- }
-
- //Assign and store all the flock objects in this group
- objects = new Transform[tempFlocks.Length];
- otherFlocks = new Flock[tempFlocks.Length];
-
- for(int i = 0;i<tempFlocks.Length;i++)
- {
- objects[i] = tempFlocks[i].transform;
- otherFlocks[i] = (Flock)tempFlocks[i];
- }
-
- //Null Parent as the flock leader will be UnityFlockController object
- transform.parent = null;
-
- //Calculate random push depends on the random frequency provided
- StartCoroutine(UpdateRandom());
- }
-
- IEnumerator UpdateRandom ()
- {
- while(true)
- {
- randomPush = Random.insideUnitSphere * randomForce;
- yield return new WaitForSeconds(randomFreq + Random.Range(-randomFreq / 2.0f, randomFreq / 2.0f));
- }
- }
-
- void Update ()
- {
- //Internal variables
- float speed= velocity.magnitude;
- Vector3 avgVelocity = Vector3.zero;
- yield return new WaitForSeconds(randomFreq + Random.Range(-randomFreq / 2.0f, randomFreq / 2.0f));
- }
- }
-
- void Update ()
- {
- //Internal variables
- float speed= velocity.magnitude;
- Vector3 avgVelocity = Vector3.zero;
- Vector3 avgPosition = Vector3.zero;
- float count = 0;
- float f = 0.0f;
- float d = 0.0f;
- Vector3 myPosition = transformComponent.position;
- Vector3 forceV;