unity3d自动寻路
在游戏开发过程中,实现角色的自动寻路并非易事。下面,我们将详细学习Unity3D自动寻路的具体过程。
自动寻路概述
自动寻路是游戏开发中的难点,属于AI(人工智能)范畴。一个游戏AI设计的优劣,可能直接影响游戏的命运。而自动寻路是AI的重要分支,其算法十分复杂。不过,Unity3D提供了一套成熟的组件来解决这一难题,接下来我们将深入了解Unity3D自带的自动寻路系统。
入门示例
在学习新知识时,我们可以先通过一个简单的例子激发兴趣。初次接触自动寻路时,建议先不要纠结操作背后的原理,以免走弯路。
步骤1:创建工程与场景
- 新建一个工程,命名为“NavMeshProject”。
- 制作场景:使用Cube创建一个地面,同时引入一个名为“Robot”的模型,并添加灯光,更换地面颜色以避免单调。
- 保存场景,命名为“TestNavgation1”。
步骤2:烘焙场景
- 打开Unity3D编辑器的菜单,选择“Window -> Navigation”,此时编辑器会出现一个“Navigation”窗口。
- 注意窗口右下角的“Bake”按钮。
- 在“Hierarchy”面板中选中地面(Plane),在“Navigation”面板的“Object”选项卡下找到“Navigation Static”复选框并勾选。
- 点击“Navigation”面板右下角的“Bake”按钮。此时,“Project”面板下会多出一个文件夹,其中包含一个子文件“NavMesh”,同时“Scene”窗口中的地面会发生变化。
步骤3:添加组件
为“Robot”添加“NavMeshAgent”组件。具体操作是在“Hierarchy”面板中选中“Robot”,然后在Unity3D菜单中选择“Component -> Navigation -> NavMeshAgent”。此时,“Robot”身上会出现一个类似胶囊体碰撞器的绿色线框包围体,可调节该组件中的“Height”、“BaseOffset”等参数。
步骤4:编写脚本
- 新建一个名为“Scripts”的文件夹。
- 编写两个脚本:
- NavMeshMove脚本:用于设置导航网格代理的目的地。
using UnityEngine; using System.Collections;
- NavMeshMove脚本:用于设置导航网格代理的目的地。
public class NavMeshMove : MonoBehaviour { public Transform[] NavMeshTransforms; // 导航网格的目的地组 private NavMeshAgent nma; // Robot的导航网格代理
void Start () {
if(NavMeshTransforms == null) {
return;
}
nma = gameObject.GetComponent
void Update () { if(nma.remainingDistance == 0) { // 当导航网格代理到达了目的地时,随机更换目的地 nma.SetDestination(NavMeshTransforms[Random.Range(0, NavMeshTransforms.Length)].position); } } }
- **NavNeshAnimation脚本**:用于为导航网格代理编写动画控制脚本。
using UnityEngine; using System.Collections;
public class NavNeshAnimation : MonoBehaviour { public float nAnimaitonSpeed = 4.0f; // 定义最大的跑步速度,一般为导航网格代理的速度 public float speedThreshold = 0.1f; // 定义动画从idle过渡的临界速度 private string loadAnimation = "load_Idle"; // 定义需要执行的协同函数名 private NavMeshAgent nma; // 定义导航网格代理
IEnumerator Start () {
nma = gameObject.GetComponent
IEnumerator load_Idle() { do { UpdateAnimationBlend(); yield return null; } while(nma.remainingDistance == 0); loadAnimation = "load_Run"; yield return null; }
IEnumerator load_Run() { do { UpdateAnimationBlend(); yield return null; } while (nma.remainingDistance != 0); loadAnimation = "load_Idle"; yield return null; }
void AnimationSetup() { animation["idle"].layer = 1; animation["run"].layer = 1; animation.SyncLayer(1); animation.CrossFade("idle", 0.1f, PlayMode.StopAll); }
void UpdateAnimationBlend() { Vector3 velocityXZ = new Vector3(nma.velocity.x, 0.0f, nma.velocity.z); float speed = velocityXZ.magnitude; animation["run"].speed = speed / nAnimaitonSpeed; if (speed > speedThreshold) { animation.CrossFade("run"); } else { animation.CrossFade("idle"); } } }
3. 将这两个脚本绑定到“Robot”身上。
4. 在“Hierarchy”面板中新建一个空的GameObject,重命名为“NavMeshPoint”,并新建5个Sphere作为它的子对象。
5. 将这5个Sphere放置在地面(Plane)的不同位置。
6. 选中“Robot”,在“Inspector”面板的“NavMeshMove”脚本上设置“NavMeshTransforms”的个数为5,并将5个Sphere分别拖拽到相应位置。
### 步骤5:运行工程
运行工程后,我们可以看到“Robot”会不断跑步,到达目标点后停下来,然后转身朝向下一个目标点,如此循环。这对于编写NPC的AI非常有帮助。
## 自动寻路相关组件
Unity中与自动寻路相关的组件主要有两个:NavMeshAgent(导航网格代理)和OffMeshLink(分离网格链接)。使用这两个组件时,必须先烘焙地形,生成NavMesh(导航网格),因为导航网格决定了带有导航网格代理的角色的活动范围。NavMeshAgent组件需附着在寻路角色(如怪物)身上,而OffMeshLink组件主要用于构造寻路角色的部分寻路路径。
### NavMeshAgent组件
NavMeshAgent组件是Unity3D寻路系统的核心组件。官方解释为:“The NavMeshAgent component is connection with pathfinding, and is the place to put information about how this agent navigates the NavMesh”,即该组件用于存放代理周游导航网格的路径信息。角色的移动依靠代理完成,每个需要自动寻路的角色都必须附着该组件,除非使用其他复杂的寻路算法。
#### 示例
1. 新建一个Scene,命名为“TestNavgation2”。
2. 使用Cube创建一个地面。
3. 在平面上创建一个Sphere,重命名为“Hero”,并为其添加“NavMeshAgent”组件(Unity菜单:Component -> Navigation -> NavMeshAgent),同时为“Hero”上色。
#### 属性解释
- **Radius**:导航代理的半径,可适当调节。
- **Speed**:导航网格代理寻路时的最大速率。
- **Acceleration**:加速度,即代理速度从0加速到“Speed”时的最大加速度。
- **Angular Speed**:最高的角速度。
- **Stopping distance**:制动距离,当代理距目的地的距离小于该值时开始减速。
- **Auto Traverse OffMesh Link**:自动移动并关闭OffMeshLinks,对利用程序操纵OffMeshLink很关键。
- **Auto Repath**:自动重新寻路,若现有路径失效,将获取新路径,一般勾选该选项。
- **Height**:导航代理的高度。
- **Base Offset**:基本偏移,可调整代理自身的包围盒。
- **Obstacle Avoidace Type**:代理躲避的水平,一般选择默认的“High Quality”。
- **NavMesh Walkable**:导航网格代理可以通过的网格层类型。
#### 常用变量和方法
- **重要变量**:
- **destination**:设置导航网格代理的目的地,如“nma.destination = Vector3类型的值”,等同于“nma.SetDestination( Vector3类型的值 )”。
- **stoppingDistance**:对应“Inspector”面板中的“Stopping Distance”。
- **velocity**:导航网格代理周游时的实时速度。
- **nextPosion**:下一个位置,在“Update”函数中打印该属性,结果与导航网格代理的路径一致。
- **steeringTarget(只读)**:导航网格代理在导航网格中周游时所经过的拐点,对制作寻路网游同步角色的Transform很重要。
- **desiredVelocity(只读)**:导航网格代理的期望速度,与当前速度不同。
- **remainingDistance( 只读 )**:导航网格代理离目的地的剩余距离,若为0则表示到达目的地。
- **isOnOffMeshLink**:判断导航网格代理当前位置是否位于OffMeshLink。
- **重要方法**:
- **SetDestination( Vector3 v )**:设置目的地,设置成功返回“true”,否则返回“false”。
- **ActivateCurrentOffMeshLink( bool activated )**:与OffMeshLink有关,“activated”为“true”时激活OffMeshLink。
- **CompleteOffMeshLink ()**:让导航网格代理完成在OffMeshLink上的周游。
- **Move( Vector3 v )**:让导航网格代理朝向量“v”的世界坐标系方向平移“v”的长度。
- **Stop()**:让导航网格代理停止寻路,可通过“Resume()”恢复寻路状态。
- **Resume()**:恢复寻路状态,角色将恢复到上次停止时的状态,目的地不变。
### 烘焙场景生成导航网格
Unity3D自带的寻路系统通过烘焙记录地形信息,并存储在NavMesh文件中。烘焙步骤如下:
1. 打开“Navigation”窗口(Window -> Navigation)。
2. 选中地面(Plane),勾选“Navigation Static”复选框。每个GameObject可标记为静态或非静态,不同的静态选项背后包含不同技术,如“Lightmap Static”用于生成光照贴图优化场景,“Occluder Static”和“Occludee Static”与遮挡剔除技术有关。导航网格代理在导航网格上周游,因此地面必须生成导航网格,需勾选“Navigation Static”属性框。
3. 勾选“OffMeshLink Generation”选项后,可不借助OffMeshLink组件生成OffMeshLink。OffMeshLink是自定义的样条线,每个OffMeshLink组件只能形成一条样条线。
#### 示例
1. 创建两个Plane,分别命名为“Plane1”和“Plane2”,在“Navigation”窗口勾选“Navigation Static”和“Off Mesh Link Generation”选项,并选择“Navigation Layer”为“Default”,然后点击“Bake”按钮。
2. 使用Cube创建一个围墙,在“Navigation”面板勾选“Navigation Static”,并将“Navigation Layer”下拉框选择为“Not Walkable”,使“Hero”绕过障碍物到达目的地。“Not Walkable”是导航网格层的内建层,也可自定义导航网格层。
3. 若烘焙后场景未出现OffMeshLink,可调整“Jump Distance”值(如调至4),再次烘焙。
4. 新建一个Cube,命名为“DS”,放置在指定位置,并编写脚本:
using UnityEngine; using System.Collections;
public class SetHeroDes : MonoBehaviour { public Transform ds; // 目的Cube的Transform private Vector3 origin; // 存储导航网格代理的初始位置 private NavMeshAgent nma; // 存储导航网格代理组件
void Start () {
nma = gameObject.GetComponent
void OnGUI() { if(GUILayout.Button("StartRun")) { nma.SetDestination(ds.position); // 设置导航网格代理的目的地 } if (GUILayout.Button("Resume")) { transform.position = origin; // 恢复导航网格代理的位置为初始位置 } } }
5. 将脚本绑定到“Hero”上,将“DS”拖拽到指定位置,运行工程后可看到“Hero”越过障碍物到达目的地。若去掉“Hero”中导航网格代理组件的“Auto Traverse Off Mesh”选项,“Hero”将无法越过沟壑到达目的地,因为该选项的作用是自动移动并关闭OffMeshLinks。
### OffMeshLink组件
#### 使用方法
1. 新建两个空的GameObject(或使用模型),分别命名为“StartPoint”和“EndPoint”。为便于识别,可使用Sphere并为其上色,将“StartPoint”放置在“Plane1”上,“EndPoint”放置在“Plane2”上。
2. 新建一个空的GameObject,命名为“SingleOffMeshLink”,将“StartPoint”和“EndPoint”作为其子物体。为“SingleOffMeshLink”添加“OffMeshLink”组件(Unity菜单栏:Component -> Navigation -> Off Mesh Link)。
3. 将“StartPoint”拖拽到“Start”上,“EndPoint”拖拽到“End”上。
4. 烘焙场景(去掉“Plane1”和“Plane2”在“Navigation”中的“OffMeshLink Generation”勾选,以自定义OffMeshLink),此时只会生成一条样条线。
#### 思考问题
最后留给读者两个问题:
1. 若打开两个平面的OffMeshLink,“Hero”会从自定义的OffMeshLink还是Plane自身生成的OffMeshLink上掠过?
2. 若去掉“Hero”身上的“Auto Traverse Off Mesh”勾选,是否会出现无法越过的问题?弄清楚这两个问题,这篇文章的目的也就达成了。后续文章将深入介绍导航网格层等相关知识,敬请期待。同时,建议读者先自行了解相关组件,与本文内容进行对比探讨。