【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:
                	//山体:将造成【移动能力】额外 -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);

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何根据控制框图写传递函数

控制框图&#xff08;也称为方块图或信号流图&#xff09;是控制系统工程中常用的一种图形表示方法&#xff0c;用于描述系统中各个组件之间的关系以及信号流向。传递函数则是描述线性时不变系统动态特性的数学模型&#xff0c;通常用于分析和设计控制系统。 识别组件&#xff…

Learn To Rank

在信息检索中&#xff0c;给定一个query&#xff0c;搜索引擎召回一系列相关的Documents&#xff0c;然后对这些Documents进行排序&#xff0c;最后将Top N的Documents输出。 排序问题最关注的是各Documents之间的相对顺序关系&#xff0c;而不是各个Documents的预测分最准确。…

GD32实战篇-双向数控BUCK-BOOST-BOOST升压理论基础

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 向上代码兼容GD32F450ZGT6中使用 后续项目主要在下面该专栏中发布&#xff1a; https://blog.csdn.net/qq_62316532/category_12608431.html?spm1001.2014.3001.5482 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转…

即时通讯平台项目测试(登录/注册页面)

http://8.130.98.211:8080/login.html项目访问地址&#xff1a;即时通讯平台http://8.130.98.211:8080/login.html 本篇文章进行登录和注册页面的测试。自动化脚本的依赖在文章末尾。 登录页面测试 UI测试 测试环境&#xff1a;Win11&#xff1b;IntelliJ IDEA 2023.2&#…

C语言课设--读取文件并统计数据

读取文件并统计数据 // 统计记事本英文字符数 //功能 &#xff1a; 读 文件 a.txt 统计出各种 数字 大写字母 小写字母 0~9 A~Z a ~z各有多少个 程序主体框架如下&#xff1a; #include<stdio.h> int a[128]{0};//其中数组元素a[i]保存 ASCII 码为i的字符的数量 v…

css样式学习样例之边框

成品效果 边框固定 .login_box{width: 450px;height: 300px;background-color: aliceblue;border-radius: 3px;position: absolute;left: 50%;top: 50%;transform: translate(-50%,-50%); }这段CSS代码定义了一个名为.login_box的类的样式&#xff0c;它主要用于创建一个登录框…

【在Linux世界中追寻伟大的One Piece】HTTPS协议原理

目录 1 -> HTTPS是什么&#xff1f; 2 -> 相关概念 2.1 -> 什么是"加密" 2.2 -> 为什么要加密 2.3 -> 常见的加密方式 2.4 -> 数据摘要 && 数据指纹 2.5 -> 数字签名 3 -> HTTPS的工作过程 3.1 -> 只使用对称加密 3.2 …

Linux系统安装软件包的方法rpm和yum详解

起因&#xff1a; 本篇文章是记录学习Centos7的历程 关于rpm 常见命令 1&#xff09;查看已经安装的软件包 rpm -q 软件包名 2&#xff09;查看文件的相关信息 rpm -qi 软件包名 3&#xff09;查看软件包的依赖关系 就是说要想安装这个软件包&#xff0c;就必须把一些前…

亚信安全发布2024年6月威胁态势,高危漏洞猛增60%

近日&#xff0c;亚信安全正式发布《2024年6月威胁态势报告》&#xff08;以下简称“报告”&#xff09;&#xff0c;报告显示&#xff0c;6月份新增信息安全漏洞 1794个&#xff0c;高危漏洞激增60%&#xff0c;涉及0day漏洞占67.67%&#xff1b;监测发现当前较活跃的勒索病毒…

CountDownLatch内部原理解析

文章目录 1、CountDownLatch介绍1.1、功能介绍1.2、demo1.3、问题 2、前置知识2.1、AQS整体结构2.1.1、整体结构2.1.2、state属性2.1.3、head和tail属性 3、CountDownLatchAPI源码解析3.1、countDown方法3.1.1、Sync类3.1.2、releaseShared方法3.1.3、tryReleaseShared方法 3.2…

