文章目录

  • 今天实现的内容:
    • 脚本结构的优化
    • 切换武器逻辑的初步实现
    • 切换武器的GameObject实现
  • BUG以及缺陷:
  • 值得注意的:

今天实现的内容:

脚本结构的优化

为接下来的切换枪械做铺垫,脚本需要再进行一次大改,目的是再次将枪械的操作用一个脚本统一管理,方便接下来对不同枪械能使用同一套代码进行操作。将之前在AssaultRifle脚本中实现的控制操作放到新脚本中进行,再进行封装以及将一些写在AssaultRifle脚本中的枪械共有的属性再搬到Firearms中。

以下是新脚本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 currentWeapon;// FPCharacterControllerMovement的引用 用于传递Animatorprivate FPCharacterControllerMovement controller;// 切换武器private void SwapWeapon(){if (Input.GetAxis("Mouse ScrollWheel") != 0) //当使用滚轮时{currentWeapon.gameObject.SetActive(false); //隐藏现在的武器currentWeapon = (currentWeapon == mainWeapon) ? secondaryWeapon : mainWeapon; //切换武器currentWeapon.gameObject.SetActive(true); //显示切换后的武器controller.SetupAnimator(currentWeapon.gunAnimator); //切换Animator}else if (Input.GetKeyDown(KeyCode.Alpha1)) //当按下键盘1键时{currentWeapon.gameObject.SetActive(false);currentWeapon = mainWeapon;currentWeapon.gameObject.SetActive(true);controller.SetupAnimator(currentWeapon.gunAnimator);}else if (Input.GetKeyDown(KeyCode.Alpha2)) //当按下键盘2键时{currentWeapon.gameObject.SetActive(false);currentWeapon = secondaryWeapon;currentWeapon.gameObject.SetActive(true);controller.SetupAnimator(currentWeapon.gunAnimator);}}private void Start(){if(currentWeapon == null){Debug.Log("current weapon is null");}currentWeapon = mainWeapon;currentWeapon.gameObject.SetActive(true);secondaryWeapon.gameObject.SetActive(false);controller = GetComponent<FPCharacterControllerMovement>();controller.SetupAnimator(currentWeapon.gunAnimator);}private void Update(){// 如果当前没有武器 什么都不执行if (!currentWeapon) return;// 换弹if (Input.GetKeyDown(KeyCode.R)){currentWeapon.ReloadAmmo();}//按住扳机if (Input.GetMouseButton(0)){currentWeapon.HoldTrigger();}//松开扳机if(Input.GetMouseButtonUp(0)){currentWeapon.ReleaseTrigger();}// 瞄准 按下就会瞄准if (Input.GetMouseButtonDown(1)){currentWeapon.Aiming(true);}// 松开按键退出瞄准if (Input.GetMouseButtonUp(1)){currentWeapon.Aiming(false);}SwapWeapon();}
}

优化后的AssaultRifle类,将瞄准放到Firearms中进行实现,同时将isAllowShooting放到AssaultRifle中实现。isAllowShooting我认为除了自动武器的射速控制,还可以用来实现武器半自动,栓动。Shooting也略有不同,判断是否还有子弹被放到了Firearms中实现。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;namespace Scripts.Weapon
{public class AssaultRifle : Firearms{// 是否正在装填 装填时不能打断 也不能再执行装填private bool m_isReloading;// 弹仓里是否有子弹 关系到能发射的子弹是否多一颗private bool m_isBulletLeft;protected override void Start(){base.Start();m_isReloading = false;m_isAiming = false;m_isAimingIn = false;m_startAimInTime = 0;}// 发射子弹 需要派生类来具体实现protected override void Shooting(){// 如果能够继续射击 这里是射速限制if (!isAllowShooting()) return;// 是否正在举枪 为了规避BUG 举枪动画播放时不能播放Fire动画if (!m_isAimingIn){// 播放开火动画 注意我们会根据是否处于瞄准播放不同层的FiregunAnimator.Play("Fire", m_isAiming ? 2 : 0, 0);}// 弹药减一currentAmmoInMag -= 1;// 播放开火音效firearmsShootingAudioSource.clip = firearmsShootingAudioData.ShootingAudio;firearmsShootingAudioSource.Play();// 运用后坐力cameraLook.DoRecoil();// 创建子弹轨迹CreateBullet();// 播放枪口粒子特效muzzleParticle.Play();// 播放抛壳粒子特效casingParticle.Play();// 重置上一次发射时间lastFireTime = Time.time;}// 装填 protected override void Reload(){// 首先要有子弹可以换if (currentAmmoCarried > 0){// 其次是确实需要换弹并且没有在换弹if(currentAmmoInMag <= ammoInMag && !m_isReloading){// 将换弹layer的权重设置为1// 接下来Reload动画层的权重会一直为1gunAnimator.SetLayerWeight(1, 1);// 判断当前弹仓里是否有子弹m_isBulletLeft = (currentAmmoInMag > 0);// 当前需要播放哪个动画?gunAnimator.SetTrigger(m_isBulletLeft ? "ReloadLeft" : "ReloadOutOfAmmo");// 当前需要播放哪个音效?firearmsReloadingAudioSource.clip = (m_isBulletLeft ? firearmsShootingAudioData.ReloadLeft : firearmsShootingAudioData.ReloadOutOfAmmo);firearmsReloadingAudioSource.Play();// 设置状态m_isReloading = true;// 将所有的子弹放到currentAmmoCarriedcurrentAmmoCarried += currentAmmoInMag;// 将currentAmmoInMag设置为0 一是为了方便接下来的计算 二是为了让换弹时无法射击currentAmmoInMag = 0;// 开始执行换弹协程 只有动画快要播放完成时才真正换弹StartCoroutine(CheckReloadAmmoAnimationEnd()); }else{// 不需要换弹#if UNITY_EDITORDebug.Log("Dont need to reload!");#endif}}else{// 备用子弹打光了#if UNITY_EDITORDebug.Log("Out of Ammo!");#endifreturn;}}// 瞄准 已在Firearms中实现所需要的逻辑// 创建子弹protected override void CreateBullet(){// 实例化子弹对象GameObject temp_bullet = Instantiate(bulletPrefab, muzzlePoint.position, muzzlePoint.rotation);// 引用子弹脚本Bullet temp_bulletScript = temp_bullet.GetComponent<Bullet>();temp_bulletScript.bulletSpeed = bulletVelocity;// 给子弹随机散射角度temp_bullet.transform.eulerAngles += CalculateBulletSpreadOffset();}// 是否能够射击 用于武器的射速限制protected override bool isAllowShooting(){return (Time.time - lastFireTime > 1 / fireRate);}// 检查换弹动画是否播放完成// 如果播放完成了就可以从逻辑上换弹了private IEnumerator CheckReloadAmmoAnimationEnd(){while (true){yield return null;// 一定要在每帧赋值 才能得到current stategunStateInfo = gunAnimator.GetCurrentAnimatorStateInfo(1);if (gunStateInfo.IsTag("ReloadAmmo")) //这个基本上肯定是true 因为我们先设置了播放换弹动画{if (gunStateInfo.normalizedTime >= 0.9f) //当换弹动画快要播完时{     if(m_isBulletLeft) //如果我们装填时弹仓中有子弹{// 剩下的子弹能不能填满完整个弹匣?currentAmmoInMag = (currentAmmoCarried > ammoInMag + 1) ? ammoInMag + 1 : currentAmmoCarried;// 装了多少就减多少currentAmmoCarried -= currentAmmoInMag;}else //如果我们装填时弹仓中没子弹{// 剩下的子弹能不能填满完整个弹匣?currentAmmoInMag = (currentAmmoCarried > ammoInMag) ? ammoInMag : currentAmmoCarried;// 装了多少就减多少currentAmmoCarried -= currentAmmoInMag;}m_isReloading = false;yield break;}}}}// 瞄准时要干什么 已在Firearms中实现所需要的逻辑}
}

Firearms 类除了承接原来写在AssaultRifle中的参数,方法,还要再定义一套方法用于作为WeaponManager中使用的接口。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;namespace Scripts.Weapon
{// 枪械类public abstract class Firearms : MonoBehaviour,IWeapon{// 子弹初速public float bulletVelocity = 100f;// 枪口位置 用于生成子弹public Transform muzzlePoint;// 抛出蛋壳的位置//public Transform casingPoint;// 摄像机 用于瞄准时修改FOVpublic Camera eyeCamera;// 摄像机FOV的原数值protected float originFOV;// 枪焰粒子效果public ParticleSystem muzzleParticle; //粒子材质有自发光效果 所以不再需要灯效 枪焰火光效果//public Light muzzleLight;// 抛壳粒子效果public ParticleSystem casingParticle;// 音效public FirearmsAudioData firearmsShootingAudioData; //开火音效数据public AudioSource firearmsShootingAudioSource; //开火AudioSourcepublic AudioSource firearmsReloadingAudioSource; //装填AudioSource// 弹匣弹药量public int ammoInMag = 30;// 能携带的最大弹药数public int maxAmmoCarried = 120;// 子弹预制体public GameObject bulletPrefab;// 子弹散射最大角public float spreadAngle;// 射速速率 一秒钟能打几发public float fireRate;// 上一次开火的时间protected float lastFireTime;// 是否正在开火protected bool isShooting = false;// 当前弹匣弹药量protected int currentAmmoInMag = 30;// 当前携带的弹药数protected int currentAmmoCarried = 120;// 枪械动画[HideInInspector]public Animator gunAnimator;// 枪械动画机的信息protected AnimatorStateInfo gunStateInfo;// 是否正在瞄准protected bool m_isAiming;// 是否正在举枪 用于规避BUGprotected bool m_isAimingIn;// 举枪动画开始时间 用于规避BUGprotected float m_startAimInTime;// 瞄准时的目标FOVprotected float m_targetFOV = 48f;// 处理瞄准协程方法的协程变量protected IEnumerator doAimCoroutine;// 摄像机控制脚本 用于枪械后坐力功能protected FPCameraLook cameraLook;// --------------------------------------------------- 方法 ------------------------------------------------------- //// 执行攻击public void DoAttack(){// 对于枪械来说 发动攻击的方式就是发射子弹Shooting();}// 瞄准 每种枪的瞄准会有不同(也许吧) 画面放大会有不同(机瞄,二倍镜,三倍镜) 需要派生类来具体实现internal void Aiming(bool isAiming){m_isAiming = isAiming;// 如果还没有进入瞄准if (m_isAiming){m_isAimingIn = true;m_startAimInTime = Time.time;}// 设置参数gunAnimator.SetBool("Aim", m_isAiming);// 开启DoAim协程 防止协程冲突if (doAimCoroutine == null) // 说明协程还没有打开{doAimCoroutine = DoAim();StartCoroutine(doAimCoroutine);}else // 不为空说明协程已经打开了{// 首先停下之前的协程StopCoroutine(doAimCoroutine);doAimCoroutine = null;// 赋值以后再执行起来doAimCoroutine = DoAim();StartCoroutine(doAimCoroutine);}}// 按下扳机 作为外部接口 定义当按住枪械扳机时会发生什么internal void HoldTrigger(){// 当枪里没子弹时 无法射击 或者也许可以发出一个音效?if (currentAmmoInMag <= 0){isShooting = false;    return; }// 发动攻击isShooting = true;DoAttack();}// 松开扳机 作为外部接口 定义当松开枪械扳机时会发生什么internal void ReleaseTrigger(){isShooting = false;}// 松开扳机 作为外部接口 定义当装填弹药时会发生什么internal void ReloadAmmo(){// 当装填弹药时 会发生装填弹药这件事Reload();}// 发射子弹的逻辑 每种枪的子弹发射是不同的(也许吧) 需要派生类来具体实现protected abstract void Shooting();// 装填的逻辑 每种枪的换弹逻辑是不同的 需要派生类来具体实现protected abstract void Reload();// 实例化子弹的逻辑 需要派生类来具体实现protected abstract void CreateBullet();// 是否能够射击 用于武器的射速限制 或者半自动/栓动武器的实现protected abstract bool isAllowShooting();// 计算子弹散射量protected Vector3 CalculateBulletSpreadOffset(){// 子弹的散射会根据摄像机fov大小做调整float temp_spreadPercent = spreadAngle / eyeCamera.fieldOfView;// 使用随机数return Random.insideUnitCircle* temp_spreadPercent;}// 定义瞄准时需要做的 枪械瞄准时摄像机FOV都会放大protected IEnumerator DoAim(){float temp_currentFOV = 0;while (true){yield return null;// 处理瞄准时视野放大功能eyeCamera.fieldOfView =Mathf.SmoothDamp(eyeCamera.fieldOfView,m_isAiming ? m_targetFOV : originFOV,ref temp_currentFOV,Time.deltaTime * 10f);if (Mathf.Abs(eyeCamera.fieldOfView - m_targetFOV) < 0.001f){eyeCamera.fieldOfView = m_targetFOV;yield break;}}}protected virtual void Awake(){gunAnimator = GetComponent<Animator>();}// virtual修饰符方便子类修改protected virtual void Start(){currentAmmoInMag = ammoInMag;currentAmmoCarried = maxAmmoCarried;originFOV = eyeCamera.fieldOfView;cameraLook = FindObjectOfType<FPCameraLook>();// 现在doAimCoroutine是对DoAim协程方法的引用了doAimCoroutine = DoAim();}// 武器的主要控制由WeaponManager来进行// 这里只进行一些辅助控制protected void Update(){if ((Time.time - m_startAimInTime) > 0.3f) //是否正在举枪 举枪时不同时播放开火动画{m_isAimingIn = false;}if (!isShooting){cameraLook.RecoverRecoil();}}}
}

切换武器逻辑的初步实现

如果要我来写的话,切换武器的逻辑就是,我拿原本资源里有的手枪预制件放到和AK预制件的相同层,切换就是预制件的开关,到时候专门做一个脚本,名字都想好了就叫WeaponSwitcher,来专门管这两个预制件就行了。不过逻辑就是这样的逻辑。关键是看在Unity中GameObject要如何制作。

    // 切换武器private void SwapWeapon(){if (Input.GetAxis("Mouse ScrollWheel") != 0) //当使用滚轮时{currentWeapon.gameObject.SetActive(false); //隐藏现在的武器currentWeapon = (currentWeapon == mainWeapon) ? secondaryWeapon : mainWeapon; //切换武器currentWeapon.gameObject.SetActive(true); //显示切换后的武器}else if (Input.GetKeyDown(KeyCode.Alpha1)) //当按下键盘1键时{currentWeapon.gameObject.SetActive(false);currentWeapon = mainWeapon;currentWeapon.gameObject.SetActive(true);}else if (Input.GetKeyDown(KeyCode.Alpha2)) //当按下键盘2键时{currentWeapon.gameObject.SetActive(false);currentWeapon = secondaryWeapon;currentWeapon.gameObject.SetActive(true);}}

切换武器的GameObject实现


可以看到,我们将资源当中的手枪和手臂的部分与原本的步枪放到同一级,接下来要做的就是配置手枪相应的动画,参数。以及在换枪时将新枪的动画传递过去,其实要传递的我觉得不止是动画,还有新枪的后坐力参数,振屏参数等,这些以后再做。

至于手枪的动画,我是直接将步枪动画机复制了一份,将其中的动画片段都改为手枪的。有素材就是任性。

最后,在切枪的逻辑代码中,在枪械切换后为controller脚本修改Animator。

    // 切换武器private void SwapWeapon(){if (Input.GetAxis("Mouse ScrollWheel") != 0) //当使用滚轮时{currentWeapon.gameObject.SetActive(false); //隐藏现在的武器currentWeapon = (currentWeapon == mainWeapon) ? secondaryWeapon : mainWeapon; //切换武器currentWeapon.gameObject.SetActive(true); //显示切换后的武器controller.SetupAnimator(currentWeapon.gunAnimator); //切换Animator}else if (Input.GetKeyDown(KeyCode.Alpha1)) //当按下键盘1键时{currentWeapon.gameObject.SetActive(false);currentWeapon = mainWeapon;currentWeapon.gameObject.SetActive(true);controller.SetupAnimator(currentWeapon.gunAnimator);}else if (Input.GetKeyDown(KeyCode.Alpha2)) //当按下键盘2键时{currentWeapon.gameObject.SetActive(false);currentWeapon = secondaryWeapon;currentWeapon.gameObject.SetActive(true);controller.SetupAnimator(currentWeapon.gunAnimator);}}

BUG以及缺陷:

Firearms 类中的接口有时会过度封装。

        // 松开扳机 作为外部接口 定义当装填弹药时会发生什么internal void ReloadAmmo(){// 当装填弹药时 会发生装填弹药这件事Reload();}

第一次切换武器时,传递的Animator为空,这个BUG其实原因很简单,手枪的脚本在一开始的Active为false,不会执行脚本中的Start,也就是说一开始我们并没有获取手枪中的Animator,当我们换枪时,SwapWeapon传递的currentWeapon.gunAnimator自然就是null。要解决这个问题可以在编辑器中手动添加,或者让脚本在Awake中获取Animator。

        protected virtual void Awake(){gunAnimator = GetComponent<Animator>();}

开枪会打到自己,没错,原因是子弹的射线并没有屏蔽掉玩家自己。我们要做的就是给射线一个LayerMask。

        // mask用来屏蔽掉玩家自己 防止开枪时的子弹打到自己public LayerMask bulletMask;// 在Raycast参数中运用maskif (Physics.Raycast(bulletTransform.position, (prevPosition - bulletTransform.position).normalized,//射线发射方向为从上一帧的位置发射到这一帧的位置 out RaycastHit temp_hit,(bulletTransform.position - prevPosition).magnitude, bulletMask.value))//射线最大长度为这两个位置之间的长度{......}

每次切换到步枪时,都会播放开火声,经过排查是不知何时勾选了AudioSource组件的Play On Wake。

最后还有一个问题,手枪开枪时能看到子弹轨迹“戳”到枪里去了,我觉得造成的原因一部分是因为子弹的TrailRenderer的设置问题,可能还有一部分是因为GunCamera的问题。


值得注意的:

我们的枪械切换功能还没做完。


枪械切换(1)——Unity随手记(2021.2.16)相关推荐

  1. 枪械切换(2)——Unity随手记(2021.2.17)

    文章目录 今天实现的内容: 加入放下武器的动画 运用放下武器的动画以及改善的切枪逻辑 BUG以及缺陷: 值得注意的: 今天实现的内容: 加入放下武器的动画 之前的切换武器是旧枪直接消失,然后把新枪掏出 ...

  2. 黑魂复刻游戏的玩家输入模块——Unity随手记(2021.3.14)

    文章目录 前言 今天实现的内容: 按键封装 获取输入及输入信号优化 输入的渐变 模块的软开关 处理输入 BUG以及缺陷: 值得注意的: 前言 好久不见,在接下来的Unity随手记里,我会学着B站上的视 ...

  3. 从其他应用切换回Unity使用VS的devenv.com自动编译Assets外部的C#工程(需含有.sln)

    最近调到新项目工作,为了热更将代码移到Assets外部,打成dll给Unity使用,导致Unity无法检测到是否修改,每次修改代码都要使用VS进行手动编译,特别麻烦,有时候都忘了是否进行手动,导致的各 ...

  4. 微软必应(Bing)打不开解决方案(2021.12.16)

    2021.12.19 0:10更新:现在Bing已修复,可直接通过https://cn.bing.com/访问~ 问题描述 2021.12.16开始必应就打不开了.. 解决方案 1. 打开主页 将原先 ...

  5. ds填空题2021/2/16

    2021/2/16 在一棵二叉树中,假定度为2的结点个数为5个,度为1的结点个数为6个,则叶子结点数为 6 个. 需要知道二叉树 叶子结点数和度为2结点数 的性质:二叉树的叶子结点数永远比度为2的结点 ...

  6. 2021.07.16 总结

    2021.07.16 总结 ​ 今天状态不怎么好,几道那么容易的题就只有140分,毕竟也就打了前两道 T1 花生采摘 题目描述 鲁宾逊先生有一只宠物猴,名叫多多.这天,他们两个正沿着乡间小路散步,突然 ...

  7. 2021.07.16【普及组】模拟赛C组

    2021.07.16[普及组]模拟赛C组 文章目录 2021.07.16[普及组]模拟赛C组 前言 花生采摘 题目 解析 代码 FBI树 题目 解析 代码 火星人 题目 解析 代码 麦森数 题目 解析 ...

  8. 2021.7.16模拟赛C组总结(转载XJY)

    2021.7.16模拟赛C组总结 这次比赛,题虽然不难,但丝毫不影响我打挂-唉- 0+100+50+0=150 题解 T1 题目描述: ​ 鲁宾逊先生有一只宠物猴,名叫多多.这天,他们两个正沿着乡间小 ...

  9. 黑魂复刻游戏的玩家控制器(基础移动,动画实现及优化)——Unity随手记(2021.3.15)

    文章目录 今天实现的内容: 动画机设计理念 动画机的运用及模型旋转 玩家角色的位移 爬坡测试 跑步 旋转的优化 跑步动画的优化 BUG以及缺陷: 值得注意的: 今天实现的内容: 动画机设计理念 要我说 ...

最新文章

  1. 无人驾驶、VR、AR时代即将开启,中国电信2018年将完成5G商用版本
  2. 网络安全 — 安全架构
  3. zabbix报错:Zabbix服务启动不了
  4. AI之FL:联邦学习(Federated Learning)的简介、入门、应用之详细攻略
  5. python列表换行写入_如何使用Python3中的换行符将列表写入文件
  6. nvme驱动_耗时3天2夜,搞定了macbook pro(2015款)更换nvme固态,经验分享一下,希望能帮到有需要的人!...
  7. 使用Curator和ZooKeeper发现Hazelcast成员
  8. nuxt webpack配置css,基于nuxt通过webpack配置ant-Design-vue的主题切换配置
  9. EAS 表格、查询方案存储表
  10. android webview_在 Flutter 中使用 WebView
  11. windows 截屏快捷键x220_电脑截屏快捷键是什么啊
  12. Drool实战系列(二)之eclipse安装drools插件
  13. 屏幕录制生成gif文件神器和相关操作 GifCam
  14. 绿坝老板不诚实,蒙骗政府官员
  15. windows下超越dirx的opencv视频转化库
  16. 通过监听手势滑动解决DrawerLayout只能边缘打开抽屉问题
  17. java compile_java中compile函数用法
  18. LeetCode精选TOP面试题(中等篇)【出现率降序】
  19. 什么是肿瘤免疫逃逸?
  20. ct与x光的哪个辐射大_X光和CT,哪个辐射大?这些数字,医生可能并没告诉你

热门文章

  1. 设备树 DTS DTB
  2. pytorch 实现径向基函数网络(RBF Network)
  3. RTK-Real Time kinematic实时动态
  4. 迅宏超低成本双卡双待Android手机平台量产,价格低于1000元
  5. 如何修改避免闪烁(Anti-Flicker)默认值
  6. 爸爸妈妈一直认为我还是个小孩子
  7. The difference between Failure and Success
  8. 怎么用java实现一二级菜单,以及对应的数据库表怎么设计?
  9. 新年礼OPPO R11s星幕新年版已开售,你想好要送给谁了吗?
  10. 不确定高度的盒子怎样垂直居中呢?