【Unity】RPG2D龙城纷争(八)寻路系统

更新日期:2024年7月4日。
项目源码:第五章发布(正式开始游戏逻辑的章节)

索引

  • 简介
    • 一、寻路系统
    • 二、寻路规则(角色移动)
    • 三、寻路规则(角色攻击)
    • 四、角色移动寻路
      • 1.自定义寻路规则
      • 2.寻角色的所有可行走地块
      • 3.寻角色到达指定地块的路径
    • 五、角色攻击寻路
      • 1.自定义寻路规则
      • 2.寻找角色的攻击范围内的地块
    • 六、角色登场寻路
    • 七、整合

简介

寻路系统是整个游戏最核心的功能之一,角色的移动战斗都是基于寻路系统来进行的,毕竟我们的游戏有三分之一的战棋血统。

一、寻路系统

由于HTFrameworkAI模块正好支持如下我们游戏需要的寻路核心功能:

  • 1.两点间寻路;
  • 2.寻可行走节点。

所以首先就是引入该模块,更多信息请参阅:【Unity】 HTFramework框架(二十七)A*寻路。

二、寻路规则(角色移动)

对于我们角色的移动和攻击而言,移动速度攻击距离便是其寻路计算时的最大依据。

比如移动速度=10,则角色初始移动能力=10,每移动一格(地块),移动能力-1,当移动能力减至0时,角色无法再继续移动。

同时,不同类型的地块对移动能力还会产生额外的削减:

  • 1.地面:-0;
  • 2.山体:-1;
  • 3.森林:-1;
  • 4.湖泊:-1;
  • 5.雪地:-2;
  • 6.障碍:不可通行;
  • 7.敌方占领地块:不可通行。

这也是角色行走到山体上会被减速的功能点的实现方式。

不过,一些特殊加成型要诀能够抵消地块的额外削减,但是,在我们的进程中,特殊加成型要诀尚在构思阶段,所以,具体的实现我们后续一步步来。

三、寻路规则(角色攻击)

角色攻击是同理的,不过角色攻击寻路规则跟地块类型的关系有所不同:

  • 1.地面:可以跨越攻击;
  • 2.山体:可以跨越攻击;
  • 3.森林:可以跨越攻击;
  • 4.湖泊:可以跨越攻击;
  • 5.雪地:可以跨越攻击;
  • 6.障碍:不可跨越攻击;
  • 7.敌方占领地块:可以跨越攻击。

由于角色攻击寻路几乎只针对远程攻击近程攻击只能攻击身边的4格,用不着寻路),所以这里的可以跨越攻击不可跨越攻击也即是指攻击时是否能够跨越该地块攻击敌人。

同理,一些特殊加成型要诀能够改变如上的规则。

四、角色移动寻路

1.自定义寻路规则

要做到如上这么多自由的想法,自定义寻路规则是必须的,所幸HTFrameworkAIA*寻路支持自定义寻路规则,那么我们便立即开始吧(继承至AStarRule即可):

    /// <summary>/// 寻路规则(角色移动)/// </summary>public class MoveRule : AStarRule{/// <summary>/// 当前的关卡/// </summary>public Level CurrentLevel;/// <summary>/// 当前寻路的角色/// </summary>public Role CurrentRole;/// <summary>/// 目标地块/// </summary>public Block TargetBlock;//寻路前,对所有A*节点应用自定义规则public override void Apply(AStarNode node){//通过节点索引找到其对应的地块Block block = CurrentLevel.Blocks[node.XIndex, node.YIndex];//如果地块上存在敌人(阵营不同)if (block.StayRole != null && block.StayRole.Camp != CurrentRole.Camp){//则该地块不可行走node.IsCanWalk = false;return;}switch (block.Type){case BlockType.Ground:// OCost 为该节点的额外估价,寻路计算时将造成【移动能力】的额外削减// 此处 = 0,则表明无额外削减node.OCost = 0;node.IsCanWalk = true;break;case BlockType.Moutain://山体:将造成【移动能力】额外 -1node.OCost = 1;node.IsCanWalk = false;break;case BlockType.Forest:node.OCost = 1;node.IsCanWalk = true;break;case BlockType.Water:node.OCost = 1;node.IsCanWalk = false;break;case BlockType.Snow:node.OCost = 2;node.IsCanWalk = true;break;case BlockType.Obstacle://障碍:将造成该地块不可行走node.IsCanWalk = false;break;}}}

