1.15 从0开始学习Unity游戏开发--游戏UI

news2025/1/16 11:03:05

上一章中,我们剩下最后一个任务,需要支持鼠标控制准心来进行设计,那么准心本质上就是一个始终呈现在屏幕上的一个图片,你当然可以用一个3D物体来制作,之前讲解渲染概念的时候也提到过,我们的屏幕就是相机的近裁面,只要我们将3D物体保持放在近裁面的地方,我们就可以保证这个物体看起来就像是一直在屏幕上一样。

但是这样做需要你在可能极小的区域(近裁面往往只有0.1或者更小)进行物体摆放,非常的不方便,而且3D物体本身也没有专门针对UI所需的内容进行处理,还得自己造轮子,那么Unity为了方便游戏内制作UI,提供了一套内置的方案:UGUI。

UGUI

UGUI其实就是个名字,本质上是Unity提供了一套特殊的组件,这套组件的渲染流程走的不是Mesh Renderer,而是专门针对渲染在屏幕上的内容进行特化,如果之前做过传统软件的UI开发,那么类似Qt,WPF,MFC之类的框架都会有很多控件例如Button,Label等,方便快速的构建一个界面出来,显然UGUI也应该会提供类似的东西。

那么我们现在开始创建UI内容,同样的和创建普通的3D物体一样的路径,我们也是在Hierarchy里面右键选择UI->Image,会突然多出来好多东西:

我们慢慢解释:

  • Canvas物体:

之所以我这里叫物体,是因为这个物体里面有个组件也叫Canvas,为了区分,我们把Hierarchy里面存在的GameObject叫Canvas物体,我们分析这个物体自动给创建的组件有哪些:

这里RectTransform组件其实就是继承自Transform组件,这里Unity覆盖了原本的Transform组件面板的数据显示,直接显示RectTransform,也算是Unity默认搞了点潜规则,毕竟这里编辑UI的3D坐标数据没啥意义。

而RectTransform里面其实配置的参数就跟我们做传统软件UI上的锚点,定位,像素位置,大小,就类似了,这里之所以不能改,因为这个Canvas物体上层没有更多拥有Canvas组件的父元素了,那这个Canvas本身就代表了全屏,所以你试着调整Game窗口的大小,这个里面的数值就会跟着变,如果是子元素的话,则这里是可以修改的,后面我们会看到。

Canvas组件就是类似Mesh Renderer组件的功能,承载物体下面所有子元素里面包含的UI内容的渲染,相比于我们渲染3D物体需要提供Mesh,Material之类的东西,这里Unity屏蔽了这些细节,只需要我们指定RenderMode,渲染到哪个显示目标等等。这里我们只需要暂时关注Render Mode,默认情况下是Screen Space - Overlay,说人话其实就是Unity会用一套潜规则逻辑,独立的将你这坨UI内容渲染到屏幕上,至于这个潜规则是啥,以及其他RenderMode又怎么用,等到进阶教程里面会详细聊使用场景,现在我们只需要知道这个组件就是承载UI显示用的。

Canvas Scaler和Graphics Raycaster暂时可以不管,遇到了我们再讲。

  • EventSystem,这个其实就是UI系统需要使用的输入事件如何处理的逻辑,里面有两个组件,但是我们现在先不用关注它,反正只要知道UI需要这个组件才能响应点击之类的操作。

  • Image

最后就是我们这次需要画准心用的Image

可以看到,RectTransform组件已经可以修改值了,我们可以修改里面的数据来调整位置大小和布局。

Canvas Renderer组件则是告诉父元素里面的Canvas组件,这里有一个需要渲染的UI元素。

Image组件则是提供需要渲染的内容,可以直观的看到这里Source Image肯定就是我们要显示的图片赋值的地方,Color则是给这个图片叠加一个颜色,这里默认是白色,也就是说我们即使不赋值图片,也会显示白色。其他的参数部分我们先不管。

解释完默认创建的组件,其实这里Canvas和EventSystem都是因为要显示一个Image所必须的东西,Unity检查你场景里面没有这两个东西就默认给创建了,如果你现在再到Canvas物体里面创建一个Image,则只会出现一个新的Image,不会再继续额外创建Canvas和EventSystem。

那么现在我们看一下场景内的现象:

可以看到我们场景里面出现了一个硕大的白色,Game里面左下角也显示了一个白色的块,这其实就是我们的Image显示出来的效果。

那么如何编辑呢?我们滑动鼠标滚轮,把Scene场景的视角拉远多一些:

