目前的跳跃动画包括了跳起来和落下,我们单独抽象出掉落状态接管Jump空中以及落地的逻辑,这样移动时突然掉落也可以复用掉落状态的逻辑。
public enum PlayerState
{
Idle,
Move,
Jump,
AirDown
}
增添空中下落状态。
public class PlayerJumpState : PlayerStateBase
{
public override void Enter()
{
player.PlayAnimation("Jump");
player.PlayerModel.SetRootMotionAction(OnRootMotion);
}
public override void Update()
{
if (CheckAnimatorStateName("JumpStart", out float animationTime) && animationTime >= 0.9f)
{
player.ChangeState(PlayerState.AirDown);
}
}
public override void Exit()
{
player.PlayerModel.ClearRootMotion();
}
public void OnRootMotion(Vector3 deltaPosition, Quaternion deltaRotation)
{
deltaPosition.y *= player.jumpForce;
player.CharacterController.Move(deltaPosition);
}
}
由于单独抽象出来了下落状态,所以原来跳跃只负责起跳,空中位移部分移至下落状态处理。
//PlayerAirDownState
private void AirControll()
{
//处理空中位移
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
Vector3 motion = new Vector3(0, player.gravity * Time.deltaTime, 0);
if (h != 0 || v != 0)
{
Vector3 input = new Vector3(h, 0, v);
Vector3 dir = Camera.main.transform.TransformDirection(input);
motion.x = player.moveSpeedForJump * Time.deltaTime * dir.x;
motion.z = player.moveSpeedForJump * Time.deltaTime * dir.z;
//处理旋转
//获取相机的旋转值y
float y = Camera.main.transform.rotation.eulerAngles.y;
//让四元数和向量相乘,表示这个向量按照这个四元数所表达的角度进行旋转后得到的向量
Vector3 targetDir = Quaternion.Euler(0, y, 0) * input;
//Slerp插值
player.PlayerModel.transform.rotation = Quaternion.Slerp(player.PlayerModel.transform.rotation, Quaternion.LookRotation(targetDir), Time.deltaTime * player.rotateSpeed);
}
//Vector3 dir2 = Quaternion.Euler(0, Camera.main.transform.rotation.eulerAngles.y, 0) * new Vector3(h, 0, v);
//player.CharacterController.Move();
player.CharacterController.Move(motion);
}
下落时位移不再使用RootMotion(为了能够在非跳跃下落时也复用),所以位移部分需要添加motion y方向的重力矢量。
// 检测下落
if (player.CharacterController.isGrounded == false)
{
player.ChangeState(PlayerState.AirDown);
return;
}
分别在角色Idle,Ground状态添加下落检测逻辑。
private float playEndAnimationHeight = 1.6f; // 如果空中检测到距离地面有3米则启动翻滚
private float endAnimationHeight = 1.5f; // End动画播放需要的高度,从这个高度开始播放
private LayerMask groundLayerMask = LayerMask.GetMask("Env");
private bool needEndAnimation;
private AirDownChildState airDownState;
private AirDownChildState AirDownState
{
get => airDownState;
set
{
airDownState = value;
switch (airDownState)
{
case AirDownChildState.Loop:
player.PlayAnimation("JumpLoop");
break;
case AirDownChildState.End:
player.PlayAnimation("JumpEnd");
break;
}
}
}
public override void Enter()
{
AirDownState = AirDownChildState.Loop;
// 判断当前角色的高度是否有可能切换到End
needEndAnimation = !Physics.Raycast(player.transform.position + new Vector3(0, 0.5f, 0), player.transform.up * -1, playEndAnimationHeight + 0.5f, groundLayerMask);
}
public override void Update()
{
switch (airDownState)
{
case AirDownChildState.Loop:
if (needEndAnimation)
{
// 高度检测
if (Physics.Raycast(player.transform.position + new Vector3(0, 0.5f, 0), player.transform.up * -1, endAnimationHeight + 0.5f, groundLayerMask))
{
AirDownState = AirDownChildState.End;
return;
}
}
else
{
if (player.CharacterController.isGrounded)
{
player.ChangeState(PlayerState.Idle);
return;
}
}
AirControll();
break;
case AirDownChildState.End:
if (CheckAnimatorStateName("JumpEnd", out float animationTime))
{
if (animationTime >= 0.8f)
{
// 此时依然踩空了,继续下坠
if (player.CharacterController.isGrounded == false)
{
AirDownState = AirDownChildState.Loop;
}
else
{
player.ChangeState(PlayerState.Idle);
}
}
else if (animationTime < 0.6f)
{
AirControll();
}
}
break;
}
}
- 将空中下落分为两个子状态,空中下落循环和接近地面翻滚。
- 在循环下落过程中进行高度检测来切换子状态,要发两次射线检测。
- 第一次检测playEndAnimationHeight是切换至翻滚的当前高度前提,射线检测不触发,代表当前高度大于此高度,这么设置是为了避免角色从较低位置下落也会触发翻滚运动,必须是由高处下落才会触发。
- 第二次检测endAnimationHeight是开始播放翻滚动画的高度,小于这个高度时播放触发翻滚运动。
- 由大于playEndAnimationHeight的高处下落至endAnimationHeight触发翻滚,小于等于playEndAnimationHeight的低处下落则执行空中位移逻辑,如果落地则切换至待机状态。
- 翻滚播放进度80%时检测地面,没有地面则回到空中循环下落状态,否则切换至待机,小于60%进度时保持空中位移。
最终效果如下: