[Unity]寻路导航

自动寻路

【Unity3D】自动寻路系统Navigation实现人物上楼梯、走斜坡、攀爬、跳跃 - 百度文库

⼀、Navigation⾯板
这⾥写图⽚描述
Navigation⾯板中包括⼏个模块
Agents
这⾥写图⽚描述
这个是可以添加多个NabigationAgents可以⽤不同的Agents
参数:
Name:设置烘培Agents的名字
Radius:烘培的半径,也就是物体的烘培的半径。这个值影响物体能通过的路径的⼤⼩
越⼩,能⾏⾛的路径越⼤,边缘区域越⼩
Height: 具有代表性的物体的⾼度,可以通过的最低的空间⾼度。这个值越⼩,能通过的最⼩⾼度越⼩。打个⽐⽅就是,1m7的⼈能通过
1m7的洞是正常的,你可以设置height为1m,就能通过1m的⾼度
Step Height :梯⼦的⾼度,这个还是要看模型阶梯的⾼度来设置
Max Slope:烘培的最⼤的⾓度,就是坡度了
Areas
这⾥写图⽚描述
这个是可以设置⾃动寻路烘培的层
配合Nav Mesh Agents使⽤
这⾥写图⽚描述
Nav Mesh Agents->Area Mask->可以设置可以通过哪些层
Bake
这⾥写图⽚描述
这个就是设置烘培参数的
参数:
Radius:具有代表性的物体半径,半径越⼩⽣成的⽹格⾯积越⼤。
Height:具有代表性的物体的⾼度。
Max Slope:斜坡的坡度。
Ste Height:台阶⾼度。
Drop Height:允许最⼤的下落距离。
Jump Distance:允许最⼤的跳跃距离。
Min Region Area:⽹格⾯积⼩于该值则不⽣成导航⽹格。
Height Mesh:勾选后会保存⾼度信息,同时会消耗⼀些性能和存储空间。
Object
这⾥写图⽚描述
这个是设置去烘培哪个对象,⽐如地形之类的,就是可以⾏⾛的范围路径
参数:
Scene Filter:选择场景中那些对象,可以选择全部(All),地形(Terrains),有⽹格对象(Mesh Renderers)
Navigation Static:可以烘培
Generate OffMeshLinks:可以跳跃的地⽅
两种⽣成OffMeshLink的⽅法:
第⼀种⽣成OffMeshLink的⽅法
添加OffMeshLink组件
在这⾥插⼊图⽚描述
参数:
Start:设置起点
End:设置终点
两者顺序没有关系
Cost Override:这个参数还没有研究
BI Directional:同上
Activated:同上
Auto Update Position:同上
Navigation Area:同上
第⼆种⽣成OffMeshLink的⽅法
在Navigation⾯板的Object栏⾥⾯把OffMeshLink Generation选项打上勾
这⾥写图⽚描述
这⾥要说的⼀点是,是不是有的⼈把OffMeshLink Generation选项打上勾之后Back之后还是没有出现OffMeshLink
这⾥写图⽚描述
这是因为你没有设置Jump Distance的值
这⾥写图⽚描述
这个值越⼤,能跳的距离越远,然后能跳的越⾼
Navigation Area:表⽰是哪个Area,这个需要先设置,系统默认是Walkble、Jump、NotWalkble三种
这个也要配合Nav Mesh Agent使⽤
这⾥写图⽚描述
⼆、NavMeshAgent组件
2.1 Agent Size
Radius
物体的半径
Height
物体的⾼度,如果AgentHeight的值⼤于这个值,那么就不能通过
Base Offset
偏移值
2.2 Steering
Speed
物体⾃动寻路的速度
Angular Speed
转⾓的速度,就是转弯的速度
Acceleration
加速度
Stopping Distance
物体停下来的距离,设置为0就是跟⽬标点的距离为0时停下来
Auto Braking
是否⾃动停下来
2.3 Obstacle Avoidance
Quality
如果要防⽌⼀群寻路的物体围住⽬标点
可以设置Quality为None,即可以让寻路物体互相穿过
Priority
优先权
2.4 Path Finding⾃动寻路
Auto Traverse Off Mesh Link
⾃动跳跃链接
Auto Repath
⾃动复制路径
Area Mask
能通过的Maks层,这个可以配合Navigation组件中Areas(设置层的)使⽤
三、NavMeshObstacle组件障碍物组件
这⾥写图⽚描述
如果想要在场景中,动态的放置障碍物,然后也不想在场景开始前就洪培好地形的话,就可以在物体上加上这个组件,然后设置好参数,将
⾃动寻路组件NavMeshAgent的寻路避让优先级调⾼⼀点
Shape
障碍物的模型,有Box和Capsule两个选项,从单词意思就可以看出来什么意思就不解释了
Center
中⼼点,如果障碍模型的中⼼点不在模型的中⼼点上就可以做⼀些调整
Size
设置⼤⼩
Carve
Move Threshold 模型 移动某个距离后进⾏烘焙
Time To Stationary 指定模型在某个位置停⽌⼀段时间 后 在进⾏烘焙
Carve One Stationary 勾选后,模型移动时不会实时烘焙
四、实例例⼦
步骤⼀般是这样的:
1.在场景中摆放各种模型,包括地板,斜坡,⼭体,扶梯等
2.为所有的模型加上Navigation Static和OffMeshLink Generatic(这个根据需要,例如地板与斜坡相连,斜坡就不需要添加
OffMeshLink)
3.特殊处理扶梯,需要⼿动添加Off Mesh Link,设置好开始点和结束点
4.保存场景,烘焙场景
例⼦⼀:简单寻路
我们要实现⼀个功能:点击场景中的⼀个位置,⾓⾊可以⾃动寻路过去。⾓⾊会绕过各种复杂的障碍,找到⼀条理论上”最短路径“。
步骤:
1.创建地形
2.添加⾓⾊
3.创建多个障碍物,尽量摆的复杂⼀点,来检查Navmesh的可⽤性和效率。
4.选中地形,在Navigation窗⼝中,设置Navigation Static
5.依次选中障碍物,在avigation窗⼝中,设置Navigation Static
7.Navigation窗⼝中,选择Bake(烘焙)界⾯,点击Bake按钮,进程场景烘焙,就可以烘焙出寻路⽹格了
8.为⾓⾊添加NavMeshAgent组件。Component->Navigation->Nav Mesh Agent
9.为⾓⾊新增⼀个脚本PlayerController.cs,实现点击⽬标,⾃动寻路功能
代码:
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour
{
    private NavMeshAgent agent;
    void Start()
    {
        //获取组件
        agent = GetComponent<NavMeshAgent>();
    }
    void Update()
    {
        //⿏标左键点击
        if (Input.GetMouseButtonDown(0))
        {
            //摄像机到点击位置的的射线
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                //判断点击的是否地形
                if (!hit.collider.name.Equals("Terrain"))
                {
                    return;
                }
                //点击位置坐标
                Vector3 point = hit.point;
                //转向
                transform.LookAt(new Vector3(point.x, transform.position.y, point.z));
                //设置寻路的⽬标点
                agent.SetDestination(point);
            }
        }
        //播放动画,判断是否到达了⽬的地,播放空闲或者跑步动画
        if (agent.remainingDistance == 0)
        {
            animation.Play("idle");
        }
        else
        {
            animation.Play("run");
        }
    }
}
例⼦⼆:上下斜坡
烘焙上下斜坡的问题
这⾥写图⽚描述
在⽤Unity的⾃动寻路系统的时候,如果⼈物不能实现按照规定到达⽬的地,有绝⼤的原因是烘焙寻路出现了问题,所以这是我们⾸先需要
重视的地⽅。
下⾯就是⼀开始我烘焙的寻路,⼤家可能发现问题了,就是在两个红圈的位置是没有烘焙上的,并且区域很⼤,当⼈物寻路到这⾥的时候很
容易卡在这⾥。
这⾥写图⽚描述
那就让我们来设置烘焙的参数吧。
这⾥写图⽚描述
⾸先来介绍⼀个各个参数的含义:
Agent Radius:烘焙的半径,其数值越⼩则烘焙效果越好;
Agent Height:烘焙的⾼度,⼈物通过的⾼度;
Max Slope:烘焙的最⼤坡度,⼤于这个坡度的⾯将不会烘焙;
Step Height:烘焙的台阶⾼度,如果⾼度差⼩于设置值,将视为连接。
烘焙参数设置
所以我们将烘焙半径调⼩点就可以解决这个问题了。
我将烘焙半径设置为0.1,烘焙效果如下图,上坡和下边的地⾯连接处没有烘焙上的区域就很⼩啦。
烘焙好的效果图
这⾥写图⽚描述
斜坡⾓度和连接问题
如果上坡的⾓度很⼤,⼈物也会卡在上坡中,我现在设置的上坡⾓度是40度。如果把⾓度设置为30度或者以下,⼈物就可以很顺利的爬上
斜坡啦。
上坡⾓度很⼤
这⾥写图⽚描述
如果下坡的⾓度很⼤,⼈物就会直接跳下斜坡,我现在设置的下破的⾓度是50度。可以从图中看到⼈物是直接跳下来的。如果把⾓度设置
为40度或者以下,⼈物就可以很顺利的下斜坡啦。
下坡⾓度很⼤
这⾥写图⽚描述
还有就是斜坡与地⾯和站台连接处的问题,它们的连接之间⼀定不能有空隙,否则⼈物也容易卡在空隙处。如下图中,斜坡与站台没有完全
连接上,有个很⼩的缝隙,即使寻路也烘焙得没有问题,⼈物有时候也会卡在这个地⽅。
斜坡连接处处理
这⾥写图⽚描述
⼈物容易卡在寻路的边缘处
因为寻路就是解决的⼈物通过查找最短的路径(在忽略消耗体⼒值前提下),并最终达到⽬的地的问题,所以在上下坡也经常会遇到⼈物会
沿着斜坡⼀边运动,这个就可能使⼈物卡在烘焙好的寻路边缘处。
我的解决办法是设置中间⽬标物,让其绕开寻路边缘运动,这就需要设置⼏个中间⽬标,当⼈物到达⼀个⽬标的时候,然后向着下⼀个⽬标
运动。
从图中可以看出设置了三个⽬标物,这样⼈物就可以顺利到达⽬标3啦。
这⾥写图⽚描述
代码:
[RequireComponent(typeof(NavMeshAgent))]
public class NavigationTest : MonoBehaviour {
    public Transform targetOne;
    public Transform targetTwo;
    public Transform targetThree;
    private NavMeshAgent navAgent;
    private float distanceOne;
    private float distanceTwo;
    // Use this for initialization
    void Start () {
        navAgent = transform.GetComponent<NavMeshAgent>();
        navAgent.SetDestination(targetOne.position);
    }
    // Update is called once per frame
    void Update () 
    {
        CheckReachTarget();
    }
    void CheckReachTarget()
    {
        distanceOne = Vector3.Distance(transform.position,targetOne.position);
        distanceTwo = Vector3.Distance(transform.position,targetTwo.position);
        if (distanceOne < 1f)
        {
            navAgent.SetDestination(targetTwo.position);
        }
        if (distanceTwo<1f)
        {
            navAgent.SetDestination(targetThree.position);
        }
    }
}
例⼦三:简单的⾃动寻路
1.在Scene中新建三个Cube,如下图摆放。
这⾥写图⽚描述
2.选中上图三个Cube,并在Inspector⾯板中选中为静态(static)下拉选项的Navigation Static,如下图。
这⾥写图⽚描述
3.依次选择菜单栏中的Windows - Navigation ,打开后⾯板如下。
这⾥写图⽚描述
单击该⾯板右下⾓的Bake按钮,即可⽣成导航⽹格,下图为已⽣成的导航⽹格。
在这⾥插⼊图⽚描述
4.下⾯就可以让⼀个运动体根据⼀个导航⽹格运动到⽬标位置。
⾸先新建⼀个Cube为⽬标位置,起名TargetCube。然后创建⼀个capsule(胶囊)运动体,为该胶囊挂在⼀个Nav Mesh
Agent(Component - Navigation - Nav Mesh Agent);最后写⼀个脚本就可以实现⾃动寻路了。脚本如下:
using UnityEngine;
using System.Collections;
public class Run : MonoBehaviour {
    public Transform TargetObject = null;
    void Start () {
        if (TargetObject != null)
        {
            GetComponent<NavMeshAgent>().destination = TargetObject.position;
        }
    } 
    void Update () {
    }
}
脚本新建完成后挂载到胶囊体上,然后将TargetCube赋予给胶囊体的Run脚本,运⾏场景,如下图,胶囊体会按照箭头的⽅向运动到
Cube位置。
这⾥写图⽚描述
这样⼀个简单的⾃动寻路就完成了,如果要更精细的寻路,或要实现上坡,钻"桥洞"等,可根据下⾯介绍的相关参数进⾏调节。
下⾯介绍Navigation组件和Nav Mesh Agent组件的相关参数。
Navigation
Object:物体参数⾯板
Navigation Static:勾选后表⽰该对象参与导航⽹格的烘培。
OffMeshLink Generation:勾选后可跳跃(Jump)导航⽹格和下落(Drop)。
Bake:烘培参数⾯板  
Radius:具有代表性的物体半径,半径越⼩⽣成的⽹格⾯积越⼤。
Height:具有代表性的物体的⾼度。
Max Slope:斜坡的坡度。
Ste Height:台阶⾼度。
Drop Height:允许最⼤的下落距离。
Jump Distance:允许最⼤的跳跃距离。
Min Region Area:⽹格⾯积⼩于该值则不⽣成导航⽹格。
Height Mesh:勾选后会保存⾼度信息,同时会消耗⼀些性能和存储空间。
Nav Mesh Agent:导航组建参数⾯板    
Radius:物体的半径
Speed:物体的⾏进最⼤速度
Acceleration:物体的⾏进加速度
Augular Speed:⾏进过程中转向时的⾓速度。
Stopping Distance:离⽬标距离还有多远时停⽌。
Auto Traverse Off Mesh Link:是否采⽤默认⽅式度过链接路径。
Auto Repath:在⾏进某些原因中断后是否重新开始寻路。
Height:物体的⾼度。
Base Offset:碰撞模型和实体模型之间的垂直偏移量。
Obstacle Avoidance Type:障碍躲避的的表现登记,None选项为不躲避障碍,另外等级越⾼,躲避效果越好,同时消耗的性能越多。
Avoidance Priority:躲避优先级。
NavMesh Walkable:该物体可以⾏进的⽹格层掩码。
例⼦四:Navigation实现⾼低落差以及跳跃的做法
在这⾥插⼊图⽚描述
在这⾥插⼊图⽚描述
不管是爬楼梯,还是跳跃,NavMesh都是通过了OffMeshLink来做的。创建OffMeshLink的⽅法有两种,接下来会通过制作上⾯的例⼦
来进⾏说明:
这⾥写图⽚描述
为了做这个例⼦,我们预先在场景⾥⾯准备了⼀些物体:摄像机是必须的,⼀个作为地⾯的Plane,然后是F1——F5⼏个⾼低落差不⼀样
的台阶,L1和L2是楼梯模型, 控制⼈物主体man,还有移动的⽬标点target。
其中man⾝上必须带有NavMesh Agent组件,为了观察⽅便在target⾝上带了light组件。
这⾥写图⽚描述
按照上⼀节所讲的,plane和F1——F5台阶在Navigation⾯板勾选Navigation Static选项,然后Bake,观察Scene视窗,会发现已经⽣成
了我们所要的NavMesh⽹格,现在我们可以像上⼀节那样在plane上⾯给⼈物做寻路和移动了,但⼈物是不会爬楼梯的。
这时候,我们找到L1楼梯,在楼梯的开始和结束的位置放置两个点,这两个点只需要拾取它的位移的,你可以⽤empty Gameobject来
做,我这⾥为了便于观察,就拿了cube来做。开始点命名为startPoint,结束点命名为endPoint。
这⾥写图⽚描述
注意:startPoint和endPoint的位置要稍微⽐所在的平⾯⾼⼀点点。
接下来介绍第⼀种⽣成OffMeshLink的⽅法。选择L1楼梯,然后在Component下拉选项中选择Navigation——Off Mesh Link。
这⾥写图⽚描述
选择后,OffMeshLink组件已经添加到了L1的⾝上,我们可以在Inspector⾯板看到:
我们把刚才放置在场景⾥⾯的startPoint和endPoint指定到OffmeshLink组件的Start和End位置,其他选项默认不改变
这⾥写图⽚描述
再次Bake
这⾥写图⽚描述
这⾥写图⽚描述
现在我们发现,在scene⾯板⾥⾯,在startPoint和endPoint之间⽣成了⼀条线,⽽⽅向是从startPoint指向endPoint的。
在这⾥插⼊图⽚描述
这时候,你应该可以通过移动⽬标点让⾓⾊开始爬楼梯了。但爬上去之后⾓⾊暂时不能跳下来,如果把⽬标点移动到plane上,⾓⾊会顺着
楼梯爬下来。
我们使⽤同样的⽅法对L2进⾏⽣成OffMeshLink。这时候,⾓⾊应该可以爬两层楼梯了。到此第⼀个⽬标完成了。
接下来我们进⾏第⼆个⽬标的制作,⾸先先来分析⼀下我们的场景:
这⾥写图⽚描述
我们希望⼈物能从2.5M的⾼度往下跳,超过2.5M⼈物就不能跳了,太⾼会有危险。然后横向我们希望⼈物能跳过2⽶的沟。
根据这个设定,我们的场景会是这样的情况:L1和L2只能通过爬楼梯,L2和L3之间可以跳跃,L3——L5是可以往下跳的。
于是,我们在Navigation⾯板⾥⾯找到Bake栏,Drop Height(掉落⾼度)填2.5,Jump Distance(跳跃距离)填2,单位都是⽶
这⾥写图⽚描述
接下来介绍第⼆种⽣成OffMeshLink的⽅法:
我们把L1——L5的物体选中,在Navigation⾯板的Object栏⾥⾯把OffMeshLink Generation选项打上勾
这⾥写图⽚描述
再次Bake,回到scene视窗:
这⾥写图⽚描述
这时候,场景⾥⾯会出现很多⼼的OffMeshLink,这是unity通过计算,把可以跳跃或者下落的地⽅⾃动⽣成了OffMeshLink了。
这时候,你应该已经可以通过移动⽬标点,让⾓⾊进⾏跳跃和下落了。
进⾏到这⾥,我们的第⼆个⽬标也完成了。
不过有些朋友可能会提出疑问,在做的过程中,假如没有这个⼤兵的模型,⽽是⽤⼀个胶囊体来代替⼈物的话,它爬楼梯和跳跃的时候好像
是在⼀瞬间完成的,没有⼤兵那个爬楼梯和跳跃动作的过程。如这样:
这⾥写图⽚描述
这⾥写图⽚描述
的确是这样,因为默认的NaveMesh Agent组件上⾯是勾选了Auto Traverse Off Mesh Link(⾃动通过OffMeshLink)选项的。这样的
意思是⼈物只要到了OffMeshLink的开始点,就会⾃动的移动到OffMeshLink的结束点。
这⾥写图⽚描述
假如各位需要对越过OffMeshLink时候进⾏⾃⼰的控制,是需要另外写脚本的。我这⾥简单的介绍⼀下⽅法,有兴趣的朋友可以⾃⼰试
试。
⾸先各位最好有⽤状态来控制⾓⾊的概念。⽐如⼈物可以分为站⽴、⾛路、跑步、上下楼梯、横向跳跃和往下掉落⼏种状态,针对
NavMesh来说,⼈物简单的可以分为站⽴、正常的NavMesh寻路,和通过OffMeshLink移动⼏种状态。
先把 Auto Traverse Off Mesh Link选项取消。
然后,当⼈物在通过OffMeshLink移动的状态(可以⽤NavMeshAgent.isOnOffMeshLink来判断),获取到当前通过的
OffMeshLink:
OffMeshLinkData link = NavMeshAgent.currentOffMeshLinkData;
这样你就能获取到link的开始点和结束点的坐标(link.startPos和link.endPos),这时候你的⼈物就可以⽤最简单的Vector3.Lerp来进⾏
移动,当⼈物的位移到达了结束点的坐标,⼈物的OffMeshLink移动状态就可以结束,⼜重新变回正常寻路或者站⽴的状态了。在这个
Vector3.Lerp的过程中,你可以随意的控制⼈物的爬⾏或者跳跃的动作。
例⼦五:⾃动寻路Navmesh之跳跃,攀爬,斜坡
步骤:
1.在场景中摆放各种模型,包括地板,斜坡,⼭体,扶梯等
2.为所有的模型加上Navigation Static和OffMeshLink Generatic(这个根据需要,例如地板与斜坡相连,斜坡就不需要添加
OffMeshLink)
3.特殊处理扶梯,需要⼿动添加Off Mesh Link,设置好开始点和结束点
4.保存场景,烘焙场景
5.添加⾓⾊模型,为其加Nav Mesh Agent组件
6.为⾓⾊添加⼀个新脚本,AgentLocomotion.cs,⽤来处理⾃动寻路,已经⾓⾊动画变换。代码⽐较长,⼤家可以结合注释来理解
代码:
using UnityEngine;  
using System.Collections;  
public class AgentLocomotion : MonoBehaviour  
{  
    private Vector3 target;//⽬标位置
    private NavMeshAgent agent;  
    private Animation anim;//动画
    private string locoState = "Locomotion_Stand";  
    private Vector3 linkStart;//OffMeshLink的开始点
    private Vector3 linkEnd;//OffMeshLink的结束点
    private Quaternion linkRotate;//OffMeshLink的旋转
    private bool begin;//是否开始寻路
    // Use this for initialization  
    void Start()  
    {  
        agent = GetComponent<NavMeshAgent>();  
        //⾃动移动并关闭OffMeshLinks,即在两个隔离障碍物直接⽣成的OffMeshLink,agent不会⾃动越过
        agent.autoTraverseOffMeshLink = false;  
        //创建动画
        AnimationSetup();  
        //起⼀个协程,处理动画状态机
        StartCoroutine(AnimationStateMachine());  
    }  
    void Update()  
    {  
        //⿏标左键点击
        if (Input.GetMouseButtonDown(0))  
        {  
            //摄像机到点击位置的的射线
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
            RaycastHit hit;  
            if (Physics.Raycast(ray, out hit))  
            {  
                //判断点击的是否地形
                if (hit.collider.tag.Equals("Obstacle"))  
                {  
                    begin = true;  
                    //点击位置坐标
                    target = hit.point;  
                }  
            }  
        }  
        //每⼀帧,设置⽬标点
        if (begin)  
        if (begin)  
        {  
            agent.SetDestination(target);  
        }  
    }  
    IEnumerator AnimationStateMachine()  
    {  
        //根据locoState不同的状态来处理,调⽤相关的函数
        while (Application.isPlaying)  
        {  
            yield return StartCoroutine(locoState);  
        }  
    }  
    //站⽴
    IEnumerator Locomotion_Stand()  
    {  
        do  
        {  
            UpdateAnimationBlend();  
            yield return new WaitForSeconds(0);  
        } while (agent.remainingDistance == 0);  
        //未到达⽬标点,转到下⼀个状态Locomotion_Move  
        locoState = "Locomotion_Move";  
        yield return null;  
    }  
    IEnumerator Locomotion_Move()  
    {  
        do  
        {  
            UpdateAnimationBlend();  
            yield return new WaitForSeconds(0);  
            //⾓⾊处于OffMeshLink,根据不同的地点,选择不同动画
            if (agent.isOnOffMeshLink)  
            {  
                locoState = SelectLinkAnimation();  
                return (true);  
            }  
        } while (agent.remainingDistance != 0);  
        //已经到达⽬标点,状态转为Stand  
        locoState = "Locomotion_Stand";  
        yield return null;  
    }  
    IEnumerator Locomotion_Jump()  
    {  
        //播放跳跃动画
        string linkAnim = "RunJump";  
        Vector3 posStart = transform.position;  
        agent.Stop(true);  
        anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll);  
        transform.rotation = linkRotate;  
        do  
        {  
            //计算新的位置
            float tlerp = anim[linkAnim].normalizedTime;  
            Vector3 newPos = Vector3.Lerp(posStart, linkEnd, tlerp);  
            newPos.y += 0.4f * Mathf.Sin(3.14159f * tlerp);  
            transform.position = newPos;  
            yield return new WaitForSeconds(0);  
        } while (anim[linkAnim].normalizedTime < 1);  
        } while (anim[linkAnim].normalizedTime < 1);  
        //动画恢复到Idle  
        anim.Play("Idle");  
        agent.CompleteOffMeshLink();  
        agent.Resume();  
        //下⼀个状态为Stand  
        transform.position = linkEnd;  
        locoState = "Locomotion_Stand";  
        yield return null;  
    }  
    //梯⼦
    IEnumerator Locomotion_Ladder()  
    {  
        //梯⼦的中⼼位置
        Vector3 linkCenter = (linkStart + linkEnd) * 0.5f;  
        string linkAnim;  
        //判断是在梯⼦上还是梯⼦下
        if (transform.position.y > linkCenter.y)  
            linkAnim = "Ladder Down";  
        else  
            linkAnim = "Ladder Up";  
        agent.Stop(true);  
        Quaternion startRot = transform.rotation;  
        Vector3 startPos = transform.position;  
        float blendTime = 0.2f;  
        float tblend = 0f;  
        //⾓⾊的位置插值变化(0.2内变化)
        do  
        {  
            transform.position = Vector3.Lerp(startPos, linkStart, tblend / blendTime);  
            transform.rotation = Quaternion.Lerp(startRot, linkRotate, tblend / blendTime);  
            yield return new WaitForSeconds(0);  
            tblend += Time.deltaTime;  
        } while (tblend < blendTime);  
        //设置位置
        transform.position = linkStart;  
        //播放动画
        anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll);  
        agent.ActivateCurrentOffMeshLink(false);  
        //等待动画结束
        do  
        {  
            yield return new WaitForSeconds(0);  
        } while (anim[linkAnim].normalizedTime < 1);  
        agent.ActivateCurrentOffMeshLink(true);  
        //恢复Idle状态
        anim.Play("Idle");  
        transform.position = linkEnd;  
        agent.CompleteOffMeshLink();  
        agent.Resume();  
        //下⼀个状态Stand  
        locoState = "Locomotion_Stand";  
        yield return null;  
    }  
    private string SelectLinkAnimation()  
    {  
        //获得当前的OffMeshLink数据
        OffMeshLinkData link = agent.currentOffMeshLinkData;  
        //计算⾓⾊当前是在link的开始点还是结束点(因为OffMeshLink是双向的)
        float distS = (transform.position - link.startPos).magnitude;  
        float distE = (transform.position - link.endPos).magnitude;  
        float distE = (transform.position - link.endPos).magnitude;  
        if (distS < distE)  
        {  
            linkStart = link.startPos;  
            linkEnd = link.endPos;  
        }  
        else  
        {  
            linkStart = link.endPos;  
            linkEnd = link.startPos;  
        }  
        //OffMeshLink的⽅向
        Vector3 alignDir = linkEnd - linkStart;  
        //忽略y轴
        alignDir.y = 0;  
        //计算旋转⾓度
        linkRotate = Quaternion.LookRotation(alignDir);  
        //判断OffMeshLink是⼿动的(楼梯)还是⾃动⽣成的(跳跃)
        if (link.linkType == OffMeshLinkType.LinkTypeManual)  
        {  
            return ("Locomotion_Ladder");  
        }  
        else  
        {  
            return ("Locomotion_Jump");  
        }  
    }  
    private void AnimationSetup()  
    {  
        anim = GetComponent<Animation>();  
        // 把walk和run动画放到同⼀层,然后同步他们的速度。
        anim["Walk"].layer = 1;  
        anim["Run"].layer = 1;  
        anim.SyncLayer(1);  
        //设置“跳跃”,“爬楼梯”,“下楼梯”的动画模式和速度
        anim["RunJump"].wrapMode = WrapMode.ClampForever;  
        anim["RunJump"].speed = 2;  
        anim["Ladder Up"].wrapMode = WrapMode.ClampForever;  
        anim["Ladder Up"].speed = 2;  
        anim["Ladder Down"].wrapMode = WrapMode.ClampForever;  
        anim["Ladder Down"].speed = 2;  
        //初始化动画状态为Idle  
        anim.CrossFade("Idle", 0.1f, PlayMode.StopAll);  
    }  
    //更新动画融合
    private void UpdateAnimationBlend()  
    {  
        //⾏⾛速度
        float walkAnimationSpeed = 1.5f;  
        //奔跑速度
        float runAnimationSpeed = 4.0f;  
        //速度阀值(idle和walk的临界点)
        float speedThreshold = 0.1f;  
        //速度,只考虑x和z  
        Vector3 velocityXZ = new Vector3(agent.velocity.x, 0.0f, agent.velocity.z);  
        //速度值
        float speed = velocityXZ.magnitude;  
        //设置Run动画的速度
        anim["Run"].speed = speed / runAnimationSpeed;  
        anim["Run"].speed = speed / runAnimationSpeed;  
        //设置Walk动画的速度
        anim["Walk"].speed = speed / walkAnimationSpeed;  
        //根据agent的速度⼤⼩,确定animation的播放状态
        if (speed > (walkAnimationSpeed + runAnimationSpeed) / 2)  
        {  
            anim.CrossFade("Run");  
        }  
        else if (speed > speedThreshold)  
        {  
            anim.CrossFade("Walk");  
        }  
        else  
        {  
            anim.CrossFade("Idle", 0.1f, PlayMode.StopAll);  
        }  
    }  
}  
效果图:
点击任何⼀个地点,⾓⾊都可以⾃动寻路过去。中间可能经过不同的障碍物,我们可以看到⾓⾊如我们所预料的⼀样,可以跳跃下来,可以
爬楼梯,最终到达⽬标点。
这⾥写图⽚描述
源码:
例⼦六:⾃动寻路Navmesh之⾼级主题
隔离层⾃动⽣成寻路⽹格
1.创建Plane实例P1,P2,两者之间出现⼀条鸿沟。直接控制⾓⾊位移是⽆法通过的。
2.打开Navigation窗⼝,分别选中P1,P2,分别设置Navigation Static 和OffMeshLink Generatic
这⾥写图⽚描述
3.保存场景,点击场景烘焙按钮Bake。结束后我们可以看到P1,P2除了⾃⾝⽣产寻路⽹格外,它们直接还⽣成了连接纽带。
4.添加⾓⾊模型Solder,为其添加NavMeshAgent(Component->Navigation->NavMeshAgent)
5.给Solder添加PlayerController脚本
代码:
using UnityEngine;  
using System.Collections;  
public class PlayerController : MonoBehaviour  
{  
    private NavMeshAgent agent;  
    public bool setAgentWalkMask;//是否需要动态修改寻路层,在scene4的实例中要⽤到
    void Start()  
    {  
        //获取寻路组件
        agent = GetComponent<NavMeshAgent>();  
    }  
    void Update()  
    {  
        //⿏标左键点击
        if (Input.GetMouseButtonDown(0))  
        {  
            //摄像机到点击位置的的射线
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
            RaycastHit hit;  
            if (Physics.Raycast(ray, out hit))  
            {  
                //判断点击的是否地形
                if (!hit.collider.tag.Equals("Plane"))  
                {  
                    return;  
                }  
                //点击位置坐标
                Vector3 point = hit.point;  
                //转向
                transform.LookAt(new Vector3(point.x, transform.position.y, point.z));  
                //设置寻路的⽬标点
                agent.SetDestination(point);  
            }  
        }  
        //播放动画
        if (agent.remainingDistance == 0)  
        {  
            animation.Play("Idle");  
        }  
        else  
        {  
            animation.Play("Run");  
        }  
    }  
}  
5.点击任意的位置,可以看到⾓⾊都能⾃动寻路过去
效果图
这⾥写图⽚描述
⼿动指定寻路⽹格⽅向
1.将P1,P2的OffMeshLink Generatic去除
2.在P1上新建⼀个空的GameObject Start,P2上新建⼀个空的GameObject End
3.选中start,为它添加Off Mesh Link组件 Component->Navigation->OffMeshLink
4.设置Off Mesh Link组件的属性,Start Point 为 start,End Point为end
5.烘焙场景。我们可以看到有⼀条纽带从start指向end
点击地图,可以看到⾓⾊如果要跨越P1和P2,⼀定是沿着我们⼿动创建的路径
这⾥写图⽚描述
导航⽹格障碍物 Navmesh Obstacle
之前我们都是⽤固定的物体作为障碍物,然后烘焙场景。Unity还提供了动态的障碍物。任何⼀个GameObject都可以添加Navmesh
Obstacle组件,变成⼀个障碍物。具体步骤是Component->Navigation->Navmesh Obstacle.它有两个属性:半径和⾼度,可以设置跟
你的物品差不多的体积⼤⼩。
寻路⽹格层的应⽤
1.新建P1,P2,P3,P4等4个Plane,具体摆设形状见效果图
2.在Navigation窗⼝中,添加两个层Layers:Blue层和Red层
3.P1,P2的Navigation Layer设置为Default,P4的Navigation层设置为Red,P3设置为Blue
4.添加两个⾓⾊,设置他们的NavMeshAgent寻路层(NavMesh Walkable)。⼀个将Red层去掉,⼀个将Blue层去掉
5.点击P2的坐标,可以看到他们沿着不同的路径去⽬标点,⼀个⾛上层路线,⼀个⾛下层路线了。
效果图
这⾥写图⽚描述
动态改变寻路⽹格层
1.在scene3.unity基础上做⼀下修改。只保留⼀个⾓⾊
2.新增两个按钮,“⾛上层”和“⾛下层”,在游戏运⾏时,可以改变Agent的寻路层。
代码:
//动态设置寻路路径层
 void OnGUI()  
   {  
       if (!setAgentWalkMask)  
       {  
           return;  
       }  
       if (GUI.Button(new Rect(0, 0, 100, 50), "⾛下层"))  
       {  
           agent.walkableMask = 65;  
       }  
       if (GUI.Button(new Rect(0, 100, 100, 50), "⾛上层"))  
       {  
           agent.walkableMask = 129;  
       }  
   }  