如上的代码应该很好理解了,足以可见,自定义寻路规则是何其的简单。

2.寻角色的所有可行走地块

角色移动前,能够根据角色自身移动速度周围地块属性等,寻找出所有可以移动的地块以供玩家选择:

			private static MoveRule _moveRule;private static List<Block> _resultBlocks = new List<Block>();/// <summary>/// 寻路规则(移动)/// </summary>private static MoveRule CurrentMoveRule{get{if (_moveRule == null){_moveRule = new MoveRule();}return _moveRule;}}/// <summary>/// 寻找角色的可行走地块/// </summary>/// <param name="level">关卡</param>/// <param name="role">角色</param>public static List<Block> FindWalkableBlocks(Level level, Role role){if (level == null || role == null || role.Speed == 0){_resultBlocks.Clear();return _resultBlocks;}CurrentMoveRule.CurrentLevel = level;CurrentMoveRule.CurrentRole = role;//WalkableNodefinding 为 A* 寻路方法,具体参阅 HTFrameworkAI//参数1:role.StayBlock.Pos 寻路起点//参数2:role.Speed 移动速度//参数3:传入自定义寻路规则List<AStarNode> nodes = level.Map.WalkableNodefinding(role.StayBlock.Pos, role.Speed, CurrentMoveRule);//寻路结果为A*节点集合,通过节点索引获取对应的地块即可_resultBlocks.Clear();for (int i = 0; i < nodes.Count; i++){_resultBlocks.Add(level.Blocks[nodes[i].XIndex, nodes[i].YIndex]);}return _resultBlocks;}

寻找到所有可行走地块后,接下来只需要高亮这些地块即可,同时让玩家可以点击选择(高亮方式就取决于自己了,当然这块逻辑也有涉及,不过在最后的实现UI界面时讲解):

在这里插入图片描述

比如这里的角色络英俊,移动速度为7,周围高亮的都是可行走的地块,其他在移动范围内的便是不可行走的地块。

3.寻角色到达指定地块的路径

上一步已经寻找到了所有可移动地块,如果玩家点击了其中的一个,则表明期望角色移动到该地块,所以需要寻角色到达该地块的路径:

            /// <summary>/// 寻找角色到达指定地块的路径/// </summary>/// <param name="level">关卡</param>/// <param name="role">角色</param>/// <param name="block">目标地块</param>public static List<Block> FindPathBlocks(Level level, Role role, Block block){if (level == null || role == null || block == null){_resultBlocks.Clear();return _resultBlocks;}CurrentMoveRule.CurrentLevel = level;CurrentMoveRule.CurrentRole = role;//Pathfinding 为 A* 寻路方法,具体参阅 HTFrameworkAI//参数1:role.StayBlock.Pos 寻路起点//参数2:block.Pos 寻路终点//参数3:传入自定义寻路规则List<AStarNode> nodes = level.Map.Pathfinding(role.StayBlock.Pos, block.Pos, CurrentMoveRule);_resultBlocks.Clear();for (int i = 0; i < nodes.Count; i++){_resultBlocks.Add(level.Blocks[nodes[i].XIndex, nodes[i].YIndex]);}return _resultBlocks;}

请添加图片描述

当然,这里的角色移动动画涉及到战斗系统中的内容了,在我们的进程中它还不存在,我们先忽略。

五、角色攻击寻路

1.自定义寻路规则

同样的,角色攻击寻路也必须单独自定义一个寻路规则

    /// <summary>/// 寻路规则(角色攻击)/// </summary>public class AttackRule : AStarRule{/// <summary>/// 当前的关卡/// </summary>public Level CurrentLevel;/// <summary>/// 当前寻路的角色/// </summary>public Role CurrentRole;public override void Apply(AStarNode node){Block block = CurrentLevel.Blocks[node.XIndex, node.YIndex];switch (block.Type){case BlockType.Obstacle://遵循我们一开始制定的规则,只有【障碍】是不可跨越攻击的,其他的都可//且攻击寻路时,任何类型的地块均不会产生额外的削减(OCost = 0)node.OCost = 0;node.IsCanWalk = false;break;default:node.OCost = 0;node.IsCanWalk = true;break;}}}

