【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、数据类型 整数类型ÿ…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)
错误一:yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因,后面把yaml.safe_dump直接替换成yaml.dump,确实能保存,但出现乱码: 放弃yaml.dump,又切…...

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
2024年初,人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目(一款融合大型语言模型能力的云端AI编程IDE)时,技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力,TRAE在WayToAGI等…...