可以看到我们UI显示的部分(白色线框)以及我们的白色Image显示的位置,而我们的Cube和Wall都远到看不清了。这其实就是Unity给我们默认做的一个效果,RenderMode为Screen Sapce -Overlay的情况下,会在坐标原点的位置生成一个硕大的屏幕来承载我们的UI显示内容,当然这只是在Scene窗口里面是这样,而Game窗口则会老老实实的显示在屏幕的位置上。

知道了这一点,其实我们就知道了,UI的编辑跟编辑3D场景物体没有什么两样,我们同样可以拖动坐标轴箭头来移动我们的Image,当然我们在3D视角下是有透视投影的,如果我们希望做平面设计,那么Scene窗口顶部的工具条上有个2D的按钮,按下去会直接变成2D的模式,这样更方便调整UI。

制作准心图片

当然我们目前的需求是做一个准心,不是白块,所以我们需要找一张准心图片,直接随便搜一张吧:

http://static.fotor.com.cn/assets/stickers/freelancer_ls_20180125_26/ba9b50fb-1efd-4854-928b-0b40ae26e36f_medium_thumb.jpghttp://static.fotor.com.cn/assets/stickers/freelancer_ls_20180125_26/ba9b50fb-1efd-4854-928b-0b40ae26e36f_medium_thumb.jpg

这个链接是个jpg图片,自己存下来,放到工程的Assets文件夹下任意位置。

我这里改了个名字叫aim,选中它,然后在Inspector面板上指定这个图片的用途:

选择Sprite(2D and UI),这个类型是我们的Image组件Source Image需要的类型,然后不要忘记点Apply让这个选择生效,生效后可以看到我们的图片正常显示了透明背景。

然后接下来就是需要将这个图片拖拽赋值给Image组件:

如果发现拖拽赋值不上,那看看是不是上一步没有做好,类型不对是不会让赋值的。

赋值成功后可以看到左侧Game窗口里面显示了我们的准心图片。

如果觉得不够显眼,可以尝试自己换其他图片,或者将Image组件里面的Color参数改成红色之类的叠加上去,这样更鲜艳。

动态修改UI元素的位置

上面讲了,我们只需要修改RectTransform的PosX或者PosY就可以修改准心的位置,我们需要的是运行的时候根据鼠标的位置来动态的调整准心位置。

既然我们需要动态的根据逻辑调整,当然是需要新建一个组件放在Image这里来通过代码调整,很快啊,我让GPT给我写了一个

using UnityEngine;

public class AimController : MonoBehaviour
{
    private RectTransform rectTransform;

    private void Start()
    {
        rectTransform = GetComponent<RectTransform>();
    }

    private void Update()
    {
        rectTransform.anchoredPosition = Input.mousePosition;
    }
}

这里指的注意的是,虽然RectTransform继承自Transform,但是我们不会直接使用position,应为这个坐标是3D空间的坐标,不是我们的UI系统下的坐标,取而代之的是使用anchoredPosition,这个是个Vector2,正好就是我们屏幕坐标X和Y,我们直接把鼠标坐标给赋值上去。

跑起来看看?

奇怪,为什么准心跟鼠标固定有一个间隔呢?我们再看看这个anchoredPosition的说明:

https://docs.unity3d.com/ScriptReference/RectTransform-anchoredPosition.htmlhttps://docs.unity3d.com/ScriptReference/RectTransform-anchoredPosition.html

The position of the pivot of this RectTransform relative to the anchor reference point.

翻译过来就是,这个坐标是这个组件的pivot点相对于anchor点的坐标。

说人话:

pivot就是所谓的中心点,不论是移动,旋转,这个Image肯定是需要有个中心点的,那么这些都是靠这个中心点计算,Image本身是有形状大小的,那么中心点可以相对于真实的形状中心做调整。

anchors感觉很神秘,其实就是相对于父元素的布局,点开里面这个地方可以很直观的选择几种布局方法:

详细的定义可以阅读官方文档:

https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/UIBasicLayout.htmlhttps://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/UIBasicLayout.html

可以看到我们默认的布局是相对于父元素居中,但是Input.mousePosition是从左下角开始为坐标原点,所以我们这里相当于在以屏幕中心点偏移一个鼠标相对于左下角的偏移量,那么鼠标永远会和准心相差半个屏幕。

那么我们可以简单的选择左下角的对齐方式来修改Anchor:

修改完毕后再跑一下游戏,看看是不是准心已经跟手了。

发射子弹

