zoukankan      html  css  js  c++  java
  • 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);  
              }  
          }  
      }  
    效果图如下,点击任何一个地点,角色都可以自动寻路过去。中间可能经过不同的障碍物,我们可以看到角色如我们所预料的一样,可以跳跃下来,可以爬楼梯,最终到达目标点。


    • 总结

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

    • 源码

    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
  • 相关阅读:
    【学习笔记】pip3 安装使用国内源
    【学习笔记】Team Explorer for Microsoft Visual Studio2015 安装时发生严重错误
    微信聊天记录长图 打印
    Go语言中用 os/exec 执行命令的五种姿势
    Python 代码调试神器:PySnooper
    终于来了!!Pyston v2.0 发布,解决 Python 慢速的救星
    超详细讲解如何使用 pdb 在服务器上调试代码
    超详细图文教你如何使用 PyCharm 进行远程调试
    最全的 pip 使用指南,50 % 你可能都没用过~
    学 Python 一定要学会的几个高阶函数
  • 原文地址:https://www.cnblogs.com/lihonglin2016/p/4361162.html
Copyright © 2011-2022 走看看