Unity3D学习FPS游戏(8)装弹和弹夹UI显示
前言:实现了武器的基本发射功能,但是我们弹夹数量是有限,之前并没有做装弹和弹夹显示的功能。本篇实现装弹和弹夹显示。
装弹和弹夹UI显示
- 装弹
- 目标
- 思路和实现
- 弹夹UI显示
- 目标
- 弹夹UI的思路和实现
- UI代码的思路和实现
- 武器控制的完整代码
- 效果
- 补充知识
- 锚点
装弹
目标
Unity官方的项目中,如果不是满弹夹的情况,子弹的数量会随时间自动增加。
思路和实现
如果子弹没有满,且在没有开枪的情况下,子弹会自动装填。
由于子弹的自动装填是随着时间慢慢增加的,可以通过协程来实现。
public float reloadBulletTime = 2F;// 每次装弹的时间
public int reloadBulletNum = 10;// 每次装弹的数量
IEnumerator ReloadBullet()
{while (!isFire&¤tBulletNum<bulletNum)// 达到弹夹容量,协程自己会停止运行{yield return new WaitForSeconds(reloadBulletTime);currentBulletNum= (reloadBulletNum+currentBulletNum)>bulletNum?bulletNum:(reloadBulletNum + currentBulletNum);// 判断一下会不会超过弹夹容量}
}
当子弹左键按下的时候,就表示要开枪,这时候就应该停止装弹协程。
当子弹左键松开的时候,就表示停止开枪,这时候就应该开启装弹协程。
private void OpenFire()
{if (Input.GetMouseButtonDown(0)){StopCoroutine("ReloadBullet");isFire = true;StartCoroutine("Shoot");}if (Input.GetMouseButtonUp(0)){isFire = false;StopCoroutine("Shoot");StartCoroutine("ReloadBullet");}
}
弹夹UI显示
目标
子弹数量发生变化的时候,弹夹会有一个UI进度条提醒当前子弹的情况。
弹夹UI的思路和实现
UI毫无疑问得在Canvas上进行。
在之前显示做准星的Canvas上,右键UI-Slider,添加一个滑动条。
默认的Slider中三个部分分别对应界面内容如下,Backgroud是滑动条底色,Fill Area是滑动区域,Handle Slider Area是滑动区域末尾小圆球。

其中Handle Slider Area小圆球,我们可以删掉,因为我们并不用。
把Fill Area中的Fill拖到和Backgroud平级,并调整strech(伸展)左右铺满,Fill Area作用不大可以删掉只是更好约束了Fill的strech。

接下来调整颜色和样式,给Backgroud和Fill选择方形的背景图片TEX_Black和TEX_White。下面演示了是如何找到素材和切换背景图片。

然后修改Fill的颜色,模仿Unity官方案例的蓝色,然后调整Slider大小。
位置为常在的右下角,这个Slider直接拖动到右下角是没有用的,可以通过锚点来常驻右下角。锚点调到右下角后,再拖动到合适位置。这样屏幕无论怎么样变化,Slider位置都是相对右下角进行变化的。


添加弹夹的UI,Slider下面右键新增UI-Image,然后切换Source Image为武器的图标。切换图片后,可以Set Native Size,可将图像框的尺寸设置为纹理的原始像素大小,不会变形太厉害。

调整武器的图标大小到合适。