好了,现在我们准心有了,那么就剩下发射子弹了,想象一下,如果我们要对准准心发射子弹,那么准星应该就是对准的远处被预期击中的位置,也就是说准心是覆盖在被瞄准的物体上,再说直白一点就是你的眼睛到准心的连线的延长线能和被瞄准的物体相交。

眼睛的位置其实就是相机的位置,这个很好理解。

但是我们准心在屏幕上啊?没关系,我们之前也说了,屏幕就是近裁面,屏幕上的任何点可以换算到3D空间坐标下。

这种常见的算法,Unity都给我们封装了:

https://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.htmlhttps://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html

看到我们需要传入屏幕坐标xy,但是还有一个z是什么呢?就是距离相机的距离(其实就是说屏幕上一个点可以覆盖3D世界里面无数个点,那这些点就由离相机的距离来决定),屏幕上的UI固定是距离一个近裁面的距离,也即是我们之前有看过Camera组件里面的那个Near的值。

而Camera就是我们用的MainCamera,好我们修改一下脚本:

AimController里面我们计算一下相机到准心的方向,并且传递给我们之前的开火控制代码,一样的,如果我们需要引用其他功能模块,除了传统的代码设计中通过直接传实例,如果类本身是MonoBehaviour,那我们可以通过设置成public成员来提供给编辑器里面拖拽赋值(虽然对程序员来说并不是很友好)。

using UnityEngine;

public class AimController : MonoBehaviour
{
    public Camera mainCamera;
    public FireController fireController;

    private RectTransform rectTransform;

    private void Start()
    {
        rectTransform = GetComponent<RectTransform>();
    }

    private void Update()
    {
        rectTransform.anchoredPosition = Input.mousePosition;
        // 获取屏幕上当前鼠标位置(也就是准心位置)所在的3D空间位置
        Vector3 aimWorldPosition = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,
            Input.mousePosition.y, mainCamera.nearClipPlane));
        // 通过坐标相减可以得到方向向量
        Vector3 fireDirection = aimWorldPosition - mainCamera.transform.position;
        // 归一化后传递给开火控制脚本
        fireController.SetDirection(fireDirection.normalized);
    }
}

注意这里新增了一个public成员,用来让我能在编辑器里面赋值具体调用SetDirection是对谁调用的,所以我们需要在Image的AimController里面拖拽赋值上我们之前创建的FireController物体,需要注意的是,我们这里并不是因为物体叫FireController才可以拖拽,而是这个GameObject拥有一个FireController类的组件才可以拖拽,这也意味着我们可以不仅限于赋值GameObject,直接赋值组件本身也是ok并且更推荐的。

好,接下来就是改写一下FireController,之前我们只是单纯创建子弹,并没有提供发射方向,现在接收到了来自瞄准功能的方向,我们需要让子弹创建的时候设入飞行方向:

using UnityEngine;

public class FireController : MonoBehaviour
{
    private bool isMouseDown = false;
    private float lastFireTime = 0f;
    private Vector3 fireDirection;
    public float fireInterval = 0.1f;
    public AddVelocity bullet;

    void Update()
    {
        if (Input.GetButton("Fire1"))
        {
            if (!isMouseDown)
            {
                isMouseDown = true;
                lastFireTime = Time.time;
                Fire();
            }
            else if (Time.time - lastFireTime > fireInterval)
            {
                lastFireTime = Time.time;
                Fire();
            }
        }
        else
        {
            isMouseDown = false;
        }
    }

    void Fire()
    {
        // 在这里实现每次触发的逻辑

        // 创建新的子弹,每次都是从模板bullet复制一个出来
        AddVelocity newBullet = Object.Instantiate(bullet);
        newBullet.SetDirection(fireDirection);
    }

    public void SetDirection(Vector3 direction)
    {
        fireDirection = direction;
    }
}

可以看到我们补上了SetDirection函数的实现,将值存储为成员变量,然后我们需要给每个新创建的子弹都传入这个速度,所以子弹也需要有接口,但是我们之前创建子弹是直接用GameObject,这里我们直接偷个懒,直接用上面仅有的我们自己写的组件类AddVelocity,注意看,我们的bullet成员从原先的GameObject类变为了AddVelocity,这样我们将能够直接用AddVelocity类型实例化GameObject,并且返回值是AddVelocity,这样做的效果相当于实例化GameObject后又使用GetComponent获取到了上面的AddVelocity组件,但是写法更加优雅高效(每次动态调用GetComponent是相对更慢的)。

