如何使用Unity做游戏中的寻路导航

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用Unity做游戏中的寻路导航相关的知识,希望对你有一定的参考价值。

参考技术A 现在的大部分mmo游戏都有了自动寻路功能。点击场景上的一个位置,角色就会自动寻路过去。中间可能会有很多的障碍物,角色会自动绕过障碍物,最终达到终点。使用Unity来开发手游,自动寻路可以有很多种实现方式。
  最近,一名海外开发者在博客中分享了自己用Unity引擎重做此前研发的Flash游戏寻路导航的心得,希望可以给大家带来帮助:
  大家好,最近我一直都在忙于把2006年的一款Flash游戏用Unity引擎重做出来,尽管我们在《Arrival in Hell》这个项目已经工作了一年多,但这里我希望从头开始来写开发者博客,因为这样才能让读者们有比较完整的印象。
  如果你们不太熟悉这款游戏的话,我这里做几句话的介绍,我们在对2006年我和朋友Eduardo Mojica以及Richard Rout三人研发的一款Flash游戏进行重做,这是一款点击式操作的冒险游戏,我们将用Unity引擎进行重做。我做编程和研发游戏已经有十年左右的经验,但这是我使用Unity引擎做的首款游戏。
  在其他事情之前,我首先想要说的就是玩家角色的移动,由于这款游戏现在是真正的3D,因此玩家角色需要在3D空间里寻路。幸运的是,Unity引擎已经有了一些不错的内置寻路功能,你只要打开窗口-导航(Navigation),选择你想要使用的物体并且放到路径中,然后把他们标记为‘导航静态(Navigation static)’这就会告诉Unity这些物体是静态的(非移动),在寻路的时候应该被考虑进去。

把物体设置为‘导航静态’
  这里我想要说一说这个功能有多么强大。过去,我和大多数的游戏开发者一样,都必须打造自己的寻路系统,我之前就做过一个A*tile和基于节点的寻路系统,在两种情况下,特别是基于节点系统的寻路所产生的walls让人非常头痛。在基于节点的寻路系统中,你必须手动地把AI使用的点在两者之间进行导航。Unity不仅做导航功能,还使用了导航网格(Navigation meshes),这比手动放置节点更有效率而且更流畅。更重要的是,你还可以一键重新计算整个导航网格,彻底摆脱了手动修改导航节点的做法。

我用基于节点系统做的失败的寻路系统之一
  在把静态物体加入了导航网格之后,你可以选择一系列的设定然后点击bake按钮,比如在考虑加入一堵墙之前确定坡有多陡以及台阶应该多高。这样你就可以获得可以预览的视图。值得注意的一件事是,不要仅仅因为物体存在在场景中就意味着它是导航网格的一部分。比如说在这款游戏中,我不在乎玩家们是否会踩到瓦砾,所以我并没有把任何瓦砾标识为导航静态,这加快了当行网格的生成速度。

《Arrival in Hell》中其实是有数值的
  在导航网格生成之后,我简单地给玩家模型增加了一个NavMeshAgent组件,这款游戏现在就可以进行寻路了,唯一剩下的就是增加鼠标输入控制NavMeshAgent的目的地。

用NavMesh做的bake

NavMeshAgent设定
  为了告诉NavMeshAgent导航我做了以下指令:
  1.注意听取鼠标输入
  2.把鼠标放进屏幕空间
  3.把屏幕空间转变成来自摄像头的一束光
  4.在光达到地面的时候把它移除
  5.把NavMeshAgent的目的地设定到地板的对应位置。
  C#代码是这样的:

可视化视图下的目的地与路径
  这就解决了我这款游戏的大多数导航需求,唯一的例外就是导航网格由于游戏内的一些活动而发生改变的时候。比如第一个房间的们最开始是关闭的,后来当它打开的时候,当行网格需要更新反映此次变化,允许玩家从新开的们中走过去。我并没有在游戏运行的时候rebake完整的静态导航网格,而是使用了NavMeshObstacle组件,该组件可以让你把寻路过程中的动态物体加进去,如果物体移动,Unity的寻路算法就会根据实际情况而更新。

导航路径会根据NavMeshObstacle的变化而自动发生改变

可视化视图
  所以,游戏寻路导航就这么做好了,这就是《Arrival in Hell》游戏中的导航工作原理,这一些只需要Unity内自带的导航功能就可以完成了。

Unity导航

  在如今当下的互联网时代背景下,Unity3D作为游戏开发的主要工具,为我们游戏开发提供了诸多便利.

  说到游戏我们不得不提到一个不可或缺的组成部分:导航系统;

      NavMesh(导航网格)是3D游戏世界中用于实现动态物体自动寻路的一种技术,从而实现游戏自动寻路的功效.

那我们今天就来搭建一个简单的自动寻路的导航系统:

首先:我们要有一个可以供我们游戏物体移动的游戏场景,如:一个Plane或者一个Scene;

