zoukankan      html  css  js  c++  java
  • Unity手游:自动寻路Navmesh 跳跃 攀爬 斜坡

    原地址:http://dong2008hong.blog.163.com/blog/static/46968827201403114644210/

    步骤

    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

  • 相关阅读:
    Spring集成XFire开发WebService
    【ASP.NET】验证控件
    中文分词——正向最大匹配法
    fastdfs storage server的设计与实现
    php浮点数精确运算
    完整导出IntelliJ IDEA的快捷键
    时空理论-结构-空间,运动-时间
    结构论-系统的结构逻辑
    结构主义理论-时空一体才是存在的本源
    分形几何学
  • 原文地址:https://www.cnblogs.com/123ing/p/3912031.html
Copyright © 2011-2022 走看看