Unity手游之路<十>自动寻路Navmesh之跳跃,攀爬,斜坡

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值