[RPG战斗系统] 10&14.角色急停
提供角色由奔跑到待机状态的急停动画过渡。可以在单独给出来一个急停状态进行过渡,但没必要,急停状态只包含一个动画,将移动状态逻辑上细分为两个子状态即可,子状态没有Exit逻辑,通过属性的get方法实现状态进入播放动画,Update正常检测切换状态。
private enum MoveChildState
{
Move,
Stop
}
private MoveChildState moveState;
private MoveChildState MoveState
{
get { return moveState; }
set
{
moveState = value;
//状态进入
switch (value)
{
case MoveChildState.Move:
player.PlayAnimation("Move");
break;
case MoveChildState.Stop:
player.PlayAnimation("RunStop");
break;
}
}
}
通过MoveState属性Set值时播放对应的动画,相当于子状态的Enter,下一步,考虑何时更新MoveState值。
public override void Enter()
{
MoveState = MoveChildState.Move;
player.PlayerModel.SetRootMotionAction(OnRootMotion);
}
在进入Move状态时,默认是奔跑的动画。
public override void Update()
{
//检测玩家跳跃
if (Input.GetKeyDown(KeyCode.Space))
{
player.ChangeState(PlayerState.Jump);
return;
}
switch(moveState)
{
case MoveChildState.Move:
MoveOnUpdate();
break;
case MoveChildState.Stop:
StopOnUpdate();
break;
}
}
在状态更新的每一帧,判断当前状态,如果是Move状态,那需要检测输入和玩家走跑的过渡进度来决定是否需要进入Stop状态急停。
void MoveOnUpdate()
{
// h和v用来做实际的移动位置参考
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
// rawH和rawV用来判断是否待机,急停
float rawH = Input.GetAxisRaw("Horizontal");
float rawV = Input.GetAxisRaw("Vertical");
if(rawH == 0 && rawV == 0) //玩家松开按键
{
if(walk2RunTranstion > 0.4f)
{
//进入急停
MoveState=MoveChildState.Stop;
return;
}
//停止运动回到Idle
player.ChangeState(PlayerState.Idle);
return;
}
else
{
//走到跑的过渡
if (Input.GetKey(KeyCode.LeftShift))
{
walk2RunTranstion = Mathf.Clamp(walk2RunTranstion + Time.deltaTime * player.walk2Transition, 0, 1);
}
else
{
walk2RunTranstion = Mathf.Clamp(walk2RunTranstion - Time.deltaTime * player.walk2Transition, 0, 1);
}
player.PlayerModel.Animator.SetFloat("Move", walk2RunTranstion);
//影响动画播放速度,来影响rootmotion位移速度
player.PlayerModel.Animator.speed = Mathf.Lerp(player.walkSpeed, player.runSpeed, walk2RunTranstion);
//角色旋转
Vector3 input = new Vector3(h, 0, v);
//获取相机的旋转值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);
}
}
为了让急停能够及时反应,使用GetAxisRaw(-1/0/1)来无缓冲的监测玩家是否停止移动输入,如果玩家停止了移动,且跑步动画到走路动画的过渡量>40%,那有必要进行急停,否则直接回到Idle状态(注意判断里有return,所以省去了else)。注意,一旦状态切换到Stop,则OnMoveUpdate就不再执行,子状态的Update是有条件的执行的,动画只会播放一次。
private void StopOnUpdate()
{
if(CheckAnimatorStateName("RunStop",out float animationTime))
{
if (animationTime >= 1) player.ChangeState(PlayerState.Idle);
}
}
如果当前是急停状态,则播放完急停动画切换到Idle状态,为了方面获取当前动画的播放进度,在PlayerStateBase封装个功能方法检查并获取当前指定名称动画的播放进度。
protected virtual bool CheckAnimatorStateName(string stateName,out float normalizedTime)
{
AnimatorStateInfo info = player.PlayerModel.Animator.GetCurrentAnimatorStateInfo(0);
normalizedTime = info.normalizedTime;
return info.IsName(stateName);
}
最后为急停动画帧添加对应的动画事件。
最终效果如,角色奔跑时送卡按键,则急停,再待机。
Unity战斗系统 文章被收录于专栏
Unity战斗系统实现笔记