找回密码
 立即注册
查看: 708|回复: 0

Unity寻路插件(A* Pathfinding)进阶教程九:基于turn-based的游戏实战

[复制链接]
发表于 2020-11-25 08:56 | 显示全部楼层 |阅读模式
本系列的教程文章基于 A*Pathfinding Project 4.2.8的官网教程翻译,每一章节的原文地址都会在教程最下方给出。
介绍
如果你还没看过入门介绍,建议你先阅读开篇之后再进行本章阅读。
基于转弯的游戏往往会要求更详细的控制单位可以遍历节点,并且可以设置这些节点的权值,并且不同的单位之间的权值可能都不一样。另一方面来说,也不总是需要最大化寻路的性能,很多的单位在生命周期内都是静止不动的。
很多游戏都会在每次请求路径之前都更新好每个节点的权值以及属性。但这种方案其实并不是一个优秀的解决方案,本插件会提供另一个实现方式。
如果你需要更多的控制权,你可以参照下方 ITraversalProvider区域的内容。
阻断节点(Blocking nodes)

一个常用的场景就是设置一个节点,一些单位可以通过,另一些不能。比如,一个turn based的游戏,一个单位希望阻止其他单位站在同一个tiles上,但是它肯定不能阻止它自己。
因为这个原因,BlockManager组件会有一个辅助的SingleNodeBlocker组件存在。SingleNodeBlocker是一个用来标识特定阻塞节点的一个简单实现。每一个SingleNodeBlocker组件都会完全阻塞节点。BlockManager会追踪每个SingleNodeBlocker节点,并允许创建可在路径中使用的TraversalProvider实例,来指定被阻塞的其他节点。
可以在示例 Turnbased 中查看,pro版本特有示例。但是本章所讲述的特性并不是pro版本才有的。
这篇教程里,我们创建一个新的场景,并且写几个测试脚本去测试相关API。
创建一个场景命名为TurnBasedTest,或者任何你想要的名字。增加一个新的GameObject并且命名为BlockManager,然后绑定BlockManager组件。
现在Add一个sphere到坐标(1.5, 0, 2.5),这会把它放在一个节点的中心位置。添加SingleNodeBlocker组件到这个sphere上,然后把之前创建的BlockManager组件拖拽到manager的字段区域。确保你删除了sphere的collider,不然它会被当做一个障碍物,但其实我们是想要它的SingleNodeBlocker功能。再创建一个物体命名为A*然后绑定AstarPath组件(Menu bar ->Pathfinding-> Pathfinder),添加一个你想要的 足够大的Plane当做地面。添加一个Grid Graph到AstarPath足尖上,然后设置Unwalkable when no ground选项为false,这个时候点击Scan按钮,你会看到一个小的空的grid和一个sphere,请注意这个时候Sphere并不会阻碍任意的节点。
现在我们想使用SingleNodeBlocker来阻挡某些位置了,所以新建一个文件 BlockerTest.cs 然后填入以下代码:
  1. using UnityEngine;
  2. using System.Collections;
  3. using Pathfinding;
  4. public class BlockerTest : MonoBehaviour {
  5.     public void Start () {
  6.         var blocker = GetComponent<SingleNodeBlocker>();
  7.         blocker.BlockAtCurrentPosition();
  8.     }
  9. }
复制代码
好了把这个新建的脚本添加到刚才的Sphere上吧,当游戏启动的时候,SingleNodeBlocker组件就会告诉BlockManager,它已经占据了这个位置。但是这还不够,因为没有路径知道这个节点已经被阻断了。
那么我们再创建一个脚本BlockerPathTest.cs来计算带阻断的路径。
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using Pathfinding;
  4. public class BlockerPathTest : MonoBehaviour {
  5.     public BlockManager blockManager;
  6.     public List<SingleNodeBlocker> obstacles;
  7.     public Transform target;
  8.     BlockManager.TraversalProvider traversalProvider;
  9.     public void Start () {
  10.         // Create a traversal provider which says that a path should be blocked by all the SingleNodeBlockers in the obstacles array
  11.         traversalProvider = new BlockManager.TraversalProvider(blockManager, BlockManager.BlockMode.OnlySelector, obstacles);
  12.     }
  13.     public void Update () {
  14.         // Create a new Path object
  15.         var path = ABPath.Construct(transform.position, target.position, null);
  16.         // Make the path use a specific traversal provider
  17.         path.traversalProvider = traversalProvider;
  18.         // Calculate the path synchronously
  19.         AstarPath.StartPath(path);
  20.         path.BlockUntilCalculated();
  21.         if (path.error) {
  22.             Debug.Log("No path was found");
  23.         } else {
  24.             Debug.Log("A path was found with " + path.vectorPath.Count + " nodes");
  25.             // Draw the path in the scene view
  26.             for (int i = 0; i < path.vectorPath.Count - 1; i++) {
  27.                 Debug.DrawLine(path.vectorPath[i], path.vectorPath[i + 1], Color.red);
  28.             }
  29.         }
  30.     }
  31. }