C++库函数--next_permutation(详细)

next_permutation介绍 用于生成某个序列的下一个排列。它通常在需要生成排列的问题中使用&#xff0c;比如全排列问题。 使用方法 普通序列 &#xff1a;next_permutation&#xff08;起始地址&#xff0c;末尾地址1&#xff09; 结构体&#xff1a;next_permutation&#…

解决 Layout Inspector无法查看Component Tree 布局层级信息 | Android Studio Koala

问题描述 Tool -> Layout Inspector 显示下图&#xff0c;无法生成.li文件查看Component Tree&#xff0c;变成实时的Preview并功能点击操作&#xff0c;跟模拟器一样。 原因&#xff1a;默认勾选了"Enable embedded Layout Inspector"&#xff0c;启用了嵌入式…

笔记本电脑内存不够

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

flask使用定时任务flask_apscheduler(APScheduler)

Flask-APScheduler描述: Flask-APScheduler 是一个 Flask 扩展&#xff0c;增加了对 APScheduler 的支持。 APScheduler 有三个内置的调度系统可供您使用&#xff1a; Cron 式调度&#xff08;可选开始/结束时间&#xff09; 基于间隔的执行&#xff08;以偶数间隔运行作业…

RabbitMq - Java客户端基础【简单案例 +Work模型】

目录 1、前置知识 1.1、AMQP怎么理解 1.2、Spring AMQP是什么 1.3、为什么要了解Spring-AMQP&#xff1f; 2、使用Spring-AMQP实现一个发消息案例 3、Work模型 问题&#xff1a; 优化&#xff1a; 小结&#xff1a;Work模型的使用&#xff1a; 1、前置知识 1.1、AMQP怎…

[激光原理与应用-101]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 5 - 3C行业应用 - 电子布局类型

目录 前言&#xff1a; 一、激光在3C行业的应用概述 1.1 概述 1.2 激光焊接在3C-电子行业应用 二、3C电子行业中激光焊接 2.1 纽扣电池 2.2 均温板 2.3 指纹识别器 2.4 摄像头模组 2.5 IC芯片切割 三、3C行业中激光切割 四、激光在3C行业中的其他应用 4.1 涂层去除…

Towards Accurate and Robust Architectures via Neural Architecture Search

基于网络架构搜索的准确性与鲁棒性结构研究 论文链接&#xff1a;https://arxiv.org/abs/2405.05502 项目链接&#xff1a;未开源 Abstract 为了保护深度神经网络免受对抗性攻击&#xff0c;对抗性训练因其有效性而受到越来越多的关注。然而&#xff0c;对抗训练的准确性和鲁…

服务器本地部署文件服务器minio

minio类似于阿里云的OSS&#xff0c;为不方便把图、文、日志等形式的文件保存在公有云上的&#xff0c;可以在自己的服务器上部署文件服务器 看过本人前几个文章的&#xff0c;使用docker就会很快上手部署&#xff0c;直接上所有代码 #添加镜像 docker search minio docker p…

jvm 03 JVM的运行时数据区域 ,(类常量池,运行时常量池,字符串常量池这个三个的区别),操作系统内存模型JMM和JVM的内存模型联系

方法区在jdk8后&#xff0c;改成元空间 JVM内存模型&#xff1a; JMM 主内存&#xff1a;本地方法区和堆 工作内存&#xff1a;私有的工作栈 其实一个JVM内存模型&#xff08;主要就是运行时数据区域&#xff09;一个Java进程的JMM&#xff0c;工作内存JVM中线程的内存区域…

关于umjs的主题切换实现

注意本文写作日期2024年7月7日&#xff0c;我目前是最新版本的 注意&#xff1a;该功能仅 antd v5 可用 最后目标实现 先说一下&#xff0c;umijs布局默认是内置ant-design/pro-layout布局写的 看一下官网ProLayout - 高级布局和布局与菜单 直接在app.tsx加入以下&#xff…