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

news2024/9/20 8:06:39

更新日期: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:
                	//山体:将造成【移动能力】额外 -1
                    node.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.coloradmin.cn/o/1904748.html

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

相关文章

基于ROS的智能网联车远程交互软件,全UI无需记忆指令,剑指核心原理。

基于ROS的智能网联车远程交互软件&#xff0c;全UI无需记忆指令&#xff0c;剑指核心原理。 服务于中汽恒泰&#xff0c;伟大的项目&#xff0c;希望看官点赞&#xff0c;谢谢~~ 进程&#xff08;节点&#xff09;列表化&#xff0c;参数面板化&#xff0c;实现快速机器人配置…

52-5 内网代理2 - LCX端口转发(不推荐使用LCX)

环境搭建: 本地开3台虚拟机:kali(必须)、windows2012与2008 (可换成其他windows虚拟机) kali - 网络配置成桥接模式 windows2012 - 设置两个网卡,NAT与桥接模式 注意:windows2012要关闭防火墙,要不然其他主机ping不通 关闭防火墙后再开启远程桌面连接 windwos20…

计算机网络体系结构详解:协议与分层

在学习计算机网络时&#xff0c;理解网络协议与分层体系结构是至关重要的。本文将详细介绍这些概念&#xff0c;帮助基础小白快速入门。 1. 什么是网络协议 网络协议是计算机网络中用于数据交换的规则和标准。这些规则规定了数据格式、时序以及发送和接收数据时的动作。网络协…

【2024_CUMCM】数据预处理、数据分析、数据可视化

目录 2023-c题-问题1 问题分析 偏度 峰度 箱线图 读图 重采样、降采样、升采样 重采样 降采样 升采样 解题代码 2023-c题-问题1 问题分析 问题说白了就是探究品类和销售量这两个数据他们各自内在联系&#xff0c;根据题意&#xff0c;我们先进行数 据预处理&#…

归并排序的实现(递归与非递归)

概念 基本思想&#xff1a;归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide andConquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使…

【数据结构】经典链表题目详解集合(反转链表、相交链表、链表的中间节点、回文链表)

文章目录 一、反转链表1、程序详解2、代码 二、相交链表1、程序详解2、代码 三、链表的中间节点1、程序详解2、代码 四、回文链表1、程序详解2、代码 一、反转链表 1、程序详解 题目&#xff1a;给定单链表的头节点 head &#xff0c;请反转链表&#xff0c;并返回反转后的链…

Open3D 删除点云中重叠的点(方法二)

目录 一、概述 1.1原理 1.2应用 二、代码实现 三、实现效果 3.1原始点云 3.2处理后点云 3.3数据对比 一、概述 在点云处理中&#xff0c;重叠点&#xff08;即重复点&#xff09;可能会对数据分析和处理的结果产生负面影响。因此&#xff0c;删除重叠点是点云预处理中常…

element-plus的文件上传组件el-upload

el-upload组件 支持多种风格&#xff0c;如文件列表&#xff0c;图片&#xff0c;图片卡片&#xff0c;支持多种事件&#xff0c;预览&#xff0c;删除&#xff0c;上传成功&#xff0c;上传中等钩子。 file-list&#xff1a;上传的文件集合&#xff0c;一定要用v-model:file-…

数据库系统原理练习 | 作业1-第1章绪论(附答案)

整理自博主本科《数据库系统原理》专业课完成的课后作业&#xff0c;以便各位学习数据库系统概论的小伙伴们参考、学习。 *文中若存在书写不合理的地方&#xff0c;欢迎各位斧正。 专业课本&#xff1a; 目录 一、选择题 二&#xff1a;简答题 三&#xff1a;综合题 一、选择…

验证回文串-string题目

用双指针&#xff0c;left right从两头往中间对比&#xff0c;不是字母的都略过&#xff0c;比的时候化成小写字母 125. 验证回文串 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isPalindrome(string s) {if(s.size() < 1)return true;int left …

一.2.(2)基本共射放大电路组成、工作原理;

1.基本共射放大电路组成 共什么取决于输入输出&#xff0c;共剩下的那一极 2.工作原理 输入信号ui通过电容C1加到三极管的基 极&#xff0c;引起基极电流iB的变化&#xff0c;iB的变化又使集电极电流ic发生变 化&#xff0c;且ic的变化量是iB变化量的β倍。由于有集电极电压&…

谷哥剪映助手使用教程-剪映自动化批量视频剪辑软件-批量混剪素材替换

谷哥剪映助手是一款提高视频剪辑效率的软件&#xff0c;很多人问具体怎么使用&#xff0c;我会抽点时间把各个功能拆分开来&#xff0c;一个个介绍。 一、按组精准替换素材 该功能可以按组精确替换图片或视频素材&#xff0c;如果你草稿里只有一个素材需要替换&#xff0c;请…

零知识证明技术:隐私保护的利器

在当今信息时代&#xff0c;数据安全和隐私保护的重要性日益凸显。随着技术的发展&#xff0c;密码学在保障信息安全方面发挥着越来越重要的作用。其中&#xff0c;零知识证明技术作为一种新兴的密码学方法&#xff0c;为隐私保护提供了强有力的支持。本文将简要介绍零知识证明…

【ETABS】【RHINO】案例:Swallow to ETABS

文章目录 01. Swallow Overview总览1 LOAD&#xff1a;Defination of LoadCase、Response Combo2 SectionArea Section and Area Load&#xff08;面截面定义与指定&#xff0c;面荷载指定&#xff09;Frame Section with rebarattr and linear load&#xff08;带钢筋属性框架…

【JVM基础篇】Java的四种垃圾回收算法介绍

文章目录 垃圾回收算法垃圾回收算法的历史和分类垃圾回收算法的评价标准标记清除算法优缺点 复制算法优缺点 标记整理算法&#xff08;标记压缩算法&#xff09;优缺点 分代垃圾回收算法&#xff08;常用&#xff09;JVM参数设置使用Arthas查看内存分区垃圾回收执行流程分代GC算…

传统IO和NIO文件拷贝过程

参考&#xff1a;https://blog.csdn.net/weixin_57323780/article/details/130250582

【代码管理的必备工具:Git的基本概念与操作详解】

一、Git 初识 1.提出问题 不知道你工作或学习时&#xff0c;有没有遇到这样的情况&#xff1a;我们在编写各种⽂档时&#xff0c;为了防止⽂档丢失&#xff0c;更改失误&#xff0c;失误后能恢复到原来的版本&#xff0c;不得不复制出⼀个副本&#xff0c;比如&#xff1a; “…

跨越界限的温柔坚守

跨越界限的温柔坚守 —— 郑乃馨与男友的甜蜜抉择在这个光怪陆离、瞬息万变的娱乐圈里&#xff0c;每一段恋情像是夜空中划过的流星&#xff0c;璀璨短暂。然而&#xff0c;当“郑乃馨与男友甜蜜约会”的消息再次跃入公众视野&#xff0c;它不仅仅是一段简单的爱情故事&#xf…

html+css+JavaScript 实现两个输入框的反转动画

开发时遇到了一个输入框交换的动画 做完之后觉得页面上加些许过渡或动画&#xff0c;其变化虽小&#xff0c;却能极大的提升页面质感&#xff0c;给人一种顺畅、丝滑的视觉体验。它的实现过程主要是通过css中的transition和animation来实现的。平时在开发的时候增加一些动画效…

Spring源码十六:Bean实例化入口探索

上一篇Spring源码十六&#xff1a;Bean名称转化我们讨论doGetBean的第一个方法transformedBeanName方法&#xff0c;了解Spring是如何处理特殊的beanName&#xff08;带&符号前缀&#xff09;与Spring的别名机制。今天我们继续往方法下面看&#xff1a; doGetBean 这个方法…