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/ 树可以看成是一个连通且 无环 的 无向 图。 给定往…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...