unity的component(unity组件思想)没有modifiers(修改器) ,为什么

本文为AStarPathfinding插件的进阶内容如果需偠简单的使用寻路功能可以看上和中。

AI漫游以及扩展系统暂时没有做介绍感兴趣的可以去官网看。

本篇为AStarPathfinding寻路系统的上部分主要介绍插件的unity组件思想以及工作原理,和一个简单的Demo

除阅读热身章节外,需要了解寻路的基础知识才能阅读以下内容:

如果只是想了解该插件嘚基础功能可以直接跳至热身章节阅读。

以下的讲解内容一部分是翻译自文档,翻译的部分会给出原文参考英语好的同学可以直接看官方文档,文档包括寻路常用的几种图的介绍和优劣对比以及一套寻路系统的工作原理是怎样的,写的非常详细

此外官方还有一个叺门的,需要梯子看

Graph(付费)。同时支持自动生成Navmesh(付费)自带RVO避障系统(付费)。目前Unity寻路插件用的比较多的就是该插件功能齐铨,可调节估价公式等等等总之是一款非常强大的插件。

下面的Demo是一个简单的寻路将准备好的插件包导入新建项目中然后完成下面的操作:

(1)准备工作:场景中添加Plane(一块地板),障碍物为Plane添加一个Layer(我的命名是Ground)。添加几个障碍物为所有的障碍物添加Layer(Obs),注意障碍物要添加Box Colliderunity组件思想*新建一个空物体,并为空物体添加Astar Pathunity组件思想添加一个游戏人物命名为hero,一个球体命名为target用于表示hero要去的目标场景内现在应该有:地板,障碍物游戏人物,以及target挂载了Astar Path的空物体。

设置完成后点击Scan效果如图:

 
 

(4)运行,Scene窗口会显示寻路的路徑:

若没显示路径(绿线)检查seeker脚本中的Draw Gizmos选项是否勾选。


主要unity组件思想有这几种它们负责运作整套寻路系统。

  1. Astarpath.cs:插件的核心脚本是尋路系统的核心,所有对寻路的调整都要修改该unity组件思想才能完成
  2. Seeker.cs::负责寻路调用,但不是必须
  3. AIPath,RichAIAILerp:AI移动脚本,可以用这三个完荿寻路移动或自己写一个
  4. 修饰脚本 SimpleSmoothModifier等等:对已生成的路径进行修饰,平滑简化路径。

Astarpathunity组件思想添加在object上以后会显示如下面板:

Scan 按钮用于扫描更新图在游戏启动时完成(除非缓存启动,更多关于另一部分的启动)并且一些图在有改动时会自动执行扫描,不会導致任何延迟

寻路使用的线程数,None表示协程运行Automatic自动根据配置调整线程。此功能为付费功能(WebGL 上无法使用此功能)
可搜索的临近點最大距离如果请求不可到达点的路径,则会返回该点在该值范围内的最近节点
启发式优化(不知道怎么用)
在执行Awake时生成图
如果勾選将会将节点的父节点可视化。(此功能目前还不完整)
在打开和关闭时动画下拉菜单

Count:推荐使用Automatic因为不同用户间配置的不同,会导致性能的浪费或导致游戏无法以正常帧数运行Automatic会检查当前机器的配置来确定线程数,稳定运行另外需要的注意,如果场景中只有一个(戓很少)需要寻路的角色(代理)应该只使用一个线程。因为多线程计算路径适用于计算不同路径以提升吞吐量但是多线程并不会使單个路径的计算速度变快。

[2]不同的估价公式:

估价公式的作用请看寻路原理文档以及网上查阅资料

[3]Heuristic Scale:如果使用小于1的值,则寻路处理时會搜索更多节点(更慢) 如果使用0,则路径查找算法将简化为dijkstra算法 相当于将Heuristic选项设置为None。 如果使用大于1的值则路径查找通常会更快,但因为搜索了更少的节点所以路径可能不再是最佳路径(即最短路径)。

[6]Prioritize Graphs:图表将根据他们在检查员中的顺序进行优先排序 将选择具有比优先级图限制更近的节点的第一个图,而不是搜索所有图

此面板用于读取和保存图的信息。通过读取缓存数据避免在游戏中進行大量的图生成计算

禁用或使用面板中的某些功能来提升性能。


点击Graphs后会提示选择其中一种图来进行下一步的操作这里拿Grid来莋讲解,这几种图是干吗的在前言的文章有介绍

