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

发表于2017-12-23
评论1 5.5k浏览
Navmesh作为Unity官方内置的寻路插件,下面我们就来给大家介绍下自动寻路插件Navmesh是怎么实现游戏中跳跃、攀爬、斜坡这几个功能的,具体步骤如下:

步骤
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);  
        }  
    }  
}  

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



总结

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


源码:http://pan.baidu.com/s/1i35cVOD


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引