http://blog.csdn.net/janeky/article/details/17598113

在之前的几篇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,用来处理自动寻路,已经角色动画变换。代码比较长,大家可以结合注释来理解

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

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

  • 总结

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

Unity手游之路<十>自动寻路Navmesh之跳跃,攀爬,斜坡的更多相关文章

  1. Unity手游之路<八>自动寻路Navmesh之入门

    http://blog.csdn.net/janeky/article/details/17457533 在的大部分mmo游戏都有了自动寻路功能.点击场景上的一个位置,角色就会自动寻路过去.中间可能会 ...

  2. Unity 自动寻路Navmesh之跳跃,攀爬,斜坡

    在之前的几篇Blog总,我们已经系统学习了自动寻路插件Navmesh的相关概念和细节.然而,如果要做一个场景精美的手游,需要用到各种复杂的场景地形,而不仅仅是平地上的自动寻路.今天我们将通过一个完整的 ...

  3. Unity手游之路<九>自动寻路Navmesh之高级主题

    http://blog.csdn.net/janeky/article/details/17492531 之前我们一起学习了如何使用Navmesh组件来实现最基本的角色自动寻路.今天我们再继续深入探索 ...

  4. Unity手游之路<十二>手游资源热更新策略探讨

    http://blog.csdn.net/janeky/article/details/17666409 上一次我们学习了如何将资源进行打包.这次就可以用上场了,我们来探讨一下手游资源的增量更新策略. ...

  5. [转]Unity手游之路<十二>手游资源热更新策略探讨

    最近梳理了下游戏流程.恩,本来想写下,但是,还是看前辈的吧 版权声明: https://blog.csdn.net/janeky/article/details/17666409 上一次我们学习了如何 ...

  6. 【学习】Unity手游之路<十二>手游资源热更新策略探讨

    http://blog.csdn.net/janeky/article/details/17666409 =============================================== ...

  7. Unity手游之路<七>角色控制器

    Unity手游之路<七>角色控制器 我们要控制角色的移动,可以全部细节都由自己来实现.控制角色模型的移动,同时移动摄影机,改变视角.当然Unity也提供了一些组件,可以让我们做更少的工作, ...

  8. Unity手游之路自动寻路Navmesh之入门

    http://blog.csdn.net/janeky/article/details/17457533 现在的大部分mmo游戏都有了自动寻路功能.点击场景上的一个位置,角色就会自动寻路过去.中间可能 ...

  9. Unity手游:自动寻路Navmesh 跳跃 攀爬 斜坡

    原地址:http://dong2008hong.blog.163.com/blog/static/46968827201403114644210/ 步骤 1.在场景中摆放各种模型,包括地板,斜坡,山体 ...

随机推荐

  1. JavaWeb---总结(十)HttpServletRequest对象(一)

    一.HttpServletRequest介绍 HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象 ...

  2. Saltstack之Syndic(十)

    Saltstack之Syndic 使用条件: 1.salt syndic必须运行在一台master上 2.salt syndic必须依赖更高级的master 安装 yum install -y sal ...

  3. Ps 技巧

    一.动作(批处理) 二.让图片更清晰 三.标尺 四.画面还原 五.内容识别比例(改变身材) 六.移花接木 七.多人头像 八.多重曝光 九.突出肌肉线条或者脸部轮廓 十.给照片换一个天空 十一.制作光束 ...

  4. spring-data-jpa 的@Query注解的使用

    // ------------------------------------ 使用 @Query 注解 // 没有参数的查询 @Query("select p from Person p ...

  5. quartz_jobs.xml标准配置

    <?xml version="1.0" encoding="UTF-8"?><job-scheduling-data xmlns=" ...

  6. 运用java集合Collections对List进行max和min操作

    我想创建了一个List,里面有一堆的数,一个需求是去掉一个最大值,一个最小值,List有remove的方法,但是找到最大值和最小值的方法不在List里,而是Collections的静态方法.真心觉得还 ...

  7. thinkphp模板引擎

    $this->assing('result'.$result) html页面代码 <foreach name='result item='vo'> <div>{$vo[' ...

  8. php ceil() 函数向上舍入为最接近的整数。

    代码: <?php echo(ceil(0.60); echo(ceil(0.40); echo(ceil(); echo(ceil(5.1); echo(ceil(-5.1); echo(ce ...

  9. iOS项目重命名以及Xcode修改Scheme名称图文详解 (yoowei)

    在iOS开发中,有时候想改一下项目的名字,都会遇到很多麻烦.温馨提醒:记得备份. 看到项目名称,总感觉有点low,所以尝试着要将其更改一下. 项目原结构如下: 更改后的项目结构如下: 下面开始更改操作 ...

  10. Java并发编程核心方法与框架-Future和Callable的使用

    Callable接口与Runnable接口对比的主要优点是Callable接口可以通过Future获取返回值.但是Future接口调用get()方法取得结果时是阻塞的,如果调用Future对象的get ...