今天实现的内容:
加入放下武器的动画
之前的切换武器是旧枪直接消失,然后把新枪掏出来,现在在这之前多了一个将旧枪放下的动画。新动画能够让切换武器的过程显得更自然。
为两把枪都添加上这个动画,再打上标签。
设置新的parameter Trigger,用于触发动画。
运用放下武器的动画以及改善的切枪逻辑
参考之前的换弹,我们设计了一个协程来检查放下武器的动画是否播放完成,播放完成后再执行逻辑上的切枪,为了实现以上功能,还优化了代码逻辑。
以下是改善过后的WeaponManager。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Scripts.Weapon;
// 用于武器的控制 切换
public class WeaponManager : MonoBehaviour
{
// 主武器
public Firearms mainWeapon;
// 副武器
public Firearms secondaryWeapon;
// 当前手上拿着的武器
private Firearms m_currentWeapon;
// FPCharacterControllerMovement的引用 用于传递Animator
private FPCharacterControllerMovement m_controller;
// 用于防止换枪方法的协程冲突
private IEnumerator m_checkHolsterAnimationEndCoroutine;
// 逻辑上的切换武器
private void SwapWeapon()
{
// 以下操作为防止协程冲突
if (m_checkHolsterAnimationEndCoroutine == null)
m_checkHolsterAnimationEndCoroutine = CheckHolsterAnimationEnd(); // 当前没有正在执行协程
else
return; //当前正在执行协程,什么都不做
StartCoroutine(m_checkHolsterAnimationEndCoroutine); // 如果当前
// 检查收枪动画是否播放完成
// 完成后该方法自动调用逻辑上的换枪方法
// 播放收枪动画
m_currentWeapon.gunAnimator.SetTrigger("Holster");
// 现在正在切换武器
m_currentWeapon.isSwapingWeapon = true;
}
// 检查换枪动画(收枪动画)是否完成
private IEnumerator CheckHolsterAnimationEnd()
{
while(true)
{
// 一定要在每帧赋值 才能得到current state
AnimatorStateInfo temp_animatorStateInfo = m_currentWeapon.gunAnimator.GetCurrentAnimatorStateInfo(0);
if (temp_animatorStateInfo.IsTag("Holster")) //这个基本上肯定是true 因为我们先设置了播放换弹动画
{
if (temp_animatorStateInfo.normalizedTime >= 0.9f) //当换弹动画快要播完时
{
// 逻辑切枪
SetupWeaponChange();
m_checkHolsterAnimationEndCoroutine = null; //协程执行完毕 将该IEnumerator清空
m_currentWeapon.isSwapingWeapon = false; //武器切换完成
yield break;
}
}
yield return null;
}
}
// 执行枪械切换时的逻辑
private void SetupWeaponChange()
{
m_currentWeapon.gameObject.SetActive(false); //隐藏现在的武器
m_currentWeapon = (m_currentWeapon == mainWeapon) ? secondaryWeapon : mainWeapon; //切换武器
m_currentWeapon.gameObject.SetActive(true); //显示切换后的武器
m_controller.SetupAnimator(m_currentWeapon.gunAnimator); //切换controller的Animator引用
}
private void Start()
{
if(m_currentWeapon == null)
{
Debug.Log("current weapon is null");
}
m_currentWeapon = mainWeapon;
m_currentWeapon.gameObject.SetActive(true);
secondaryWeapon.gameObject.SetActive(false);
m_controller = GetComponent<FPCharacterControllerMovement>();
m_controller.SetupAnimator(m_currentWeapon.gunAnimator);
}
private void Update()
{
// 如果当前没有武器 什么都不执行
if (!m_currentWeapon) return;
// 换弹
if (Input.GetKeyDown(KeyCode.R))
{
m_currentWeapon.ReloadAmmo();
}
//按住扳机
if (Input.GetMouseButton(0))
{
m_currentWeapon.HoldTrigger();
}
//松开扳机
if(Input.GetMouseButtonUp(0))
{
m_currentWeapon.ReleaseTrigger();
}
// 瞄准 按下就会瞄准
if (Input.GetMouseButtonDown(1))
{
m_currentWeapon.Aiming(true);
}
// 松开按键退出瞄准
if (Input.GetMouseButtonUp(1))
{
m_currentWeapon.Aiming(false);
}
//当使用滚轮时 切换武器
if (Input.GetAxis("Mouse ScrollWheel") != 0)
{
SwapWeapon();
}
//当按下键盘1键时
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// 如果当前拿着主武器 什么都不做
if (m_currentWeapon == mainWeapon) {
}
else
SwapWeapon();
}
//当按下键盘2键时
if (Input.GetKeyDown(KeyCode.Alpha2))
{
// 如果当前拿着副武器 什么都不做
if (m_currentWeapon == secondaryWeapon) {
}
else
SwapWeapon();
}
}
}
可以看到SwapWeapon已经大变,原来的判断功能转移到了Update中进行,同时使用IEnumerator对象来判断协程是否正在进行,如果协程正在进行,则什么都不做。防止玩家能一直划滚轮就一直触发开始切枪。
BUG以及缺陷:
在切换武器时开枪,会导致武器从此无法切换,建议控制脚本使得切换武器时无法射击,或射击时打断切换武器的协程。我个人认为做成切枪时无法开火要好一些。
这是解决方案
// 当前是否正在切换武器 为了防止BUG也为了游戏设定 正在切换武器时不能开枪
// 该参数交由WeaponManager进行控制
internal bool isSwapingWeapon;
在Firearms中添加这一个参数isSwapingWeapon,交由我们的WeaponManager来具体控制,在切枪开始时设置为true,切枪结束时设置为false。
// 按下扳机 作为外部接口 定义当按住枪械扳机时会发生什么
internal void HoldTrigger()
{
// 当枪里没子弹时 无法射击 或者也许可以发出一个音效?
if (currentAmmoInMag <= 0)
{
isShooting = false;
return;
}
// 正在切换武器时 不能开枪
if (isSwapingWeapon)
return;
// 发动攻击
isShooting = true;
DoAttack();
}
执行HoldTrigger,也就是扣下扳机时,再执行一次判断,如果isSwapingWeapon为true,说明当前正在切换武器,则不能开枪。
在换弹时换枪,会导致这把枪之后无法开枪。主要原因是因为换弹开始时,枪里的子弹被设置为0,但是如果再进行换枪就打断了换弹过程,也就导致枪里的子弹一直为0。
我的解决方案可能有点复杂。
首先,在Firearms中定义新成员currentAmmoInMagWhileReload ,该成员用于记录换弹时从弹匣中取出来多少子弹。
// 记录换弹开始时弹匣中的子弹 如果换弹被打断 会派上用场
protected int currentAmmoInMagWhileReload = 0;
在将currentAmmoInMag清零之前,先用currentAmmoInMagWhileReload记录下currentAmmoInMag。
// 记录此时弹匣中的子弹
currentAmmoInMagWhileReload = currentAmmoInMag;
// 将所有的子弹放到currentAmmoCarried
currentAmmoCarried += currentAmmoInMag;
// 将currentAmmoInMag设置为0 一是为了方便接下来的计算 二是为了让换弹时无法射击
currentAmmoInMag = 0;
接着,如果出现了换枪时换弹的情况,再将currentAmmoInMagWhileReload赋值给currentAmmoInMag,相当于把子弹重新“塞回去”。
// 停止重新装填 当换弹时换枪会打断重新装填
internal virtual void StopReloading()
{
// 将当前武器的m_isReloading设置为false 防止换弹协程中断导致的m_isReloading无法修改
m_isReloading = false; // 这个方法没有解决问题
// 停止换弹协程
StopCoroutine(checkReloadAmmoAnimationEndCoroutine);
checkReloadAmmoAnimationEndCoroutine = null;
// 将弹匣重新放回枪 这是因为换弹时会将子弹清零
currentAmmoInMag = currentAmmoInMagWhileReload;
}
为了方便SwapWeapon的工作,专门做了方法对停止换弹的代码做封装。
// 当前是否正在换弹 正在换弹时切换武器 要停止换弹协程 并且修正换弹相关参数
if(m_currentWeapon.IsReloading())
{
Debug.Log(111);
m_currentWeapon.StopReloading();
}
// 播放收枪动画
m_currentWeapon.gunAnimator.SetTrigger("Holster");
SwapWeapon在判断是否正在换弹以及停止换弹的工作就很简单。
这应该是最后一个缺陷了,换弹时切枪,不会播放收枪动画,原因是换弹动画没有停,就把收枪动画覆盖掉了。我觉得,要么手动停止换弹动画,要么将收枪动画单独放在最新的Animator Layer。我还是手动停止换弹动画算了。
制作新参数,StopReloading。记得手枪步枪都用上。
// 停止换弹动画
gunAnimator.SetTrigger("StopReloading");
紧接着,在需要停止换弹动画的地方写上上面那句话。需要停止换弹动画的地方,除了换弹时切枪,还有正常换弹成功时。
我的代码已经肉眼可见的臃肿了。
2021.2.25更新,新问题,瞄准时切枪会导致AimIn一直处于true的状态,导致切回来时开枪播放的是瞄准时的开枪动画。
值得注意的:
在SwapWeapon中,
// 播放收枪动画
m_currentWeapon.gunAnimator.SetTrigger("Holster");
// 现在正在切换武器
m_currentWeapon.isSwapingWeapon = true;
以上代码要安排到协程冲突判断之后执行,确保return发挥作用。