那么最后我们要做的就是在AddVelocity里面也实现SetDirection来实现给子弹赋予飞行方向:

using UnityEngine;

public class AddVelocity : MonoBehaviour
{
    public float speed; // 初始速度
    public float lifeTime = 5.0f;
    private float lifeStartTime;
    private Vector3 fireDirection;

    void Start()
    {
        Rigidbody rb = GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.velocity = fireDirection * speed;
        }

        lifeStartTime = Time.time;
    }

    void Update()
    {
        if (Time.time - lifeStartTime > lifeTime)
        {
            Destroy(gameObject);
        }
    }

    public void SetDirection(Vector3 direction)
    {
        fireDirection = direction;
    }
}

这里除了存入方向,我们还修改了之前的初速度Vector3为float类型的speed变量,因为我们不再需要人工指定速度方向,取而代之的我们需要调节这个初速度的强度,也就是我们将会有一个系数乘上去,所以最终我们初速度就是fireDirection * speed。

因为修改了成员,我们同样需要对Bullet prefab修改speed数值到一个比较舒服的值,比如说10。

需要注意的是,因为我们在FireController里面修改了bullet成员的类型,之前编辑器里面拖拽赋值的内容是以GameObject类型绑定上去的,所以当我们改了代码之后,这里赋值已经无效化,Unity会帮我们清空掉,我们不要忘记重新拖拽Bullet prefab去赋值到FireController的bullet成员上去。

好了,跑一下游戏看看?

好家伙,准心和子弹各管各的,子弹一直都没有改变自己的方向,那么问题出在哪里呢?还记得上一章我们讲过,新创建出来的GameObject的位置默认是原点(当然Prefab自己已有位置信息就另说),那么我们无论怎么修改方向,发射其实都是从原点开始,根本不是从我们的眼睛射出的,所以我们需要修改这个发射起点,那么我们目前有两个办法:

  1. 看一下目前相机的位置信息,然后手写到子弹创建的代码那里赋值,这样做肯定是不符合后续易于维护的目的,只要相机自己变了位置就完蛋了。
  2. 把相机的位置信息传过来

很显然第二种是更好的,那么顺理成章的我们需要在FireController里面新增一个开火起始点:

using UnityEngine;

public class FireController : MonoBehaviour
{
    private bool isMouseDown = false;
    private float lastFireTime = 0f;
    private Vector3 fireDirection;
    public float fireInterval = 0.1f;
    public AddVelocity bullet;
    public Transform fireBeginPosition;

    void Update()
    {
        if (Input.GetButton("Fire1"))
        {
            if (!isMouseDown)
            {
                isMouseDown = true;
                lastFireTime = Time.time;
                Fire();
            }
            else if (Time.time - lastFireTime > fireInterval)
            {
                lastFireTime = Time.time;
                Fire();
            }
        }
        else
        {
            isMouseDown = false;
        }
    }

    void Fire()
    {
        // 在这里实现每次触发的逻辑

        // 创建新的子弹,每次都是从模板bullet复制一个出来
        AddVelocity newBullet = Object.Instantiate(bullet);
        newBullet.transform.position = fireBeginPosition.position;
        newBullet.SetDirection(fireDirection);
    }

    public void SetDirection(Vector3 direction)
    {
        fireDirection = direction;
    }
}

我们新增了一个fireBeginPosition的变量,但是是Transform类型,之前我们也一直说过Transform组件存储物体位置信息,我们大可不必赋值一个相机,我们要的只是位置。

拿到位置后,我们在子弹创建的时候通过newBullet.transform.position = fireBeginPosition.position;赋值给新创建的子弹改变它的初始位置。

改完代码后我们在编辑器里面拖拽Main Camera到FireController的fireBeginPosition成员上赋值。

然后我们再跑一下游戏看看?

1.15 从0开始学习Unity游戏开发--游戏UI

看起来并不是发射子弹,而是投石机,不管怎么样至少这个瞄准是起效了。

思考题

  1. 上面我们有说到,子弹实际上为了更加符合瞄准哪里就应该打哪里的直觉,应该从眼睛里面射出,但是实际上子弹应该是从枪管射出,而枪管位置肯定不是眼睛的位置,那么让子弹从枪管射出是否可以实现,如果可以实现,当眼睛能瞄准物体但枪管却被墙挡住的时候,应该如何处理?
  2. 最后我们成功的向预期方向射出子弹,但是速度有点慢,你尝试加大子弹配置的speed参数,以便让子弹能真飞直一点,但是却发现子弹都穿过墙体飞过去了,为什么没有碰撞反弹?如何解决?

