赞
踩
目录
②路径更新与计算SetDestination()与Move()
首先我们找到Window-AI-Navigation,点击之后会在Unity中出现Navigation的面板,而这就是我们今天学习的关键面板了。见图1-1,1-2
图1-1 AI-Navgation
图1-2 Navigation面板
打开面板之后,我们就可以选择想要烘焙出路径的区块,将其设置为静态Static *不设置无法烘焙成功*,烘焙成功后应有蓝色网格出现见下图1-3,1-4
图1-3 Static设置
图1-4 成功图
好的有了场景的导航网格,我们现在还需要给想要实现AI移动的物体添加NavMeshAgent导航网格代理,我这里是随便找了个胶囊体当做需要实现AI逻辑的NPC/主角。有了代理我们就可以开始编写脚本了。见下图1-5
图1-5 NavMeshAgent的添加
我们新建脚本NaviAI,在头部调用UnityEngine.AI,首先是比较关键的移动逻辑:
public bool SetDestination (Vector3 target);
设置或更新目标,从而触发新路径计算。请注意,路径可能在几帧之后才可用。 计算路径时,pathPending 将为 true。 如果有效路径可用,代理将恢复移动。
我这里编写了一个实现点击鼠标右键NPC就会走向目标点的逻辑,见以下代码段
- public class NaviAI : MonoBehaviour
- {
- [SerializeField]
- private GameObject target; //目标点
-
- private NavMeshAgent thisNavAgent; //导航网格代理
-
- void Start()
- {
- thisNavAgent = this.GetComponent<NavMeshAgent>();
- }
-
- void Update()
- {
- NavPath();
- }
-
- /// <summary>
- /// 导航路径
- /// </summary>
- void NavPath() {
- if (Input.GetMouseButtonDown(1)) {
- MouseMoveToSetDestination();
- }
- }
-
- void MouseMoveToSetDestination()
- {
- RaycastHit hit;
- NavMeshHit navHit;
- NavMeshPath navPath = new NavMeshPath(); //应当初始化
- Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
-
- //创建射线有碰撞,且物体到碰撞的坐标位置上没有障碍物的话
- if (Physics.Raycast(ray, out hit)) {
- thisNavAgent.SetDestination(hit.point);
- }
- }
-
- }
同时注意,因为涉及到射线碰撞逻辑所以给地面加上刚体是必要的,具体射线检测的总结可以看我前几期博客,将脚本挂载与设置了网格代理的物体上,运行测试。见下图2-1,2-2
图2-1 地面刚体
图2-2 鼠标+SetDestination()
public void Move (Vector3 offset)
将相对移动应用于当前位置。
如果代理有路径,路径将被调整。
我们把SetDestination(hit.point)改为Move(hit.point),运行看一下效果,见下图2-3,2-4
图2-3 Move()
图 2-4 鼠标+Move()
public bool Raycast (Vector3 targetPosition, out AI.NavMeshHit hit);
在导航网格中跟踪一条直线路径到达目标位置,而无需移动代理。此函数遵循代理位置和指定目标位置之间的"射线"的路径。 如果在线路中遇到 障碍物,则返回 true,且障碍对象的位置和详细信息存储 在 hit
参数中。 这可用于检查角色和目标对象之间是否存在 清晰的镜头或视线。 此函数相较类似的 Physics.Raycast 更加可取,因为线路跟踪是使用导航网格以一种更加简单的方式完成的, 并且处理开销较低。
而我们在原来判断逻辑上,加上路段遮挡判断就行了,注意这里的navHit在前面代码就有定义,见以下代码段
- //创建射线有碰撞,且物体到碰撞的坐标位置上没有障碍物的话
- if (Physics.Raycast(ray, out hit) && !thisNavAgent.Raycast(hit.point, out navHit)) {
- thisNavAgent.SetDestination(hit.point);
- }
图3-1 加上障碍物
我们运行测试,可以看鼠标点击后所返回的hit.point位置与当前物体形成的路径被遮挡的时候*小于可翻越高度或跳跃高度*,前往更新目的路径的逻辑就不会执行。见图3-2
图3-2 Raycast的遮挡判断
为NavMeshObstacle做准备,我们把上面写的遮挡判断逻辑删掉,回到Unity中将NavMeshObstacle导航网格障碍添加到先前设置的物体上,并勾选Carve同时取消勾选CarveOnlyStationary和物体Static,见下图4-1,4-2
图4-2 NavMeshObstacle
图4-1 去掉剔除
设置成功后就可以看到,如果我们移动物体,导航网格也会从静态的变成实时烘焙的,见图4-3
图4-3 网格障碍的实施烘焙
public void SetAreaCost (int areaIndex, float areaCost);
public float GetAreaCost (int areaIndex);
获取在跨越特定类型的区域时的路径计算成本。
路径的成本是指计算它的过程中涉及的"困难"数量 - 如果路径会通过难走的地面(例如泥泞、雪地等),则最短的路径可能并非最佳路径。在 Unity 中,不同类型的区域通过导航网格区域来表示。特定区域的成本以每个距离单位的成本单位来表示。请注意,路径成本仅适用于探路,不会自动影响代理在遵循该路径时的移动速度。实际上,路径的成本可能表示其他因素,例如危险(安全但要通过雷区的长路径)或者可见性(角色一直处于阴影中的长路径)。
GetAreaCost简单来说就是获取走过某个路径所需要花费的代价,而这代价是可以通过Unity自己设计的,见下图5-1
图5-1 区域代价设计
我们也可以通过代码编写来获取指定的区域,并在后续加以利用,使用SetAreaCost来实现点击坐标离那座桥最近则走哪一座,见以下代码段,效果见图5-2
- [SerializeField]
- private LineRenderer line; //画线
-
- line.SetPositions(new Vector3[] { this.transform.position, currentHitPoint }); //画线
- private Vector3 currentHitPoint; //最近的碰撞点
-
- if (Physics.Raycast(ray, out hit)/* && !thisNavAgent.Raycast(hit.point, out navHit)*/) {
- currentHitPoint = hit.point;
- Debug.Log("B1:" + thisNavAgent.GetAreaCost(3) );
- Debug.Log("B2:" + thisNavAgent.GetAreaCost(4) );
-
- //寻找最近路程
- if (Vector3.Distance(currentHitPoint, point1.transform.position) <= testMinDistance)
- {
- thisNavAgent.SetAreaCost(3, 2);
- }
-
- if (Vector3.Distance(currentHitPoint, point2.transform.position) <= testMinDistance)
- {
- thisNavAgent.SetAreaCost(4, 2);
- }
-
- thisNavAgent.SetDestination(currentHitPoint);
-
- }
图5-2 Get & Set路径代价
因为这些算法都是更加底层的核心的东西,这里不详细去一个个讲了,列出来大家感兴趣的自行去学习,对数据结构的学习也会有很好的提升。那接下来开始重点:AI行为树-UnityBehaviorDesigner
对于AI行为树,在此也先单单讲一下安装和简单的使用,更多的东西如自编节点等...后面慢慢再出。
安装配置
将我在B站视频简介中准备好的BehaviorDesigner链接内容下载完毕后,解压将两个文件都添加到当前项目的Asset文件夹中,然后重启Unity就可以了。
图1-1 环境配置与插件文件
图1-2 配置成功图
界面介绍
图1-3 界面介绍*建议放大查看*
同时为方便后续理解各种区块的节点,首先理解一个词——任务Task,我们把每一个节点都看作是任务,顶部的为父任务,底部的为子任务,而执行顺序也是从上至下,先左后右,根据后续对行为树反馈类型的设置会对执行成功和失败有不同的效果。
第一个,Parallel并行,与Sequence序列任务相似,一假即假,和编程语言中的&&并运算很像,但不同于Sequence的是,并行执行Parallel的所有子任务是同步执行的,所以当出现了任务执行失败或者传回假,那么并行将停止所有子任务的执行。
例如:我们搭建一个寻路加追逐的AI行为,用Patrol和Seek同时连接在Paralle下,设置好两个巡逻点和追逐目标,运行查看结果。见图2-1
图2-1 并行组合
可以看得出来正如上述所说,无论如何因为是同时进行,追逐Seek一旦完成将不会在继续执行巡逻逻辑,和先后顺序也没有关系。
第二个,Selector选择,有点类似于编程语言的||或运算,一真即真,只要有一个子任务返回为执行成功Success整个行为树将判定为Success立刻停止,若没有子任务成功则返回Failure。
例如:我们搭建一个追逐加逃离的AI行为,用Seek和Flee连接在Selector下,设置追逐目标,运行查看结果。见图2-2
图2-2 选择组合
可以明显看到,执行完左边第一个任务Seek,整个行为树就停止运转了,因为Seek的动作也完成了,直接返回给组合节点True的结果。
第三个,Sequence顺序序列与上述第一种并行说的一样,一假即假,只要有一个自认无返回失败了,那么整个行为树将判定失败Failure立刻停止。
例如:同上,用Seek和Flee连接在Sequence下,设置追逐目标,运行查看结果。见图2-3
图2-3 序列组合
可以明显看到,执行完左边第一个任务Seek,行为树依旧会去执行下一个任务Flee,因为第一个任务执行成功了。
请结合下列三大组合失败情况示意图,去进一步理解。
图2-4 并行其中之一失败
图2-5 选择其中之一失败
图2-6 序列其中之一失败
关于Action节点,内容过多不能一一讲述,我们重点来讲一下其中的Movement,从中选取几个较有特色的节点。
Cover-Find a place to hide and move to it.找到某个可隐藏的位置并躲过去,其中比较关键的属性是:AvaliableLayerCovers可用于隐蔽掩护的层,只有对该层设置并在场景中配置好相应的障碍物才会有效果。
Evade-Evade the target specified.规避指定的目标,当规避目标进入视野之后远离至相应的距离位置。其中比较关键的属性是:LookAheadDistance用于设置前方视野距离,EvadeDistance应当规避距离。
Flee-Flee from the target specified.逃离指定的目标,当逃离目标进入视野后逃出相应的距离,总体感觉和Evade区别不打,可能就是一些对于行为的细微模仿有别。其中比较关键的属性是:LookAheadDistance用于设置前方视野距离,FleeDistance应当规避距离。
Flock-Flock around the scene.聚集在Scene周边,所有添加在Agent列表里且拥有NavMeshAgent的物体会以设置节点的物体周围到处乱动,貌似是随机,有点像是牧羊人和羊群。其中比较关键的属性是:Agents代理们。
Follow/LeaderFollow-Follow the specified target.跟随指定目标,小鸭子排队,没什么好讲的。其中比较关键的属性是:MoveDistance开始跟随的距离。
Patrol-Patrol around the specified.在周围巡逻,这里说是在周围巡逻,其实还是需要设置特定的点位的,设置好后对象将默认去找离自己最近的巡逻点,除非设置随机巡逻。其中比较关键的属性是:WayPoints巡逻点,RandomPartol随机巡逻。
RotateTowards-Rotate towards the specified rotation.旋转面向目标,其中比较关键的属性是:RotationEpesilon旋转误差值,小于该值就判定已经完成旋转。
Search-Search for a target combining the wander.组合漫游寻找目标,说白了就是无方向的随机找一个东西,找到后就完成,涉及的参数是最多的,其中比较关键的属性是:ObjectLayerMask寻找的物体所在层,ReturnObject返回所找到的物体。
Seek-Seek the target specified.追击指定的目标,和规避逃离相反,追击目标,到一定的距离算是追击成功。
Wander-Wander.漫游,字面意思随机逛街。
下一次博客和视频将会带着大家用行为树做一个比较有意思的潜行游戏模板,喜欢的话还请关注B站账号:波仔羔。好了如果觉得对你有帮助的话不如给个点赞加关注,如果有问题也欢迎评论或私聊我解决哦。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。