UI代码的思路和实现
代码控制Slider,先在代码中添加Slider组件,添加后Unity把组件拖过来就行了。
public Slider bulletSlider;// 弹夹Slider UI
在Start函数中初始化Slider,最大值和当前值。
void Start()
{if (bulletSlider){bulletSlider.maxValue = bulletNum;bulletSlider.value = currentBulletNum;}
}
在子弹数量会发生变化的地方,进行Slider更新,会发生变化的地方只有发射子弹(Shoot)和装弹(ReloadBullet)两个协程中。
IEnumerator Shoot()
{while (isFire){if (currentBulletNum >0){GameObject newBullet = bulletPool.Get();currentBulletNum--;// 更新弹夹数量if (bulletSlider)bulletSlider.value = currentBulletNum;}yield return new WaitForSeconds(shootInterval);}
}
IEnumerator ReloadBullet()
{while (!isFire&¤tBulletNum<bulletNum){yield return new WaitForSeconds(reloadBulletTime);currentBulletNum = (reloadBulletNum+currentBulletNum)>bulletNum?bulletNum:(reloadBulletNum + currentBulletNum);// 更新弹夹数量if (bulletSlider)bulletSlider.value = currentBulletNum;}
}
武器控制的完整代码
这次主要修改的WeaponController的代码,下面是修改后WeaponController的完整代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;public class WeaponController : MonoBehaviour
{[Header("武器数值")]public Vector3 defaultPosition= new Vector3(0.4F, -0.6F, 1.15F);// 默认位置public Vector3 centerPosition = new Vector3(0F, -0.6F, 0.807F);// 中心位置public float positionLerpRatio = 0.5f;// 线性插值参数[Header("子弹数值")]public Transform shootPoint;// 子弹发射位置public GameObject bullet;// 子弹预制体public float shootInterval = 1;// 子弹间隔时间private bool isFire;// 发射状态public int bulletNum = 100;// 弹夹public int currentBulletNum;// 当前子弹的数量public float reloadBulletTime = 2F;// 每次装弹的时间public int reloadBulletNum = 10;// 每次装弹的数量public Slider bulletSlider;// 弹夹Slider UIprivate ObjectPool<GameObject> bulletPool;// 子弹对象池private void Awake(){currentBulletNum = bulletNum;bulletPool = new ObjectPool<GameObject>(CreateBullet,BulletOnGet, BulletOnRelease, BulletOnDestory,true,10,bulletNum);}GameObject CreateBullet(){GameObject obj = Instantiate(bullet, shootPoint);obj.GetComponent<BulletController>().bulletPool = bulletPool;return obj;}void BulletOnGet(GameObject obj){obj.GetComponent<BulletController>().BulletReset();obj.gameObject.SetActive(true);}void BulletOnRelease(GameObject obj){obj.gameObject.SetActive(false);}void BulletOnDestory(GameObject obj){Destroy(obj);}void Start(){// 弹夹UI初始化if (bulletSlider){bulletSlider.maxValue = bulletNum;bulletSlider.value = currentBulletNum;}}void Update(){ChangePosition();OpenFire();}private void OpenFire(){if (Input.GetMouseButtonDown(0)){StopCoroutine("ReloadBullet");isFire = true;StartCoroutine("Shoot");}if (Input.GetMouseButtonUp(0)){isFire = false;StopCoroutine("Shoot");StartCoroutine("ReloadBullet");}}IEnumerator Shoot(){while (isFire){if (currentBulletNum >0){//GameObject newBullet = Instantiate(bullet, shootPoint);GameObject newBullet = bulletPool.Get();currentBulletNum--;// 弹夹UI更新if (bulletSlider)bulletSlider.value = currentBulletNum;}yield return new WaitForSeconds(shootInterval);}}private void ChangePosition(){// 按下左键if (Input.GetMouseButtonDown(1)){StopCoroutine("ToDefault");StartCoroutine("ToCenter");}// 松开左键if (Input.GetMouseButtonUp(1)){StopCoroutine("ToCenter");StartCoroutine("ToDefault");}}IEnumerator ToCenter() {while (transform.localPosition!=centerPosition){transform.localPosition = Vector3.Lerp(transform.localPosition, centerPosition, positionLerpRatio);yield return null;// 等待一帧}}IEnumerator ToDefault(){while (transform.localPosition != defaultPosition){transform.localPosition = Vector3.Lerp(transform.localPosition, defaultPosition, positionLerpRatio);yield return null;// 等待一帧}}IEnumerator ReloadBullet(){while (!isFire&¤tBulletNum<bulletNum){yield return new WaitForSeconds(reloadBulletTime);currentBulletNum = (reloadBulletNum+currentBulletNum)>bulletNum?bulletNum:(reloadBulletNum + currentBulletNum);// 弹夹UI更新if (bulletSlider)bulletSlider.value = currentBulletNum;}}
}
效果

补充知识
锚点
Canvas下创建UI会自带四个△为锚点,如下图。

锚点可以分开,可以构成矩形。

Unity中UI属性可以设置锚点,红色框部分是锚点在一起,黄色框部分是锚点分开。这两个属性在本篇弹夹显示的UI中都用到的了。

锚点在一起的时候,图片的大小不会随着父对象的大小改变而改变,但是图片定位是相对锚点进行定位的。例如本文实现弹夹显示在右下角的时候,锚点就被设置在了右下角。
锚点分开的时候,用作与拉伸,锚点的位置会随着父物体的大小进行变动。例如本文实现弹夹显示条,调整父物体Slider的大小时候,里面的显示条也会跟着拉伸。
相关文章:
Unity3D学习FPS游戏(8)装弹和弹夹UI显示
前言:实现了武器的基本发射功能,但是我们弹夹数量是有限,之前并没有做装弹和弹夹显示的功能。本篇实现装弹和弹夹显示。 装弹和弹夹UI显示 装弹目标思路和实现 弹夹UI显示目标弹夹UI的思路和实现UI代码的思路和实现 武器控制的完整代码效果补…...
Android 托管 Github Action 发布 Github Packages ,实现 Mvn 免费自动化托管
自从多年前 JCenter 关闭服务之后,GSY 项目版本就一直发布在 Jitpack 上,如今每个月也都有大概 10w 左右下载,但是近年来时不时就会出现历史版本丢失的问题,而且有时候还不是某个具体版本丢失,而是版本里的某几个依赖突…...
火山引擎VeDI数据服务平台:在电商场景中,如何解决API编排问题?
01 平台介绍 数据服务平台可以在保证服务高可靠性和高安全性的同时,为各业务线搭建数据服务统一出口,促进数据共享,为数据和应用之间建立了一座“沟通桥梁”。 同时,解决数据理解困难、异构、重复建设、审计运维困难等问题&#x…...
【每日C/C++问题】
一、 结构体和联合体有什么区别?能否在声明过程当中缺省名字?(需要写清楚使用方法) 结构体的各个成员占用不同的内存空间,总大小是所有成员大小之和(结构体字节对齐): typedef str…...
layaair做帧动画,等待一秒之后移动坐标,坐标位置明明相同,执行的时候却会抖动。
如下图:我将1秒后面的位置与0秒的坐标位置设置为一样,然后再第二秒的时候再设置位置移动。也就是想实现这个效果,小狗先停在这里一秒,然后再开始行走。现在的问题是停在这里依然抖动一下,也就是根本就停不住。还是会变…...
SAP分包业务中能否应用后继物料?
近期物流用户在工作中遇到新的问题。在分包业务中的原材料后继(物料主数据设定非连续标识及后继物料)不成功问题。对于未知应用,需要先研究期可行性,与问题或故障不同,如果系统本身就不支持,再多分析测试也…...
【数据结构】二叉树——判断是否为完全二叉树
一、思路 有完全二叉树的解释 我们想要判断二叉树是否为完全二叉树 我们可以用队列来实现 我们先将根节点入队列 再将根节点出队列,判断取出节点是否为空、 若不为空将该节点的左右节点入队列 左右节点为空也入队列 若为空则停止入队列 然后判断队列中是否有 NUL…...
FFmpeg 4.3 音视频-多路H265监控录放C++开发十. 多线程控制帧率。循环播放,QT connect 细节,
在前面,我们总结一下前面的代码。 在 FactoryModeForAVFrameShowSDL 构造函数中 init SDL。 通过 QT timerevent机制,通过startTimer(10);每隔10ms,就会调用timerEvent事件。 在timerEvent事件中,真正的去 读取数据,…...
近百万奖金!2024 Web3.0 创新大赛重磅来袭!
10月30日,中国互联网协会与香港Web3.0协会共同组织举办的2024 Web3.0 创新大赛在上海举行启动会,宣布大赛正式在DataFountain竞赛平台(简称DF平台,http://www.datafountain.cn)启动上线。 大赛面向社会各界征集参赛团队…...
gRPC 一种现代、开源、高性能的远程过程调用 (RPC) 可以在任何地方运行的框架
背景介绍 gRPC 是一种现代开源高性能远程过程调用 (RPC) 可以在任何环境中运行的框架。它可以有效地连接服务 在数据中心内和数据中心之间,具有对负载平衡、跟踪、 运行状况检查和身份验证。它也适用于最后一英里 分布式计算,用于…...
cmake系列-怎么构建不同的C++程序目标文件(可执行程序、动态库、静态库)
目录 生成可执行程序生成动态库生成静态库 我们编写的C代码不仅仅只是为了生成可执行程序,有的时候可能是为了生成动态库或者静态库,那么如果用cmake来构建的话,应该怎么做呢,怎么指定是生成可执行程序,还是生成动态库…...
使用ffmpeg和mediamtx模拟多通道rtsp相机
首先下载ffmpeg,在windows系统上直接下载可执行文件,并配置环境变量即可在命令行当中调用执行。 下载地址: https://ffmpeg.org/再在github上下载mediamtx搭建rtsp服务器,使用ffmpeg将码流推流到rtsp服务器。 下载地址࿱…...
windows系统类似于linux的nohup命令后台启动jar服务
一、首先新建一个后缀名为.bat文件 二、将jar包放在与jar包同一个路径下 三、编写.bat文件 echo off start javaw -Xms512m -Xmx1024m -XX:PermSize256m -XX:MaxPermSize512m -XX:MaxNewSize512m -jar xxxxx-22900.jar >> StartupLog.log 2>&1 & exit 四…...
2024 Rust现代实用教程 流程控制与函数
文章目录 一、if流程控制与match模式匹配1.流程控制2. IF流程控制3.match 表达式 二、循环与break continue以及与迭代的区别1.Rust中的循环Loops2.break && continue3.迭代4.循环与迭代的不同 三、函数基础与Copy值参数传递1.函数的基础知识2.Copy by value 四、函数值…...
stm32入门教程--USART外设 超详细!!!
目录 简介 什么是UART? 什么是USART? 简介 USART(Universal Synchron /Asynchronous Receiver /Transmitter)通用同步/异步收发器 1、USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧…...
再探“构造函数”(2)友元and内部类
文章目录 一. 友元‘全局函数’作友元‘成员函数’作友元‘类‘作友元 内部类 一. 友元 何时会用到友元呢? 当想让(类外面的某个函数/其它的类)访问 某个类里面的(私有或保护的)内容时,可以选择使用友元。 友元提供了一种突破&a…...
ffmpeg+vue2
一、安装依赖 npm install ffmpeg/core ffmpeg/ffmpeg "ffmpeg/core": "^0.10.0", "ffmpeg/ffmpeg": "^0.10.1",二、配置ffmpeg 安装好插件以后,需要配置一下代码,否则会报错: 1、在vue.config.js…...
基于深度学习YOLOv10的电动二轮车目标检测、轨迹跟踪、测距算法
基于深度学习YOLOv10的电动二轮车目标检测、轨迹跟踪、测距算法 基于深度学习YOLOv10的电动二轮车目标检测、轨迹跟踪、测距算法引言YOLOv10简介目标检测轨迹跟踪测距算法实际应用结论 基于深度学习YOLOv10的电动二轮车目标检测、轨迹跟踪、测距算法 二轮电动车的目标检测、跟踪…...
鸿蒙ArkTS中的image组件
开发文档很详尽,就在DevEco中的API参考,可以随时调出来进行学习。 在鸿蒙官网也有非常详尽的资料,地址:开发说明-API参考概述 - 华为HarmonyOS开发者 (huawei.com) 这里,就学习image组件的一般用法以及使用SVG图标和字…...
LeetCode 684.冗余连接:拓扑排序+哈希表(O(n)) 或 并查集(O(nlog n)-O(nα(n)))
【LetMeFly】684.冗余连接:拓扑排序哈希表(O(n)) 或 并查集(O(nlog n)-O(nα(n))) 力扣题目链接:https://leetcode.cn/problems/redundant-connection/ 树可以看成是一个连通且 无环 的 无向 图。 给定往…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...
五、jmeter脚本参数化
目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …...
