A*算法之在U3d下实现简单的自动寻路
算法简介:
A*搜寻算法俗称A星算法。A*算法是比较流行的启发式搜索算法之一,被广泛应用于路径优化领域[。它的独特之处是检查最短路径中每个可能的节点时引入了全局信息,对当前节点距终点的距离做出估计,并作为评价该节点处于最短路线上的可能性的量度。[1] - 百度百科
通俗点说,就是在起点与目标点之中找出一条可通行的最短路线。常见于各类RPG游戏中的自动寻路功能:点击某个任务,人物会自动移动过去;点击地图上某个点,人物也会照着显示出来(或者隐藏了)的路线前进。玩过LoL,红色警戒等类似游戏的小伙伴都知道,右击小地图的某一处,小地图会出现一条从当前位置到所点击位置的红色路线,然后英雄就会随着这条路线一直走到目标点。这种功能,就是A*算法的在游戏中的常见应用之处。
场景布置:
1 布置地面:场景中新建一个Plane(地面)重置一下Transform,然后将Scale拉伸至20倍(此时地面的大小是200x200),地面是不带y坐标的,即只有xz平面,此时平面上左下角坐标点是(-100,-100),右上角是(100,100)这个很重要,后面的判断坐标点是否越界(超出地面范围)就是依据这个地面的大小和坐标
2 布置障碍物:新建一个空物体Bars,在Bars下创建一个cube,将cube随便拉伸做成一堵墙,然后复制,摆放到场景上各个位置。
3 创建玩家:场景中新建一个Capsule(为了好控制,就选用胶囊体了),取名为Player,为它挂上Character Controller角色控制器。
4 路线:新建一个Sphere球,涂成红色,改名叫Way,卸载掉Collider(一定要卸载),做成预制体,用来表示计算出来的路线上的每一点。新建一个空物体Ways,用来存储路线
5 层级设置:为了代码中好检测,为地面Plane设立一个单独的层Plane,层级号位9.所有障碍物层级为Bars,层级号为8
脚本编辑:
脚本没写的太过复杂,就编写了两个脚本:
PlayerCtrl.cs 用来控制玩家移动
AStarRun.cs 用A*算法来计算起点到目标点的最佳路径并返回
将这两个脚本都挂载在玩家上(Player)
PlayerCtrl.cs 用来控制玩家移动
using UnityEngine;using System.Collections;//玩家控制器public class PlayerCtrl : MonoBehaviour {
public GameObject wayLook;//寻路线的红点
public float moveSpeed = 10f;//角色前进速度
private CharacterController cc;//角色控制器
private Transform waysParent;//寻路线的放置位置
private Ray ray;//射线检测鼠标点击点
private RaycastHit hit;
private bool IsMove = false;//是否正在寻路过程
void Start ()
{
cc = GetComponent<CharacterController>();
waysParent = GameObject.Find("Ways").transform;
}
void Update ()
{
//鼠标单击移动
if(Input.GetMouseButtonDown(0))
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);//获取主相机到鼠标点击点的射线
//检测射线是否碰撞到地面:地面的层级是9
if(Physics.Raycast(ray,out hit,1 << 9))
{
//往目标点移动过去
//return;
Vector3 starPoint = new Vector3(transform.position.x,0,transform.position.z);//寻路的起点
Vector3 targetPoint = new Vector3(hit.point.x,0,hit.point.z);//寻路的终点
if(!IsMove)
StartCoroutine(AutoMove(starPoint,targetPoint));//开启自动寻路
}
}
}
/// <summary>
/// 自动寻路协程
/// </summary>
/// <returns>The move.</returns>
/// <param name="starPoint">起点.</param>
/// <param name="targetPoint">目标点.</param> IEnumerator AutoMove(Vector3 starPoint,Vector3 targetPoint)
{
IsMove = true;
yield return new WaitForFixedUpdate();
//运用A星算法计算出到起点到目标点的最佳路径
Vector3[] ways = GetComponent<AStarRun>().AStarFindWay(starPoint,targetPoint);
if(ways.Length == 0)
{
IsMove = false;
yield break;
}
//打印显示出寻路线
foreach(var v in ways)
{
GameObject way = Instantiate<GameObject>(wayLook);
way.transform.parent = waysParent;
way.transform.localPosition = v;
way.transform.rotation = Quaternion.identity;
way.transform.localScale = Vector3.one;
}
//让玩家开始沿着寻路线移动
int i = 0;
Vector3 target = new Vector3(ways[i].x,transform.position.y,ways[i].z);
transform.LookAt(target);
while(true)
{
yield return new WaitForFixedUpdate();
Debug.Log("run run run !!!");
cc.SimpleMove(transform.forward * moveSpeed * Time.deltaTime);
if(Vector3.Distance(transform.position,target) < 1f)
{
Debug.Log("run is ok !!!");
++i;
if(i >= ways.Length)
break;
target = new Vector3(ways[i].x,transform.position.y,ways[i].z);
transform.LookAt(target);
}
}
//移动完毕,删除移动路径
for(int child = waysParent.childCount - 1;child >= 0;--child)
Destroy(waysParent.GetChild(child).gameObject);
//等待执行下一次自动寻路
IsMove = false;
}
}
AStarRun.cs 用A*算法来计算起点到目标点的最佳路径并返回
using UnityEngine;using System.Collections;using System.Collections.Generic;
//A*寻路public class AStarRun : MonoBehaviour {
private Map map = new Map();//格子地图
开启列表
private List<MapPoint> open_List = new List<MapPoint>();
//关闭列表
private List<MapPoint> close_List = new List<MapPoint>();
//定义一个路径数组
private ArrayList way = new ArrayList();
//判断某点是否在开启列表中
private bool IsInOpenList(int x,int z)
{
foreach(var v in open_List)
{
if(v.x == x && v.z == z)
return true;
}
return false;
}
//判断某点是否在关闭列表中
private bool IsInCloseList(int x,int z)
{
foreach(var v in close_List)
{
if(v.x == x && v.z == z)
return true;
}
return false;
}
//从开启列表中找到那个F值最小的格子
private MapPoint FindMinFInOpenList()
{
MapPoint minPoint = null;
foreach(var v in open_List)
{
if(minPoint == null || minPoint.GetF > v.GetF)
minPoint = v;
}
return minPoint;
}
//从开启列表中找到格子
private MapPoint FindInOpenList(int x,int z)
{
foreach(var v in open_List)
{
if(v.x == x && v.z == z)
return v;
}
return null;
}
/// <summary>
/// a星算法寻路
/// </summary>
/// <returns>寻到的路结果.</returns>
/// <param name="starPoint">起点 </param>
/// <param name="targetPoint">终点</param>
///
public Vector3[] AStarFindWay(Vector3 starPoint,Vector3 targetPoint)
{
//清空容器 way.Clear();
open_List.Clear();
close_List.Clear();
//初始化起点格子
MapPoint starMapPoint = new MapPoint();
starMapPoint.x = (int)starPoint.x;
starMapPoint.z = (int)starPoint.z;
//初始化终点格子
MapPoint targetMapPoint = new MapPoint();
targetMapPoint.x = (int)targetPoint.x;
targetMapPoint.z = (int)targetPoint.z;
//将起点格子添加到开启列表中 open_List.Add(starMapPoint);
//寻找最佳路径
//当目标点不在打开路径中时或者打开列表为空时循环执行
while(!IsInOpenList(targetMapPoint.x,targetMapPoint.z) || open_List.Count == 0)
{
//从开启列表中找到那个F值最小的格子
MapPoint minPoint = FindMinFInOpenList();
if(minPoint == null)
return null;
//将该点从开启列表中删除,同时添加到关闭列表中 open_List.Remove(minPoint);
close_List.Add(minPoint);
//检查改点周边的格子 CheckPerPointWithMap(minPoint,targetMapPoint);
}
//在开启列表中找到终点
MapPoint endPoint = FindInOpenList(targetMapPoint.x,targetMapPoint.z);
Vector3 everyWay = new Vector3(endPoint.x,0,endPoint.z);//保存单个路径点
way.Add(everyWay);//添加到路径数组中
//遍历终点,找到每一个父节点:即寻到的路
while(endPoint.fatherPoint != null)
{
everyWay.x = endPoint.fatherPoint.x;
everyWay.z = endPoint.fatherPoint.z;
everyWay.y = 0;
way.Add(everyWay);
endPoint = endPoint.fatherPoint;
}
//将路径数组从倒序变成正序并返回
Vector3[] ways = new Vector3[way.Count];
for(int i = way.Count - 1;i >= 0;--i)
{
ways[way.Count - i - 1] = (Vector3)way[i];
}
//清空容器 way.Clear();
open_List.Clear();
close_List.Clear();
//返回正序的路径数组
return ways;
}
//判断地图上某个坐标点是不是障碍点
private bool IsBar(int x,int z)
{
//判断地图上某个坐标点是不是障碍点
Vector3 p = new Vector3(x,0,z);
//检测该点周边是否有障碍物
//障碍物层级为8
Collider[] colliders = Physics.OverlapSphere(p,1,1 << 8);
if(colliders.Length > 0)
return true;//有障碍物,说明该点不可通过,是障碍物点
return false;
}
//计算某方块的G值
public int GetG(MapPoint p)
{
if(p.fatherPoint == null)
return 0;
if(p.x == p.fatherPoint.x || p.z == p.fatherPoint.z)
return p.fatherPoint.G + 10;
else
return p.fatherPoint.G + 14;
}
//计算某方块的H值
public int GetH(MapPoint p,MapPoint targetPoint)
{
return (Mathf.Abs(targetPoint.x - p.x) + Mathf.Abs(targetPoint.z - p.z)) * 10;
}
//检查某点周边的格子
private void CheckPerPointWithMap(MapPoint _point,MapPoint targetPoint)
{
for(int i = _point.x-1; i <= _point.x + 1;++i)
{
for(int j = _point.z - 1; j <= _point.z + 1; ++j)
{
//剔除超过地图的点
if(i < map.star_X || i > map.end_X || j < map.star_Z || j > map.end_Z)
continue;
//剔除该点是障碍点:即周围有墙的点
if(IsBar(i,j))
continue;
//剔除已经存在关闭列表或者本身点
if(IsInCloseList(i,j) || (i == _point.x && j == _point.z))
continue;
//剩下的就是没有判断过的点了
if(IsInOpenList(i,j))
{
//如果该点在开启列表中
//找到该点
MapPoint point = FindInOpenList(i,j);
int G = 0;
//计算出该点新的移动代价
if(point.x == _point.x || point.z == _point.z)
G = point.G + 10;
else
G = point.G + 14;
//如果该点的新G值比前一次小
if(G < point.G)
{
//更新新的G点
point.G = G;
point.fatherPoint = _point;
}
}
else
{
//如果该点不在开启列表内
//初始化该点,并将该点添加到开启列表中
MapPoint newPoint = new MapPoint();
newPoint.x = i;
newPoint.z = j;
newPoint.fatherPoint = _point;
//计算该点的G值和H值并赋值
newPoint.G = GetG(newPoint);
newPoint.H = GetH(newPoint,targetPoint);
//将初始化完毕的格子添加到开启列表中 open_List.Add(newPoint);
}
}
}
}
}
//地图类public class Map
{
public int star_X;// 横坐标起点
public int star_Z;// 纵坐标起点
public int end_X;// 横坐标终点
public int end_Z;//纵坐标终点
public Map()
{
star_X = - 100;
star_Z = - 100;
end_X = 100;
end_Z = 100;
}
}
//每一个格子的信息public class MapPoint
{
//F = G + H
//G 从起点A移动到指定方格的移动代价,父格子到本格子代价:直线为10,斜线为14
//H 使用 Manhattan 计算方法, 计算(当前方格到目标方格的横线上+竖线上所经过的方格数)* 10
public int x;//格子的x坐标
public int z;//格子的z坐标
public int G;
public int H;
public int GetF{
get
{
return G + H;
}
}
public MapPoint fatherPoint;//父格子
public MapPoint(){}
public MapPoint(int _x,int _z,int _G,int _H,MapPoint _fatherPoint)
{
this.x = _x;
this.z = _z;
this.G = _G;
this.H = _H;
this.fatherPoint = _fatherPoint;
}
}
更多unity2018的功能介绍请到paws3d爪爪学院查找。链接https://www.paws3d.com/learn/,也可以加入unity学习讨论群935714213
近期更有资深开发人士直播分享unity开发经验,详情请进入官网或加入QQ群了解
A*算法之在U3d下实现简单的自动寻路相关推荐
- python比较两个列表的重合度_#源代码#超几何分布算法介绍及python下的实现代码...
原标题:#源代码#超几何分布算法介绍及python下的实现代码 超几何分布是统计学上一种离散概率分布.它描述了由有限个物件中抽出n个物件,成功抽出指定种类的物件的次数(不归还). 在产品质量的不放回抽 ...
- 马尔科夫模型在Gowalla数据集下的简单实践
马尔科夫模型在Gowalla数据集下的简单实践 马尔科夫模型实践第一战 基础知识 数学知识 代码知识 数据处理 单独一次转移的概率计算函数设计 生成转移概率矩阵 生成初始向量 结论 马尔科夫模型实践第 ...
- [Android] Android MVP 架构下 最简单的 代码实现
Android MVP 架构下 最简单的 代码实现 首先看图: 上图是MVP,下图是MVC MVP和MVC的区别,在于以前的View层不仅要和model层交互,还要和controller层交互.而 ...
- 用C语言编写一个Linux下的简单shell程序
这是一个简单的C程序,展示了如何进行系统调用执行logout cd ls pwd pid rm mkdir mv cp等命令,这是一个简单的命令解释程序shell,其源代码如下: #include & ...
- nginx Win下实现简单的负载均衡(2)站点共享Session
快速目录: 一.nginx Win下实现简单的负载均衡(1)nginx搭建部署 二.nginx Win下实现简单的负载均衡(2)站点共享Session 三.nginx Win下实现简单的负载均衡(3) ...
- ubuntu 运行c++_06_Linux下VSCode简单编程(远程开发WSL_Ubuntu_18.04) | C语言入门
06_Linux下VSCode简单编程(远程开发WSL_Ubuntu_18.04) 本系列主题 Linux下C语言彩色控制台编程实践_基于gcc,gdb,VSCode,git和WSL_Ubuntu_1 ...
- artDialog对话框在PHP下的简单应用-artDialog弹出层篇
本教程使用的是artDialog 4.1.7版本,由于需要iframe的支持,所以选择这个版本,artDialog 5.0.3不支持iframe. 本教程是基于本站站长在网页设计写代码过程中与PHP页 ...
- linux上用的端口转发工具,linux下最简单好用的的端口转发工具
linux下最简单好用的的端口转发工具 解压安装 tar zxvf rinetd.tar.gz make make install 编辑配置 vi /etc/rinetd.conf 0.0.0.0 8 ...
- 算法----最大承载量下的最大价值问题
算法----最大承载量下的最大价值问题 代码: 栈代码:(存储哪些是需要的价值物) #pragma once #include<stdio.h> #define maxSize 100 t ...
最新文章
- 《高效程序员的45个习惯》之体会
- 从源码分析DEARGUI之画图和删图
- opencv_图像反转
- c# winform窗体边框风格的设计
- zabbix数据库表结构
- 单片机蜂鸣器编程音乐_工程师,还有6个引脚封装的单片机?涨知识了
- 【UVA202】Repeating Decimals(模拟除法)
- 手机qq2008触屏版_手机版卖家中心在哪里
- Fredholm第二类积分方程的MATLAB代码实现(1)
- GOM登录器技术研究,闪退、掉线的原因分析和解决
- 1916 Problem C	合唱队形
- 烤仔TVのCCW | 带宽不可能三角(下)
- 2022年山东省安全员A证特种作业证考试题库模拟考试平台操作
- 易语言 html 服务器,易语言模拟网页Web服务器源代码
- D1net阅闻:Facebook上线求职功能,以挑战LinedIn
- 关于@ComponentScan 的使用 和springboot启动类所在位置的关系
- linux 下载文件到本地
- 记SQL Server实战修复死锁总结
- 萌新接触前端的第二课——CSS
- Xilinx下载电缆找不到,WARNING:iMPACT:923 - Can not find cable, check cable setup