这里,作者使用了一个简单的塔防场景;

游戏场景设置好了,我们下一步做什么呢?

   找到检视窗口点击Navigation烘焙路径; 当然我们这里要将我们要烘焙的游戏物体呢,设置为Static;当然不需要有路径的地方就不需要设置Static了;

设置好了以后一定要记得点击下方的Bake烘焙;

 

第二步: 给我们的player设置一个StartPoint(起始点)和EndPoint(目标点);  

这里使用空物体来创建起始点和目标点,并为了方便查看给它们添加标记;

好了,起始点和目标点设置好了,那我们就要对player进行操作了;

因为我们这里要让player在行进的途中有行走操作,所以我们要用到动画; 动画还记得吗? idle,walk

 

第三步:拖一个player模型到参差视图中,添加Nav Mesh Agent组件,并为它设置Animator中的Controller(动画状态机);

 

双击打开进行编辑状态机; 将idle状态和walk状态拖到状态机里面,并设置idle为最初始状态 ,并设置状态参数,这里我们使用的是int;

 

 

 

 

 

 

然后将我们设置好的状态机设置给我们的Player;

 

 

第四步:添加让player移动和播放动画的脚本;然后将player拖成预设体,将原来层次视图中的player删除;

 

 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 using UnityEngine.AI;
 5 
 6 public class PlayerMoveScript : MonoBehaviour {
 7     
 8     private Animator playerAnimator;
 9     private NavMeshAgent agent;  // 用来接收获得的组件
10     private float time; // 计时
11     private int index; 
12     private int move; // 用来转换哈希值
13 
14 //    public Transform[] targetPoint;
15     public Transform endPoint;  // 设置目的地
16 
17     void Start () {
18         // 获得自动寻路的组件
19         agent = GetComponent<NavMeshAgent> ();
20         // 获得动画的组件
21         playerAnimator = GetComponent<Animator> ();
22         //转换哈希值
23         move = Animator.StringToHash ("Move");
24 
25     }
26 
27     void Update () {
28 
29         time+=Time.deltaTime;
30         // 一开始有1秒的idle时间
31         if (time >= 1.0f) {
32             // 如果开始生成的时候处于idle状态
33             if (playerAnimator.GetCurrentAnimatorStateInfo (0).shortNameHash == Animator.StringToHash ("idle")) {
34                 //设置状态参数,并开始移动的动画;
35                 playerAnimator.SetInteger (move,1);
36                 // 设置目标点
37                 agent.destination = endPoint.position;
38             }
39         }
40         //如果起始点和终止点的位置距离为0
41         if (agent.remainingDistance == 0) {
42             //并且现在的游戏物体处于walk状态
43             if (playerAnimator.GetCurrentAnimatorStateInfo (0).shortNameHash == Animator.StringToHash ("walk")) {
44                 //那么执行下一步动画操作,设置状态参数
45                 playerAnimator.SetInteger (move, 0);
46                 //删除游戏物体
47                 Destroy (gameObject);
48             }    
49         }
50     }
51         
52 }

这样player的一切组件和脚本准备就绪,那么下一步我们是不是就要生成player!(因为我们player是预设体);

 

第五步:生成player(在StartPiont位置生成);建立一个空物体来放置脚本

 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 
 5 public class CreatPlayerScript : MonoBehaviour {
 6 
 7     public GameObject PlayerPerfabs; // 预设体
 8     public Transform startPoint; // 开始生成预设体的位置
 9     public Transform endPoint; //目标位置
10 
11     void Start () {
12         
13     }
14     
15     void Update () {
16         //按下鼠标左键做什么事情
17         if (Input.GetMouseButtonDown (0)) {
18             //在startPoint位置生成预设体,并让预设体不能旋转;
19             GameObject Player = Instantiate (PlayerPerfabs, startPoint.position, Quaternion.identity)as GameObject;
20             // 设置目标点
21             Player.GetComponent<PlayerMoveScript> ().endPoint = endPoint;
22         }
23         
24     }
25 }

然后将我们外联进行设置;

 

我们可以创建一个Camera给我们的player,设置好位置,就可以去Game场景去查看我们的效果了.

好了,一个简单的导航就实现了,当然了小伙伴们可以去试试给游戏场景添加一个MiniMap(小地图);

 

注意:我们在做小地图的时候呢,为了不占用过多资源(就是优化嘛);可以将我们的小地图的Camera的渲染层数减少;进行一个优化

 

以上是关于如何使用Unity做游戏中的寻路导航的主要内容,如果未能解决你的问题,请参考以下文章

unity3d 如何动态寻路?

unity astarpathfindingproject 的寻路数据 怎么导出到后端使用

游戏的寻路算法

Pacman 的寻路算法

Unity A星(A Star/A*)寻路算法

Unity 之 手把手教你实现自己Unity2D游戏寻路逻辑 文末源码