A* Pathfinding插件学习记录(一)
前言
之前在2d项目其实用过A*插件,但隔了太久了基本都忘了。而且当时也是赶时间瞎琢磨,也没留下啥笔记。这次使用A*用于3D寻路,也是考虑到其系统比NavMesh更加自由灵活,顺便系统性学习一下。说实在的如果想要快手上手,可以直接参考案例场景,把案例里面的一个一个拆解下来也能懂个大概。
简述
AstarPath:一个单例组件,管理场景中所有的Graph数据。
Graph:agent能够移动的区域,由点组成。
MovementScript:用于沿着路径移动。可以使用内置的脚本,例如AIPath,RichAI,AILerp。也可以自定义脚本扩展更多高级功能
Seeker:根据计算出来的路径驱动移动
详细
为环境分好层
添加A*系统AstarPath(add the A* Pathfinding System to the scene to enable Pathfinding.)。
AstarPath:
添加PathFinder组件。
GraphData存放着所有Graph,之前使用过A*在多个graph交集处动态去选择合适的Graph。
创建一个 GridGraph,它以网格模式生成节点。
点击Scans来生成网格化数据
Height Testing
为了将节点放置在正确的高度,A*系统会向场景发射一束光线。射线从网格上方向下发射,节点被放置在它击中的地方。如果它没有击中任何东西,则如果切换了“无地面时不可行走”变量,则使其无法行走,或者如果节点设置为 false,则相对于网格将节点放置在 Y=0 处。
设置地面的层用于测试
Collision Testing
用于检查其可行走性,通常使用与将在世界上四处走动的AI角色具有相同直径和高度的胶囊,不过最好比实际稍微大一点。
设置障碍物的层用于测试
AI-Agent
MovementScript
AIPath和AILerp可以用于任何Graph,RichAI主要用于NavMesh类型的Graph。
AIPath 和 RichAI 脚本松散地遵循路径, AILerp 脚本使用插值非常精确地沿路径移动
AIDestinationSetter
此组件可以非常简单帮助你告诉 AIPath 脚本移动到目标位置。
Searching for paths(寻路)
可以通过多种方式搜索路径。最容易的方式是将Seeker组件附加到你想要得到路径的角色上,然后调用Seeker.StartPath()方法,seeker组件会自动处理一系列修改器,最后告知MovementScript进行移动。
Tips:Seeker 一次只会接受一个寻路调用,如果您在前一个路径完成之前请求新的路径,则之前的路径请求将被取消。
默认的路径计算方式并不是一帧就计算完成的,而是为了提高性能分帧进行的。可以通过判断Path的状态或注册路径计算完成的回调事件来做取得正确路径。如果在某些情况下,希望立即计算路径,可以使用 Pathfinding.Path.BlockUntilCompute 方法。在调用这个方法前需要保证Path开始被计算,即使用AstarPath.StartPath(path)启动路径的计算。
Seeker.StartPath的确很简单,不过Seeker在计算路径完成之后也会立刻移动。有时候我们还不希望立刻移动,那么我们可以自己通过ABPath.Construct (transform.position, transform.position+transform.forward*10, null);
创建一个Path对象(通过a*接口创建的path应该内部都是有做池化的)。这样我们就可以在移动之前对path做一些处理,比如获取Path路径的每个路径点Vectors、路径长度(GetTotalLength)。
OnPathComplete会在路径计算完成后都会被执行,无论成功与否。在Seeker脚本下不必为每个Path对象创建时都注册OnPathComplete,seeker下有一字段供我们注册,它不会像前面那样每次都在堆上分配一个新委托。seeker.pathCallback += OnPathComplete;
不过需要在seeker销毁前取消注册seeker.pathCallback -= OnPathComplete;
如果想要中断当前的寻路请求,可调用Seeker.CancelCurrentPathRequest()
。
除了之外,A*Pathfinder还提供了其他类型的路径,例如多个目标点的路径(MultiTargetPath),我的理解是它会同时计算到每个目标点的距离,然后在计算完成后返回这些路径,同时也可以通过最后一个参数选择只计算最近的那个点的路径。seeker.StartMultiTargetPath (transform.position, endPoints, true);
在某些情况下希望对每条路径进行更多控制或同时计算大量路径。那么可以直接调用 AstarPath 组件的AstarPath.StartPath。使用AstarPath计算的路径将不会进行后处理(应该就是Path Modifiers),如果需要那么得在计算路径后调用 Seeker.PostProcess。
局部回避ROV
局部碰撞回避算法与全局路径规划的贪心算法(A*算法)通常是配合使用的。局部的碰撞回避算法主要关注的是动态物体之间即将发生的碰撞,而路径规划主要关注的是到达目的地的最佳路径。
Link
类似UnityNavMeshAgent中的JumpLink,就是给高低两平面或规定的两点建立连接。
start:开始位置,默认为挂载link组件的对象位置。
end:目标位置。
CostFactor:从start到end的预估开销,这里可以理解为优先级。最后选取最佳路径时会将link的开销一并同当前位置到link的start位置所用开销考虑。
OneWay:个人认为是限制link的单向。但是测试好像没啥作用,后期再说吧。
注意:
- 如果到达目标平台路径上需多个Link连接,那么要注意agent可能不会理想得到达目的地。如果这个高台下方的平面是可行走的话,那么Agent在完成第一个Link的时候可能会直接朝目标点下方的投射的点去移动(不是同一个平面),这是一个Bug。如果目标点不在Link附近,那么完成一个Link后它可能不会自动寻找附近的Link。解决方法就是将寻路分段进行,每到达一个JumpLinkPoint就将寻路目标设置到下一个JumpLink,直到到达目标点所在的平台。如图这种连续的Link,可以直接将目标点设置在最后一个Link的Start处。在后面发现问题出在AIPath的pickNextWaypointDist属性上,该属性规定了agent可以找到一个以它为半径的圆在路径上的一交点,以确定下一个移动到的点,从而简化移动的路径。正因为简化了路径,Link连接处被忽略,所以....
- agent完成一次link受其代理的高度影响。在A*Pathfinder中并不会实时体现出哪些link可以完成,这就很麻烦。
Path Modifiers(路径后处理)
将modifier后处理修改器添加到seeker所在物体即可。
注意修改器由于后处理的特性可能会导致路径穿墙,尤其是Simple Smooth这类忽略物理环境的修改器。
Simple Smooth
Add that to our AI.
作用让路径更平滑。细分段的间距,直到段的长度小于Max Segment Length
FunnelModifier
Another good modifier to use is the FunnelModifier which will simplify the path a great deal. This modifier is almost always used when using navmesh/recast graphs.
作用:使路径简单化,总是被用在Navmesh和Recast类型的Graph上。会使路径沿着墙角或墙壁走。
Raycast Modifier
通过朝路径上某个点发射射线的方式来优化线段。如果线段之间没有碰撞发生,那么就可以优化路径忽略中间的点。
A*PathFinder这个插件很自由很开放,但是系统设计的不够统一,很容易造成混乱。其中最复杂最混乱的就是MovementScript,不同的MovementScript和Graph产生的效果差距蛮大的,更重要的是需要根据环境配置AStar以及选择合适的驱动组件。总之需要不断地翻阅文档,在项目中不断熟悉摸索,踩过一个个坑。待我这段时间摸索完,再回来更新下一章节吧。
来源:麦瑞克博客
链接:https://www.playcreator.cn/archives/unity/3283/
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!
问一下,如果用这个插件在一些海洋中寻路应该怎么处理,就是那些本身有流速并且有海浪,也就是说船本身的运动轨迹会受到其他因素的影响应该怎么处理
[…] 继A* Pathfinding插件学习记录(一) 这篇文章详细介绍一下笔者在项目中重点使用到的做个分享,也欢迎大家交流指正。(别只做个伸手党,我写博客一是为了做个记录,其二还是希望可以得到反馈,相互交流,共同成长。) […]