【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.自定义寻路规则
要做到如上这么多自由的想法,自定义寻路规则
是必须的,所幸HTFrameworkAI
的A*寻路
支持自定义寻路规则,那么我们便立即开始吧(继承至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);
相关文章:

【Unity】RPG2D龙城纷争(八)寻路系统
更新日期:2024年7月4日。 项目源码:第五章发布(正式开始游戏逻辑的章节) 索引 简介一、寻路系统二、寻路规则(角色移动)三、寻路规则(角色攻击)四、角色移动寻路1.自定义寻路规则2.寻…...

C++ 函数高级——函数重载——基本语法
作用:函数名可以相同,提高复用性 函数重载满足条件: 1.同一个作用域下 2.函数名称相同 3.函数参数类型不同 或者 个数不同 或者 顺序不同 注意:函数的返回值不可以作为函数重载的条件 示例: 运行结果:...

Go语言实现的端口扫描工具示例
Go语言实现的端口扫描工具示例 创建一个端口扫描工具涉及到网络编程和并发处理,下面是一个简单的Go语言实现的端口扫描工具示例。这个工具会扫描指定IP地址的指定范围内的端口。 请注意,使用端口扫描工具可能会违反某些网络的使用条款,甚至…...

SpringSecurity初始化过程
SpringSecurity初始化过程 SpringSecurity一定是被Spring加载的: web.xml中通过ContextLoaderListener监听器实现初始化 <!-- 初始化web容器--><!--设置配置文件的路径--><context-param><param-name>contextConfigLocation</param-…...

Python爬取股票信息-并进行数据可视化分析,绘股票成交量柱状图
为了使用Python爬取股票信息并进行数据可视化分析,我们可以使用几个流行的库:requests 用于网络请求,pandas 用于数据处理,以及 matplotlib 或 seaborn 用于数据可视化。 步骤 1: 安装必要的库 首先,确保安装了以下P…...

秋招突击——7/4——复习{}——新作{最长公共子序列、编辑距离、买股票最佳时机、跳跃游戏}
文章目录 引言复习新作1143-最长公共子序列个人实现 参考实现编辑距离个人实现参考实现 贪心——买股票的最佳时机个人实现参考实现 贪心——55-跳跃游戏个人实现参考做法 总结 引言 昨天主要是面试,然后剩下的时间都是用来对面试中不会的东西进行查漏补缺ÿ…...

udp发送数据如果超过1个mtu时,抓包所遇到的问题记录说明
最近在测试Syslog udp发送相关功能,测试环境是centos udp头部的数据长度是2个字节,最大传输长度理论上是65535,除去头部这些字节,可以大概的说是64k。 写了一个超过64k的数据(随便用了一个7w字节的buffer)发送demo,打…...

电子电气架构 --- 智能座舱万物互联
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…...

笔记本电脑内存不够
笔记本电脑内存不够是众多笔记本用户面临的常见问题,尤其是对于一些需要处理大型文件或者运行复杂软件的用户,这个问题可能会严重影响笔记本的使用体验。那么,我们应该如何解决笔记本电脑内存不够的问题呢?本文将从几个方面进行详…...

Vue项目使用mockjs模拟后端接口
文章目录 操作步骤1. 安装 mockjs 和 vite-plugin-mock2. 安装 axios3. 创建mock路径4. 配置 viteMockConfig5. 编写第一个mock接口6. 创建 createProdMockServer7. 配置 axios8. 编写请求接口9. 在页面中使用 操作步骤 1. 安装 mockjs 和 vite-plugin-mock vite-plugin-mock …...

Linux下使用arping检测IP地址是否冲突
arping简介 在Linux中,arping是一个用来发送ARP请求到一个相邻主机的工具,通常用于检测网络上的IP地址冲突。 使用arping检测IP地址是否冲突的方法 例1:使用如下命令检测10.206.216.95是否冲突 (使用-I参数指定网络接口) # arping -I eth…...

为什么 npm run serve 正常,npm run build 就报错:digital envelope routines::unsupported
这个错误通常与 Node.js 版本和使用的加密算法有关。让我解释一下原因和可能的解决方案: 错误原因 这个错误(“error:0308010C:digital envelope routines::unsupported”)通常发生在以下情况: 使用较新版本的 Node.js…...

模电基础 - 简介
目录 零 .简介 一. 学习方法 二. 教材推荐 三. 总结 零 .简介 “模电”即模拟电子技术,是电子信息工程、电气工程及其自动化等相关专业的一门关键基础课程。 首先,在半导体器件方面,二极管是一种具有单向导电性的器件,由 P 型…...

uniapp中实现瀑布流 短视频页面展示
直接上干货 第一部分为结构 <swiper class"list" :currentindex change"swiperchange" scrolltolower"onReachBottom"><swiper-item style"overflow: scroll;" v-for"(item,index) in 2" :key"index"&g…...

