不少游戏中,你只需在屏幕上单击一下,角色就可以向那个地方奔跑,然后到达目的地停了下来 ,Dota里面就是这样的 。今天,就让我们来实现这一功能吧。我们这次是借助Unity3d里面的自动寻路组件来实现这一功能,所以对此组件不熟悉的读者我希望您事先看一下这方面的资料,不会花你多长时间的。好了,让我们开始吧!
首先,我们得有一个工作目录,所以我们得新建一个工程,我把它命名为:TestNavMeshAgent,然后保存当前的Scene,给它一个名字:RobotNavMesh。接下来我们开始搭建我们的场景了。因为我不会做模型,所以像往常一样,我从别的工程里面抠出了一个角色:Robot,还是官方的CharacterAnimation这个工程里的一个机器人模型。我们得新建一个平面(用Cube做的)作为Robot落脚的地方,还是给这个平面一个颜色吧,并且加一个灯光吧。调节一下摄像机的位置与旋转角到一个比较合适的程度,就像下面这样:
我们接下来该烘焙平面,生成导航网格了。如果读者对此部分不是很熟悉,建议认真研究一下,也没什么难度,如果不熟悉英文,就到Unity圣典上看中文算了。
好了,我们开始烘焙场景了。选中我们的地面,然后打开Navigation窗口:Window->Navigation。勾选Navigation Static 这个选项,并且打开Navigation Layer下拉菜单,然后选择Default,最后单击Navigation窗口的右下角的Bake按钮。我们可以发现,Scene中的地面颜色发生了改变,如:
这就是导航网格生成后的场景。然后我们往Robot身上添加一个组件:NavMeshAgent,即:Unity菜单栏:Component->Navigation->Nav Mesh Agent。这个组件就是寻路系统的核心组件,我们接下来将利用这个组件来完成Robot的移动了。接下来我们必须写几个脚本,我写了3个脚本:RayCastPerFrame.cs, NavMeshController.cs, RobotNavAnimaiton.cs。RayCastPerFrame.cs的代码如下:
using UnityEngine;
using System.Collections;
public class RayCastPerFrame : MonoBehaviour {//这个脚本将要被附着到Robot身上
private RaycastHit hitInfo ;
void Update () {
if(Input.GetMouseButtonDown(1)){//当我们点击鼠标右键时,
Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out hitInfo);
//以摄像机为原点,向鼠标光标世界坐标点发射射线投射碰撞
}
}
public RaycastHit GetHitInfo()
{
return hitInfo;//得到碰撞信息
}
}
这个脚本比较简单,是专门获取射线碰撞信息hitInfo的。
NavMeshController.cs的代码如下:
using UnityEngine;
using System.Collections;
public class NavMeshController : MonoBehaviour {//此脚本将附着到Robot上
public RayCastPerFrame rcpf;//待会会将Robot拖拽到这个变量上,为的是直接访问Robot身上的RayCastFrame脚本。
private NavMeshAgent nma;//用于存储Robot身上附带的NavMeshAgent组件
void Start () {
nma = gameObject.GetComponent();
//实例化nma
}
void Update () {
}
RobotNavAnimaiton.cs的代码如下:
using UnityEngine;
using System.Collections;
public class RobotNavAnimation : MonoBehaviour {//此脚本呢附着在Robot身上
private NavMeshAgent nma;//用于存储Robot身上附带的NavMeshAgent组件
void Start()
{
nma = gameObject.GetComponent();//实例化nma
animation.AddClip(animation["shoot"].clip, "shootUpperBody");
animation["shootUpperBody"].AddMixingTransform(transform.Find("mover/gun"));
animation["shootUpperBody"].AddMixingTransform(transform.Find("mover/roothandle/spine1"));
animation.wrapMode = WrapMode.Loop;
animation["jump"].wrapMode = WrapMode.Clamp;
animation["shoot"].wrapMode = WrapMode.Clamp;
animation["shootUpperBody"].wrapMode = WrapMode.Clamp;
animation["idle"].layer = -1;
animation["***n"].layer = -1;
animation.Stop();
}
void Update () {
if (nma.remainingDistance != 0)//如果导航代理还没有到达目的地,则播放跑的动画。
{
animation.CrossFade("***n");
//animation["***n"].speed = Mathf.Sign(Input.GetAxis("Vertical"));
}
else //否则播放站立的动画
{
animation.CrossFade("idle");
}
if (Input.GetButtonDown("Jump"))
{
animation.CrossFade("jump", 0.3f);
}
if (Input.GetButtonDown("Fire1"))
{
if (animation["***n"].weight > 0.5f)
animation.CrossFadeQueued("shootUpperBody", 0.3f, QueueMode.PlayNow);
else
animation.CrossFadeQueued("shoot", 0.3f, QueueMode.PlayNow);
}
}
}
这个脚本涉及到了动画的混合,现在就先将就着看吧,我或许会在接下来的日子里写出我的第四篇有关与动画方面的文章,有兴趣的可以留意一下。
最后,我们将这三个脚本绑定在Robot上面,并且在Hierarchy面板中将Robot拖拽到NavMeshController这个脚本中的Rcpf这个选项上,如下图:
寻路时的状态,已经达到了我们想要的效果。
我们还可以家一个障碍物,这个就留给读者自己添加吧。可是你有没有注意到一点,RayCastPerFrame .cs这个射线检测的脚本是每帧都在检测,NavMeshController.cs也是每帧都在获取碰撞信息,每帧设定目的地。这个就相当消耗性能。那么这个问题就留给读者自行解决吧!我提个思路,比如说当距离上次检测碰撞信息的时间超过0.1s时设置导航网格代理的目的地,这个应该是很好实现的。
首先,我们得有一个工作目录,所以我们得新建一个工程,我把它命名为:TestNavMeshAgent,然后保存当前的Scene,给它一个名字:RobotNavMesh。接下来我们开始搭建我们的场景了。因为我不会做模型,所以像往常一样,我从别的工程里面抠出了一个角色:Robot,还是官方的CharacterAnimation这个工程里的一个机器人模型。我们得新建一个平面(用Cube做的)作为Robot落脚的地方,还是给这个平面一个颜色吧,并且加一个灯光吧。调节一下摄像机的位置与旋转角到一个比较合适的程度,就像下面这样:
我们接下来该烘焙平面,生成导航网格了。如果读者对此部分不是很熟悉,建议认真研究一下,也没什么难度,如果不熟悉英文,就到Unity圣典上看中文算了。
好了,我们开始烘焙场景了。选中我们的地面,然后打开Navigation窗口:Window->Navigation。勾选Navigation Static 这个选项,并且打开Navigation Layer下拉菜单,然后选择Default,最后单击Navigation窗口的右下角的Bake按钮。我们可以发现,Scene中的地面颜色发生了改变,如:
这就是导航网格生成后的场景。然后我们往Robot身上添加一个组件:NavMeshAgent,即:Unity菜单栏:Component->Navigation->Nav Mesh Agent。这个组件就是寻路系统的核心组件,我们接下来将利用这个组件来完成Robot的移动了。接下来我们必须写几个脚本,我写了3个脚本:RayCastPerFrame.cs, NavMeshController.cs, RobotNavAnimaiton.cs。RayCastPerFrame.cs的代码如下:
using UnityEngine;
using System.Collections;
public class RayCastPerFrame : MonoBehaviour {//这个脚本将要被附着到Robot身上
private RaycastHit hitInfo ;
void Update () {
if(Input.GetMouseButtonDown(1)){//当我们点击鼠标右键时,
Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out hitInfo);
//以摄像机为原点,向鼠标光标世界坐标点发射射线投射碰撞
}
}
public RaycastHit GetHitInfo()
{
return hitInfo;//得到碰撞信息
}
}
这个脚本比较简单,是专门获取射线碰撞信息hitInfo的。
NavMeshController.cs的代码如下:
using UnityEngine;
using System.Collections;
public class NavMeshController : MonoBehaviour {//此脚本将附着到Robot上
public RayCastPerFrame rcpf;//待会会将Robot拖拽到这个变量上,为的是直接访问Robot身上的RayCastFrame脚本。
private NavMeshAgent nma;//用于存储Robot身上附带的NavMeshAgent组件
void Start () {
nma = gameObject.GetComponent();
//实例化nma
}
void Update () {
if(!rcpf){
return;
}
RaycastHit hit = rcpf.GetHitInfo();//获取碰撞点}
if (hit.transform)
{
nma.SetDestination(hit.point);//设置目的地位射线与平面的碰撞点
}
}
RobotNavAnimaiton.cs的代码如下:
using UnityEngine;
using System.Collections;
public class RobotNavAnimation : MonoBehaviour {//此脚本呢附着在Robot身上
private NavMeshAgent nma;//用于存储Robot身上附带的NavMeshAgent组件
void Start()
{
nma = gameObject.GetComponent();//实例化nma
animation.AddClip(animation["shoot"].clip, "shootUpperBody");
animation["shootUpperBody"].AddMixingTransform(transform.Find("mover/gun"));
animation["shootUpperBody"].AddMixingTransform(transform.Find("mover/roothandle/spine1"));
animation.wrapMode = WrapMode.Loop;
animation["jump"].wrapMode = WrapMode.Clamp;
animation["shoot"].wrapMode = WrapMode.Clamp;
animation["shootUpperBody"].wrapMode = WrapMode.Clamp;
animation["idle"].layer = -1;
animation["***n"].layer = -1;
animation.Stop();
}
void Update () {
if (nma.remainingDistance != 0)//如果导航代理还没有到达目的地,则播放跑的动画。
{
animation.CrossFade("***n");
//animation["***n"].speed = Mathf.Sign(Input.GetAxis("Vertical"));
}
else //否则播放站立的动画
{
animation.CrossFade("idle");
}
if (Input.GetButtonDown("Jump"))
{
animation.CrossFade("jump", 0.3f);
}
if (Input.GetButtonDown("Fire1"))
{
if (animation["***n"].weight > 0.5f)
animation.CrossFadeQueued("shootUpperBody", 0.3f, QueueMode.PlayNow);
else
animation.CrossFadeQueued("shoot", 0.3f, QueueMode.PlayNow);
}
}
}
这个脚本涉及到了动画的混合,现在就先将就着看吧,我或许会在接下来的日子里写出我的第四篇有关与动画方面的文章,有兴趣的可以留意一下。
最后,我们将这三个脚本绑定在Robot上面,并且在Hierarchy面板中将Robot拖拽到NavMeshController这个脚本中的Rcpf这个选项上,如下图:
寻路时的状态,已经达到了我们想要的效果。
我们还可以家一个障碍物,这个就留给读者自己添加吧。可是你有没有注意到一点,RayCastPerFrame .cs这个射线检测的脚本是每帧都在检测,NavMeshController.cs也是每帧都在获取碰撞信息,每帧设定目的地。这个就相当消耗性能。那么这个问题就留给读者自行解决吧!我提个思路,比如说当距离上次检测碰撞信息的时间超过0.1s时设置导航网格代理的目的地,这个应该是很好实现的。