复制代码
这个脚本每一帧都会计算它到目标点的位置,使用TraversalProvider来确保不会碰到任何List里记录的障碍物。
BlockManager.TraversalProvider有2个模式,AllExceptSelector 和 OnlySelector。如果是AllExceptSelector 模式,那么所有被SingleNodeBlocker阻挡的节点都会被当做不可行走的区域,但是除了列表里的这些物体。这就会让某一个物体需要躲避除它自己之外的所有其他物体变的简单。或者你可以让所有敌人不能通过,而队友可以。如果是OnlySelector模式,则相反,只有SingleNodeBlockers标识的那些List物体不能通过,出于性能的考量的话,这样会让List更小。
创建一个名为“Target”的物体,并放置在(3.5, 0, 3.5)。再创建一个物体叫Path Searcher 然后添加刚才创建的BlockerPathTest脚本,移动它到(-2.5, 0, 3.5)。然后设定它的"Block Manager"和"Target" 字段。点击Play,你会看到一条红线链接Path Searcher 和Target两个物体。注意它会穿过球体,就像没碰到一样。这是因为我们没有给它到"Obstacles"列表。
如果你停止游戏,然后给sphere添加SingleNodeBlocker组件,再点play你会看到它就被绕开了。


https://www.zhihu.com/video/1132402218425651200
好了到这我们就很容易扩展了。比如我现在有一些红色的spheres和一些蓝色的spheres,做两个searchers,其中一个会把蓝色的当做障碍物,另一个吧红色当做障碍物,嗯如下视频:


https://www.zhihu.com/video/1132402749173805056
ITraversalProvider

上面的系统工作看起来很简单,也很容易使用。然而如果有更复杂的场景的话,就需要用到ITraversalProvider接口了。任何路径都可以用traversalProvider提供的2个方法:
  1. bool CanTraverse (Path path, GraphNode node);
  2. uint GetTraversalCost (Path path, GraphNode node);
复制代码
CanTraverse方法根据单位是否能通过节点来返回true或者false。GetTraversalCost返回通过某个节点的权值。如果默认没有使用tags或者penalties的时候,通过的权值都是0。1000就相当于世界里的1单位。
下面有个示例可以参照一下:
  1. public class MyCustomTraversalProvider : ITraversalProvider {
  2.     public bool CanTraverse (Path path, GraphNode node) {
  3.         // Make sure that the node is walkable and that the 'enabledTags' bitmask
  4.         // includes the node's tag.
  5.         return node.Walkable && (path.enabledTags >> (int)node.Tag & 0x1) != 0;
  6.         // alternatively:
  7.         // return DefaultITraversalProvider.CanTraverse(path, node);
  8.     }
  9.     public uint GetTraversalCost (Path path, GraphNode node) {
  10.         // The traversal cost is the sum of the penalty of the node's tag and the node's penalty
  11.         return path.GetTagPenalty((int)node.Tag) + node.Penalty;
  12.         // alternatively:
  13.         // return DefaultITraversalProvider.GetTraversalCost(path, node);
  14.     }
  15. }
复制代码
通过继承,自己写了自定义的ITraversalProvider之后,你可以改变penalties值来适配你的游戏需求。不过要注意grid graph因为性能的原因,已经移除了所有与不能通行的节点的链接。所以如果移除"node.Walkable &&"这个判定,grid graphs仍然是不能通过不可行走的节点。你只能把一个节点变为unwalkable,不能把再把unwalkable的节点变为walkable。
ITraversalProvider's 如果在多线程的环境下工作,那么它们会被独立计算。这就代表,你不能使用Unity的任何API,并且你需要保证你的线程安全。
如果你写了自定义的接口,你可以这么把他绑定到路径上。
  1. var path = ABPath.Construct(...);
  2. path.traversalProvider = new MyCustomTraversalProvider();
复制代码

原文地址:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-12-26 03:11 , Processed in 0.103428 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表