返回总目录

第七章 寻路与地图对象(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)


1 什么是A*寻路算法(Definition)

在计算机科学中,A*(A Star)是一种广泛应用于寻路和图形遍历的计算机算法,它是在多个节点(Node)之间进行寻找路径的过程。

由于其性能和精度非常好,所以得到了广泛的应用。

同时,它还是一种最优搜索算法(best-first search)。

它的目标在于寻找给定起始节点(start node)到目标节点(goal node)的最小消耗(最短距离、最短用时等)。

它是从起始节点开始向外延伸路径,直到找到符合标准的目标节点。

在这个过程中,它通过选择最小化的消耗(cost)实现:

f(n)=g(n)+h(n)f(n)=g(n)+h(n)

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)相关推荐

  1. 【Android游戏开发二十三】自定义ListView【通用】适配器并实现监听控件!

    本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/374.html L ...

  2. SRPG游戏开发(三)第二章 创建项目

    返回目录 第二章 创建项目 本章开始我们来创建我们的项目,导入用到的素材. 一 创建项目 打开Unity3D,点击New按钮.这时我们看到创建项目的设置界面. 图 2 - 1创建项目 Project ...

  3. SRPG游戏开发(六十三)第十一章 地图动作与地图事件 - 十二 完善地图信息与测试(Perfect MapEventInfo and Testing)

    返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...

  4. SRPG游戏开发(六十)第十一章 地图动作与地图事件 - 九 触发事件与切换回合(Trigger Events and Change Turn)

    返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...

  5. SRPG游戏开发(六十一)第十一章 地图动作与地图事件 - 十 NPC操作(NPC Control)

    返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...

  6. SRPG游戏开发(二十七)第七章 寻路与地图对象 - 五 搜索移动范围与路径(Search Move Range and Path)

    返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 文章目录 第七章 寻路与地图对象(Pathfinding and ...

  7. SRPG游戏开发(二十六)第七章 寻路与地图对象 - 四 地图对象(Map Object)

    返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 第七章 寻路与地图对象(Pathfinding and Map O ...

  8. SRPG游戏开发(三十)第七章 寻路与地图对象 - 八 优化地图(Optimize MapGraph)

    返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 第七章 寻路与地图对象(Pathfinding and Map O ...

  9. SRPG游戏开发(六十四)间章 第十一点五章 总结(Summary)

    返回<SRPG游戏开发>导航 间章 第十一点五章 总结(Summary) 这一章,是对第十章与第十一章的一个补充性质的文章. 文章目录 间章 第十一点五章 总结(Summary) 一 说明 ...

最新文章

  1. 简述BT下载技术及其公司发展现状
  2. 模型(Model)– ASP.NET MVC 4 系列
  3. java 分析jstack日志_望闻问切使用jstack和jmap剖析java进程各种疑难杂症
  4. 第一篇博客——ACM之路!
  5. windows下GIT使用记录--00准备阶段
  6. 文件上传之传统方式上传代码回顾
  7. ewebeditor在上传文件时,总是提示“请选择一个有效的文件”,
  8. mysql unicode转汉字_任意汉字显示,给你的嵌入式系统(含MCU)装上字库
  9. 垃圾回收机制与引用类型
  10. leetcode —— 238. 除自身以外数组的乘积
  11. iOS项目的完整重命名方法图文教程
  12. mybatis的mapper接口与xml传参问题
  13. 智能手机上最没有用的功能是什么?
  14. perl DBI 总结
  15. java对象与json字符串的互相转换
  16. Linux手势控制软件,让 linux 实现触摸板多点触控与手势操作
  17. 2023年东南大学集成电路设计考研考情与难度、参考书及上岸前辈备考经验
  18. wps怎么关闭修改痕迹_WPS文字如何保留修改痕迹?WPS文字保留修改痕迹教程
  19. 猿创征文|走技术创新路,展时代宏图梦
  20. win10需要修复计算机,超好用!Win10自带修复系统 隐藏太深

热门文章

  1. 2020 ACM-ICPC澳门区域赛 B Boring Problem 主元法
  2. 酷MOS管选型及技巧-可申请样品及技术支持
  3. The Matrix Cookbook(译)
  4. 正方形里面两个扇形相交部分_计算下图中阴影部分的面积,最简单的方法是用正方形的面积除以2...
  5. 一场奔向春天的骑行——际恒集团2017年度团建年会成功举办
  6. web前端 --- CSS(04) -- 盒子模型、div+css网页布局、css3新特性
  7. OOAD实验六教务管理系统设计之状态机图
  8. 视频RTMP推流实践
  9. android 查看系统允许内存,查看Android系统内存使用的方法
  10. 轻松玩转AI(从Python开始之Python3入门)