下一章

本章我们详细讲解了Unity中UI制作怎么入门,然后顺带做完了整套瞄准射击的逻辑,并以此理顺了场景中多个逻辑之间交互都是怎么沟通和传递信息的。

下一章我们将会加入人物运动,在不涉及人物骨骼动画等更复杂的东西的前提下,简单的实现一个三人称视角的操作模式和人物运动控制方法,以此可以实现三人称下射击demo。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/423185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

传智健康_day3

本章对检查组管理进行开发 一.新增检查组 1.修改新增弹层可见属性&#xff0c;添加重置表单功能 2.动态刷新检查组包含的检查项信息 <tr v-for"c in tableData"> 使用for循环来遍历查询出tableData中的数据 tableData是一个数组对象&#xff0c;定义在VUE…

hadoop分布式安装

文章目录1. 将安装包hadoop-3.1.3.tar.gz上次至linux中2. 进行解压操作3. 修改目录名称4. 配置环境变量5. 远程传输5.1 scp远程传输6. 免密登录7. 集群规划8. 修改自定义配置文件8.1 hadoop-env.sh8.2 core-site.xml8.3 hdfs-site.xml8.4 mapred-site.xml8.5 yarn-site.xml8.6 …

ReactNative入门

React基本用法&#xff1a; react与js不同的点在于 react使用的是虚拟DOM js是真实DOM 作用&#xff1a;当有新的数据填充 可以复用之前的&#xff0c;而js需要整体重新渲染 创建虚拟DOM还可以使用jsx语法直接声明&#xff1a; 注意要用babel标签将jsx转化为js 但是建议采用j…

UNIX环境高级编程——进程环境

7.1 引言 本章主要讲解了进程的环境。 7.2 main函数 C程序总是从main函数开始执行&#xff0c;其函数原型为&#xff1a; int main(int argc, char *argv[]);argc是命令行参数的数目&#xff0c;argv是指向参数的各个指针所构成的数组&#xff1b;当内核执行C程序时&#x…

SpringBoot集成Kafka详解

一、使用idea创建SpringBoot项目 1.1 使用Spring Initializr创建一个SpringBoot程序 点击Next。 1.2 添加依赖 依赖说明&#xff1a; Lombok简化实体类开发。 Spring Web让项目集成web开发所有依赖&#xff0c;包括Spring MVC&#xff0c;内置tomcat等。 Spring for Apache…

HNU-操作系统OS-2023期中考试复习-刷题

往年期中卷极难获得&#xff0c;这里找了几套卷子。可以看看。 因为往年都是从第一周开始上课的&#xff0c;所以进度会快一点&#xff0c;这学期是从第四周开始上课的&#xff0c;所以进程会慢些&#xff0c;讲到第九章所以只考到第九章。 同样因为太忙了&#xff0c;答案找…

图像分类卷积神经网络模型综述

图像分类卷积神经网络模型综述遇到问题 图像分类&#xff1a;核心任务是从给定的分类集合中给图像分配一个标签任务。 输入&#xff1a;图片 输出&#xff1a;类别。 数据集MNIST数据集 MNIST数据集是用来识别手写数字&#xff0c;由0~9共10类别组成。 从MNIST数据集的SD-1和…

ctfshow web入门web119-124

1.web119 和118题类似&#xff0c;只不过是过滤了PATH 0可以用任何字符代替&#xff0c;比如A,{A},A,{0} KaTeX parse error: Expected }, got # at position 2: {#̲SHLVL}1&#xff0c;或者{##},${#?} {PHP_VERSION:~A}2,php版本为x.x.2时 ${#IFS}3(linux下是3&#xff0c;…

IntelliJ IDEA 2023.1正式发布,Maven项目大提速支持Apache Dubbo

你好&#xff0c;我是YourBatman&#xff1a;做爱做之事❣交配交之人。 &#x1f4da;前言 一年一个大版本&#xff0c;共计3个中型版本&#xff0c;北京时间2023年3月月29日终于迎来了IntelliJ IDEA今年的首个版本2023.1。老规矩&#xff0c;吃肉之前&#xff0c;可以先把这…

顺丰科技x腾讯安全iOA联合案例获云安全联盟CSA 2022安全革新奖

近年来&#xff0c;随着云计算、大数据、物联网等技术的加速创新和应用&#xff0c;一场数字化的变革开始席卷各行各业。远程办公、业务协同、分支互联等需求涌现&#xff0c;随之而来的还有更加复杂多元的高级网络攻击。在此背景下&#xff0c;传统的基于边界的网络安全防护理…