python-开关灯(赛氪OJ)
[题目描述] 假设有 N 盏灯(N 为不大于 5000 的正整数),从 1 到到 N 按顺序依次编号,初始时全部处于开启状态;第一个人( 1 号)将灯全部关闭,第二个人( 2 号)将…...

基于改进高斯-拉普拉斯滤波器的一维时间序列平滑与降噪(MATLAB)
以图像处理为例,拉普拉斯算子是基于图像的二阶导数来找到边缘并搜索过零点,传统的拉普拉斯算子常产生双像素宽的边缘,对于较暗区域中的亮斑进行边缘检测时,拉普拉斯运算就会使其变得更亮。因此,与梯度算子一样…...

计算组的妙用!!页面权限控制
需求描述: 某些特殊的场景下,针对某页看板,需要进行数据权限卡控,但是又不能对全部的数据进行RLS处理,这种情况下可以利用计算组来解决这个需求。 实际场景 事实表包含产品维度和销售维度 两个维度属于同一公司下面的…...

Self-Instruct构造Prompt的例子
人工构造一批Prompt做种子。(Starting with a small seed set of human-written tasks)每次把一些种子后来生成的Prompt,放到Input里做few-shot examples,用LLM生成更多的Prompt;(Using the LLM to generat…...

友好前端vue脚手架
企业级后台集成方案vue-element-admin-CSDN博客在哔站学习,老师说可以有直接的脚手架(vue-element-admin)立马去搜索,找到了这博主这篇文章 介绍 | vue-element-admin 官方默认英文版: git clone https:/…...

SQL Server特性
一、创建表 在sql server中使用create table来创建新表。 create table Customers( id int primary key identity(1,1), name varchar(5) ) 该表名为Customers其中包含了2个字段,分别为id(主键)以及name。 1、数据类型 整数类型ÿ…...

华为HCIP Datacom H12-821 卷25
1.单选题 Smurf攻击一般使用以下哪种协议 A、TCP B、BGP C、ICMP D、DHCP 正确答案: C 解析: Smurf攻击是一种病毒攻击,以最初发动这种攻击的程序“Smurf”来命名。这种攻击方法结合使用了IP欺骗和ICMP回复方法使大量网络传输充斥目…...

如何在 Selenium Python 中解决验证码 | 2024 完整指南
由于在进行网络自动化时遇到验证码是让许多人感到不知所措的问题。这些验证码专为区分人类用户和自动化脚本而设计,对于使用Selenium进行网络爬虫或自动化任务而言,无疑是一个巨大的挑战。2024年的完全指南将为您提供全面的解决方案,帮助您高…...

ASCII码对照表【2024年汇总】
🍺ASCII相关文章汇总如下🍺: 🎈ASCII码对照表(255个ascii字符汇总)🎈🎈ASCII码对照表(Unicode 字符集列表)🎈🎈ASCII码对照表&#x…...

刷题之买股票的最佳时机(leetcode)
买股票的最佳时机 动态规划入门题。 最简单的模拟式解法: class Solution { public:int maxProfit(vector<int>& prices) {//也可以换一种思路,因为只交易一次,那么找出股票最便宜的时候买入,最贵的时候卖出ÿ…...

Apache Seata透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题
本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 Apache Seata透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题 透过源码解决SeataAT…...

1.2 如何让机器说人话?万字长文回顾自然语言处理(NLP)的前世今生 —— 《带你自学大语言模型》系列
本系列目录 《带你自学大语言模型》系列部分目录及计划,完整版目录见:带你自学大语言模型系列 —— 前言 第一部分 走进大语言模型(科普向) 第一章 走进大语言模型 1.1 从图灵机到GPT,人工智能经历了什么࿱…...

【QT】按钮类控件
按钮类控件 按钮类控件1. PushButton2. Radio Button3. Check Box4. Tool Button 按钮类控件 1. PushButton 使⽤ QPushButton 表示⼀个按钮,这也是当前我们最熟悉的⼀个控件了. QPushButton 继承⾃ QAbstractButton . 这个类是⼀个抽象类. 是其他按钮的⽗类. 在…...

RedHat运维-Linux软件包管理基础-RHEL9软件包管理基础
Linux软件包管理基础-RHEL9 1. 对于RHEL9来说,软件包管理基础分为增、删、改、查四个部分。对于增来说,有:增加一个仓库的信息文件、启用一个仓库的信息文件、安装rpm包、解压rpm包、安装软件、安装软件组、更新软件。在这里先讲软件包管理中…...

uniapp----- 判断小程序版本有没有更新
const updateManager uni.getUpdateManager();// 当向小程序后台请求完新版本信息,会进行回调updateManager.onCheckForUpdate(function (res) {console.log(是否有新版本, res.hasUpdate);});// 当新版本下载完成,会进行回调updateManager.onUpdateRea…...

Spring Boot的无缝衔接:深入解析与实践
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 🚀The begin🚗点点关注,收藏不迷路🚩 引言 在快速迭代的软件开发环境中,无缝衔接是提升开发效率、降低维护成本、增强系统稳定性的关键。Spring Boo…...