前言

制作RPG游戏的时候,一般我们会用寻路系统Navigation,假如要制作一个跳跃功能,需要注意跳跃的时候不能跳到地形外面,并且起跳的时候,要把NavMeshAgent关闭,落地的时候再重新激活,下面就用一个简单的例子教大家。

1 烘焙地形

选择地形,设置为Navigation Static

unity3d 人物觉得下坡会抖动_navigation

点击菜单Windwo/Navigation,在Navigation窗口的Bake标签页中点击Bake按钮,开始烘焙

unity3d 人物觉得下坡会抖动_NavMeshAgent_02

2 角色控制

本例中用的是一个Cube代替角色,给Cube挂上NavMeshAgent组件

unity3d 人物觉得下坡会抖动_NavMeshAgent_03


编写Player.cs脚本,按方向键控制Cube移动,按空白键控制Cube跳跃

代码如下

// Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class Player : MonoBehaviour
{
    public NavMeshAgent navAgent;
    JumpLogic m_jumpLogic = new JumpLogic();
 
    // Use this for initialization
    void Start () {

        m_jumpLogic.Init(navAgent, transform);
    }
	
	// Update is called once per frame
	void Update ()
    {
        m_jumpLogic.UpdateJump();
        if (Input.GetKeyDown(KeyCode.Space))
        {
            m_jumpLogic.Jump();
        }

        if (Input.GetKey(KeyCode.UpArrow))
        {
            transform.position += new Vector3(0, 0, Time.deltaTime);
        }
        if (Input.GetKey(KeyCode.DownArrow))
        {
            transform.position += new Vector3(0, 0, -Time.deltaTime);
        }
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            transform.position += new Vector3(-Time.deltaTime, 0, 0);
        }
        if (Input.GetKey(KeyCode.RightArrow))
        {
            transform.position += new Vector3(Time.deltaTime, 0, 0);
        }
    }
}

3 跳跃逻辑

编写跳跃逻辑脚本JumpLogic.cs,实现二段跳功能,并确保不会跳到地形外面,代码如下