2.寻找角色的攻击范围内的地块

角色攻击前,能够根据所选要诀的攻击距离周围地块属性等,寻找出所有在攻击范围内的地块:

			private static AttackRule _attackRule;/// <summary>/// 寻路规则(攻击)/// </summary>private static AttackRule CurrentAttackRule{get{if (_attackRule == null){_attackRule = new AttackRule();}return _attackRule;}}/// <summary>/// 寻找角色的攻击范围内的地块/// </summary>/// <param name="level">关卡</param>/// <param name="role">角色</param>/// <param name="ability">使用的要诀</param>public static List<Block> FindAttackableBlocks(Level level, Role role, Ability ability){if (level == null || role == null || ability == null){_resultBlocks.Clear();return _resultBlocks;}CurrentAttackRule.CurrentLevel = level;CurrentAttackRule.CurrentRole = role;//参数1:role.StayBlock.Pos 寻路起点//参数2:ability.AttackDistance 攻击距离//参数3:传入自定义寻路规则List<AStarNode> nodes = level.Map.WalkableNodefinding(role.StayBlock.Pos, ability.AttackDistance, CurrentAttackRule);_resultBlocks.Clear();for (int i = 0; i < nodes.Count; i++){_resultBlocks.Add(level.Blocks[nodes[i].XIndex, nodes[i].YIndex]);}return _resultBlocks;}

当然,如此寻找出来的是所有在攻击距离内的地块,我们只需要判断上面是否站有敌人,就能搜罗出周围所有能够被攻击的敌人,以供玩家选择了。

六、角色登场寻路

此处有一个难点,那就是我们设定为延后登场的角色,如果他的登场地块在特殊情况下被占用了(一个地块只能站一个角色),那么就需要基于其登场地块寻找四周的最近的空地块,以便于完成登场任务:

            /// <summary>/// 以当前地块为起点,寻找周围最近的没有停留角色的地块/// </summary>/// <param name="level">关卡</param>/// <param name="block">当前地块</param>public static Block FindNullBlock(Level level, Block block){if (level == null || block == null || block.StayRole == null)return block;//开启列表:存放所有【未知地块】,需检测其是否【合格】(合格:没有停留角色的【地面】类型地块)List<Block> openList = new List<Block>();//关闭列表:存放所有【已知地块】HashSet<Block> closeList = new HashSet<Block>();//相邻列表HashSet<Block> neighborList = new HashSet<Block>();//从当前地块开始openList.Add(block);//如果存在【未知地块】while (openList.Count > 0){//获取该【未知地块】,同时该地块转为【已知地块】Block b = openList[0];openList.RemoveAt(0);closeList.Add(b);//发现合格地块,直接返回if (b.Type == BlockType.Ground && b.StayRole == null){return b;}else{//否则,获取其周围九宫格范围内的地块neighborList.Clear();GetNeighborBlock(level, b, neighborList);//检测这些地块foreach (var item in neighborList){//如果该地块不是【已知地块】,将其添加到【未知地块】if (!closeList.Contains(item) && !openList.Contains(b)){openList.Add(item);}}}}//如果整个关卡都搜完了还是没有空地块,那......return null;}/// <summary>/// 获取一个地块的相邻地块(九宫格)/// </summary>/// <param name="level">关卡</param>/// <param name="block">地块</param>/// <param name="blocks">缓存列表</param>private static void GetNeighborBlock(Level level, Block block, HashSet<Block> blocks){if (level == null || block == null || blocks == null)return;for (int i = -1; i <= 1; i++){for (int j = -1; j <= 1; j++){if (i == 0 && j == 0)continue;Vector2Int index = block.Pos + new Vector2Int(i, j);if (index.x >= 0 && index.x < level.MapSize.x && index.y >= 0 && index.y < level.MapSize.y){blocks.Add(level.Blocks[index.x, index.y]);}}}}