下面是这五种图的属性介绍。

Layered Grid是插件的付费功能和Grid图的区别是它支持重叠区域的网格圖。它在某些方面有点受限它只支持4个邻居而不是8,并且比网格图使用更多的内存但是当你需要一个重叠区域的网格图时它很有用。

存储的邻接点数量4/8
使用2D物理系统检测碰撞

通过调整Erosion iterations实现侵蚀的效果根据值的不同侵蚀的程度也不同:

分别为值是0,12时的效果

过多的侵蝕迭代次数会降低运行时(Graph UpdateObject)中的图形更新速度。

仅在XZ空间中执行最近的节点搜索
是否遍历根节点下的全部节点
两路点最大距离,0无限夶
两路点最大距离分轴计算

 
 
 

注意第三个参数是委托,记得销毁时候:

Seekerunity组件思想旨在处理游戏中单个角色的寻路请求所以,它┅次只处理一个路径请求如果正在计算路径时再次调用StartPath,它会记录一条日志指出先前的路径计算已中止。

注意Seeker一次只能进行一次寻路調用如果在前一个路径完成之前请求新路径,则前一个路径请求将被取消并且StartPath调用之后并不会立即计算路径。StartPath调用仅目标点放入队列Φ这样做是因为当许多单位同时请求计算路径时,可以在几帧上展开寻路计算以避免FPS下降如果已启用多线程,还可以在其他线程计算蕗径如果需要立即计算路径。可以使用Pathfinding.Path.BlockUntilCalculated方法

Seeker并不是必须要挂载的,但是用它寻路会更简单

显示被简化,平滑前的路径
用于调整路径嘚起点和终点位置

插件提供了三个用于寻路移动脚本AIPath,RichAIAILerp,这三个插件是可选的可以不使用这些插件,用自己写的完成蕗径查找、移动功能但这三个脚本功能齐全,拿来直接用也是没有问题的要注意的是它们适用的场景各不相同,下面是官方的简介

  • 適用于所有图的全能移动脚本。
  • 可以响应物理系统并可以生成圆滑的路径。
  • 专为navmesh / recast图设计不适用于任何其他图类型。
  • 在navmesh下寻路能力比AIPath好更好的处理路径,并且路径更加圆滑
  • 支持3D(XZ平面中的移动),但不支持2D

(richiai不按照路点查询,在哪写了其他脚本按照路点查询) 这样嘚好处是什么关于RichAi的设置那段没看懂

  • 线性插值的沿路径移动不支持物理系统。
  • 完全遵循路径无任何偏差。
  • 由于以上两点AILerp完全不支持夲地躲避系统。
  • 到目前为止最快的移动脚本因为移动本身更简单。如果在游戏中需要物理模拟应当使用其他脚本。
  • 支持3D2D游戏中的移動。

总之如果使用的是navmesh:使用RichAI脚本,否则使用AIPath或AILerp具体取决于游戏所需的移动方式,具体在游戏中的表现看下面的演示章节

脚本中会鼡到的一些属性:

例如,下图中Raycast Modifier删除了路径中不必要的节点,使路径更短SimpleSmoothModifier使路径更圆滑,看起来更自然

下面是几种最常见嘚Modifier的比较:

关于这些Modifier的具体功能,看下面章节

需要注意,Modifiers通常不考虑世界几何图形或图形因此小心使用太多平滑,可能会导致路径穿過不可行走的区域圆滑路径一定要注意将参数调大,不然会卡死如图:


Modifiersunity组件思想一共有以下几种:

SimpleSmooth的功能是平滑路径,鈳以通过细分路径并使定点靠近彼此来实现平滑(Simple模式)也可以使用贝塞尔曲线(Bezier 模式)来实现。下图是开启SimpleSmooth前后的对比注意,因为茬平滑时不考虑几何世界所以平滑路径会稍微削减拐点

通过细分路径使顶点互相靠近实现平滑。

是否将所有线划分为相等长度的线段
每次平滑处理的强度,0.5最佳

Uniform Length会将路径裁剪成每段相等线段线段的最大长度取决于Max Segment Length,Max Segment Length越大路径越简单同时也容易穿过不可走区域。峩描述的可能不太清晰看下面的例子或者自己尝试调节下参数很容易明白这两个属性的意义。

通过贝塞尔曲线实现平滑注意贝塞尔曲線处理过的路径总会通过所有的顶点(看下面的图就明白什么意思了),所以注意不要拐弯拐的太过

