前言
制作RPG游戏的时候,一般我们会用寻路系统Navigation,假如要制作一个跳跃功能,需要注意跳跃的时候不能跳到地形外面,并且起跳的时候,要把NavMeshAgent关闭,落地的时候再重新激活,下面就用一个简单的例子教大家。
1 烘焙地形
选择地形,设置为Navigation Static
点击菜单Windwo/Navigation,在Navigation窗口的Bake标签页中点击Bake按钮,开始烘焙
2 角色控制
本例中用的是一个Cube代替角色,给Cube挂上NavMeshAgent组件
编写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;
}
}
}
}
}