七、整合

如上我们的寻路系统功能也完成得七七八八了,我决定将其整合到一个静态类中:

    /// <summary>/// RPG2D实用工具/// </summary>public static class RPG2DUtility{/// <summary>/// 寻路系统/// </summary>public static class FindSystem{//我们前面编写的各种方法........}}

这样的话,后续调用就十分简单明了:

         //求得所有能够移动的地块List<Block> blocks = RPG2DUtility.FindSystem.FindWalkableBlocks(_level, player);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/40880.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C++]——同步异步日志系统(2)

同步异步日志系统 一、 不定参函数1.1 不定参宏函数的使用1.2 C 语言中不定参函数的使用1.3 C不定参数使用 二、设计模式2.1 单列模式2.2 工厂模式2.3 建造者模式2.4 代理模式 在我们开发同步异步日志系统之前&#xff0c;需要了解一些相关的技术知识。 一、 不定参函数 在初学…

从键盘输入一个3位数字字符串,将其转换为数字,并逆序,不允许使用切片,不需要做判断

分析思路&#xff1a; 首先&#xff0c;从键盘输入一个字符串类型的三位数字&#xff0c;使用input()函数获取用户的输入。 使用int()函数将输入的字符串转换为整数类型。 将输入的整数进行逆序操作&#xff0c;其中具体的步骤包括通过除法和取余操作获取个位、十位和百位上的…

VCL界面组件DevExpress VCL v24.1 - 发布全新的矢量主题

DevExpress VCL是DevExpress公司旗下最老牌的用户界面套包&#xff0c;所包含的控件有&#xff1a;数据录入、图表、数据分析、导航、布局等。该控件能帮助您创建优异的用户体验&#xff0c;提供高影响力的业务解决方案&#xff0c;并利用您现有的VCL技能为未来构建下一代应用程…

DP学习——策略模式

学而时习之&#xff0c;温故而知新。 敌人出招&#xff08;使用场景&#xff09; 业务中需要多个算法可替换&#xff0c;而不能重构代码时&#xff0c;怎么办&#xff1f;这个时候就要出策略模式这一招了。 具体招式 策略模式的招式&#xff0c;就是把需要替换的算法抽象成…

Django REST Framework(四)DRF APIVIEW

REST framework 传入视图的request对象不再是Django默认的HttpRequest对象&#xff0c;而是REST framework提供的扩展了HttpRequest类的Request类的对象。 REST framework 提供了Parser解析器&#xff0c;在接收到请求后会自动根据Content-Type指明的请求数据类型&#xff08;…

Hadoop权威指南-读书笔记-03-Hadoop分布式文件系统

Hadoop权威指南-读书笔记 记录一下读这本书的时候觉得有意思或者重要的点~ 还是老样子~挑重点记录哈&#x1f601;有兴趣的小伙伴可以去看看原著&#x1f60a; 第三章 Hadoop分布式文件系统 当数据集的大小超过一台独立的物理计算机的存储能力时&#xff0c;就有必要对它进行分…

【数据结构】(C语言):二叉搜索树(不使用递归)

二叉搜索树&#xff1a; 非线性的&#xff0c;树是层级结构。基本单位是节点&#xff0c;每个节点最多2个子节点。有序。每个节点&#xff0c;其左子节点都比它小&#xff0c;其右子节点都比它大。每个子树都是一个二叉搜索树。每个节点及其所有子节点形成子树。可以是空树。 …

CAS(比较与交换)实现原理

CAS&#xff08;Compare and Swap&#xff0c;即比较并交换&#xff09;是一种用于多线程编程中无锁原子操作的机制&#xff0c;常用于实现并发数据结构和算法。CAS 操作通过硬件支持的原子操作实现&#xff0c;可以避免锁带来的开销&#xff0c;提高并发性能。以下是CAS实现原…

选择远程调用的底层实现技术

(1)、 理论基础 feign的远程调用底层实现技术默认采用的是JDK的 URLConnection&#xff0c;同时还支持 HttpClient与 OKHttp。 由于JDK的URLConnection不支持连接池&#xff0c;通信效率很低&#xff0c;所以生产中是不会使用该默认实现的。所以在SpringCloudOpenFeign中直接将…

