在之前的几篇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.  
  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. }

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

  • 总结

今天的这个例子比较复杂,要根据寻路网格的类型,来处理角色的动作是普通寻路,还是攀爬,抑或跳跃。这个例子应该是比较接近真实项目了。大家在实际项目中如果还有更加复杂的寻路,欢迎探讨。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/17598113 在之前的几篇Blog总,我们已经系统学习了自动寻路插件Navmesh的相关概念和细节.然而,如 ...

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

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

  3. Unity自动寻路Navmesh之高级

    隔离层自动生成寻路网格 (源码scene1.unity) 1.创建Plane实例P1,P2,两者之间出现一条鸿沟.直接控制角色位移是无法通过的. 2.打开Navigation窗口,分别选中P1,P2, ...

  4. Unity自动寻路Navmesh之入门

    实例 我们要实现一个功能:点击场景中的一个位置,角色可以自动寻路过去.角色会绕过各种复杂的障碍,找到一条理论上”最短路径“. 步骤 1.创建地形 2.添加角色 3.创建多个障碍物,尽量摆的复杂一点,来 ...

  5. UDK:AdventureKit 攀爬系统

    [目标] AdventureKit攀爬系统 [思路] [步骤] 1 拷贝 2 设置config,UDKGame\Config\DefaultEngine.ini 添加包 [UnrealEd.Edito ...

  6. Unity 导出NavMesh (可行走区域判定) 数据给服务器使用

    cp790621656 博客专家 Unity 导出NavMesh (可行走区域判定) 数据给服务器使用 发表于2016/9/26 18:15:11  1089人阅读 分类: Unity MMO 这个 ...

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

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

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

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

  9. Unity手游之路自动寻路Navmesh之高级主题

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

随机推荐

  1. Android之Handler使用方法总结

    方法一:(java习惯,在android平台开发时这样是不行的,由于它违背了单线程模型) 刚刚開始接触android线程编程的时候,习惯好像java一样,试图用以下的代码解决这个问题    new T ...

  2. nodejs 版本dockerfile 文件制作,和常用命令

    Dockerfile 如下 官方的node6.3的版本有点难下载,建议去网易蜂巢  https://c.163.com/hub pull hub.c.163.com/library/node:6.9 ...

  3. 【tensorflow】tensorflow学习记录——安装、第一个程序篇

    机器学习,人工智能往后肯定是一个趋势,现阶段有必要研究一两个人工智能的工具,以免自己技术落伍,其中tensorflow就是一个很不错的项目,有谷歌开发后开源,下面开始学习安装和使用 安装篇: 很不幸, ...

  4. linux下 python源码包解压报错

    执行下面的命令 tar -zvxf Python.3.6.5.tgz 报错 gzip: stdin: not in gzip format tar: Child returned status 1 t ...

  5. POJ - 3278 Catch That Cow 【BFS】

    题目链接 http://poj.org/problem?id=3278 题意 给出两个数字 N K 每次 都可以用三个操作 + 1 - 1 * 2 求 最少的操作次数 使得 N 变成 K 思路 BFS ...

  6. JDBC超时原理与设置

    抄录自网上,因为担心以后找不到,因此抄录之.感谢分享的大神! 英文原版:http://www.cubrid.org/blog/dev-platform/understanding-jdbc-inter ...

  7. iOS SDK:iOS调试技巧

    感谢原创 在程序中,无论是你想弄清楚为什么数组中有3个对象而不是5个,或者为什么一个新的玩家开始之后,游戏在倒退——调试在这些处理过程中是比较重要的一部分.通过本文的学习,我们将知道在程序中,可以使用 ...

  8. 理解SQL原理,写出高效的SQL语句

    我们做软件开发的,大部分人都离不开跟数据库打交道,特别是erp开发的,跟数据库打交道更是频繁,存储过程动不动就是上千行,如果数据量大,人员流动大,那么我们还能保证下一段时间系统还能流畅的运行吗?我们还 ...

  9. zabbix 报表

    摘自: https://www.w3cschool.cn/zabbix_manager/zabbix_manager-z45f1zie.html

  10. Java钉钉开发_Exception_异常总结

    一.异常 1.访问ip不在白名单之中 异常信息: "errcode":60020,"errmsg":"访问ip不在白名单之中" 异常背景:若 ...