Unity手游之路<十>自动寻路Navmesh之跳跃,攀爬,斜坡" />
  详细解决方案
热度:115   发布时间:2016-05-05 04:29:30.0
Unity手游之路<十>自动寻路Navmesh之跳跃,攀爬,斜坡
 转载

Unity手游之路<十>自动寻路Navmesh之跳跃,攀爬,斜坡

分类: unity

在之前的几篇Blog总,我们已经系统学习了自动寻路插件Navmesh的相关概念和细节。然而,如果要做一个场景精美的手游,需要用到各种复杂的场景地形,而不仅仅是平地上的自动寻路。今天我们将通过一个完整的复杂的实例,来贯穿各个细节。我们将实现一个复杂的场景,角色可以在里面攀爬,跳跃,爬坡。是不是感觉很像当年的CS游戏呢?本案例将会用得一些基本的动画函数,大家可以先结合文档有个大概的了解。本实例是在官方的范例上加工而成。

(转载请注明原文地址http://blog.csdn.net/janeky/article/details/17598113

  • 步骤
1.在场景中摆放各种模型,包括地板,斜坡,山体,扶梯等
2.为所有的模型加上Navigation Static和OffMeshLink Generatic(这个根据需要,例如地板与斜坡相连,斜坡就不需要添加OffMeshLink)
3.特殊处理扶梯,需要手动添加Off Mesh Link,设置好开始点和结束点
4.保存场景,烘焙场景
5.添加角色模型,为其加Nav Mesh Agent组件
6.为角色添加一个新脚本,AgentLocomotion.cs,用来处理自动寻路,已经角色动画变换。代码比较长,大家可以结合注释来理解
[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. using UnityEngine;  using System.Collections;    public class AgentLocomotion : MonoBehaviour  {      private Vector3 target;//目标位置      private NavMeshAgent agent;      private Animation anim;//动画      private string locoState = "Locomotion_Stand";      private Vector3 linkStart;//OffMeshLink的开始点      private Vector3 linkEnd;//OffMeshLink的结束点      private Quaternion linkRotate;//OffMeshLink的旋转      private bool begin;//是否开始寻路        // Use this for initialization      void Start()      {          agent = GetComponent<NavMeshAgent>();          //自动移动并关闭OffMeshLinks,即在两个隔离障碍物直接生成的OffMeshLink,agent不会自动越过          agent.autoTraverseOffMeshLink = false;          //创建动画          AnimationSetup();          //起一个协程,处理动画状态机          StartCoroutine(AnimationStateMachine());      }        void Update()      {          //鼠标左键点击          if (Input.GetMouseButtonDown(0))          {              //摄像机到点击位置的的射线              Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);              RaycastHit hit;              if (Physics.Raycast(ray, out hit))              {                  //判断点击的是否地形                  if (hit.collider.tag.Equals("Obstacle"))                  {                      begin = true;                      //点击位置坐标                      target = hit.point;                  }              }          }          //每一帧,设置目标点          if (begin)          {              agent.SetDestination(target);          }      }        IEnumerator AnimationStateMachine()      {          //根据locoState不同的状态来处理,调用相关的函数          while (Application.isPlaying)          {              yield return StartCoroutine(locoState);          }      }        //站立      IEnumerator Locomotion_Stand()      {          do          {              UpdateAnimationBlend();              yield return new WaitForSeconds(0);          } while (agent.remainingDistance == 0);          //未到达目标点,转到下一个状态Locomotion_Move          locoState = "Locomotion_Move";          yield return null;      }        IEnumerator Locomotion_Move()      {          do          {              UpdateAnimationBlend();              yield return new WaitForSeconds(0);              //角色处于OffMeshLink,根据不同的地点,选择不同动画              if (agent.isOnOffMeshLink)              {                  locoState = SelectLinkAnimation();                  return (true);              }          } while (agent.remainingDistance != 0);          //已经到达目标点,状态转为Stand          locoState = "Locomotion_Stand";          yield return null;      }        IEnumerator Locomotion_Jump()      {          //播放跳跃动画          string linkAnim = "RunJump";          Vector3 posStart = transform.position;            agent.Stop(true);          anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll);          transform.rotation = linkRotate;            do          {              //计算新的位置              float tlerp = anim[linkAnim].normalizedTime;              Vector3 newPos = Vector3.Lerp(posStart, linkEnd, tlerp);              newPos.y += 0.4f * Mathf.Sin(3.14159f * tlerp);              transform.position = newPos;                yield return new WaitForSeconds(0);          } while (anim[linkAnim].normalizedTime < 1);          //动画恢复到Idle          anim.Play("Idle");          agent.CompleteOffMeshLink();          agent.Resume();          //下一个状态为Stand          transform.position = linkEnd;          locoState = "Locomotion_Stand";          yield return null;      }      //梯子      IEnumerator Locomotion_Ladder()      {          //梯子的中心位置          Vector3 linkCenter = (linkStart + linkEnd) * 0.5f;          string linkAnim;          //判断是在梯子上还是梯子下          if (transform.position.y > linkCenter.y)              linkAnim = "Ladder Down";          else              linkAnim = "Ladder Up";            agent.Stop(true);            Quaternion startRot = transform.rotation;          Vector3 startPos = transform.position;          float blendTime = 0.2f;          float tblend = 0f;            //角色的位置插值变化(0.2内变化)          do          {              transform.position = Vector3.Lerp(startPos, linkStart, tblend / blendTime);              transform.rotation = Quaternion.Lerp(startRot, linkRotate, tblend / blendTime);                yield return new WaitForSeconds(0);              tblend += Time.deltaTime;          } while (tblend < blendTime);          //设置位置          transform.position = linkStart;          //播放动画          anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll);          agent.ActivateCurrentOffMeshLink(false);          //等待动画结束          do          {              yield return new WaitForSeconds(0);          } while (anim[linkAnim].normalizedTime < 1);          agent.ActivateCurrentOffMeshLink(true);          //恢复Idle状态          anim.Play("Idle");          transform.position = linkEnd;          agent.CompleteOffMeshLink();          agent.Resume();          //下一个状态Stand          locoState = "Locomotion_Stand";          yield return null;      }        private string SelectLinkAnimation()      {          //获得当前的OffMeshLink数据          OffMeshLinkData link = agent.currentOffMeshLinkData;          //计算角色当前是在link的开始点还是结束点(因为OffMeshLink是双向的)          float distS = (transform.position - link.startPos).magnitude;          float distE = (transform.position - link.endPos).magnitude;            if (distS < distE)          {              linkStart = link.startPos;              linkEnd = link.endPos;          }          else          {              linkStart = link.endPos;              linkEnd = link.startPos;          }          //OffMeshLink的方向          Vector3 alignDir = linkEnd - linkStart;          //忽略y轴          alignDir.y = 0;          //计算旋转角度          linkRotate = Quaternion.LookRotation(alignDir);            //判断OffMeshLink是手动的(楼梯)还是自动生成的(跳跃)          if (link.linkType == OffMeshLinkType.LinkTypeManual)          {              return ("Locomotion_Ladder");          }          else          {              return ("Locomotion_Jump");          }      }        private void AnimationSetup()      {          anim = GetComponent<Animation>();            // 把walk和run动画放到同一层,然后同步他们的速度。          anim["Walk"].layer = 1;          anim["Run"].layer = 1;          anim.SyncLayer(1);            //设置“跳跃”,“爬楼梯”,“下楼梯”的动画模式和速度          anim["RunJump"].wrapMode = WrapMode.ClampForever;          anim["RunJump"].speed = 2;          anim["Ladder Up"].wrapMode = WrapMode.ClampForever;          anim["Ladder Up"].speed = 2;          anim["Ladder Down"].wrapMode = WrapMode.ClampForever;          anim["Ladder Down"].speed = 2;            //初始化动画状态为Idle          anim.CrossFade("Idle", 0.1f, PlayMode.StopAll);      }      //更新动画融合      private void UpdateAnimationBlend()      {          //行走速度          float walkAnimationSpeed = 1.5f;          //奔跑速度          float runAnimationSpeed = 4.0f;          //速度阀值(idle和walk的临界点)          float speedThreshold = 0.1f;            //速度,只考虑x和z          Vector3 velocityXZ = new Vector3(agent.velocity.x, 0.0f, agent.velocity.z);          //速度值          float speed = velocityXZ.magnitude;          //设置Run动画的速度          anim["Run"].speed = speed / runAnimationSpeed;          //设置Walk动画的速度          anim["Walk"].speed = speed / walkAnimationSpeed;            //根据agent的速度大小,确定animation的播放状态          if (speed > (walkAnimationSpeed + runAnimationSpeed) / 2)          {              anim.CrossFade("Run");          }          else if (speed > speedThreshold)          {              anim.CrossFade("Walk");          }          else          {              anim.CrossFade("Idle", 0.1f, PlayMode.StopAll);          }      }  }  

     

效果图如下,点击任何一个地点,角色都可以自动寻路过去。中间可能经过不同的障碍物,我们可以看到角色如我们所预料的一样,可以跳跃下来,可以爬楼梯,最终到达目标点。


 

  • 总结

 

今天的这个例子比较复杂,要根据寻路网格的类型,来处理角色的动作是普通寻路,还是攀爬,抑或跳跃。这个例子应该是比较接近真实项目了。大家在实际项目中如果还有更加复杂的寻路,[email protected]

 

  • 源码

 

http://pan.baidu.com/s/1i35cVOD

 

  • 参考资料
1.http://www.xuanyusong.com/
2.http://liweizhaolili.blog.163.com/
3.http://game.ceeger.com/Components/class-NavMeshAgent.html
  相关解决方案