【堆 优先队列】23. 合并 K 个升序链表

本文涉及知识点 堆 优先队列 LeetCode23. 合并 K 个升序链表 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#…

前端面试题5(http与https区别)

http与https区别 **HTTP&#xff08;Hypertext Transfer Protocol&#xff09;与HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;**是两种用于在互联网上传输数据的协议&#xff0c;它们有以下主要区别&#xff1a; 安全性&#xff1a; HTTP&#xff1a;HTT…

【Whisper】WhisperX: Time-Accurate Speech Transcription of Long-Form Audio

Abstract Whisper 的跨语言语音识别取得了很好的结果&#xff0c;但是对应的时间戳往往不准确&#xff0c;而且单词级别的时间戳也不能做到开箱即用(out-of-the-box). 此外&#xff0c;他们在处理长音频时通过缓冲转录

「C++系列」C++ 变量作用域

文章目录 一、C 变量作用域二、局部变量三、全局变量四、类作用域五、相关链接 一、C 变量作用域 在C中&#xff0c;变量的作用域&#xff08;Scope&#xff09;指的是变量在程序中可以被访问的区域。作用域由花括号{}定义&#xff0c;这些花括号可以出现在函数体、控制结构&a…

Apipost接口测试工具的原理及应用详解(四)

本系列文章简介&#xff1a; 随着软件行业的快速发展&#xff0c;API&#xff08;应用程序编程接口&#xff09;作为不同软件组件之间通信的桥梁&#xff0c;其重要性日益凸显。API的质量直接关系到软件系统的稳定性、性能和用户体验。因此&#xff0c;对API进行严格的测试成为…

windows安装jdk21

下载 下载zip解压 设置环境变量 设置JAVA_HOME环境变量 Path环境变量添加如下值%HAVA_HOME%\bin 打开新的cmd&#xff0c;输入java --version查看效果

恒创科技:HTTP错误码403禁止意味着什么,怎么修复它?

HTTP错误码403禁止意味着客户端无权访问特定网页或服务器。403 错误表示客户端存在问题&#xff0c;无论用户使用的是哪种网络浏览器&#xff0c;都可能发生这种情况。幸运的是&#xff0c;阻止服务器允许访问特定页面的问题通常可以修复。以下是一些常见原因和相应的解决方案。…

【ROS2】Ubuntu 24.04 源码编译安装 Jazzy Jalisco

目录 系统要求 系统设置 设置区域启用所需的存储库安装开发工具 构建 ROS 2 获取 ROS 2 代码使用 rosdep 安装依赖项安装额外的 RMW 实现&#xff08;可选&#xff09;在工作区构建代码 设置环境 尝试一些例子 下一步 备用编译器 Clang保持最新状态 故障排除 卸载 系统要求 当前…

滤波算法学习笔记

目录 引言 一、定义 二、分类 三、常见滤波算法 四、应用与优势 五、发展趋势 例程 1. 均值滤波&#xff08;Moving Average Filter&#xff09; 2. 中值滤波&#xff08;Median Filter&#xff09; 3. 高斯滤波&#xff08;Gaussian Filter&#xff09; 4.指数移动…

CSS 原生嵌套语法兼容性问题

目录 1.介绍2.兼容性问题 1.介绍 所谓的嵌套&#xff0c;就是将一个 CSS 规则放在另一个&#xff08;嵌套规则&#xff09;中&#xff0c;子规则的选择器将相对于父规则的选择器。这有利于代码的模块化和可维护性。原来只能在 CSS 预处理器Less、Sass中使用的嵌套功能&#xf…

Map进行自定义排序

在Java中&#xff0c;Map 接口本身并不提供自定义排序的功能。Map 接口的实现类 HashMap、LinkedHashMap 和 TreeMap 提供了不同的排序特性&#xff1a; HashMap&#xff1a;不保证元素的顺序&#xff0c;元素的插入顺序可能会在遍历时被随机打乱。LinkedHashMap&#xff1a;类…