// JumpLogic.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class JumpLogic
{
    public enum JUMPTYPE
    {
        JUMP01 = 1,
        JUMP02 = 2
    }

    private NavMeshAgent m_navAgent;
    private Transform m_modelTrans;

    private bool m_isJump;
    /// <summary>
    /// 是否处在跳跃中
    /// </summary>
    public bool IsJump { get { return m_isJump; } }
    private bool mIsJump2;
    /// <summary>
    /// 是否处在二段跳中
    /// </summary>
    public bool IsJump2 { get { return mIsJump2; } }


    private bool m_isJumpReady;
    private bool m_isJumpOver;


    private Vector3 m_targetPos;

    public float ShotSpeed = 6;
    public float Duration;          //代表从起始点出发到目标点经过的时长
    public float Gravity = -15;     //重力加速度
    private Vector3 mStartSpeed;        //初速度向量
    private float mUseTimeJump;
    private float mUseTimeJumpOver;
    private bool mIsSourceJump;     //是否是原地起跳
    private Vector3 mSourcePos;     //起跳之前的位置
    private float mfDisYOld;        //上一次的当前与目标高度差
    private int m_curStep;      //记录当前几段跳
    Vector3 m_targetDirection;

    //移动的目标点
    private Vector3 m_vecTargetPos;
    public Vector3 VecTargetPos
    {
        get { return m_vecTargetPos; }
        set
        {
            m_vecTargetPos = value;
        }
    }

    private Vector3 Position
    {
        get { return m_modelTrans.position; }
        set { m_modelTrans.position = value; }
    }

    public void Init(NavMeshAgent navAgent, Transform modelTrans)
    {
        m_navAgent = navAgent;
        m_modelTrans = modelTrans;
    }

    public void Jump()
    {
        int nStep = 1;
        //默认想服务器申请使用一段跳,如果正在轻功状态并且二段跳已经可以使用并且未激活,则申请发起二段跳
        if (IsJump && !IsJump2)
        {
            nStep = 2;
        }

        //如果没有使用摇杆则用人物当前的朝向
        m_targetDirection = m_modelTrans.forward;


        bool bhit = false;
        NavMeshHit hit;
        Vector3 jumpPos = Vector3.zero;
        if (nStep == (int)JUMPTYPE.JUMP02)
        {
            //二段跳
            jumpPos = Position + m_targetDirection * 10f;
            bhit = NavMesh.SamplePosition(jumpPos, out hit, 5, -1);
            if (!bhit)
            {
                jumpPos = Position + m_targetDirection * 10f;
                bhit = NavMesh.SamplePosition(jumpPos, out hit, 12, -1);
            }

            //先计算如果两个点离的很近就走原地跳跃
            if (bhit)
            {
                float dis = Vector3.Distance(hit.position, Position);
                if (dis < 4)
                {
                    bhit = false;
                    mSourcePos = hit.position;
                }
            }
            else
            {
                //二段跳如果没有找到可跳跃点,则将原始点设置为一段跳的起始点,防止二段跳的时候又跳回去了
                mSourcePos = m_targetPos;
            }
        }
        else
        {
            //一段跳
            //先计算跳跃点
            mSourcePos = Position;
            jumpPos = Position + m_targetDirection * 6f;
            bhit = NavMesh.SamplePosition(jumpPos, out hit, 3, 1);
            if (!bhit)
            {
                jumpPos = Position + m_targetDirection * 6f;
                bhit = NavMesh.SamplePosition(jumpPos, out hit, 5, 1);
            }

            //先计算如果两个点离的很近就走原地跳跃
            if (bhit)
            {
                float dis = Vector3.Distance(hit.position, Position);
                //Debugger.Log("跳跃目标距离:{0}", dis);
                if (dis < 4)
                {
                    bhit = false;
                    mSourcePos = hit.position;
                }
            }
        }

        //跳跃朝向
        Vector3 dir = Vector3.zero;
        var isYuanDi = true;
        if (!bhit)
        {
            //原地跳跃的处理
            jumpPos = mSourcePos;
            dir = m_targetDirection;//原地起跳直接用摇杆方向
            isYuanDi = true;
        }
        else
        {
            //可正常跳跃
            jumpPos = hit.position;
            isYuanDi = false;
        }

        JumpToOther(jumpPos, nStep, isYuanDi);
    }

    public void JumpToOther(Vector3 dstPos, int nStep, bool isYuanDi)
    {
        if (mIsJump2 || m_isJumpOver)
        {
            //已经处在二段跳或者跳跃结束了就不操作了
            return;
        }
        m_curStep = nStep;

        m_targetDirection = m_modelTrans.forward;

        if (nStep == (int)JUMPTYPE.JUMP02)
        {
            //二段跳
            mIsJump2 = true;
            ShotSpeed = 9;
        }
        else
        {
            //一段跳
            ShotSpeed = 7;
        }

        Vector3 dir = Vector3.zero;
        if (isYuanDi)
        {
            //原地跳
            dir = m_targetDirection;//原地起跳直接用摇杆方向
            mIsSourceJump = true;

            if (nStep == (int)JUMPTYPE.JUMP02)
            {
                Duration = .8f;
                ShotSpeed = 7f;
            }
            else
            {
                Duration = .6f;
                ShotSpeed = 5f;
            }
        }
        else
        {
            //正常跳
            Duration = Vector3.Distance(Position, dstPos) / ShotSpeed;
            dir = (new Vector3(dstPos.x, Position.y, dstPos.z) - Position);//正常跳跃用目标点的朝向
        }


        m_isJumpReady = true;

        m_isJump = true;
        //IsLightState = true;
        m_isJumpOver = false;
        m_navAgent.enabled = false;
        m_targetPos = dstPos;

        //计算初速度
        mStartSpeed = new Vector3(
            (m_targetPos.x - Position.x) / Duration,
            (m_targetPos.y - Position.y) / Duration - 0.5f * Gravity * Duration,
            (m_targetPos.z - Position.z) / Duration
            );
        mUseTimeJump = 0;

        //播放起跳动作
        if (mIsJump2)
        {
            //TODO 播放二段跳动作
            
        }
        else
        {
            //TODO 播放起跳动作
            
        }

        //立即朝向
        dir.Normalize();
        FaceToDirection(dir);
        m_isJumpReady = false;
      
        
    }

    /// <summary>
    /// 面朝方向向量
    /// </summary>
    /// <param name="_dir"></param>
    public void FaceToDirection(Vector3 dir)
    {
        if (dir == Vector3.zero)
        {
            return;
        }
        m_modelTrans.rotation = Quaternion.LookRotation(dir);
    }


    public void UpdateJump()
    {
        if (m_isJump && !m_isJumpReady)
        {
            if (!m_isJumpOver)
            {
                //跳跃中处理
                Vector3 deltaSpeed = Vector3.zero;
                deltaSpeed.y = Gravity * (mUseTimeJump + Time.deltaTime);//v=at
                Position += (mStartSpeed + deltaSpeed) * Time.deltaTime;

                float disY = Mathf.Abs(Position.y - m_targetPos.y);
                float remainTime = Duration - mUseTimeJump;
                //Debugger.Log("disY:{0}", disY);

                //是否已达到目标位置
                if ((remainTime <= .1f && (disY <= .2f || disY > mfDisYOld)) || (!mIsSourceJump && mUseTimeJump >= Duration))
                {
                    //跳跃结束,更新目标点,否则在寻路过程中跳跃,结束时会播放跑步动作
                    m_vecTargetPos = Position;
                    m_isJumpOver = true;
                    mUseTimeJumpOver = 0;
                    mIsJump2 = false;
                    m_navAgent.enabled = true;
   
                    //TODO 播放跳跃结束动作
                    //

                    
                }
                else
                {
                    mUseTimeJump += Time.deltaTime;
                    mfDisYOld = disY;//记录上一次的高度,防止卡帧时的处理
                }
            }
            else
            {
                //跳跃结束延时处理,防止一下落就行走看不到结束动画
                mUseTimeJumpOver += Time.deltaTime;
                if (mUseTimeJumpOver >= 0.1f)
                {
                    //延时一下再结束
                    m_isJump = false;
                    m_isJumpOver = false;
                    mIsSourceJump = false;
                }
            }
        }

    }
}

4 运行效果

unity3d 人物觉得下坡会抖动_unity_04