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

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

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. web前端开发-Ajax(1)

    1.简单简绍Ajax的功能 Ajax是处于前端和后端之间的这么一个东西,他可以拿到你前端form的内容,并且在你触发Ajax的时候,先将某些数据发送到服务器端,等接受到服务器 返回的数据时,执行某个函 ...

  2. PHP生成图片太慢了。。有些都不出来、

    现在为了使用不同宽高的图片,做了个动态生成的程序.每次根据图片传入的宽高来输出图片,然后 html 页面里用 <img src="xxx.com/img?src=c8d997dae15 ...

  3. linux版本查看命令

    uname -a显示电脑以及操作系统相关信息 cat /proc/version显示正在运行的内核版本 cat /etc/issue或cat /etc/redhat-release  Linux查看版 ...

  4. 2018年东北农业大学春季校赛 E wyh的阶乘 【数学】

    题目链接 https://www.nowcoder.com/acm/contest/93/E 思路 其实就是找阶乘的项中5的个数 末尾为什么会出现0 因为存在5的倍数和偶数相乘 有0存在 借鉴 htt ...

  5. ajax 异步 跨域上传图片

    客户端 <label for="text">名称</label> <input type="text" id="text ...

  6. Linux系统资源查看与设置

    /proc/sys/fs/file-max = 65536 /proc/sys/net/ipv4/tcp_fin_timeout = 15 /proc/sys/net/ipv4/tcp_tw_recy ...

  7. SpringBoot之外部Tomcat运行Spring Boot项目

    内置tomcat8.5.28 外置的要高于此版本才OK spring boot1.5是访问不了jsp页面的 以后要以2.0版本为主流的

  8. 深入理解JVM - 线程安全与锁优化 - 第十三章

    线程安全 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对 ...

  9. BZOJ 1685 [Usaco2005 Oct]Allowance 津贴:贪心【给硬币问题】

    题目链接:http://begin.lydsy.com/JudgeOnline/problem.php?id=1333 题意: 有n种不同币值的硬币,并保证大币值一定是小币值的倍数. 每种硬币的币值为 ...

  10. 分享知识-快乐自己:SpringBoot结合使用拦截器(判断是否用户是否已登陆)

    所有的开发之中拦截器一定是一个必须要使用的功能,利用拦截器可以更加有效的实现数据的验证处理,而且最为幸运的是在SpringBoot之中所使用的拦截器与Spring中的拦截器完全一样. 基础拦截器操作: ...