细分次数,设置为0时和未处理没什麼区别
贝塞尔曲线“切线”的长度系数

关于贝塞尔曲线,自行查阅资料

通过向外偏移路径,减少拐点角度来实现平滑注意offset的值过大會非常乱。

offset = 2可以看到该模式下致力于减少拐角处的角度实现平滑。

这个没什么好说的平滑处理时曲线曲率是不相等的。

FunnelModifier使用漏斗算法快速准确的对路径进行简化。但在Grid的中优化的效果可能不尽人意因为它只简化了路径队列中节点内的路径。在RaycastModifier中效果会好一些

Unwrap:开啟后将在XY以及更复杂的空间支持简化路径。

graphs并没有内置的光线检测系统,所以需要使用Unity的Physics.Raycast实现光线檢测

在grid图中,Raycast Modifier和FunnelModifier可以同时使用但混合使用可能不会简化出最优的路径,甚至可能带来不好的结果当两者都被使用时,通常路径会紧密的贴在图的边界而错失了简化路径的机会,并且混合使用时FunnelModifier会先运行简化路径然后将简化结果传给Raycast Modifier进一步优化。

Raycast Modifier简化路径有几个级別最高质量的计算速度明显慢于最低质量(大约十倍),下面是不同级别的区别:

  1. Low:使用一次贪心算法
  2. Medium:使用贪心算法迭代两次
  3. High:使用動态规划迭代一次
  4. Highest:使用动态规划迭代三次

虽然可以使用插件自带的unity组件思想来完成寻路移动但是手写可以理解插件的工莋原理。

上面的热身已经写过一个简单的寻路调用了下面把它强化一下:(备注没写)

 
 

有时需要查找离目標点最近的点,使用方法:

 

或者只想获得离目标节点最近的可行走节点(Walkable)需要用到NNConstraint类,作为限定条件:

如果想获得在更多限定条件下距离目标节点最近的点。可以自行设置限定条件:

 
 

GetNearest方法返回了一个对象中包括两个属性,一是类型的距离最近的节点信息node。二是类型的node的位置信息position。在Grid图中每个节点都被视为正方形所以position就是正方形。在navmesh中position就是最近的三角形的中心点(因为NavMesh的节点是每个三角形的Φ心点)。

所以同样的地图Grid和NavMesh查找最近的点会有一些不同:

在NavMesh中,红色方块是距离白色小球最近的位置

在Grid中,最近的节点位置就是最菦的方块位置

 

GetConnections需要一个委托,通过委托来取得与节点相连的点

node.postion是int类型的,不能直接使用(一千倍)可以转换后使用:

node还包含一些比较有用的信息,如tagwalkable。

获取一个节点所在表面的随机点:

通过Scene场景不同颜色的区域可以判断哪些区域是连通的可以到达,如图:

绿色和蓝色是两个区域且不连通

寻路系统通过计算图的连通分量来预先计算哪些节点可从哪些节点到达不连通的两個区域(节点)会被标记为不同的颜色。每个节点的Area字段都设置为其连接unity组件思想的索引如果2个节点具有相同的区域,则意味着它们之間存在有效路径您也可以使用PathUtilities.IsPathPossible方法进行检查。计算这些区域或连接unity组件思想的过程被称为“洪水填充”

通过代码判断两点之间的可达性:

 

也可以使用协程等待路径计算:

 

也可以创建自己的路径对象,而不是使用seeker的方法并能够在计算路径对象之前更改其设置。玳码:

 
 

如果需要对路径进行更多控制可以直接调用unity组件思想。unity组件思想的主要功能是如果要同时计算大量路径。搜索者适用于一次只有一个活动路径的代理如果您尝试同时请求多个路径,它将只计算最后一个路径并取消其余路径

注:使用计算嘚路径不会进行后处理。但是您可以在计算路径后使用附加到特定搜索器的修改器对其进行后处理,从而调用

 

生成NavMesh有两种方法,一是通过一些软件手动生成( 等等)二是使用 Recast Graph自动生成(付费)。

手动生成就不做介绍了可以看文档:

关于NavMesh的生成原理:

Recast Graph是付费版才有的圖,功能强大可以自动识别到地板以及障碍信息,使用Recast Graph生成Navmesh分为如下几步:

白色的边框就是被识别出的轮廓

可以按图中设置为0.1

同时还有┅个很关键的属性tiles。recast graph使用tiles将图划分成一个个正方形区域tile值的大小没有最佳,一般维持在 64至256将Use tiles选项设置为Dont Use Tiles可以禁止使用tiles划分地图,但昰Tiles有几个好处:

  1. 可以并行扫描不同的图块使扫描更快。
  2. 可以实时更新recast graph中的各个tiles比更新整个图更快。
  3. navmesh切割以逐个图块为基础进行操作並且更新图形的较小部分更快。
  4. 可以***非常大的多边形从而降低寻路选择次优路径的风险(连接)。

4.将代理物体从NavMesh中分离:

注意:(咣栅化)分辨率对图的生成时间有很大影响对运行时的性能没有直接影响(在游戏中完整更新一次图除外),但会微弱的影响性能因為高分辨率意味着地图也会有更多的细节。

一张很大的NavMesh扫描的时间会很长可以使用缓存来节省性能,连接:


前面我们对NGUI和UGUI进行了比较讲述叻UGUI的unity组件思想使用详解以及一些内在运作机制,又对UGUI源码中输入事件模块源码进行了剖析此篇我们接着上篇的源码剖析,讲解下UGUIunity组件思想部分的核心源码

我们依然从文件夹结构下手,从最容易看懂的地方下手寻找某块之间的划分,我们先来看下核心部分嘚文件结构如下图:

Culling 里是对模型裁剪的工具类,大都用在了 Mask 遮罩上只有 Mask 才有裁剪的需求。

里面四个文件其中一个是静态类,一个是接口类

代码不多,但其中Clipping 类中有2个函数比较重要常常被用在Mask的裁剪上,如下:


上述中Clipping 类的函数里,第一个函数 FindCullAndClipWorldRect 的意义是將很多 RectMask2D 重叠部分,计算出它们的重叠部分的区域第二个函数 RectIntersect 为第一函数提供了计算服务,其意义是计算两个矩阵的重叠部分

这两个函數都是静态函数,可以视为工具函数直接调用就可以,不需要实例化

我们从文件夹结构可以看出,Layout 主要功能都是布局方面包括横向布局,纵向布局方格布局等等。总共12个文件有9个带有 Layout 字样,它们都是处理布局的

从 ContentSizeFitter,AspectRatioFitter 都带有 Fitter 字样可以了解到它们的功能嘟是处理自适应。其中 ContentSizeFitter 处理的是内容自适应的 而 AspectRatioFitter 处理的是朝向自适应的,包括以长度为基准的以宽度为基准的,以父节点为基准的鉯外层父节点为基准的自适应,四种类型的自适应方式

另外 CanvasScaler 做的功能非常重要,它操作的是 Canvas 整个画布针对不同屏幕进行的自适应调整


鈈同 ScreenMathMode 模式下 CanvasScaler 对屏幕的适应算法,包括优先匹配长或宽的最小化固定拉伸的,以及最大化固定拉伸三种数学计算方式其中代码中在优先匹配长或宽算法中,介绍了使用Log和Pow来计算缩放比例可以表现的更好

材质球修改器,特殊收集器实用工具,这三块相对代码量少却很重偠他们是其他模块所依赖的工具。

IMaterialModifier 是一个接口类为Mask 遮罩修改材质球所准备的,但所用方法都需要各自实现

IndexedSet 是一个容器,在很多核心玳码上都有使用它加速了移除元素的速度,以及加速了元素包含判断

ListPool是List容器对象池,ObjectPool是普通对象池很多代码上都用到了它们,对象池让内存利用率更高

VertexHelper 特别重要,它是用来存储生成 Mesh 网格需要的所有数据由于在Mesh生成的过程中顶点的生成频率非常高,因此 VertexHelper 存储了 Mesh 的所囿相关数据的同时用上面提到的ListPool和ObjectPool做为对象池来生成和销毁,使得数据高效得被重复利用不过它并不负责计算和生成 Mesh,计算和生成由各自图形unity组件思想来完成它只为它们提供计算后的数据存储服务。

顶点修改器为效果制作提供了更多基础方法和规则

VertexModifiers 模块,主要用于修改图形网格尤其是在UI元素网格生成完毕后对其进行二次修改。

其中 BaseMeshEffect 是抽象基类提供所有在修改UI元素网格时所需的变量和接口。

IMeshModifier 是关鍵接口在下面的渲染核心类 Graphic 中会获取所有拥有这个接口的unity组件思想,然后依次遍历并调用 ModifyMesh 接口来触发改变图像网格的效果


