您的位置:首页 > 移动开发 > Unity3D

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

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

(转载请注明原文地址/article/1588729.html

步骤

1.在场景中摆放各种模型,包括地板,斜坡,山体,扶梯等

2.为所有的模型加上Navigation Static和OffMeshLink Generatic(这个根据需要,例如地板与斜坡相连,斜坡就不需要添加OffMeshLink)

3.特殊处理扶梯,需要手动添加Off Mesh Link,设置好开始点和结束点

4.保存场景,烘焙场景

5.添加角色模型,为其加Nav Mesh Agent组件

6.为角色添加一个新脚本,AgentLocomotion.cs,用来处理自动寻路,已经角色动画变换。代码比较长,大家可以结合注释来理解

[csharp] view
plaincopy





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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: