为了加入新的攻击动画,我们要设计一个攻击动画层。新的动画层一是为了使老动画层不至于臃肿,而是为了配置Avatar Mask。
新建一个Avatar Mask,我知道这里是多个Avatar Mask。我们需要的是一个全身都被选择的Mask。将Mask放到动画层设置里面。
接下来要做的就是,在攻击时调整动画层权重。这要在代码中做到。在进入代码之前,给idle添加StateMachineBehaviour脚本FSMClearSignals。我们需要清除的是attack信号来防止累积。
和翻滚一样攻击属于一次性触发信号。由于使用键盘,所以我们采用MouseButtonDown(0)。
// 一次性信号
public bool attack; //攻击信号
// Update is called once per frame
void Update()
{
// ...
// 攻击信号
attack = Input.GetMouseButtonDown(0);
attack = Input.GetKeyDown(keyAttack);
}
由于有了attack层,我们的动画机控制代码复杂了一点。我们需要使用代码修改动画层的权重。
// 进入Attack层的动画节点attack_oneHand_A时执行的方法
// 通过PlayerController动画机中的attack_oneHand_A节点上挂载的FSMOnEnter调用
public void OnAttack_oneHandAEnter()
{
// 关闭输入模块
pi.inputEnabled = false;
// 修改攻击动画层权重为1
m_anim.SetLayerWeight(m_anim.GetLayerIndex("Attack"), 1.0f);
}
// 在Attack层的动画节点attack_oneHand_A更新时执行的方法
// 通过PlayerController动画机中的attack_oneHand_A节点上挂载的FSMOnEnter调用
public void OnAttack_oneHandAUpdate()
{
// 计算攻击时的冲量
m_thrustVec = model.transform.forward * m_anim.GetFloat("attackOneHandAVelocity") * m_planarVec.magnitude * 4f;
}
// 进入Attack层的动画节点idle时执行的方法
// 通过PlayerController动画机中的idle节点上挂载的FSMOnEnter调用
public void OnAttackIdleEnter()
{
// 打开输入模块
pi.inputEnabled = true;
// 修改攻击动画层权重为0
m_anim.SetLayerWeight(m_anim.GetLayerIndex("Attack"), 0);
}
方法依旧是熟悉的配方,通过FSMOnEnter的SendMessage来告诉PlayerController当前进入攻击动画了,进而重新设置层权重。m_anim.GetLayerIndex来让程序帮你找到LayerIndex。
接下来,攻击时我们的角色不能再按玩家的输入任意移动,否则会很奇怪(很明显了)。所以在攻击时要输入模块关闭,不用锁定平面移动(m_lockPlanar)。
如果角色在攻击时本身处于移动状态,我们就需要表现出惯性,角色需要向前冲一点,要实现这个效果,可以使用和jab类似的方案,也就是在攻击动画中加入一个曲线表示冲量的速度大小,只不过这次还要和m_planarVec.magnitude相乘,才能和角色的速度相关,得到的就是看起来不错的惯性效果。至于为什么移动时攻击会在关闭输入模块之后还能得到惯性效果,是因为我们的输入模块不是直接将输入值拿来就用,而是使用了Slerp。
我们的攻击代码还不完善,你首先会发现我们在翻滚以及跳跃时依旧可以执行攻击,而且动画还会被攻击动画覆盖掉。 所以我们还要将这个问题解决了。我们的第一个解决方案是,在判断是否攻击时,不能只判断当前是否按下了攻击键,还要再检查当前是否处在ground动画状态。
但是试过之后发现,这个解决方案其实不太行,如果我们按的快,在翻滚的同时按了攻击键,还是会触发攻击。原因是因为动画转态需要时间,而这段时间内动画机认为动画还没有离开先前的状态。所以上述办法行不通了,我们需要用别的办法进行判断。我们将使用单独的一个bool变量canAttack,当我们按下jab_roll键和jump键时,将canAttack设置为false,在进入ground动画状态时,再将canAttack设置为true。这样就避免了上述问题。
// 是否能够攻击 用于规避BUG
private bool canAttack = true;
// Update is called once per frame
void Update()
{
// ...
// 触发roll 会根据forward来判断具体该执行哪个动画
if (pi.jab_roll)
{
m_anim.SetTrigger("jab_roll");
canAttack = false;
}
// 触发jump 必须要先冲刺才能jump
if (pi.jump)
{
m_anim.SetTrigger("jump");
canAttack = false;
}
// 触发attack 要求当前动画必须在ground状态才能攻击
if (pi.attack && canAttack)
{
m_anim.SetTrigger("attack");
}
// ...
}
// 进入Base层的动画节点ground时执行的方法
// 通过PlayerController动画机中的ground节点上挂载的FSMOnEnter调用
public void OnGroundEnter()
{
// ...
// 进入ground状态以后才能够进行攻击
canAttack = true;
}
对于我自己的代码,在处理攻击逻辑时,我做了额外一件事,也就是防止在按下攻击键后按下jab_roll键,避免造成位移。具体方法是和之前一样的套路,就不写出来了。话说回来,之所以会有这样一步限制,是因为我的翻滚逻辑和老师的略微不同,老师的逻辑是通过在OnRollUpdate中给m_thrustVec赋值,而我的目前是直接将m_planarVec锁定,然后给他赋值作为翻滚时的平面位移量由FixedUpdate中的位移直接实现翻滚时的位移,所有会导致如果说我们在攻击时按下翻滚键会造成移动,简单来说因为我的代码在翻滚时修改了m_planarVec,而攻击的冲量受此影响。
关于添加额外bool变量作为信号这种事,老师的意见是不到万不得已,不加信号没办法解决问题的时候,就不要加信号,这会导致你的代码可读性降低,如果后期加了一堆信号,会导致理解困难。在我之前的项目中,添加额外的bool变量对代码进行控制一直是我的方案,最后导致的结果确实如老师所说的那样,代码会变得很难看。看来要写出好的代码,我还有很长的路要走。