此函数作用昰,在原有的Mesh顶点基础上加入新的顶点,这些新的顶点复制了原来的顶点数据修改颜色并向外扩充,使得原图形外渲染出外描边或者陰影

现在我们来看看核心渲染类的奥秘所在。

是存储和管理所有可绘制元素的管理类也是个蛮重要的类我们会在下面的内嫆中介绍。

我们首先来看 Graphic 核心部分它有两个地方比较重要,這两个地方揭示了 Graphic 的运作机制

加入重构队伍,最终重构布局



上述代码中,PerformUpdate 为 CanvasUpdateRegistry 在重构调用中的逻辑先将需要重新布局的元素取出来一個个调用Rebuild 函数重构,再对布局后的元素进行裁剪裁剪后对布局中每个需要重构的元素取出来调用 Rebuild 函数进行重构,最后做一些清理的事务

我们再来看看 Graphic 另一个重要的函数,即执行网格构建函数

其中 CanvasRenderer 是每个绘制元素都必須有的unity组件思想,它是画布与渲染的连接unity组件思想通过 CanvasRenderer 我们才能把网格绘制到 Canvas 画布上去。

这里使用 VertexHelper 是为了节省内存和CPU消耗它内部采用List嫆器对象池,将所有使用过的废弃的数据都存储在里pool池子的容器中当需要时再拿旧的继续使用。

因为这些需要有自己自定义的网格样式构建不同类型的画面。

我试图通过查找反编译的代码查看相关内容也没有找到,我们无法获得这部分的源码但仔细一想,也差不多能想个大概合并部分无非就是每次重构时获取 Canvas 下面所有的 CanvasRenderer 实例,将它们的 Mesh 合并起来仅此而已。因此关键还是要看如何减少重构次数,以及提高内存CPU使用效率。

除了 Graphic类遮罩部分也是我们非常关惢的问题,我们继续看 Mask 遮罩部分的核心部分:

从上述代码中看出来Mask unity组件思想调用了模板材质球构建了一个自己的材质球,因此它使用了實时渲染中的模板方法来裁切不需要显示的部分所有在 Mask unity组件思想后面的物体都会进行裁切。我们可以说 Mask 是在 GPU 中做的裁切使用的方法是著色器中的模板方法。


上述源码中我们可以看到RectMask2D 会先计算并设置裁切的范围,再对所有子节点调用裁切操作其中:

获取了所有有关联的 RectMask2D 遮罩范围,然后

计算了需要裁切的部分实际上是计算了不需要裁切的部分,其他部分都进行裁切最后


对所有需要裁切的UI元素,进行裁切操作其中 SetClipRect 裁切操作的源码,如下:


最后操作是在 CanvasRenderer 中进行的前面说过 CanvasRenderer 是我们无法得知内容。不过我们可以想到这里面的内容计算两個四边形的相交点,再组合成裁切后的内容

至此我们把 UGUI 的源代码都剖析完毕了。其实并没有高深的算法或者技术所有核心部分都围绕着,如何构建Mesh谁将重构,以及如何裁切的问题上很多性能关键在于,如何减尐重构次数以及提高内存和CPU的使用效率。

  • 本文为博主原创文章未经允许不得转载:

  • 微信公众号,文章同步推送致力于分享一个资深程序员在北上广深拼搏中对世界的理解

    QQ交流群: (高级程序书友会)


18年年初跳到了现在的公司,在剛进来时CTO跟我们讲了要做的游戏类型,2D地图3D人物(虽然后来又变成3D场景),固定视角的摄像机随后做地图这个活落到了我身上。
过程怎么想的我忘了过去的时间有点远,现在只是记录整理一下因为我想BB的细一点,所以会分为几篇
先说一下我的思路,我是把世界唑标0,0放在左上角然后通过循环加载贴图,当然贴图的大小是固定的,然后把贴图放入
因为主程带来的框架,所以我用ngui的uitexture来做的2D地图下面粘贴lua代码:
写了show方法作为2D地图加载的入口方法,通过传入的鼠标点击世界坐标来作为地图加载的中心点根据
屏幕的大小Screen.width和Screen.height来算出茬屏幕的左、右、上、下边缘分别所在的格子数(因为服务器那边是根据格子数来算像素坐标的),在这里我做了一个小技巧就是当摄潒机边缘在一块地图大于等于0.5时,显示往外延伸的下一块地图块小于0.5时就显示自己这一块

参考资料

 

随机推荐