Unity3D之Navigation导航系统学习及案例讲解(适合初学者)

引言:很多游戏都有自动寻路功能,点击场景中的一个位置,角色会自动选择一条相对较优的路线过去。大多数端游页游都会使用A*寻路算法,小生之前也总结过,刚兴趣的可以学习一下。现在学习的是Unity内置的导航系统,并结合案例进行讲解。
开发版本:Unity 2017.1.1f1、VS 2017

适合人群:初学Unity者

一.导航系统定义

在Unity手册中是这样定义的:The Navigation System allows you to create characters which can navigate the game world. It gives your characters the ability to understand that they need to take stairs to reach second floor, or to jump to get over a ditch. 

大致翻译为:导航系统允许你创建一个在游戏世界中能导航的角色。它可以让你的角色有能力去理解他们需要去爬楼梯到二楼,或者跳越一条沟渠。(本人英语翻译能力有限哈!)

二.基础知识

1.导航网格Nav Mesh

首先需要将地形设置为Navigation Static,如下图所示:

如果有不想烘焙的地方,取消勾选即可。打开Window-Navigation导航窗口,选择Bake页面,如下所示:


  • Agent Radius:定义网格和地形边缘的距离
  • Agent Height:定义可以通行的最高度
  • Max Slope:定义可以爬上楼梯的最大坡度
  • Step Height:定义可以登上台阶的最大高度
  • Drop Height:允许最大下落距离
  • Jump Distance:允许最大的跳跃距离

设置好后,可以点击Bake进行烘焙,导航网格效果如下图所示:


2.Nav Mesh Agent

为角色添加NavMeshAgent组件,用来控制角色在导航网格上移动,Unity版本不同,可能面板不太一样,如上所示:

  • Steering(操纵)
  • Speed:最大移动速度
  • Angular Speed: 行进时的最大角速度
  • Acceleration:最大加速度,控制速度的变化快慢
  • Stopping Distance:制动距离,到达目标点的距离小于这个值
  • Auto Braking:勾选,到达目标点后停止运动,没有缓冲运动

常用API:

  • ActivateCurrentOffMeshLink :激活或禁止当前off-MeshLink. 
  • CalculatePath :计算到某个点的路径并储存 
  • CompleteOffMeshLink :完成当前offMeshLink的移动 
  • Move :移动到相对于当前位置的点 
  • ResetPath :清除当前路径 
  • SetDestination :设置目标点 
  • SetPath :设置一条路线 
  • Warp :瞬移到某点
  • remainingDistance:到目标点的距离
  • desiredVelocity:期望速度,方向指向的是到达目标点的最短路径的方向

3.Nav Mesh Area

用来设置路径估值(Cost),比如走楼梯,消耗体能20,而坐电梯,消耗体能5,自然会选择后者方式。

设置完消耗代价后,在Object面板中设置类型

Unity内置了三种area,Walkable设置可行走区域,NotWalkable设置不可行走区域,如果不想让角色行走的区域,可以设置成这个。需要注意,做了修改,一定要重新烘焙网格,设置才会有效哦!

4.Off Mesh Links

地形之间可能有间隙,形成沟壑,或者是高台不能跳下,导航网格处于非连接的状态,角色无法直接跳跃,要绕一大圈才能到达目的地。off mesh links用于解决这种问题。

可以自动或者手动创建off mesh links,需要先设置bake选项卡中的Drop Height或者Jump Distance属性,前者控制跳跃高台最大高度,后者控制跳跃沟壑的最大距离。

选中需要创建Links的对象,在Object 选项卡内勾选Generate OffMeshLinks,再重新烘焙即可。需要注意,这里平台1设置了Links,而平台2没有设置。此时,角色只能从平台1跳到平台2,如下图箭头所示:

手动添加Links,需要为地形对象添加off mesh link组件,可以创建两个空对象,分别用来控制跳跃的开始和结束点,如下所示:


  • Cost Override:路径估值,和之前的Area一样
  • Bi Directional:控制跳跃是单向的还是双向的
  • Activated:控制Link是否激活
  • Auto Update Position:自动更新位置,当移动开始结束点的时候,自动更新

5.Nav Mesh Obstacle

  • Shape:选择障碍的几何形状
  • Carve:如果勾选,会重新渲染网格,效果如下所示

三.案例讲解

实现点击目标,自动寻路功能,效果如下:


前期准备:渲染好导航网格,在玩家身上挂载Nav Mesh Agent组件

添加如下脚本:

public class Player : MonoBehaviour
{
    private NavMeshAgent navMeshAgent;

    private void Start()
    {
        navMeshAgent = gameObject.GetComponent<NavMeshAgent>();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray,out hit))
            {
                Transform parent = hit.collider.transform.parent;
                if (parent != null && parent.name.Equals("Env"))
                {
                    navMeshAgent.SetDestination(hit.point);
                }
            }
        }
    }
}

下面实现代码控制角色移动,navMeshAgent.updatePosition = false; navMeshAgent.updateRotation = false;可以禁止Agent更新位置和旋转。但会造成Agent的绿框和角色分离,如下图所示,设置绿框的位置和角色位置相同即可解决问题,navMeshAgent.nextPosition = transform.position;


public class Player : MonoBehaviour
{
    private NavMeshAgent navMeshAgent;

    private void Start()
    {
        navMeshAgent = gameObject.GetComponent<NavMeshAgent>();
        //禁用NavMeshAgent更新角色位置
        navMeshAgent.updatePosition = false;
        navMeshAgent.updateRotation = false;
    }

    private 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("Floor"))
                {
                    navMeshAgent.nextPosition = transform.position;
                    navMeshAgent.SetDestination(hit.point);
                }
            }
        }
        Move();
    }

    private void Move()
    {
        if (navMeshAgent.remainingDistance < 0.5f) return;
        navMeshAgent.nextPosition = transform.position;
        if (navMeshAgent.desiredVelocity == Vector3.zero) return;
        Quaternion targetQuaternion = Quaternion.LookRotation(navMeshAgent.desiredVelocity,Vector3.up);
        transform.rotation = Quaternion.Lerp(transform.rotation, targetQuaternion, Time.deltaTime *3);
        transform.Translate(Vector3.forward * Time.deltaTime*3);
    }
}

其他可以参考学习的博客:

https://www.jianshu.com/p/70da4f9461ae

https://blog.csdn.net/janeky/article/details/17457533

https://www.jianshu.com/p/f9a32d293d2f


猜你喜欢

转载自blog.csdn.net/qq_35361471/article/details/79857501