SRPG游戏开发(二十三)第七章 寻路与地图对象 - 一 A*寻路算法(A* Search Algorithm)
返回总目录
第七章 寻路与地图对象(Pathfinding and Map Object)
这一章主要进行寻路与地图对象的部分工作。
- 第七章 寻路与地图对象(Pathfinding and Map Object)
- 一 A*寻路算法(A* Search Algorithm)
- 1 什么是A*寻路算法(Definition)
- 2 节点与格子信息(Node and Cell Data)
- 2.1 新建CellData.cs
- 2.2 填充网格信息到CellData.cs
- 2.3 填充寻路信息到CellData.cs
- 2.4 填充Dispose函数到CellData.cs
- 3 寻路主函数(Search Method)
- 3.1 如何寻路接口(IHowToFind.cs)
- 3.2 填充FindNext函数
- 3.3 移动范围与攻击范围
- 一 A*寻路算法(A* Search Algorithm)
一 A*寻路算法(A* Search Algorithm)
1 什么是A*寻路算法(Definition)
在计算机科学中,A*(A Star)是一种广泛应用于寻路和图形遍历的计算机算法,它是在多个节点(Node)之间进行寻找路径的过程。
由于其性能和精度非常好,所以得到了广泛的应用。
同时,它还是一种最优搜索算法(best-first search)。
它的目标在于寻找给定起始节点(start node)到目标节点(goal node)的最小消耗(最短距离、最短用时等)。
它是从起始节点开始向外延伸路径,直到找到符合标准的目标节点。
在这个过程中,它通过选择最小化的消耗(cost)实现:
f(n) = g(n) + h(n)
其中:
g(n)g(n)g(n) 是第n个节点的实际消耗;
h(n)h(n)h(n) 是一个启发函数,这里是第n个节点的预估消耗;
f(n)f(n)f(n) 是第n个节点的最小化的消耗;
注意: A*算法在计算中,使用二叉树能获得更好的性能,但由于我们的计算量不是特别大,可能不会使用二叉树。
2 节点与格子信息(Node and Cell Data)
在A*中,每个节点也就相当于游戏中的格子,所以我们将节点信息写入到 Cell Data 中。首先新建我们的CellData.cs
文件。
2.1 新建CellData.cs
先建立文件,然后再进行填充:
namespace DR.Book.SRPG_Dev.Maps
{/// <summary>/// 地图上每个格子的信息/// </summary>public class CellData : IDisposable{public void Dispose(){}}
}
IDisposable
接口可不加,加上只是为了以防万一以后要使用。
由于这里需要用到地图对象,我们可以先建立一个空的地图对象:
namespace DR.Book.SRPG_Dev.Maps
{/// <summary>/// 空的地图对象,以后填充/// </summary>[DisallowMultipleComponent]public abstract class MapObject : MonoBehaviour{}
}
2.2 填充网格信息到CellData.cs
首先, Cell Data 表示我们每个小格子的信息,就像之前提过的,我们需要它包含位置信息、是否含有Tile(这个可以不加,在 Map Graph 里判断)和地图对象:
#region Common Field/Propertyprivate Vector3Int m_Position;private bool m_HasTile;private MapObject m_MapObject;/// <summary>/// 坐标位置/// </summary>public Vector3Int position{get { return m_Position; }}/// <summary>/// 是否有Tile/// </summary>public bool hasTile{get { return m_HasTile; }set { m_HasTile = value; }}/// <summary>/// 地图对象/// </summary>public MapObject mapObject{get { return m_MapObject; }set { m_MapObject = value; }}/// <summary>/// 是否有地图对象/// </summary>public bool hasMapObject{get { return m_MapObject != null; }}#endregion#region Constructorpublic CellData(Vector3Int position){m_Position = position;}public CellData(int x, int y){m_Position = new Vector3Int(x, y, 0);}#endregion
2.3 填充寻路信息到CellData.cs
其次是节点信息,在寻路中:
需要搜寻它周围的节点,所以还要存储它的邻居;
需要知道它是怎么搜寻到的,所以记录它的父亲节点;
当然还有我们的损失。
#region AStar Field/Propertyprivate List<CellData> m_Adjacents = new List<CellData>();private CellData m_Previous;private Vector2 m_AStarGH;/// <summary>/// 邻居CellData/// </summary>public List<CellData> adjacents{get { return m_Adjacents; }}/// <summary>/// 寻找的前一个CellData/// </summary>public CellData previous{get { return m_Previous; }set { m_Previous = value; }}/// <summary>/// AStar的G值,移动消耗/// </summary>public float g{get { return m_AStarGH.x; }set { m_AStarGH.x = value; }}/// <summary>/// AStar的H值,预计消耗/// </summary>public float h{get { return m_AStarGH.y; }set { m_AStarGH.y = value; }}/// <summary>/// AStar的F值,F=G+H/// </summary>public float f{get { return m_AStarGH.x + m_AStarGH.y; }}#endregion
在每次寻路时,起始节点与目标节点有可能是不同的,虽然会重新计算,但最好可以手动重置它们(邻居是不会改变的,除非地图有变化或有特殊需求):
#region Reset AStar Methodpublic void ResetAStar (){m_Previous = null;m_AStarGH = Vector2.zero;}#endregion
2.4 填充Dispose函数到CellData.cs
最后,是我们的Dispose
函数(这个接口可不加,加上只是为了以防万一以后要使用):
public void Dispose(){m_Position = Vector3Int.zero;m_MapObject = null;m_Adjacents = null;m_Previous = null;m_AStarGH = Vector2.zero;}
3 寻路主函数(Search Method)
在写我们的寻路函数之前,先来梳理一下A星算法。
首先,A星算法的典型实现是利用优先级队列选择最小损失的节点进行扩展;这里的优先级队列一般称为开放集(open set);
其次,在算法的每一步,都从开放集中移除 f(n)f(n)f(n) 最小的节点,并且计算邻居节点的 g(n)g(n)g(n) ,然后将其邻居加入到开放集中;
最后,直到搜索到目标节点,或者队列为空时,结束搜索。
注意:我们的游戏并不是纯寻路,还需要显示范围(移动、攻击等)。所以需要进行修改。
基于以上,我们来建立类:
namespace DR.Book.SRPG_Dev.Maps.FindPath
{public class PathFinding{private MapGraph m_Map;/// <summary>/// 开放列表/// </summary>private List<CellData> m_Reachable = new List<CellData>();/// <summary>/// 关闭列表/// </summary>private List<CellData> m_Explored = new List<CellData>();/// <summary>/// 结果/// </summary>private List<CellData> m_Result = new List<CellData>();private Vector2 m_Range;private CellData m_StartCell;private CellData m_EndCell;private CellData m_CurrentCell;private bool m_Finished;private int m_SearchCount = 0;#region Constructorpublic PathFinding(MapGraph map){m_Map = map;}#endregion/// <summary>/// 搜寻下一次,return finished/// </summary>/// <returns></returns>private bool FindNext(){// TODO}#region Reset Method/// <summary>/// 重置/// </summary>public void Reset(){if (m_Reachable.Count > 0){foreach (CellData cell in m_Reachable){cell.ResetAStar();}m_Reachable.Clear();}if (m_Explored.Count > 0){foreach (CellData cell in m_Explored){cell.ResetAStar();}m_Explored.Clear();}m_Result.Clear();m_Range = Vector2.zero;m_StartCell = null;m_EndCell = null;m_CurrentCell = null;m_Finished = false;m_SearchCount = 0;}#endregion}
}
在写FindNext()
函数之前,我们先来说说下面的问题。
就像上面所说的,每种显示范围的算法是不同的,移动范围是要和移动点数与移动损耗相关联,攻击范围需要和攻击距离相关联。例如,地形是一个山头,某些职业移动不到,但可以攻击到。这就使我们不能使用一样的算法。
我们把这一部分分离出去,建立一个接口来完成这项工作。
3.1 如何寻路接口(IHowToFind.cs)
这个接口包含的内容包括:
从开放列表选择节点;
计算A星的G值与H值;
判断是否搜索结束;
其它辅助性函数。
则接口初步为:
namespace DR.Book.SRPG_Dev.Maps.FindPath
{public interface IHowToFind{/// <summary>/// 获取检测的Cell/// </summary>/// <param name="search"></param>/// <returns></returns>CellData ChoseCell(PathFinding search);/// <summary>/// 选择Cell后,是否结束/// </summary>/// <param name="search"></param>/// <returns></returns>bool IsFinishedOnChose(PathFinding search);/// <summary>/// 计算移动到下一格的消耗/// </summary>/// <param name="search"></param>/// <param name="adjacent"></param>/// <returns></returns>float CalcGPerCell(PathFinding search, CellData adjacent);/// <summary>/// 无视范围,直接寻路用,计算预计消耗值,(这里用距离)/// </summary>/// <param name="search"></param>/// <param name="adjacent"></param>/// <returns></returns>float CalcH(PathFinding search, CellData adjacent);/// <summary>/// 是否能把邻居加入到检测列表中/// </summary>/// <param name="search"></param>/// <param name="adjacent"></param>/// <returns></returns>bool CanAddAdjacentToReachable(PathFinding search, CellData adjacent);/// <summary>/// 生成最终显示的范围/// </summary>/// <param name="search"></param>void BuildResult(PathFinding search);}
}
3.2 填充FindNext函数
有了接口,我们在填充每一步的寻路函数之前,先加入一些新的变量。
private IHowToFind m_HowToFind;// 最大迭代次数public int m_MaxSearchCount = 2000;
然后来看FindNext()
的步骤:
1 选择节点;
2 判断是否搜索结束;
3 将邻居节点加入到开放列表中。
/// <summary>/// 搜寻下一次,return finished/// </summary>/// <returns></returns>private bool FindNext(){// 已经有结果if (m_Result.Count > 0){return true;}// 选择节点m_CurrentCell = m_HowToFind.ChoseCell(this);// 判断是否搜索结束if (m_HowToFind.IsFinishedOnChose(this)){// 如果结束,建立结果m_HowToFind.BuildResult(this);return true;}// 当前选择的节点不为nullif (m_CurrentCell != null){for (int i = 0; i < m_CurrentCell.adjacents.Count; i++){// 是否可以加入到开放集中if (m_HowToFind.CanAddAdjacentToReachable(this, m_CurrentCell.adjacents[i])){m_Reachable.Add(m_CurrentCell.adjacents[i]);}}}return false;}
3.3 移动范围与攻击范围
这里是对外调用的函数。
查找迭代
private bool SearchRangeInternal(){while (!m_Finished){m_SearchCount++;m_Finished = FindNext();if (m_SearchCount >= m_MaxSearchCount){Debug.LogError("Search is timeout. MaxCont: " + m_MaxSearchCount.ToString());return false;}}return true;}
移动范围
/// <summary>/// 寻找移动范围/// </summary>/// <param name="howToFind"></param>/// <param name="start"></param>/// <param name="movePoint"></param>/// <returns></returns>public bool SearchMoveRange(IHowToFind howToFind, CellData start, float movePoint){if (howToFind == null || start == null || movePoint < 0){return false;}Reset();m_HowToFind = howToFind;m_StartCell = start;m_Range.y = movePoint;m_Reachable.Add(m_StartCell);return SearchRangeInternal();}
攻击范围
/// <summary>/// 搜寻攻击范围/// </summary>/// <param name="howToFind"></param>/// <param name="start"></param>/// <param name="minRange"></param>/// <param name="maxRange"></param>/// <returns></returns>public bool SearchAttackRange(IHowToFind howToFind, CellData start, int minRange, int maxRange){if (howToFind == null || start == null || minRange < 1 || maxRange < minRange){return false;}Reset();m_HowToFind = howToFind;m_StartCell = start;m_Range = new Vector2(minRange, maxRange);m_Reachable.Add(m_StartCell);return SearchRangeInternal();}
路径
/// <summary>/// 搜寻路径/// </summary>/// <param name="howToFind"></param>/// <param name="start"></param>/// <param name="end"></param>/// <returns></returns>public bool SearchPath(IHowToFind howToFind, CellData start, CellData end){if (howToFind == null || start == null || end == null){return false;}Reset();m_HowToFind = howToFind;m_StartCell = start;m_EndCell = end;m_Reachable.Add(m_StartCell);m_HowToFind.CalcH(this, m_StartCell);return SearchRangeInternal();}
SRPG游戏开发(二十三)第七章 寻路与地图对象 - 一 A*寻路算法(A* Search Algorithm)相关推荐
- 【Android游戏开发二十三】自定义ListView【通用】适配器并实现监听控件!
本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/374.html L ...
- SRPG游戏开发(三)第二章 创建项目
返回目录 第二章 创建项目 本章开始我们来创建我们的项目,导入用到的素材. 一 创建项目 打开Unity3D,点击New按钮.这时我们看到创建项目的设置界面. 图 2 - 1创建项目 Project ...
- SRPG游戏开发(六十三)第十一章 地图动作与地图事件 - 十二 完善地图信息与测试(Perfect MapEventInfo and Testing)
返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...
- SRPG游戏开发(六十)第十一章 地图动作与地图事件 - 九 触发事件与切换回合(Trigger Events and Change Turn)
返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...
- SRPG游戏开发(六十一)第十一章 地图动作与地图事件 - 十 NPC操作(NPC Control)
返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...
- SRPG游戏开发(二十七)第七章 寻路与地图对象 - 五 搜索移动范围与路径(Search Move Range and Path)
返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 文章目录 第七章 寻路与地图对象(Pathfinding and ...
- SRPG游戏开发(二十六)第七章 寻路与地图对象 - 四 地图对象(Map Object)
返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 第七章 寻路与地图对象(Pathfinding and Map O ...
- SRPG游戏开发(三十)第七章 寻路与地图对象 - 八 优化地图(Optimize MapGraph)
返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 第七章 寻路与地图对象(Pathfinding and Map O ...
- SRPG游戏开发(六十四)间章 第十一点五章 总结(Summary)
返回<SRPG游戏开发>导航 间章 第十一点五章 总结(Summary) 这一章,是对第十章与第十一章的一个补充性质的文章. 文章目录 间章 第十一点五章 总结(Summary) 一 说明 ...
最新文章
- 简述BT下载技术及其公司发展现状
- 模型(Model)– ASP.NET MVC 4 系列
- java 分析jstack日志_望闻问切使用jstack和jmap剖析java进程各种疑难杂症
- 第一篇博客——ACM之路!
- windows下GIT使用记录--00准备阶段
- 文件上传之传统方式上传代码回顾
- ewebeditor在上传文件时,总是提示“请选择一个有效的文件”,
- mysql unicode转汉字_任意汉字显示,给你的嵌入式系统(含MCU)装上字库
- 垃圾回收机制与引用类型
- leetcode —— 238. 除自身以外数组的乘积
- iOS项目的完整重命名方法图文教程
- mybatis的mapper接口与xml传参问题
- 智能手机上最没有用的功能是什么?
- perl DBI 总结
- java对象与json字符串的互相转换
- Linux手势控制软件,让 linux 实现触摸板多点触控与手势操作
- 2023年东南大学集成电路设计考研考情与难度、参考书及上岸前辈备考经验
- wps怎么关闭修改痕迹_WPS文字如何保留修改痕迹?WPS文字保留修改痕迹教程
- 猿创征文|走技术创新路,展时代宏图梦
- win10需要修复计算机,超好用!Win10自带修复系统 隐藏太深
热门文章
- 2020 ACM-ICPC澳门区域赛 B Boring Problem 主元法
- 酷MOS管选型及技巧-可申请样品及技术支持
- The Matrix Cookbook(译)
- 正方形里面两个扇形相交部分_计算下图中阴影部分的面积,最简单的方法是用正方形的面积除以2...
- 一场奔向春天的骑行——际恒集团2017年度团建年会成功举办
- web前端 --- CSS(04) -- 盒子模型、div+css网页布局、css3新特性
- OOAD实验六教务管理系统设计之状态机图
- 视频RTMP推流实践
- android 查看系统允许内存,查看Android系统内存使用的方法
- 轻松玩转AI(从Python开始之Python3入门)