大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是blog.csdn.net/qinyuanpei。这段时间博主将大部分的精力都放在了研究官方示例项目上,主要是希望能够从中挖掘出有价值的东西分享给大家,这样博主和大家可以共同学习。好了,那么今天博主想和大家分享的是自动寻路与Mecanim动画系统结合起来实现的一个小案例,希望对大家学习Unity3D能够有所帮助。
博主曾经告诉大家,博主是一个仙剑迷,平时学习编程累了的时候,博主就会玩玩仙剑,如图是博主偶尔回去玩玩的《新仙剑OL》,仙剑的网游化道路似乎一直不曾平坦,从最初的《仙剑OL》到现在的《新仙剑OL》,仙剑的网游化一直处于不温不火的状态。
虽然博主比较反感网游,可是作为一款由博主喜欢的单机游戏改编的网游,博主还是忍不住去尝试了一下。基于Unity3D的《新仙剑OL》虽然从画面质量上与同类网页游戏拉开了距离,然而在游戏的玩法和体验上依然没有摆脱国产网游的固定模式,博主叹息之余,便根据这款游戏在自动寻路上的设计写出了这篇文章。说到自动寻路,这几乎是目前所有网页游戏的标准配置了,在博主看来,自动寻路在简化玩家寻找目标的途径的同时,弱化了玩家的参与,使得本该玩家自行探索的游戏世界,变成了枯燥无味的鼠标游戏。曾几何时,我们在迷宫中探索着远方的路,曾几何时,我们开启机关、破解阵法收获着游戏的乐趣。在这样一个快节奏的时代,我们却只能一遍遍地回想着过去的生活,那个时候没有电脑、没有智能手机、没有电影.....可是我们却常常怀念那个时候简单的生活,所以,小时候,幸福是件简单的事情,长大后,简单是件幸福的事情。尽管博主讨厌自动寻路这一设定,可是这与我们学习Unity3D无关,因为我们只是为了更好地使用这个引擎来做出好的游戏产品。好了,闲话少叙,我们继续说《新仙剑OL》中的自动寻路,在游戏中玩家可以通过鼠标来控制人物,如果距离较远,则人物会以奔跑的形式到达目标位置,否则人物将步行到目标位置。而这就是我们今天想要实现的效果。好了,下面我们正式开始今天的内容,今天的内容呢,分为两个部分,第一部分讲述Mecanim动画系统,第二部分讲述自动寻路。
第一部分:Mecanim动画系统
在这一部分,我们主要讲的是动画的切换,因为从刚才的描述中我们知道,角色的动画有三种状态,即Idle、Walk、Run。通过前面的学习呢,我们知道通过Mecanim动画系统的状态机我们可以很方便地实现动画状态的切换。由此我们就可以理出一个大致的思路,通过鼠标点击获取鼠标位置,然后我们利用射线的方法,从摄像机发射一条经过该点的射线,则射线与地面的交点就是我们寻路的目标点,我们通过计算角色与目标点之间的距离来确定角色要采用什么样的动画,而寻路则交给Unity3D的Nav Mesh Agent组件来完成,这就是我们今天的实现思路,其实博主在之前的一篇文章中已经讲述过这种方法,这里不过是在之前的基础上,结合Mecanim动画系统和自动寻路组件做了些改进而已。好了,我们打开我们的项目,如图,是博主创建的一个简单的场景,我们今天的内容就以这个场景来展开:
接下来,我们利用Mecanim动画系统来设计角色的动画,如图,在今天的项目中角色只有三种状态,因此我们可以将三种状态连接起来,这里我们定义了两个Bool类型的变量IsWalk和IsRun,默认为False,这是我们今天用来切换动画的两个变量开关。剩下的工作就是编写脚本来控制动画了,这一步我们放在第二部分来讲。
第二部分:自动寻路
在这一部分,我们首先要对场景进行烘培,因为Nav Mesh Agent组件是根据网格来计算寻路的路线的,所以烘培的过程相当于是在保存场景中的网格信息,只有这样我们才能够使用Unity3D的寻路组件。下面我们就来讲解下场景的烘焙:
首先我们选中场景中不需要与玩家发生交互的物体,或者可以认为这些物体是我们的角色需要避开的障碍物,将其设置为Static,如图:
接下来我们通过Windowe->Navigation命令打开Navigation窗口,此时被选中的物体以深色显示,确认无误后,点击Bake按钮对场景进行烘焙,这样我们就完成了场景的烘焙工作。
好了,下面我们选中场景中的角色,为其添加Nav Mesh Agent组件,此时场景中会显示出绿色的区域,其含义是这些区域角色可以到达。关于Nav Mesh Agent组件的参数,大家可以自行查阅API文档,这里不做解释了,好了,下面我们编写脚本:
using UnityEngine; using System.Collections; public class PeopleScripts : MonoBehaviour { //动画组件 private Animator mAnim; //移动速度 public float MoveSpeed=2.5F; //寻路组件 private NavMeshAgent mAgent; //寻路目标标记 private GameObject Ball; //寻路标记预制件 public GameObject PrefabBall; void Start () { //获取动画组件 mAnim=GetComponent<Animator>(); //获取寻路组件 mAgent=GetComponent<NavMeshAgent>(); } void Update () { //按下鼠标左键 if(Input.GetMouseButton(0)) { //获取鼠标位置 Vector3 mPos=Input.mousePosition; //利用射线法取得目标位置 Ray mRay=Camera.main.ScreenPointToRay(mPos); RaycastHit mHit; if(Physics.Raycast(mRay,out mHit)) { //这里对应于场景中的地面、墙体、楼梯三种结构 if(mHit.collider.tag=="Ground" || mHit.collider.tag=="Wall" || mHit.collider.tag=="Ti") { //获得目标位置 Vector3 mTarget=mHit.point; //使用完全面向目标的旋转 transform.LookAt(mTarget); //使用平滑转身转向目标 //SmoothRotate(mTarget); //计算距离 float mDistance=Vector3.Distance(mTarget,this.transform.position); //当距离大于4时奔跑到目标位置,否则步行到目标位置 if(mDistance>4F){ mAnim.SetBool("IsRun",true); }else{ mAnim.SetBool("IsWalk",true); } //根据不同的结构生成不同高度的寻路目标标记 if(mHit.collider.tag=="Ground"){ //标记寻路目标 Ball=(GameObject)Instantiate(PrefabBall,new Vector3(mTarget.x,0.5F,mTarget.z), Quaternion.identity); }else{ //标记寻路目标 Ball=(GameObject)Instantiate(PrefabBall,new Vector3(mTarget.x,3.0F,mTarget.z), Quaternion.identity); } //设置寻路目标 mAgent.SetDestination(mTarget); } } } } void OnTriggerEnter(Collider mCollider) { if(mCollider.tag=="Ball") { //获取目标标记 GameObject mBall=mCollider.gameObject; //销毁目标标记 Destroy(mBall); //将角色状态设为Idle mAnim.SetBool("IsRun",false); mAnim.SetBool("IsWalk",false); } } //平滑转身,参考自Stealth void SmoothRotate(Vector3 target) { //构造目标朝向 Quaternion targetRotation = Quaternion.LookRotation(target, Vector3.up); //对目标朝向进行插值 Quaternion mRotation = Quaternion.Lerp(transform.rotation, targetRotation, 15F * Time.deltaTime); //赋值 transform.rotation=mRotation; } }
最后,我们来看看最后的效果吧,为了让大家更清楚的看到寻路的效果,博主在寻路点处增加了一个紫色的小球,便于大家观察: