花了1天时间,看了很多前辈的A星算法思路,终于完成A星算法。
完成A*算法,首先要理解A星算法的原理,A星算法擅长解决方块类路径的寻路问题,以起始点的方块为起点,向该方块周围的方块进行探测&计算,以(为该起点周围需要探测的每个砖块赋予数据,并对数据进行比较,然后确认下一个可能移动的砖块,并以它作为下一个起始点,重复以上操作)为原理,直到找到路径终点为止,并通过路径之间的类似链表结构,从终点方块遍历到起点方块,并记录中途所有路径方块,然后让寻路者沿着记录的路径的所有方块逐一移动,直到终点为止。
A星算法 要赋予每个地图砖块几个变量
1.曼哈顿距离(A星算法曼哈顿比较容易入门)-------H
https://baike.so.com/doc/2753904-2906430.html
链接来源百科
H(n) = D * (abs ( n.x – goal.x ) + abs ( n.y – goal.y ) )
通俗易懂的二维公式:d(i,j)=|X1-X2|+|Y1-Y2|
(曼哈顿距离——两点在南北方向上的距离加上在东西方向上的距离,即D(I,J)=|XI-XJ|+|YI-YJ|。对于一个具有正南正北、正东正西方向规则布局的城镇街道,从一点到达另一点的距离正是在南北方向上旅行的距离加上在东西方向上旅行的距离因此曼哈顿距离又称为出租车距离,曼哈顿距离不是距离不变量,当坐标轴变动时,点间的距离就会不同——百度知道)
-
距离真正的起始点的移动的步数(按照多少个砖块来算)-----G
起始点第一次周围的砖块的G值为1( 因为起始点是0 )如果找到第二个点作为新的起始点,那么这个点的周围不包括真正的起始点的砖块(点)的G值为2 -
G+H的和 -------F
用于比较各个砖块的F的值
4.open列表 ------List<地图的砖块对象> open
用于存储在不断的搜寻新起始点的过程中,收集到的起始点周围砖块的信息
用于遍历Open列表中所有砖块F的值,来确定下一个起始点,也可以作为某一方向的寻路过程中,遇到墙壁后重新找新路时,遍历之前的所有可行的点,找F值最小的点来作为新的,下一个起始点,继续寻路
5.close列表 ------List<地图的砖块对象> close
用于存储在不断的搜寻新起始点的过程中,曾经当过起始点的所有砖块,避免在寻路过程中遭遇重复点(重复路径)
里面还有很多关键点,代码都有注释,由于我也是第一次尝试,所以写下了也是怕自己忘记了
自动生成地图和角色立方体的代码
这里需要2个放在Resources文件夹下的预制体 一个是生成地图用的 命名为Cube1,一个是生成主角的 命名为A
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//每个地图砖块赋予的属性
public class OnePoint
{
public float F;
public int G;
public float H;
public Vector3 curPoint = new Vector3(1000, 1000, 1000);
public OnePoint father;//父级
public OnePoint(Vector3 curPoint)
{
F = 99999;
G = 99999;
H = 99999;
this.curPoint = curPoint;
}
public void Reset()
{
F = 99999;
G = 99999;
H = 99999;
this.father = null;
}
}
public class DiTu : MonoBehaviour
{
public GameObject cube;
public List<OnePoint> pointList = new List<OnePoint>();
public Dictionary<Vector3, OnePoint> dic = new Dictionary<Vector3, OnePoint>();
public static DiTu instance;
public int x;
public int z;
IEnumerator CreatePaths(int x = 1, int y = 1)
{
for (int i = 0; i < z; i++)
{
for (int j = 0; j < x; j++)
{
GameObject go = Instantiate<GameObject>(Resources.Load<GameObject>("Cube1"), transform.position + new Vector3(j, 0, i), Quaternion.identity);
//GameObject go = CreateCubeGo(j, i, transform.position);
OnePoint temp = new OnePoint(go.transform.position + Vector3.up);
int num = Random.Range(0, 11);
if (num < 8 || (i == 0 && j == 0))
{
pointList.Add(temp);
dic.Add(go.transform.position + Vector3.up, temp);
go.tag = "ditu";
}
else
{
go.GetComponent<MeshRenderer>().materials[0].color = Color.black;
}
}
}
yield return 0;
//地图创建好后,创建主角
GameObject player = Instantiate<GameObject>(Resources.Load<GameObject>("A"), transform.position, Quaternion.identity);
player.name = "A";
player.transform.position += Vector3.up;
player.GetComponent<MeshRenderer>().materials[0].color = Color.yellow;
//创建好主角后,给主角绑定摄像机
Camera.main.transform.parent = player.transform;
}
private void Awake()
{
instance = this;
StartCoroutine(CreatePaths(x, z));
}
}
接下来是挂载在玩家 A 预制体上的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class AstarCacular : MonoBehaviour
{
//鼠标点击
Ray ray;
List<OnePoint> open = new List<OnePoint>();
List<OnePoint> close = new List<OnePoint>();
//是否找到
bool isFind = false;
//地图字典
Dictionary<Vector3, OnePoint> dic;
OnePoint curOnePoint;
OnePoint targetPoint;
List<Vector3> paths = new List<Vector3>();
Vector3 target;
int index = 0;
// Use this for initialization
private void Awake()
{
target = new Vector3(9, 1, 9);
}
void Start()
{
this.transform.position = new Vector3(0, 1, 0);
dic = DiTu.instance.dic;
curG = 0;
int length = DiTu.instance.pointList.Count;
List<OnePoint> temp = DiTu.instance.pointList;
for (int i = 0; i < length; i++)
{
if (transform.position == temp[i].curPoint)
{
curOnePoint = temp[i];
curOnePoint.curPoint = transform.position;
curOnePoint.father = null;
curOnePoint.G = 0;
//
open.Add(curOnePoint);
break;
}
}
}
bool checkFinish = false;
bool canFind = true;
float ti = 0;
private void FixedUpdate()
{
if (!isFind)
{
ti ++;
}
else
{
Debug.Log("ti: " + ti);
}
}
// Update is called once per frame
void Update()
{
MouseButtonDown();
if (canFind)
{
if (!isFind)
{
ForeachOpenList(this.target);
}
else
{
Debug.Log("!");
if (targetPoint != null)
{
while (targetPoint.father != null)//找到反向路径,并用paths记录
{
paths.Add(targetPoint.curPoint);
targetPoint = targetPoint.father;
}
if (targetPoint.father == null && checkFinish == false)//设置初始路径索引
{
checkFinish = true;
index = paths.Count - 1;
}
if (index < 0)
{
canFind = false;
return;
}
transform.position = Vector3.Lerp(transform.position, paths[index], 0.5f);
if (Vector3.Distance(transform.position, paths[index]) < 0.1f)
{
transform.position = paths[index];
index--;
}
}
}
}
}
void ForeachOpenList(Vector3 target)
{
if (open.Count <= 0)
{
Debug.Log("open列表为空");
return;
}
var minF = open[0].F;
var point = open[0];
//找到最小F对应point
for (int i = 0; i < open.Count; i++)
{
if (open[i].F < minF)
{
point = open[i];
}
}
//将该点添加进close列表中
close.Add(point);
//将该点从open列表中移除
open.Remove(point);
//分析该砖块周围的方格
#region 闲置
//List<OnePoint> points4 = new List<OnePoint>(4);
#endregion
//对各方位的砖块做判断的变量
short upNum = 0;//上
short downNum = 0;//下
short leftNum = 0;//左
short rightNum = 0;//右
//short ruNum = 0;//右上
//short rdNum = 0;//右下
//short luNum = 0;//左上
//short ldNum = 0;//左下
处理上下左右四个方向的点
CaculateUpDir(upNum, point);
CaculateDownDir(downNum, point);
CaculateLeftDir(leftNum, point);
CaculateRightDir(rightNum, point);
if (isFind)
{
Debug.Log("找到目标");
}
if (open.Count <= 0 && isFind == false)//查找失败
{
Debug.Log("查找失败");
}
}//传统A*
void MouseButtonDown()
{
if (Input.GetMouseButtonDown(0))
{
var x = Mathf.Ceil(transform.position.x);
var y = Mathf.Ceil(transform.position.y);
var z = Mathf.Ceil(transform.position.z);
transform.position = new Vector3(x, y, z);
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (hit.collider.tag == "ditu")
{
//进行一次新的路径查找
//清除列表状态
open.Clear();
close.Clear();
paths.Clear();
//获取地图的点的数量
int length = DiTu.instance.pointList.Count;
List<OnePoint> temp = DiTu.instance.pointList;
//清除所有点的状态
for (int i = 0; i < length; i++)
{
temp[i].Reset();//恢复出厂设定
}
#region 找到当前点,重置当前初始点给新的空open,并对isFind/canFind/checkFinish/index/当前点curPoint的curPoint/fater/G进行重置
for (int i = 0; i < length; i++)
{
if (transform.position == temp[i].curPoint)
{
curOnePoint = temp[i];
curOnePoint.curPoint = transform.position;
curOnePoint.father = null;
curOnePoint.G = 0;
open.Add(curOnePoint);
isFind = false;
canFind = true;
checkFinish = false;
index = 0;
//Debug.Log("curOnePoint.curPoint:" + transform.position);
//获取目标
this.target = hit.transform.position + Vector3.up;
//Debug.Log("target: " + target);
break;
}
}
#endregion
}
}
}
}
void CaculateUpDir(short upNum, OnePoint point)
{
#region 分析上面的方格
//是否可抵达
Vector3 up = point.curPoint + Vector3.forward;
if (dic.ContainsKey(up))
{
//分析是不是终点
if (up == target)
{
dic[up].father = point;
open.Add(dic[up]);
targetPoint = dic[target];
isFind = true;
}
else//还不是终点
{
//分析是否是close列表中的点
if (close.Count > 0)
{
for (int i = 0; i < close.Count; i++)
{
if (dic[up] == close[i])
{
upNum = -1;
break;
}
}
}
if (upNum < 0)
{
//该方向计算结束
}
else//该点可抵达并且不在close列表中
{
upNum = 1;
var temp = 1;
if (open.Count > 0)//判断该点是否在open列表中
{
for (int i = 0; i < open.Count; i++)
{
if (up == open[i].curPoint)
{
temp = -1;
break;
}
}
}
if (temp > 0)//如果不在,将该点添加到open中
{
dic[up].father = point;//该点的父级为之前的点
dic[up].G = dic[up].father.G + 1;//该点的G为父级的G加1
//曼哈顿算法
dic[up].H = Mathf.Abs(up.x - target.x) + Mathf.Abs(up.z - target.z);
//F求和
dic[up].F = dic[up].H + dic[up].G;
open.Add(dic[up]);
}
else if (temp < 0)//如果在open列表中
{
if (dic[up].G > dic[up].father.G + 1)
{
dic[up].father = point;//该点的父级为之前的点
dic[up].G = dic[up].father.G + 1;//该点的G为父级的G加1
//F求和
dic[up].F = dic[up].H + dic[up].G;
}
}
}
}
}
#endregion
}
void CaculateDownDir(short downNum, OnePoint point)
{
#region 分析下面的方格
//是否可抵达
Vector3 down = point.curPoint - Vector3.forward;
if (dic.ContainsKey(down))
{
//分析是不是终点
if (down == target)
{
dic[down].father = point;
open.Add(dic[down]);
targetPoint = dic[target];
isFind = true;
}
else//还不是终点
{
//分析是否是close列表中的点
if (close.Count > 0)
{
for (int i = 0; i < close.Count; i++)
{
if (dic[down] == close[i])
{
downNum = -1;
break;
}
}
}
if (downNum < 0)
{
//该方向计算结束
}
else//该点可抵达并且不在close列表中
{
downNum = 1;
var temp = 1;
if (open.Count > 0)//判断该点是否在open列表中
{
for (int i = 0; i < open.Count; i++)
{
if (down == open[i].curPoint)
{
temp = -1;
break;
}
}
}
if (temp > 0)//如果不在,将该点添加到open中
{
dic[down].father = point;//该点的父级为之前的点
dic[down].G = dic[down].father.G + 1;//该点的G为父级的G加1
//曼哈顿算法
dic[down].H = Mathf.Abs(down.x - target.x) + Mathf.Abs(down.z - target.z);
//F求和
dic[down].F = dic[down].H + dic[down].G;
open.Add(dic[down]);
}
else if (temp < 0)//如果在open列表中
{
if (dic[down].G > dic[down].father.G + 1)
{
dic[down].father = point;//该点的父级为之前的点
dic[down].G = dic[down].father.G + 1;//该点的G为父级的G加1
//F求和
dic[down].F = dic[down].H + dic[down].G;
}
}
}
}
}
#endregion
}
void CaculateLeftDir(short leftNum, OnePoint point)
{
#region 分析左边的方格
//是否可抵达
Vector3 left = point.curPoint + Vector3.left;
if (dic.ContainsKey(left))
{
//分析是不是终点
if (left == target)
{
dic[left].father = point;
open.Add(dic[left]);
targetPoint = dic[target];
isFind = true;
}
else//还没到终点
{
//分析是否是close列表中的点
if (close.Count > 0)
{
for (int i = 0; i < close.Count; i++)
{
if (dic[left] == close[i])
{
leftNum = -1;
break;
}
}
}
if (leftNum < 0)
{
//该方向计算结束
}
else//该点可抵达并且不在close列表中
{
leftNum = 1;
var temp = 1;
if (open.Count > 0)//判断该点是否在open列表中
{
for (int i = 0; i < open.Count; i++)
{
if (left == open[i].curPoint)
{
temp = -1;
break;
}
}
}
if (temp > 0)//如果不在,将该点添加到open中
{
dic[left].father = point;//该点的父级为之前的点
dic[left].G = dic[left].father.G + 1;//该点的G为父级的G加1
//曼哈顿算法
dic[left].H = Mathf.Abs(left.x - target.x) + Mathf.Abs(left.z - target.z);
//F求和
dic[left].F = dic[left].H + dic[left].G;
open.Add(dic[left]);
}
else if (temp < 0)//如果在open列表中
{
if (dic[left].G > dic[left].father.G + 1)
{
dic[left].father = point;//该点的父级为之前的点
dic[left].G = dic[left].father.G + 1;//该点的G为父级的G加1
//F求和
dic[left].F = dic[left].H + dic[left].G;
}
}
}
}
}
#endregion
}
void CaculateRightDir(short rightNum, OnePoint point)
{
#region 分析右边的方格
//是否可抵达
Vector3 right = point.curPoint + Vector3.right;
if (dic.ContainsKey(right))
{
//分析是不是终点
if (right == target)
{
dic[right].father = point;
open.Add(dic[right]);
targetPoint = dic[target];
isFind = true;
}
else//还没到终点
{
//分析是否是close列表中的点
if (close.Count > 0)
{
for (int i = 0; i < close.Count; i++)
{
if (dic[right] == close[i])
{
rightNum = -1;
break;
}
}
}
if (rightNum < 0)
{
//该方向计算结束
}
else//该点可抵达并且不在close列表中
{
rightNum = 1;
var temp = 1;
if (open.Count > 0)//判断该点是否在open列表中
{
for (int i = 0; i < open.Count; i++)
{
if (right == open[i].curPoint)
{
temp = -1;
break;
}
}
}
if (temp > 0)//如果不在,将该点添加到open中
{
dic[right].father = point;//该点的父级为之前的点
dic[right].G = dic[right].father.G + 1;//该点的G为父级的G加1
//曼哈顿算法
dic[right].H = Mathf.Abs(right.x - target.x) + Mathf.Abs(right.z - target.z);
//F求和
dic[right].F = dic[right].H + dic[right].G;
open.Add(dic[right]);
}
else if (temp < 0)//如果在open列表中
{
if (dic[right].G > dic[right].father.G + 1)
{
dic[right].father = point;//该点的父级为之前的点
dic[right].G = dic[right].father.G + 1;//该点的G为父级的G加1
//F求和
dic[right].F = dic[right].H + dic[right].G;
}
}
}
}
}
#endregion
}
下面是脚本下载地址 共三个脚本
https://pan.baidu.com/s/11hpbH5oMJ6h8krWg6ZCQkg
运行须知:
- 创建Resources文件夹,里面放2个Cube预制体, 一个命名为 "Cube1"作为地图砖块 一个命名为"A"作为寻路体
- 添加tag ====> “ditu” , 把预制体 “Cube1” 的 tag 设置为"ditu"
- 预制体 “A” 挂载脚本 “AstarCacular”
- 游戏场景创建空物体 把脚本 “DiTu” 挂载上去 自己调整共有变量数值,来调整地图大小
- 摄像机随便写的代码,直接用的话,就把摄像机向上拖一段(把Y值变大些),把脚本 "CamLookAt"挂载上去,记住Main Camera 的tag 设置成自带的MainCamera