區域網格化
在使用A星算法和物體布局的過程中,常常會使用的網格的概念,即建立在網格的基礎上,會使得游戲的相關編程變得簡單的多。
格子的代碼:
using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class Node { public Vector3 _worldPos;//格子中心點的位置 public int _gridX, _gridY;//在網格列表的下標 public Node(Vector3 Position, int x, int y) { _worldPos = Position; _gridX = x; _gridY = y; } }
網格代碼:
using System.Collections; using System.Collections.Generic; using UnityEngine; //網格,網格的起點是在左下角,終點是右上角 public class Grid : MonoBehaviour { public static Grid instance; private Node[,] grid;//網格 public Vector2 gridSize;//網格橫縱大小 public float nodeRadius;//格子的半徑 private float nodeDiameter;//格子的直徑 public int gridCntX, gridCntY;//兩個方向上的網格數量 //Test public Transform tarTrans;//目標 public Node tar; public float dir;//射程 //目標區域 public Node zoneLeftDown;//網格的左下角 public Node zoneRightUp;//網格的右上角 Vector3 pos = new Vector3(); // Start is called before the first frame update void Awake() { instance = this; nodeDiameter = nodeRadius * 2; gridCntX = Mathf.RoundToInt(gridSize.x / nodeDiameter); gridCntY = Mathf.RoundToInt(gridSize.y / nodeDiameter); grid = new Node[gridCntX, gridCntY]; CreateGrid(); }//創建網格,起始點在左下角 private void CreateGrid() { //獲得網格的左下角的坐標 Vector3 startPoint = transform.position - gridSize.x / 2 * Vector3.right - gridSize.y / 2* Vector3.up; for (int i = 0; i < gridCntX; i++) { for (int j = 0; j < gridCntY; j++) { Vector3 worldPoint = startPoint + Vector3.right * (i * nodeDiameter + nodeRadius) + Vector3.up * (j * nodeDiameter + nodeRadius); grid[i, j] = new Node(worldPoint, i, j); } } } //獲取某個坐標處的格子 public Node GetFromPosition(Vector3 position) { //首先獲得該坐標相對於網格的寬高的百分比 float percentX = (position.x + gridSize.x / 2) / gridSize.x; float percentY = (position.y + gridSize.y / 2) / gridSize.y; //保證百分比值在0到1之間 percentX = Mathf.Clamp01(percentX); percentY = Mathf.Clamp01(percentY); int x = Mathf.RoundToInt((gridCntX - 1) * percentX); int y = Mathf.RoundToInt((gridCntY - 1) * percentY); return grid[x, y]; } //獲取一個正方形區域中隨機點,length為區域的邊長 public Vector3 GetZoneRandomPos(Vector3 center,float length) { //射程一定要大於等於0 //float len = Mathf.Abs(length) / 2; //獲取射程網格區域 zoneLeftDown = GetFromPosition(center - new Vector3(length, length)); zoneRightUp = GetFromPosition(center + new Vector3(length, length)); //獲取並返回射程網格區域中的一個隨機點 int i = Random.Range(zoneLeftDown._gridX, zoneRightUp._gridX); int j = Random.Range(zoneLeftDown._gridY, zoneRightUp._gridY); return grid[i, j]._worldPos; } //獲取整個區域中的一個隨機點 public Vector3 GetZoneRandomPos() { int i = Random.Range(0, gridCntX); int j = Random.Range(0, gridCntY); return grid[i, j]._worldPos; } private void OnDrawGizmos() { //繪制網格邊界線 Gizmos.DrawWireCube(transform.position, new Vector3(gridSize.x, gridSize.y, 1)); if (grid == null) return; Gizmos.color = new Color(1, 1, 1, 0.2f); //繪制網格 foreach (var node in grid) { Gizmos.DrawCube(node._worldPos+Vector3.forward, Vector3.one * (nodeDiameter - .1f*nodeDiameter)); } } }
運行結果:
一般A星算法實例一,具體算法原理略
這里修改原來的網格代碼,從而符合A星算法的需求
using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class Node { public bool _walkable;//是否可走 public Vector3 _worldPos;//格子中心點的位置 public int _gridX, _gridY;//在網格列表的下標 public int gCost;//到起始點的曼哈頓距離 public int hCost;//到目標點的曼哈頓距離 public Node parent;//父親網格點 public int fCost {//曼哈頓距離綜合 get { return gCost +hCost; } } public Node(bool walkable,Vector3 Position, int x, int y) { _walkable = walkable; _worldPos = Position; _gridX = x; _gridY = y; } } public class Grid : MonoBehaviour { public static Grid instance; private Node[,] grid;//網格 public Vector2 gridSize;//網格橫縱大小 public float nodeRadius;//格子的半徑 private float nodeDiameter;//格子的直徑 public int gridCntX, gridCntY;//兩個方向上的網格數量 public Vector3 startPoint;//網格的最右下角的坐標 public LayerMask layer; //Test public Transform tarTrans;//目標 public Node tar; public float dir;//射程 public List<Node> path = new List<Node>(); //目標區域 public Node zoneLeftDown;//網格的左下角 public Node zoneRightUp;//網格的右上角 Vector3 pos = new Vector3(); // Start is called before the first frame update void Awake() { instance = this; nodeDiameter = nodeRadius * 2; gridCntX = Mathf.RoundToInt(gridSize.x / nodeDiameter); gridCntY = Mathf.RoundToInt(gridSize.y / nodeDiameter); grid = new Node[gridCntX, gridCntY]; CreateGrid(); } //創建網格,起始點在左下角 private void CreateGrid() { //獲得網格的左下角的坐標 startPoint = transform.position - gridSize.x / 2 * Vector3.right - gridSize.y / 2 * Vector3.up; for (int i = 0; i < gridCntX; i++) { for (int j = 0; j < gridCntY; j++) { Vector3 worldPoint = startPoint + Vector3.right * (i * nodeDiameter + nodeRadius) + Vector3.up * (j * nodeDiameter + nodeRadius); bool walkable = !Physics2D.OverlapCircle(worldPoint, nodeRadius, layer); grid[i, j] = new Node(walkable,worldPoint, i, j); } } } //獲取某個坐標處的格子,利用百分比,那么在網格區域外的一個點的網格點就是離它最近的那個網格點 public Node GetFromPosition(Vector3 position) { //首先獲得該坐標相對於網格的寬高的百分比 float percentX = (position.x + gridSize.x / 2) / gridSize.x; float percentY = (position.y + gridSize.y / 2) / gridSize.y; //保證百分比值在0到1之間 percentX = Mathf.Clamp01(percentX); percentY = Mathf.Clamp01(percentY); int x = Mathf.RoundToInt((gridCntX - 1) * percentX); int y = Mathf.RoundToInt((gridCntY - 1) * percentY); return grid[x, y]; } //獲取網格范圍內一個正方形區域中隨機點,length為區域的邊長 public Vector3 GetZoneRandomPos(Vector3 center, float length) { //射程一定要大於等於0 //float len = Mathf.Abs(length) / 2; //獲取射程網格區域 zoneLeftDown = GetFromPosition(center - new Vector3(length, length)); zoneRightUp = GetFromPosition(center + new Vector3(length, length)); //獲取並返回射程網格區域中的一個隨機點 int i = Random.Range(zoneLeftDown._gridX, zoneRightUp._gridX); int j = Random.Range(zoneLeftDown._gridY, zoneRightUp._gridY); return grid[i, j]._worldPos; } //獲取整個區域中的一個隨機點 public Vector3 GetZoneRandomPos() { int i = Random.Range(0, gridCntX); int j = Random.Range(0, gridCntY); return grid[i, j]._worldPos; } //獲取某格子周圍除了自身外的另外所有可走格子 public List<Node> GetNeibourhood(Node node) { List<Node> neibourhood = new List<Node>(); for(int i = -1; i <= 1; i++) { for(int j = -1; j<= 1; j++) { if (i == 0 && j == 0) continue; int tempX = node._gridX + i; int tempY = node._gridY + j; if (tempX > 0 && tempX < gridCntX && tempY > 0 && tempY < gridCntY) { neibourhood.Add(grid[tempX,tempY]); } } } return neibourhood; } private void OnDrawGizmos() { //繪制網格邊界線 Gizmos.DrawWireCube(transform.position, new Vector3(gridSize.x, gridSize.y, 1)); if (grid == null) return; //Gizmos.color = new Color(1, 1, 1, 0.2f); //繪制網格 foreach (var node in grid) { Gizmos.color = node._walkable ? Color.white : Color.red; Gizmos.DrawCube(node._worldPos + Vector3.forward, Vector3.one * (nodeDiameter - .1f * nodeDiameter)); } if (path != null) { foreach(var node in path) { Gizmos.color = Color.black; Gizmos.DrawCube(node._worldPos + Vector3.forward, Vector3.one * (nodeDiameter - .1f * nodeDiameter)); } } Node tarNode = GetFromPosition(tarTrans.position); if (tarNode != null && tarNode._walkable) { Gizmos.color = Color.cyan; Gizmos.DrawCube(tarNode._worldPos, Vector3.one * (nodeDiameter - .1f * nodeDiameter)); } } }
下面是A星尋路算法,這里使用了兩種不能的路徑處理結果
using System.Collections; using System.Collections.Generic; using UnityEngine; //FindingPath為經典的A星尋路算法,獲得網格路徑。 //FindingPathForFlay是在尋路結束之后,將網格路徑中不需要的網格刪除調,從而使得運動方向更加平滑,剪除的過程在GeneratePathForFly中進行, //后期優化可以放在尋路過程中進行 public class FindPath : MonoBehaviour { public Transform hunter, tar; Grid _grid; // Start is called before the first frame update void Start() { _grid = GetComponent<Grid>(); } // Update is called once per frame void Update() { //FindingPath(hunter.position, tar.position); FindingPathForFly(hunter.position, tar.position); } //經典A*算法到目標點的精確路徑查找 void FindingPath(Vector3 startPos,Vector3 endPos) { Node startNode = _grid.GetFromPosition(startPos); Node endNode = _grid.GetFromPosition(endPos); List<Node> openSet = new List<Node>(); HashSet<Node> closeSet = new HashSet<Node>(); openSet.Add(startNode); while (openSet.Count > 0) { //根據曼哈頓距離尋找新結點 Node currentNode = openSet[0]; for (int i = 0; i < openSet.Count; i++) { if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost) { currentNode = openSet[i]; } } openSet.Remove(currentNode); closeSet.Add(currentNode); //如果當前結點是最后一個結點,那么尋找結束 if (currentNode == endNode) { GeneratePath(startNode,endNode); return; } //添加周圍的可用結點到openSet foreach (var node in _grid.GetNeibourhood(currentNode)) { if (!node._walkable || closeSet.Contains(node)) continue; int newCost = currentNode.gCost + GetDistanceNodes(currentNode, node); if (newCost < node.gCost || !openSet.Contains(node)) { node.gCost = newCost; node.hCost = GetDistanceNodes(node, endNode); node.parent = currentNode; if (!openSet.Contains(node)) { openSet.Add(node); } } } } } //經典A*算法下的飛行游戲路徑查找 void FindingPathForFly(Vector3 startPos, Vector3 endPos) { Node startNode = _grid.GetFromPosition(startPos); Node endNode = _grid.GetFromPosition(endPos); List<Node> openSet = new List<Node>(); HashSet<Node> closeSet = new HashSet<Node>(); openSet.Add(startNode); while (openSet.Count > 0) { //根據曼哈頓距離尋找新結點 Node currentNode = openSet[0]; for (int i = 0; i < openSet.Count; i++) { if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost) { currentNode = openSet[i]; } } openSet.Remove(currentNode); closeSet.Add(currentNode); //如果當前結點是最后一個結點,那么尋找結束 if (currentNode == endNode) { GeneratePathForFly(startNode, endNode); return; } //添加周圍的可用結點到openSet foreach (var node in _grid.GetNeibourhood(currentNode)) { if (!node._walkable || closeSet.Contains(node)) continue; int newCost = currentNode.gCost + GetDistanceNodes(currentNode, node); if (newCost < node.gCost || !openSet.Contains(node)) { node.gCost = newCost; node.hCost = GetDistanceNodes(node, endNode); node.parent = currentNode; if (!openSet.Contains(node)) { openSet.Add(node); } } } } } //經典A*算法下根據父子關系回溯獲得路徑 void GeneratePath(Node startNode,Node endNode) { List<Node> path = new List<Node>(); Node temp = endNode; while (temp != startNode)//回溯,將所有路徑結點結合成路徑列表 { path.Add(temp); temp = temp.parent; } path.Reverse();//將列表反轉 _grid.path = path;//將列表交予Grid; } //經典A*算法下根據父子關系回溯獲得路徑,並且回溯。如果兩個網格點之間沒有障礙,可以直達,那么剪除兩個網格點之間剪除不要的網格點 void GeneratePathForFly(Node startNode, Node endNode) { List<Node> path = new List<Node>(); Node temp = endNode; while (temp != startNode)//回溯,將所有路徑網格結合成路徑列表 { if(path.Count==0) path.Add(temp); RaycastHit2D hit; //如果前一個網格點與當前網格點的父親網格之間有障礙,那么表明需要當前網格點,將其添加到路徑表中 if(hit=Physics2D.Raycast(path[path.Count-1]._worldPos, temp.parent._worldPos - path[path.Count - 1]._worldPos,Vector3.Distance(temp.parent._worldPos, path[path.Count - 1]._worldPos))) { path.Add(temp); } temp = temp.parent; } path.Reverse();//將列表反轉 _grid.path = path;//將列表交予Grid; } //獲取兩個點之間的曼哈頓距離 int GetDistanceNodes(Node a,Node b) { int cntX = Mathf.Abs(a._gridX - b._gridX); int cntY = Mathf.Abs(a._gridY - b._gridY); if (cntX > cntY) { return 14 * cntY + 10 * (cntX - cntY); } else { return 14 * cntX + 10 * (cntY - cntX); } } }
結果
剪除多余的網格點之后
對於不同體積的角色,避免碰撞的處理方案有兩種:
第一種:將網格分為不同的層,每層的格子的大小不同。
第二種:尋路時,網格可以走的條件為,walkable為真,且在設定半徑范圍內沒有其他碰撞體存在。
2D飛行游戲最簡單AI設計:
1.如果自己與目標之間沒有障礙,那么直接飛向目標
2.飛向目標的方式:轉向目標方向,如果當前的方向與到目標方向的角度小於設定的某個角度,那么開啟噴射引擎。
3.逃離目標:這里以圓形的障礙目標為例,如果檢測到障礙,那么設定障礙到自身的方向為目標方向,轉向該目標方向,從而逃離目標。由於這個過程中自身的位置 不斷變化,目標方向也是變化的,但是獲得的結果也更加真實。