矩阵键盘+CH559制作国产USB矩阵键盘

矩阵键盘+CH559制作国产USB矩阵键盘 文章目录 矩阵键盘+CH559制作国产USB矩阵键盘为什么选择CH559作为主控芯片?如何实现该款矩阵USB键盘?如何将矩阵键盘的信号转化为USB键盘信号?原材料: 矩阵键盘(附带行列键码定义)CH559开发板将矩阵键盘的所有排线连接到单片机的GPIO引…

基于stm32mp157 linux开发板ARM裸机开发教程5:ARM微处理器指令系统(连载中)

前言&#xff1a; 目前针对ARM Cortex-A7裸机开发文档及视频进行了二次升级持续更新中&#xff0c;使其内容更加丰富&#xff0c;讲解更加细致&#xff0c;全文所使用的开发平台均为华清远见FS-MP1A开发板&#xff08;STM32MP157开发板&#xff09; 针对对FS-MP1A开发板&…

Python | Python的自我介绍(前世今生)

本文概要 本篇文章主要介绍Python这门语言的前世今生&#xff0c;适合刚入门的小白或者想了解Python历史的同学&#xff0c;文中描述很详细&#xff0c;具有一定的学习价值&#xff0c;感兴趣的小伙伴快来一起学习吧。 个人简介 ☀️大家好&#xff01;我是新人小白博主朦胧的…

【机器学习】样本不均衡(class-imbalance)——解决方案与问题思考

目录问题提出问题重述与再理解第一个问题&#xff1a;假如样本不均衡&#xff0c;哪种分类器的泛化性能较好&#xff1f;第二个问腿&#xff1a;在样本不均衡的情况下&#xff0c;如何获得更健壮的模型问题解决方法样本不均衡对机器学习模型会造成什么影响什么模型适合样本不均…

小黑今天上午着急忙慌实习公司楼下笔试,晚上准备和尚香疯狂星期四明天继续现场笔试的leetcode之旅:1091. 二进制矩阵中的最短路径

小黑代码1 class Solution:def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:# 一定无解的情况if grid[0][0] 1 or grid[-1][-1] 1:return -1# 矩阵长度n len(grid)# 起点即终点if n 1:return 1# 访问集合seen {(0, 0)}# 初始化队列q collections.…

射频功率放大器在空气耦合超声检测系统中的应用

实验名称&#xff1a;空气耦合超声检测系统研究方向&#xff1a;超声测试设备&#xff1a;ATA-8202射频功率放大器、探头、ATA-5620前置放大器、超声波接收器、数据采集卡、计算机。实验过程&#xff1a;图&#xff1a;空气耦合超声检测锂电池系统锂电池空气耦合超声检测具体过…

TryHackMe-Year of the Dog(Linux渗透测试)

Year of the Dog 谁知道呢&#xff1f;狗咬了一口&#xff01; 端口扫描 循例nmap Web枚举 进80 用gobuster扫了一圈没有任何发现&#xff0c;图像也没有隐写 在主页的请求头的cookie有一个id 改成其他错误值会导致异常&#xff0c;看见叫id&#xff0c;习惯性加个了引号 爆…

【java】集合类

文章目录集合根接口LIST列表迭代器Queue|Deque|PriorityQueueSet集合HashSetLinkedHashSetTreeSet键盘读入MapHashMapLinkedHashMapStream流stream()ints()Collections工具类集合根接口 所有的集合类最终都是实现自集合根接口的&#xff0c;比如ArrayList类&#xff0c;它的祖先…

走向国际市场,怎样用Facebook广告抢占商机?

在当今全球化的商业世界中&#xff0c;企业要想在国际市场中占据一席之地&#xff0c;除了打造优质产品和服务外&#xff0c;有效的营销手段也是至关重要的。 而Facebook作为全球最大的社交媒体平台&#xff0c;其广告投放服务为企业提供了一个快速、便捷、有效的推广途径。那…

安全校验和框架---JWT和Shrio

安全架构 加密 分类 可逆加密和不可逆加密 不可逆加密&#xff1a;常见的不可逆加密算法有MD5&#xff0c;HMAC&#xff0c;SHA1、SHA-224、SHA-256、SHA-384&#xff0c;和SHA-512&#xff0c;其中SHA-224、SHA-256、SHA-384&#xff1b; 可逆加密分为对称加密和非对称加密…