3.重新点击寻路,可以看到,选择不同的寻路层,⾓⾊的寻路路径也不同
这⾥写图⽚描述
看到代码中的agent.walkableMask = 65和129,⼤家会⽐较迷惑,其实寻路层每⼀层都是2的幂,见下图
这⾥写图⽚描述
所以上层的mask = Default(1)+Blue(128) = 129,下层的mak = Default(1)+Red(64) = 65
例⼦七:Navigation烘焙
Building a NavMesh
在Unity中,NavMesh 的⽣成操作需要Navigation窗⼝(在Window> Navigation)
在你的场景中构建NavMesh只需要4个步骤:
这⾥写图⽚描述
在这⾥插⼊图⽚描述
选择场景中需要⽣成寻路的⼏何体-可⾏⾛表⾯和障碍物。
在NavMesh⾯板中选择需要烘焙寻路的物体,检测是否勾选Navigation Static.
根据你的agent⼤⼩来调整bake ⾯板的设置。
Agent Radius : agent可以距离墙体 ,窗户或边缘多近的距离。
Agent Height : agent可以通过的最低的空间⾼度。
Max Slope : agent可以直接⾏⾛上去的最⼩坡度。
Step Height: agent可以踩上(⾛上)的障碍物最⾼⾼度。
点击bake按钮烘焙NavMesh。
当你的Navigation窗⼝打开并且可见时,烘焙的NavMesh结果在场景中会以蓝⾊的覆盖层在物体的⼏何体表⾯显⽰。
这⾥写图⽚描述
烘焙完成后,您将在与NavMesh所属场景同名的⽂件夹中找到⼀个NavMesh资产⽂件。例如,如果在Assets⽂件夹中有⼀个名为First
Level的场景,NavMesh将位于 Assets > First Level > NavMesh.asset.当烘焙结束后,你可以找到⼀个名字和你的场景名⼀样的⽂件
夹,⾥⾯有⼀个NavMesh的资源⽂件,是属于这个场景的NavMesh。
让物体可烘焙的其他⽅法
这⾥写图⽚描述
直接选择物体在Inspector⾯板顶部的Static 菜单,你可以直接选择Navigation Static,⽽不⽤再打开Navigation 窗⼝。
NavMesh烘焙的⾼级设置
最⼩区域⾯积
这⾥写图⽚描述
这⾥写图⽚描述
Min Region Area 允许你剔除掉⼩的⾮连接NavMesh区域,当NavMesh区域⼩于指定值时将被剔除。
请注意:有些区域可能⽆法被移除,尽管有Min Region Area 的设置,原因是NavMesh的构建是⼀个个的⽹格并⾏构建。当⼀个区域横跨
两个⽹格将不会被移除,因为区域修剪过程中⽆法获取到周围的⽹格。
Voxel Size ⽴体像素尺⼨
Manual voxel size :允许你改变烘焙操作过程中的精确性。
Navigation在构建寻路⽹格过程中,第⼀遍会把场景光栅化为体素,然后提取可⾏⾛区域,最后可⾏⾛区域会烘焙成⽹格。因此体素尺⼨
Voxel Size的决定了寻路⽹格烘焙的精准性。
提⽰:默认的体素设置最好的权衡了准确性和烘焙速度。在烘焙场景寻路的过程中,体素的增加会造成4x倍的内存消耗和4x倍的时间消
耗。因此通常,你不需要⾃⼰去设置Voxel Size。
这⾥写图⽚描述
Smaller Agent Radius
系统烘焙寻路是也会减⼩voxel size。如果你的agent尺⼨保持不变,可能不需要增加NavMesh的构建分辨率。
更简单的⽅法如下所⽰:
设置你的半径为真实agent半径
打开Manual Voxel Size,这会保持当前的voxel的⼤⼩并且冻结它。
⼈为的将你的Agent Radius设置⼩,因为你已经勾选了Manual Voxel Size ,voxel size将不会改变。
Height mesh : 允许你为⾓⾊提供更精准的位置。
这⾥写图⽚描述
导航时,NavMesh代理被约束在NavMesh的表⾯。由于NavMesh是可步⾏空间的近似,所以在构建NavMesh时,⼀些特性会被平均化。
例如,楼梯可能在NavMesh中显⽰为⼀个斜坡。如果你的游戏需要准确的位置代理,你应该启⽤⾼度⽹格建设时,你烤NavMesh。该设置
可以在导航窗⼝的⾼级设置中找到。注意,建筑⾼度⽹格将占⽤内存和处理在运⾏时,它将需要更长的时间来烘烤NavMesh。
尽管普通的NavMesh 已经可以很好的运⾏,但是由于NavMesh只是⼀个近似的可⾏⾛的空间,只保持了⼀些均衡的特性,如果你的游戏
agent需要更精准的⾏⾛位置,你可以启⽤⾼度⽹格Height Mesh,
注意:⾼度⽹格Height Mesh将占⽤你“运⾏时”的内存和cpu,并且需要更多的烘焙时间。
结束。。。
这篇⽂章吸取了很多⼈的精华,有什么不对的地⽅希望都可以私信我。

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DAGUNIANGZHOU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值