1. 项目概述:为什么自动寻路是游戏开发的基石
在Unity里做游戏,尤其是涉及到角色移动的,自动寻路这个功能你几乎绕不开。无论是让NPC从A点走到B点,还是让怪物智能地追击玩家,甚至是RTS游戏里一群单位的集体移动,背后都离不开一套稳定高效的寻路系统。我刚入行那会儿,觉得寻路不就是给个目标点,让角色走过去嘛,后来踩的坑多了才明白,这里面门道深得很。一个粗糙的寻路实现,轻则让角色卡在墙角鬼畜抖动,重则直接拖垮游戏性能,让玩家手机发烫、帧率暴跌。
Unity官方提供了一套成熟的导航系统(Navigation System),它基于导航网格(NavMesh)来工作。简单理解,NavMesh就是给游戏场景中“能走”的地面铺上一层看不见的、由三角形构成的“地毯”。寻路算法就在这张“地毯”上计算最短路径。这套系统上手快,对于大多数常规需求来说完全够用,性能也经过了优化。但如果你做的游戏地形特别复杂(比如多层立体结构、大量动态变化的障碍物),或者对移动有特殊要求(比如单位有不同的体型、移动方式),只用官方的基础功能可能就会遇到瓶颈。
所以,这次我们不只讲怎么用Unity的NavMesh组件快速实现一个“能走”的寻路,更要深入进去,聊聊怎么把它用得“更好”、“更稳”。我会结合自己这些年做项目遇到的实际问题,从NavMesh的烘焙配置、代理(Agent)的参数调优,到动态障碍物的处理、离网格链接(Off-Mesh Link)实现跳跃等高级用法,最后再谈谈性能优化和那些官方文档里不会写的“坑”。目标是让你看完后,不仅能实现功能,更能理解背后的原理,知道在不同场景下该如何选择和调整。
2. 核心原理与Unity导航系统拆解
2.1 导航网格(NavMesh)是如何生成的
NavMesh是自动寻路的基石,它的生成过程,我们通常称为“烘焙”(Baking)。这个过程本质上是将场景中静态的、可行走的几何体,转换为一张由凸多边形(通常是三角形)连接而成的网络。Unity的导航系统使用一种体素化(Voxelization)的方法来生成NavMesh。
体素化过程可以这样理解:
- 划定区域 :系统首先根据你设置的烘焙参数,在场景中划定一个三维的包围盒。
- 体素分割 :将这个包围盒分割成无数个微小的立方体(体素)。
- 可通行性判断 :对于每个体素,判断它是否与标记为“可行走”(Walkable)的几何体相交。同时,会根据你设置的“坡度”(Max Slope)和“台阶高度”(Step Height)等参数,判断相邻体素之间是否可达。
- 生成表面 :将所有被判定为“可通行”的体素连接起来,生成一个连续的、简化的表面。这个表面会尽可能贴合原始几何体,但会进行简化以优化寻路计算。
- 多边形划分 :最后,将这个连续的表面划分成一个个凸多边形(三角形),形成最终的导航网格。
注意 :烘焙只针对静态(Static)物体。确保你的地面、墙壁等不可移动的障碍物勾选了
Static标志,并且在Navigation窗口的Object标签页中,将其Navigation Area设置为Walkable。非静态物体或标记为Not Walkable的物体不会参与NavMesh生成,但可以通过其他方式(如NavMesh障碍物)影响寻路。
烘焙参数详解(Navigation Window -> Bake 标签页):
- Agent Radius(代理半径) :这是最重要的参数之一。它决定了NavMesh边缘与障碍物之间的距离。想象一下,你的角色是有“体积”的,这个半径就是角色的“身体”半径。设置过小,角色会紧贴墙壁走,甚至卡进墙角;设置过大,狭窄的通道会被认为“不可通行”。通常,这个值应略大于你角色碰撞体的半径。
- Agent Height(代理高度) :角色能通过的最低空间高度。低于此高度的天花板或门洞,在NavMesh上会被挖空。
- Max Slope(最大坡度) :角色能爬上的最大斜坡角度(单位:度)。超过这个角度的斜坡会被视为“不可行走”(如墙壁)。
-
Step Height(台阶高度)
:角色可以一步迈上去的最大高度差。这是实现“上台阶”效果的关键。注意,它处理的是垂直方向的高度差,对于连续斜坡,则由
Max Slope控制。 -
Drop Height(下落高度)
和
Jump Distance(跳跃距离)
:这两个参数与
Off-Mesh Link(离网格链接)的自动生成有关,用于处理断层地形。
2.2 NavMesh Agent:寻路的执行者
生成了“地图”(NavMesh)后,就需要一个“导航员”来使用它,这就是
NavMeshAgent
组件。你把它挂到需要自动寻路的游戏对象(如角色)上,它就能自动处理路径寻找和移动。
NavMeshAgent的核心参数与工作流程:
-
路径请求(Path Request)
:当你调用
agent.SetDestination(targetPosition)时,Agent会向导航系统请求一条从当前位置到目标位置的路径。 -
路径查找(Path Finding)
:导航系统使用A
算法(A-Star Algorithm)在NavMesh的三角形网络上进行搜索。A
算法是一种启发式搜索算法,它通过评估每个潜在节点的代价(从起点到该点的实际代价
g+ 从该点到终点的预估代价h)来高效地找到最短路径。Unity内部对此做了高度优化。 - 路径平滑与拐角切割(Funnel Algorithm) :A*算法找到的路径是由一系列三角形中心点连接而成的“通道”。直接让角色沿着这些点走,路径会显得很生硬,贴着通道内壁。Unity会使用“漏斗算法”(Funnel Algorithm)对路径进行平滑处理,计算出贴着通道边缘的最短平滑路径,并在拐角处进行切割,使移动更自然。
-
移动控制(Steering & Avoidance)
:计算好路径后,Agent会根据
Speed、Angular Speed、Acceleration等参数,通过内部控制逻辑(如PID控制器)来驱动角色的位置和旋转,使其沿着路径移动。同时,如果开启了Obstacle Avoidance(避障),多个Agent之间会进行简单的局部避让,防止完全重叠。
关键参数调优心得:
-
Speed / Angular Speed / Acceleration
:不要只想着调高
Speed让角色跑得快。过高的速度搭配不合理的Angular Speed(角速度)和Acceleration(加速度),会导致角色在拐弯时“打滑”或冲出路径。我通常的做法是,让角速度和加速度与速度保持一个合理的比例。例如,一个高速移动的单位,其角速度和加速度也应相对较高,才能快速响应方向变化。 - Stopping Distance(停止距离) :这个值决定了Agent在距离目标点多远时开始减速并最终停下。对于远程攻击单位,可以设置一个较大的值,使其在安全距离外停下;对于近战单位,则设置一个很小的值(如0.1),以便贴近目标。
- Auto Braking(自动制动) :勾选后,Agent在接近目的地时会自动减速,移动更平滑。如果取消勾选,Agent会以恒定速度冲向目标点,然后在到达时瞬间停止,这适用于需要快速响应的RTS单位。
3. 从零开始:一个完整的自动寻路实现流程
3.1 场景准备与导航网格烘焙
我们从一个空场景开始,搭建一个简单的寻路测试环境。
-
创建地形
:在场景中创建一些基本的几何体作为地面和障碍物。例如,创建一个
Plane作为地面,再创建几个Cube作为墙壁和障碍物。 -
设置静态与导航区域
:
-
选中所有作为地面和静态障碍物的物体,在Inspector窗口右上角,勾选
Static下拉菜单中的Navigation Static。这告诉Unity这些物体会参与导航网格计算。 -
打开
Window -> AI -> Navigation窗口。 -
切换到
Objects标签页。在这里,你可以为每个物体精细地设置导航区域。通常,地面我们设置为Walkable,而墙壁则设置为Not Walkable。你还可以创建自定义区域(如Water,Mud),并为它们设置不同的移动代价(Cost),让Agent倾向于或避免走某些路。
-
选中所有作为地面和静态障碍物的物体,在Inspector窗口右上角,勾选
-
烘焙导航网格
:
-
切换到
Navigation窗口的Bake标签页。 -
根据你的角色模型大小,设置
Agent Radius(例如0.5)、Agent Height(例如2.0)、Max Slope(例如45)、Step Height(例如0.3)。初次可以先用默认值。 -
点击右下角的
Bake按钮。烘焙完成后,你会在Scene视图中看到场景地面上覆盖了一层蓝色的网格,这就是生成的NavMesh。蓝色的区域是可行走的。
-
切换到
实操踩坑记录:烘焙后NavMesh不完整或奇怪?
- 问题 :地面是蓝色的,但墙壁侧面甚至顶部也有蓝色网格。
-
原因
:你的墙壁物体可能没有正确设置为
Not Walkable,或者Agent Height设置过大,系统认为角色可以从墙壁的“顶部”走过。 -
解决
:检查墙壁物体的
Navigation Area设置。确保复杂模型(如楼梯、斜坡)的碰撞体准确,有时需要手动放置NavMesh Modifier组件来覆盖其某些面为Not Walkable。
3.2 创建可寻路的角色(NavMesh Agent)
-
创建角色
:在场景中创建一个
Capsule(胶囊体)或你的角色模型,命名为Player或NPC。 -
添加组件
:选中该对象,在Inspector中点击
Add Component,搜索并添加Nav Mesh Agent组件。 -
配置Agent参数
:根据你的游戏设计调整参数。例如:
-
Speed: 5 -
Angular Speed: 360 -
Acceleration: 8 -
Stopping Distance: 0.5 -
Auto Braking: True -
Radius: 这个值最好与烘焙时设置的Agent Radius一致或略小,以确保角色能在生成的通道内通行。
-
-
编写基础移动脚本
:创建一个新的C#脚本,命名为
SimpleAgentController,挂载到角色上。
using UnityEngine;
using UnityEngine.AI; // 引入AI命名空间
public class SimpleAgentController : MonoBehaviour
{
private NavMeshAgent agent;
private Camera mainCamera;
void Start()
{
// 获取组件引用
agent = GetComponent<NavMeshAgent>();
mainCamera = Camera.main; // 假设主相机已标记为MainCamera
}
void Update()
{
// 示例1:鼠标点击移动(常用于RPG、RTS)
if (Input.GetMouseButtonDown(0)) // 监听鼠标左键点击
{
// 从鼠标位置发射一条射线到场景中
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
// 如果射线击中了NavMesh所在的层(例如地面层)
if (Physics.Raycast(ray, out hit))
{
// 关键:检查点击的点是否在NavMesh上
NavMeshHit navHit;
if (NavMesh.SamplePosition(hit.point, out navHit, 1.0f, NavMesh.AllAreas))
{
// 设置目标点
agent.SetDestination(navHit.position);
}
}
}
// 示例2:自动追踪目标(常用于怪物AI)
// 假设有一个公共变量public Transform target;
// if (target != null) {
// agent.SetDestination(target.position);
// }
}
}
代码关键点解析:
-
NavMesh.SamplePosition:这是一个非常重要的方法。鼠标点击获取的世界坐标点(hit.point)不一定精确落在NavMesh的三角形上。SamplePosition方法会在该点附近(第三个参数是搜索距离)寻找最近的、有效的NavMesh位置。这能有效防止因点击到微小缝隙或NavMesh边缘而导致寻路失败。 -
agent.SetDestination():这是启动寻路的核心调用。一旦设置,NavMeshAgent组件就会在后台自动计算路径并控制移动,你不需要在Update中手动更新位置。
3.3 实现动态障碍物与角色避障
静态环境没问题了,但游戏世界是动态的。比如一个箱子被推开,一扇门被关上,或者一群单位互相拥挤。这就需要动态障碍物。
-
NavMesh Obstacle组件
:这是处理动态障碍物的主要工具。给任何需要在运行时阻挡寻路的物体(如可移动的箱子、临时关闭的门)添加
NavMesh Obstacle组件。 -
配置模式
:
-
Carve(雕刻)
:这是最常用的模式。勾选
Carve后,该障碍物会在NavMesh上“挖”出一个洞,Agent在寻路时会完全绕开这个区域。适用于长期或永久性阻挡。 -
非Carve模式
:不勾选
Carve,障碍物仅影响Agent的避障逻辑,不会修改NavMesh。Agent的路径规划可能仍会穿过该区域,但在靠近时会尝试局部绕开。适用于短暂、移动的障碍,或当Carve性能开销过大时。
-
Carve(雕刻)
:这是最常用的模式。勾选
-
形状与移动
:
NavMesh Obstacle支持Box、Capsule、Mesh等形状。对于会移动的障碍物(如被推的箱子),确保其NavMesh Obstacle组件的位置和旋转在移动时被正确更新。你可以通过脚本控制其enabled属性来动态启用/禁用障碍效果。
避障(Obstacle Avoidance)调优:
在
NavMeshAgent
组件上,有一个
Obstacle Avoidance
属性。它有多个质量等级(
None
,
Low
,
Medium
,
Good
,
High
)。等级越高,避障效果越自然,但CPU开销也越大。
-
心得
:对于大量同屏的低优先级单位(如一群小兵),可以设置为
Low或None,依靠路径规划来避免碰撞,牺牲一点局部自然度换取性能。 -
对于重要的主角或英雄单位
,可以设置为
Good或High,使其移动更智能。 -
注意
:避障主要处理的是
NavMeshAgent之间的相互避让,以及与非Carve模式的NavMeshObstacle的交互。对于开启了Carve的障碍物,Agent在路径规划阶段就会绕开,不需要依赖避障。
4. 高级应用与性能优化实战
4.1 离网格链接(Off-Mesh Link):实现跳跃与下落
NavMesh是连续的表面,但游戏里经常需要跳跃过沟壑、从高处跳下、或者通过梯子攀爬。这些动作连接了两个不相连的NavMesh区域,就需要用到
Off-Mesh Link
。
手动创建Off-Mesh Link:
- 创建两个空游戏对象,分别放在跳跃的起点和终点(例如,悬崖边和对面)。
-
选中起点对象,添加
Off Mesh Link组件。 -
将终点对象拖拽到组件的
End字段。 -
调整参数:
-
Cost Override:覆盖通过此链接的路径代价。默认是自动计算两点距离,你可以设置一个固定值。如果想让AI更倾向于走某条“桥”而不是跳崖,可以把跳崖的链接代价设高。 -
Bi-Directional:是否双向通行。从A跳到B,是否也能从B跳回A。 -
Activated:是否激活。 -
Auto Update Position:如果起点或终点对象会移动,请勾选。
-
让Agent使用Off-Mesh Link:
NavMeshAgent
默认就会使用场景中的
Off-Mesh Link
。当路径计算发现需要连接两个分离的NavMesh区域,并且存在一个有效的链接时,Agent会自动采用。
自定义穿越行为: 默认情况下,Agent会以“传送”的方式瞬间从起点移动到终点。这显然不适合跳跃动画。我们需要自定义这个过程。
public class JumpLinkHandler : MonoBehaviour
{
public AnimationCurve jumpCurve; // 在Inspector中绘制一个抛物线曲线
public float jumpDuration = 0.5f;
private NavMeshAgent agent;
private bool isJumping = false;
private float jumpTimer = 0f;
private Vector3 startPos;
private Vector3 endPos;
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
void Update()
{
if (isJumping)
{
// 禁用Agent的自动移动,由我们手动控制
agent.isStopped = true;
jumpTimer += Time.deltaTime;
float ratio = jumpTimer / jumpDuration;
if (ratio < 1.0f)
{
// 根据曲线在起点和终点之间插值
Vector3 lerpedPos = Vector3.Lerp(startPos, endPos, ratio);
// 应用曲线高度
lerpedPos.y += jumpCurve.Evaluate(ratio);
transform.position = lerpedPos;
}
else
{
// 跳跃结束,恢复Agent控制
transform.position = endPos;
isJumping = false;
agent.isStopped = false;
agent.CompleteOffMeshLink(); // 关键:告诉Agent链接已完成
}
}
else if (agent.isOnOffMeshLink) // 检测到Agent正在使用Off-Mesh Link
{
// 获取链接的起点和终点
OffMeshLinkData data = agent.currentOffMeshLinkData;
startPos = transform.position;
endPos = data.endPos;
// 如果是我们自定义的跳跃链接(可以通过比较GameObject判断)
if (data.linkType == OffMeshLinkType.LinkTypeJumpAcross ||
data.offMeshLink != null && data.offMeshLink.CompareTag("JumpLink"))
{
// 开始自定义跳跃流程
isJumping = true;
jumpTimer = 0f;
}
else
{
// 对于其他类型(如梯子),可以用其他逻辑,或者直接让Agent自动完成
agent.CompleteOffMeshLink();
}
}
}
}
4.2 分层导航与区域代价(Area Cost)
游戏里可能有不同的地形,比如平坦的道路、泥泞的沼泽、浅水滩。我们希望AI能优先走道路,尽量避开沼泽。
-
定义导航区域
:在
Navigation窗口的Areas标签页,你可以看到默认的区域(Walkable, Not Walkable, Jump)。你可以点击+号添加自定义区域,如Mud、Road。 -
为物体指定区域
:在
Objects标签页,选中你的沼泽地面物体,将其Navigation Area从Walkable改为Mud。同理,将道路物体的区域改为Road。 -
设置区域代价
:回到
Areas标签页,找到Mud区域,将其Cost值调高,例如设为5(Walkable的代价是1)。这意味着在寻路算法看来,走过1单位长度的沼泽,相当于走过5单位长度的普通地面。AI在计算最短路径时,自然会倾向于选择代价更低(即使几何距离稍长)的道路。 -
指定Agent可通行区域
:在
NavMeshAgent组件上,有一个Area Mask属性。它是一个位掩码,用于指定该Agent可以走哪些区域。默认是所有可走区域(Walkable, Jump, 以及你自定义的区域)。如果你想让某个单位完全不能进入沼泽,可以取消勾选Mud区域。
4.3 性能优化:应对大量单位的寻路压力
当场景中有成百上千个需要寻路的单位时(如RTS游戏),性能会成为大问题。每一次
SetDestination
调用都可能触发一次A*搜索。
优化策略:
- 降低寻路频率 :不要每帧都为所有单位寻路。对于追击玩家的怪物,可以每0.5秒或1秒更新一次目标位置。对于移动到固定点的单位,只在目标改变时寻路。
-
使用代理(Agent)的移动状态
:在调用
SetDestination前,先检查agent.pathPending(是否正在计算路径)和agent.hasPath(是否已有路径)。避免在路径计算未完成时重复请求。 - 简化NavMesh :在保证游戏性的前提下,尽可能简化场景的碰撞体。复杂的Mesh碰撞体会导致NavMesh烘焙出的三角形数量激增,增加寻路计算复杂度。对于大型平坦地面,使用简单的Box或Plane碰撞体。
- 分层烘焙与局部避障 :对于超大地图,可以将其分割成多个NavMesh,动态加载。对于大量单位的局部避障,可以考虑使用更轻量级的解决方案,如基于RVO(Reciprocal Velocity Obstacles)的第三方插件,或者自己实现简单的群体移动算法(如Boids),仅对少数领队单位使用完整的NavMesh寻路。
-
异步寻路与路径队列
:Unity的
NavMeshAgent.SetDestination在后台是异步计算的,但大量同时请求仍会造成卡顿。可以自己实现一个简单的寻路管理器,将寻路请求放入队列,每帧只处理有限数量的请求。 -
对象池管理NavMeshAgent
:对于频繁生成和销毁的单位(如发射的炮弹、召唤物),不要频繁地AddComponent和Destroy
NavMeshAgent组件。使用对象池技术,复用已经存在的Agent组件,只是启用/禁用它们,可以显著减少GC(垃圾回收)压力。
5. 常见问题排查与实战技巧
5.1 寻路失败与路径不合理的排查流程
当你的角色死活不走,或者走出一条匪夷所思的路径时,可以按照以下步骤排查:
-
检查目标点是否有效
:使用
NavMesh.SamplePosition了吗?目标点很可能不在NavMesh上。在代码里Debug.DrawLine或Gizmos画出目标点,在Scene视图里确认。 -
检查NavMesh烘焙是否覆盖了相关区域
:在Scene视图的
Navigation显示模式下(通常在下拉菜单选择Navigation),查看起点和终点之间是否有连续的蓝色NavMesh连接。中间是否有缺口?障碍物的Carve是否挖掉了必经之路? -
检查Agent参数
:
Agent Radius是否大于了通道的宽度?Step Height是否小于需要迈上的台阶高度?Area Mask是否包含了目标点所在区域? -
检查动态障碍物
:是否有
NavMeshObstacle在路径上,并且其Carve属性被意外启用,临时挖断了路径? -
检查Off-Mesh Link
:路径是否试图通过一个未激活、方向错误或代价极高的
Off-Mesh Link? -
使用
NavMeshAgent.pathStatus:在代码中打印agent.pathStatus,它会返回PathComplete、PathPartial(只找到部分路径)、PathInvalid等状态,这是最直接的错误指示。
5.2 NavMeshAgent的典型“怪异”行为及解决
-
问题:角色在拐角处“抖动”或“卡住”。
-
原因
:通常是路径的拐角点过于尖锐,或者Agent的
Radius与通道宽度匹配过于极限,导致其在拐角处不断进行微小的方向调整。 -
解决
:
-
适当减小
Agent Radius或增大烘焙时的Agent Radius,留出更多余量。 -
调高
NavMeshAgent的Angular Speed,使其转向更敏捷。 -
检查烘焙的NavMesh在拐角处是否平滑,有时手动放置
NavMesh Modifier Volume将尖锐内角标记为Not Walkable可以强制生成更圆滑的路径。
-
适当减小
-
原因
:通常是路径的拐角点过于尖锐,或者Agent的
-
问题:多个单位挤在一起,完全阻塞通道。
-
原因
:
Obstacle Avoidance能力有限,或者单位密度过高。 -
解决
:
-
降低单位的
Agent Radius,物理上允许更紧密的排列。 -
实现一个简单的“排队”或“分散”逻辑。当检测到前方路径被阻塞(例如
agent.velocity.magnitude接近0但agent.remainingDistance还很大),可以让部分单位短暂地寻找附近的其他点停顿,或者轻微修改目标点使其分散。 - 对于RTS游戏,常见的做法是使用“群体移动”(Formation Movement),只为一个编队计算一条主路径,编队内的个体根据队形进行相对移动,这比每个单位都独立寻路高效和有序得多。
-
降低单位的
-
原因
:
-
问题:角色在斜坡或不平整地面上移动时上下颠簸。
-
原因
:
NavMeshAgent默认会严格贴合NavMesh表面移动,而NavMesh是根据场景几何体生成的,会保留地面的起伏。 -
解决
:如果你希望角色在斜坡上也能平滑移动(例如,第三人称控制器),可能需要关闭Agent对垂直方向的控制。将
NavMeshAgent的updatePosition和updateRotation设置为false,然后只使用agent.desiredVelocity(Agent期望的水平移动方向向量)来控制你自己的角色控制器(如CharacterController)的移动,而垂直方向(重力、跳跃)由你自己的逻辑处理。这实现了寻路逻辑与移动表现的解耦。
-
原因
:
5.3 在服务器端或非主线程进行寻路计算
对于大型多人在线游戏(MMO),寻路计算通常需要在服务器端进行以保证公平性和安全性。Unity的
NavMesh
和
NavMeshAgent
是依赖于场景和物理引擎的,在纯服务器环境(无图形界面和物理模拟)中直接使用较为困难。
常见解决方案:
-
使用第三方纯C#寻路库
:例如
A* Pathfinding Project的付费版本提供了独立的寻路引擎,可以脱离Unity引擎运行,将烘焙好的NavMesh数据导出并在服务器端使用。或者使用开源的Recast & Detour库(Unity的NavMesh系统正是基于此),自己集成到服务器程序中。 - 简化网格与自定义算法 :对于服务器,可以使用比客户端更简化的导航网格(甚至是用二维网格代替),并使用经典的A*算法进行寻路。这需要自己维护一套服务器端的场景可行走数据。
- 客户端验证与服务器仲裁 :对于非强实时性的游戏,也可以由客户端计算路径并发送给服务器,服务器只做简单的验证(如路径长度是否合理、是否穿过不可通行区域)。对于关键移动(如PK、副本),则由服务器计算或最终仲裁。
实现自动寻路只是第一步,让它运行得高效、稳定、符合游戏设计,才是真正考验功力的地方。我最深的体会是,不要盲目相信默认参数,一定要根据自己游戏的具体场景(单位大小、地形复杂度、同屏数量)去做细致的测试和调优。多利用Scene视图的
Navigation
显示模式来可视化调试,多打印
NavMeshAgent
的各种状态信息(
pathStatus
,
remainingDistance
,
hasPath
等),数据不会骗人。遇到复杂地形,善用
NavMesh Modifier
和
Off-Mesh Link
来引导AI,而不是指望算法能理解所有设计意图。最后,性能优化要早做规划,特别是同屏单位数量多的项目,寻路很容易成为性能热点。

2852

被折叠的 条评论
为什么被折叠?



