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

向原创致敬http://blog.csdn.net/janeky/article/details/17598113

 

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

  • 步骤

1.在场景中摆放各种模型,包括地板,斜坡,山体,扶梯等
2.为所有的模型加上Navigation Static和OffMeshLink Generatic(这个根据需要,例如地板与斜坡相连,斜坡就不需要添加OffMeshLink)
3.特殊处理扶梯,需要手动添加Off Mesh Link,设置好开始点和结束点
4.保存场景,烘焙场景
5.添加角色模型,为其加Nav Mesh Agent组件
6.为角色添加一个新脚本,AgentLocomotion.cs,用来处理自动寻路,已经角色动画变换。代码比较长,大家可以结合注释来理解

[csharp] view plaincopy技术分享技术分享
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class AgentLocomotion : MonoBehaviour  
  5. {  
  6.     private Vector3 target;//目标位置  
  7.     private NavMeshAgent agent;  
  8.     private Animation anim;//动画  
  9.     private string locoState = "Locomotion_Stand";  
  10.     private Vector3 linkStart;//OffMeshLink的开始点  
  11.     private Vector3 linkEnd;//OffMeshLink的结束点  
  12.     private Quaternion linkRotate;//OffMeshLink的旋转  
  13.     private bool begin;//是否开始寻路  
  14.   
  15.     // Use this for initialization  
  16.     void Start()  
  17.     {  
  18.         agent = GetComponent<NavMeshAgent>();  
  19.         //自动移动并关闭OffMeshLinks,即在两个隔离障碍物直接生成的OffMeshLink,agent不会自动越过  
  20.         agent.autoTraverseOffMeshLink = false;  
  21.         //创建动画  
  22.         AnimationSetup();  
  23.         //起一个协程,处理动画状态机  
  24.         StartCoroutine(AnimationStateMachine());  
  25.     }  
  26.   
  27.     void Update()  
  28.     {  
  29.         //鼠标左键点击  
  30.         if (Input.GetMouseButtonDown(0))  
  31.         {  
  32.             //摄像机到点击位置的的射线  
  33.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
  34.             RaycastHit hit;  
  35.             if (Physics.Raycast(ray, out hit))  
  36.             {  
  37.                 //判断点击的是否地形  
  38.                 if (hit.collider.tag.Equals("Obstacle"))  
  39.                 {  
  40.                     begin = true;  
  41.                     //点击位置坐标  
  42.                     target = hit.point;  
  43.                 }  
  44.             }  
  45.         }  
  46.         //每一帧,设置目标点  
  47.         if (begin)  
  48.         {  
  49.             agent.SetDestination(target);  
  50.         }  
  51.     }  
  52.   
  53.     IEnumerator AnimationStateMachine()  
  54.     {  
  55.         //根据locoState不同的状态来处理,调用相关的函数  
  56.         while (Application.isPlaying)  
  57.         {  
  58.             yield return StartCoroutine(locoState);  
  59.         }  
  60.     }  
  61.   
  62.     //站立  
  63.     IEnumerator Locomotion_Stand()  
  64.     {  
  65.         do  
  66.         {  
  67.             UpdateAnimationBlend();  
  68.             yield return new WaitForSeconds(0);  
  69.         } while (agent.remainingDistance == 0);  
  70.         //未到达目标点,转到下一个状态Locomotion_Move  
  71.         locoState = "Locomotion_Move";  
  72.         yield return null;  
  73.     }  
  74.   
  75.     IEnumerator Locomotion_Move()  
  76.     {  
  77.         do  
  78.         {  
  79.             UpdateAnimationBlend();  
  80.             yield return new WaitForSeconds(0);  
  81.             //角色处于OffMeshLink,根据不同的地点,选择不同动画  
  82.             if (agent.isOnOffMeshLink)  
  83.             {  
  84.                 locoState = SelectLinkAnimation();  
  85.                 return (true);  
  86.             }  
  87.         } while (agent.remainingDistance != 0);  
  88.         //已经到达目标点,状态转为Stand  
  89.         locoState = "Locomotion_Stand";  
  90.         yield return null;  
  91.     }  
  92.   
  93.     IEnumerator Locomotion_Jump()  
  94.     {  
  95.         //播放跳跃动画  
  96.         string linkAnim = "RunJump";  
  97.         Vector3 posStart = transform.position;  
  98.   
  99.         agent.Stop(true);  
  100.         anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll);  
  101.         transform.rotation = linkRotate;  
  102.   
  103.         do  
  104.         {  
  105.             //计算新的位置  
  106.             float tlerp = anim[linkAnim].normalizedTime;  
  107.             Vector3 newPos = Vector3.Lerp(posStart, linkEnd, tlerp);  
  108.             newPos.y += 0.4f * Mathf.Sin(3.14159f * tlerp);  
  109.             transform.position = newPos;  
  110.   
  111.             yield return new WaitForSeconds(0);  
  112.         } while (anim[linkAnim].normalizedTime < 1);  
  113.         //动画恢复到Idle  
  114.         anim.Play("Idle");  
  115.         agent.CompleteOffMeshLink();  
  116.         agent.Resume();  
  117.         //下一个状态为Stand  
  118.         transform.position = linkEnd;  
  119.         locoState = "Locomotion_Stand";  
  120.         yield return null;  
  121.     }  
  122.     //梯子  
  123.     IEnumerator Locomotion_Ladder()  
  124.     {  
  125.         //梯子的中心位置  
  126.         Vector3 linkCenter = (linkStart + linkEnd) * 0.5f;  
  127.         string linkAnim;  
  128.         //判断是在梯子上还是梯子下  
  129.         if (transform.position.y > linkCenter.y)  
  130.             linkAnim = "Ladder Down";  
  131.         else  
  132.             linkAnim = "Ladder Up";  
  133.   
  134.         agent.Stop(true);  
  135.   
  136.         Quaternion startRot = transform.rotation;  
  137.         Vector3 startPos = transform.position;  
  138.         float blendTime = 0.2f;  
  139.         float tblend = 0f;  
  140.   
  141.         //角色的位置插值变化(0.2内变化)  
  142.         do  
  143.         {  
  144.             transform.position = Vector3.Lerp(startPos, linkStart, tblend / blendTime);  
  145.             transform.rotation = Quaternion.Lerp(startRot, linkRotate, tblend / blendTime);  
  146.   
  147.             yield return new WaitForSeconds(0);  
  148.             tblend += Time.deltaTime;  
  149.         } while (tblend < blendTime);  
  150.         //设置位置  
  151.         transform.position = linkStart;  
  152.         //播放动画  
  153.         anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll);  
  154.         agent.ActivateCurrentOffMeshLink(false);  
  155.         //等待动画结束  
  156.         do  
  157.         {  
  158.             yield return new WaitForSeconds(0);  
  159.         } while (anim[linkAnim].normalizedTime < 1);  
  160.         agent.ActivateCurrentOffMeshLink(true);  
  161.         //恢复Idle状态  
  162.         anim.Play("Idle");  
  163.         transform.position = linkEnd;  
  164.         agent.CompleteOffMeshLink();  
  165.         agent.Resume();  
  166.         //下一个状态Stand  
  167.         locoState = "Locomotion_Stand";  
  168.         yield return null;  
  169.     }  
  170.   
  171.     private string SelectLinkAnimation()  
  172.     {  
  173.         //获得当前的OffMeshLink数据  
  174.         OffMeshLinkData link = agent.currentOffMeshLinkData;  
  175.         //计算角色当前是在link的开始点还是结束点(因为OffMeshLink是双向的)  
  176.         float distS = (transform.position - link.startPos).magnitude;  
  177.         float distE = (transform.position - link.endPos).magnitude;  
  178.   
  179.         if (distS < distE)  
  180.         {  
  181.             linkStart = link.startPos;  
  182.             linkEnd = link.endPos;  
  183.         }  
  184.         else  
  185.         {  
  186.             linkStart = link.endPos;  
  187.             linkEnd = link.startPos;  
  188.         }  
  189.         //OffMeshLink的方向  
  190.         Vector3 alignDir = linkEnd - linkStart;  
  191.         //忽略y轴  
  192.         alignDir.y = 0;  
  193.         //计算旋转角度  
  194.         linkRotate = Quaternion.LookRotation(alignDir);  
  195.   
  196.         //判断OffMeshLink是手动的(楼梯)还是自动生成的(跳跃)  
  197.         if (link.linkType == OffMeshLinkType.LinkTypeManual)  
  198.         {  
  199.             return ("Locomotion_Ladder");  
  200.         }  
  201.         else  
  202.         {  
  203.             return ("Locomotion_Jump");  
  204.         }  
  205.     }  
  206.   
  207.     private void AnimationSetup()  
  208.     {  
  209.         anim = GetComponent<Animation>();  
  210.   
  211.         // 把walk和run动画放到同一层,然后同步他们的速度。  
  212.         anim["Walk"].layer = 1;  
  213.         anim["Run"].layer = 1;  
  214.         anim.SyncLayer(1);  
  215.   
  216.         //设置“跳跃”,“爬楼梯”,“下楼梯”的动画模式和速度  
  217.         anim["RunJump"].wrapMode = WrapMode.ClampForever;  
  218.         anim["RunJump"].speed = 2;  
  219.         anim["Ladder Up"].wrapMode = WrapMode.ClampForever;  
  220.         anim["Ladder Up"].speed = 2;  
  221.         anim["Ladder Down"].wrapMode = WrapMode.ClampForever;  
  222.         anim["Ladder Down"].speed = 2;  
  223.   
  224.         //初始化动画状态为Idle  
  225.         anim.CrossFade("Idle", 0.1f, PlayMode.StopAll);  
  226.     }  
  227.     //更新动画融合  
  228.     private void UpdateAnimationBlend()  
  229.     {  
  230.         //行走速度  
  231.         float walkAnimationSpeed = 1.5f;  
  232.         //奔跑速度  
  233.         float runAnimationSpeed = 4.0f;  
  234.         //速度阀值(idle和walk的临界点)  
  235.         float speedThreshold = 0.1f;  
  236.   
  237.         //速度,只考虑x和z  
  238.         Vector3 velocityXZ = new Vector3(agent.velocity.x, 0.0f, agent.velocity.z);  
  239.         //速度值  
  240.         float speed = velocityXZ.magnitude;  
  241.         //设置Run动画的速度  
  242.         anim["Run"].speed = speed / runAnimationSpeed;  
  243.         //设置Walk动画的速度  
  244.         anim["Walk"].speed = speed / walkAnimationSpeed;  
  245.   
  246.         //根据agent的速度大小,确定animation的播放状态  
  247.         if (speed > (walkAnimationSpeed + runAnimationSpeed) / 2)  
  248.         {  
  249.             anim.CrossFade("Run");  
  250.         }  
  251.         else if (speed > speedThreshold)  
  252.         {  
  253.             anim.CrossFade("Walk");  
  254.         }  
  255.         else  
  256.         {  
  257.             anim.CrossFade("Idle", 0.1f, PlayMode.StopAll);  
  258.         }  
  259.     }  
  260. }  

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

 

  • 总结

 

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

 

  • 源码

 

 

  • 参考资料

1.http://www.xuanyusong.com/
2.http://liweizhaolili.blog.163.com/
3.http://game.ceeger.com/Components/class-NavMeshAgent.html

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。