【unity实战】在Unity中使用有限状态机制作一个敌人AI

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • 有限状态机的主要作用和意义
  • 素材下载
  • 逻辑图
  • 敌人动画配置
  • 优雅的代码文件目录
  • 状态机代码
  • 定义敌人不同状态切换
  • 创建敌人
  • 效果
  • 更多的敌人
  • 参考
  • 源码
  • 完结

前言

有限状态机以前的我嗤之以鼻,现在的我逐帧分析。其实之前我就了解过有限状态机,但是奈何那时能力不够,并不能理解其中的奥秘,只觉得麻烦。直到我项目需要越来越多的去编写敌人的AI,大量的if else让我头晕目眩,各种状态的切换和调试耗费我大量的时间。于是我又重新查找一些状态机的教程进行深入学习。以下我我的学习记录,希望对你有帮助。

如果后续项目使用时存在任何问题我还会回来补充和调整,文章的代码我也会尽量保持完整分享,以便大家可以复制粘贴盗自己的项目中即可使用。

有限状态机的主要作用和意义

有限状态机(Finite State Machine,FSM)是一种在计算机科学和工程中常用的模型,用于描述对象或系统在有限状态集合中的行为和状态转换。它的主要作用和意义包括:

  1. 行为管理与控制: FSM通过定义有限数量的状态和状态之间的转换规则,可以有效管理和控制对象或系统的行为。每个状态代表对象可能处于的一种特定状态,例如待机、行走、攻击、受伤等,而状态之间的转换则定义了这些行为如何响应外部事件或条件变化。

  2. 简化复杂性: 将复杂的行为分解为简单的状态和状态转换,使得程序员可以更容易地理解和管理系统的行为逻辑。这种分解也有助于减少错误和提高代码的可维护性。

  3. 灵活性和扩展性: FSM可以根据具体需求进行灵活的定制和扩展。通过修改状态和状态转换规则,可以快速调整和扩展系统的行为,而无需大规模重构代码。

  4. 行为预测和调试: FSM的结构使得系统的行为预测变得相对容易,因为每个状态和转换的行为是明确定义的。这种结构也有助于调试和排查问题,因为可以更容易地追踪和理解系统在特定状态下的行为。

  5. 应用领域广泛: FSM不仅在游戏开发中常见,还在自动控制、工作流程管理、编程语言解析、通信协议等许多领域有着广泛的应用。其简单而强大的结构使得它成为许多复杂系统中行为管理的首选模型之一。

总之,有限状态机通过状态和状态转换的定义,提供了一种清晰且有效的方法来管理和控制对象或系统的复杂行为,为程序员和系统设计师提供了强大的工具,用于实现各种复杂的行为逻辑和控制流程。

素材下载

https://rvros.itch.io/animated-pixel-hero
https://jesse-m.itch.io/skeleton-pack

逻辑图

在这里插入图片描述

敌人动画配置

在这里插入图片描述

优雅的代码文件目录

在这里插入图片描述

状态机代码

新增StateType定义状态类型枚举

// 定义状态类型枚举
public enum StateType
{
    Idle, //待机
    Patrol, //巡逻
    Chase, //追击
    React, //反应
    Attack, //攻击
    Hit, //受击
    Death //死亡
}

新增Parameter,可序列化的参数类,存储了角色的各种状态参数和配置

// 可序列化的参数类,存储了角色的各种状态参数和配置
using System;
using UnityEngine;

[Serializable]
public class Parameter
{
    public int health;              // 健康值
    public float moveSpeed;         // 移动速度
    public float chaseSpeed;        // 追击速度
    public float idleTime;          // 空闲时间
    public Transform[] patrolPoints;    // 巡逻点数组
    public Transform[] chasePoints;     // 追击点数组
    [HideInInspector] public Transform target; // 目标对象
    public LayerMask targetLayer;   // 目标层
    public Transform attackPoint;   // 攻击点的位置
    public float attackArea;        // 攻击范围
    [HideInInspector] public Animator animator;       // 角色动画控制器
    [HideInInspector] public bool getHit;             // 是否被击中
    [HideInInspector] public AnimatorStateInfo animatorStateInfo;    // 动画状态信息
}

新增FSM有限状态机类

using System.Collections.Generic;
using UnityEngine;

// 有限状态机类
public class FSM : MonoBehaviour
{
    private IState currentState;        // 当前状态接口
    protected Dictionary<StateType, IState> states = new Dictionary<StateType, IState>();  // 状态字典,存储各种状态

    public Parameter parameter;     // 状态机参数

    protected virtual void Awake() {
    
    }

    protected virtual void OnEnable()
    {
        parameter.animator = transform.GetComponent<Animator>();  // 获取角色上的动画控制器组件
        TransitionState(StateType.Idle);    // 初始状态为Idle
        currentState.OnEnter();
    }

    void Update()
    {
        parameter.animatorStateInfo = parameter.animator.GetCurrentAnimatorStateInfo(0);// 获取当前动画状态信息

        currentState.OnUpdate();    // 每帧更新当前状态

        //TODO:用于测试 如果按下回车键,设置被击中状态为true
        if (Input.GetKeyDown(KeyCode.Return))
        {
            parameter.getHit = true;
        }
    }


    void FixedUpdate()
    {
        currentState.OnFixedUpdate();
    }

    // 状态转换方法
    public void TransitionState(StateType type)
    {
        if (currentState != null)
            currentState.OnExit();  // 如果当前状态不为空,调用退出方法

        currentState = states[type];    // 更新当前状态为指定类型的状态
        currentState.OnEnter();         // 调用新状态的进入方法
    }

    // 翻转角色朝向方法,使其朝向目标
    public void FlipTo(Transform target)
    {
        if (target != null)
        {
            if (transform.position.x > target.position.x)
            {
                transform.localScale = new Vector3(-1, 1, 1);    // 如果角色在目标左侧,翻转角色朝向为左
            }
            else if (transform.position.x < target.position.x)
            {
                transform.localScale = new Vector3(1, 1, 1);     // 如果角色在目标右侧,翻转角色朝向为右
            }
        }
    }

    public void Destroy(){
        Destroy(gameObject);
    }

    // 触发器进入事件,检测到玩家时设置目标为玩家
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            parameter.target = other.transform;
        }
    }

    // 触发器离开事件,玩家离开时清空目标
    private void OnTriggerExit2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            parameter.target = null;
        }
    }

    // 在Scene视图中绘制攻击范围的辅助图形
    private void OnDrawGizmos()
    {
        Gizmos.DrawWireSphere(parameter.attackPoint.position, parameter.attackArea);
    }
}

新增IState抽象基类,定义了所有状态类的基本结构

//抽象基类,定义了所有状态类的基本结构
public abstract class IState
{
    protected FSM manager;// 当前状态机
    protected Parameter parameter;// 参数
    public abstract void OnEnter();// 进入状态时的方法
    public abstract void OnUpdate();// 更新方法
    public abstract void OnFixedUpdate();// 固定更新方法
    public abstract void OnExit();// 退出状态时的方法
}

定义敌人不同状态切换

待机状态

using UnityEngine;

public class IdleState : IState
{
    private float timer;        // 计时器

    public IdleState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.animator.Play("Idle");
    }

    public override void OnUpdate()
    {
        timer += Time.deltaTime;    // 计时器累加

        // 如果被击中了,转换到受击状态
        if (parameter.getHit)
        {
            manager.TransitionState(StateType.Hit);
        }

        // 如果有目标且目标在追逐范围内,则转换到反应状态
        if (parameter.target != null &&
            parameter.target.position.x >= parameter.chasePoints[0].position.x &&
            parameter.target.position.x <= parameter.chasePoints[1].position.x)
        {
            manager.TransitionState(StateType.React);
        }

        // 如果达到空闲时间上限,则转换到巡逻状态
        if (timer >= parameter.idleTime)
        {
            manager.TransitionState(StateType.Patrol);
        }
    }

    public override void OnFixedUpdate()
    {
        
    }

    public override void OnExit()
    {
        timer = 0;    // 重置计时器
    }
}

巡逻状态,每次敌人到达巡逻点都会在原地观察一段时间

using UnityEngine;

public class PatrolState : IState
{
    private int patrolPosition;    // 当前巡逻点索引

    public PatrolState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.animator.Play("Walk");
    }

    public override void OnUpdate()
    {
        // 如果被击中了,转换到受击状态
        if (parameter.getHit)
        {
            manager.TransitionState(StateType.Hit);
        }

        // 如果有目标且目标在追逐范围内,则转换到反应状态
        if (parameter.target != null &&
            parameter.target.position.x >= parameter.chasePoints[0].position.x &&
            parameter.target.position.x <= parameter.chasePoints[1].position.x)
        {
            manager.TransitionState(StateType.React);
        }

        // 如果已经接近当前巡逻点,则转换到空闲状态
        if (Vector2.Distance(manager.transform.position, parameter.patrolPoints[patrolPosition].position) < .1f)
        {
            manager.TransitionState(StateType.Idle);
        }
    }
    
    public override void OnFixedUpdate()
    {
        // 朝向当前巡逻点
        manager.FlipTo(parameter.patrolPoints[patrolPosition]);
        
        // 移动到当前巡逻点
        manager.transform.position = Vector2.MoveTowards(manager.transform.position,
            parameter.patrolPoints[patrolPosition].position, parameter.moveSpeed * Time.deltaTime);
    }

    public override void OnExit()
    {
        patrolPosition++;    // 切换到下一个巡逻点

        // 如果超过巡逻点数组长度,循环回到第一个巡逻点
        if (patrolPosition >= parameter.patrolPoints.Length)
        {
            patrolPosition = 0;
        }
    }
}

反应状态

using UnityEngine;

public class ReactState : IState
{
    public ReactState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.animator.Play("React");
    }

    public override void OnUpdate()
    {
        // 如果被击中标志为true,转换到受击状态
        if (parameter.getHit)
        {
            manager.TransitionState(StateType.Hit);
        }

        // 如果动画播放进度超过95%,转换到追逐状态
        if (parameter.animatorStateInfo.normalizedTime >= 0.95f)
        {
            manager.TransitionState(StateType.Chase);
        }
    }

    public override void OnFixedUpdate()
    {
        
    }

    public override void OnExit()
    {

    }
}

追击状态

using UnityEngine;

public class ChaseState : IState
{
    // 构造函数
    public ChaseState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override  void OnEnter()
    {
        //TODO:如果有奔跑动画,当然切换为奔跑动画最好
        parameter.animator.Play("Walk");
    }

    public override void OnUpdate()
    {
        // 如果被击中了,转换到受击状态
        if (parameter.getHit)
        {
            manager.TransitionState(StateType.Hit);
        }

        // 如果目标不存在或者超出追逐范围,则转换到空闲状态
        if (parameter.target == null ||
            manager.transform.position.x < parameter.chasePoints[0].position.x ||
            manager.transform.position.x > parameter.chasePoints[1].position.x)
        {
            manager.TransitionState(StateType.Idle);
        }

        // 如果检测到攻击范围内有目标,则转换到攻击状态
        if (Physics2D.OverlapCircle(parameter.attackPoint.position, parameter.attackArea, parameter.targetLayer))
        {
            manager.TransitionState(StateType.Attack);
        }
    }

    
    public override void OnFixedUpdate()
    {
        manager.FlipTo(parameter.target);    // 面向目标
        // 向目标位置移动
        if (parameter.target != null)
        {
            manager.transform.position = Vector2.MoveTowards(manager.transform.position,
                parameter.target.position, parameter.chaseSpeed * Time.deltaTime);
        }
    }

    public override void OnExit()
    {
        
    }

}

攻击状态


public class AttackState : IState
{
    public AttackState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }
    public override void OnEnter()
    {
        parameter.animator.Play("Attack");
    }

    public override void OnUpdate()
    {
        if (parameter.getHit)
        {
            manager.TransitionState(StateType.Hit);
        }
        if (parameter.animatorStateInfo.normalizedTime >= .95f)
        {
            manager.TransitionState(StateType.Chase);
        }
    }

    public override void OnFixedUpdate()
    {

    }

    public override void OnExit()
    {

    }
}

受击状态

using UnityEngine;

public class HitState : IState
{
    public HitState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.animator.Play("Hit");
        parameter.health--;    // 减少角色生命值
    }

    public override void OnUpdate()
    {
        // 如果角色生命值小于等于0,转换到死亡状态
        if (parameter.health <= 0)
        {
            manager.TransitionState(StateType.Death);
        }

        // 如果动画播放进度超过95%,重新寻找玩家目标并转换到追逐状态
        if (parameter.animatorStateInfo.normalizedTime >= 0.95f)
        {
            parameter.target = GameObject.FindWithTag("Player").transform;    // 寻找标签为Player的目标

            manager.TransitionState(StateType.Chase);    // 转换到追逐状态
        }
    }

    public override void OnFixedUpdate()
    {

    }

    public override void OnExit()
    {
        parameter.getHit = false;    // 离开状态时重置受击标志
    }
}

死亡状态


using UnityEngine;

public  class DeathState : IState
{
    public DeathState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }
    public override void OnEnter()
    {
        parameter.animator.Play("Dead");
    }

    public override void OnUpdate()
    {
        // 如果动画播放进度超过95%,销毁敌人
        if (parameter.animatorStateInfo.normalizedTime >= 0.95f)
        {
            manager.Destroy();
        }
    }

    public override void OnFixedUpdate()
    {
        
    }

    public override void OnExit()
    {

    }

}

创建敌人

比如新增Skull 骷髅怪,继承FSM

public class Skull : FSM {
    protected override void Awake() {
        // 初始化各个状态,并添加到状态字典中
        states.Add(StateType.Idle, new IdleState(this));
        states.Add(StateType.Patrol, new PatrolState(this));
        states.Add(StateType.Chase, new ChaseState(this));
        states.Add(StateType.React, new ReactState(this));
        states.Add(StateType.Attack, new AttackState(this));
        states.Add(StateType.Hit, new HitState(this));
        states.Add(StateType.Death, new DeathState(this));  
    }
}

配置
在这里插入图片描述

效果

现在运行游戏,可以看到敌人现在可以发现玩家并进行追击,在进入攻击范围后攻击玩家,玩家从视野中消失或者超出追击范围后恢复到巡逻状态
在这里插入图片描述

我们很快的就搭建好了一个简单的敌人逻辑,而且代码也不显得杂乱,这就是使用有限状态机编写代码的好处,而且添加新的状态也很方便,只要在有关的状态中设置好切换条件,注册好新建的状态,然后编写自身的状态代码即可。

更多的敌人

其他敌人只要都继承这个FSM状态机即可实现代码的复用,比如我再创建不同类型的敌人哥布林

public class Goblin : FSM {
    protected override void Awake() {
        // 初始化各个状态,并添加到状态字典中
        states.Add(StateType.Idle, new GoblinIdleState(this));
        states.Add(StateType.Patrol, new GoblinPatrolState(this));
        states.Add(StateType.Chase, new GoblinChaseState(this));
        states.Add(StateType.React, new GoblinReactState(this));
        states.Add(StateType.Attack, new GoblinAttackState(this));
        states.Add(StateType.Hit, new GoblinHitState(this));
        states.Add(StateType.Death, new GoblinDeathState(this));  
    }
}

参考

https://www.bilibili.com/video/BV1k1421Z7g8
https://www.bilibili.com/video/BV1zf4y1r7FJ
https://www.bilibili.com/video/BV1xp4y137Xr

源码

https://gitcode.net/unity1/fsm
在这里插入图片描述

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

day03-主页模块-修改密码

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.获取用户资料在Vuex中共享登录成功跳转到主页之后&#xff0c;可以获取用户资料&#xff0c;获取的资料在Vuex中共享&#xff0c;这样用户就可以很方便的获取该信…

Leetcode刷题笔记 | 二叉树基本性质 | 一天的题量 | 5道题目 | 深度优先搜索 | 广度优先搜索 | 递归 | 遍历

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本期毛毛张分享的是LeetCode关于二叉树&#x1f332;的性质的一些基础题&#xff0c;做这些题目的本质还是遍历二叉树&#x1f3c3;‍➡️的过程&#…

计算机组成原理 | 储存子系统(1)概述

三级储存体系 物理与虚拟存储器 &#xff08;抽象逻辑模型&#xff09; 存储器类型 存储器的速度指标

中国民间网络外交组织(CCND)

中国民间网络外交组织Chinese Civil Network Diplomacy简称(CCDN) 是由中国网民建立起来的一个网络外交组织&#xff0c;深度贯彻党的主张和网民意志的统一&#xff0c;为保护中国中华优秀传统文化&#xff0c;民族自信&#xff0c;国家安全&#xff0c;民族利益&#xff0c;社…

昇思MindSpore学习笔记2-04 LLM原理和实践--文本解码原理--以MindNLP为例

摘要&#xff1a; 介绍了昇思MindSpore AI框架采用贪心搜索、集束搜索计算高概率词生成文本的方法、步骤&#xff0c;并为解决重复等问题所作的多种尝试。 这一节完全看不懂&#xff0c;猜测是如何用一定范围的词造句。 一、概念 自回归语言模型 文本序列概率分布 分解为每…

76. UE5 RPG 实现场景阻挡剔除功能

在俯视角游戏中&#xff0c;我们总会碰到一个问题就是&#xff0c;建筑会遮挡住角色的问题。遇到这种问题有多种解决方案&#xff0c;厂商经常使用的一种方案是&#xff0c;如果角色被遮挡&#xff0c;则使用一种纯色或者增加一些菲涅尔的效果来实现 这种效果我之前在unity内实…

SpringBoot 项目整合 MyBatis 框架,附带测试示例

文章目录 一、创建 SpringBoot 项目二、添加 MyBatis 依赖三、项目结构和数据库表结构四、项目代码1、application.yml2、TestController3、TbUser4、TbUserMapper5、TestServiceImpl6、TestService7、TestApplication8、TbUserMapper.xml9、MyBatisTest 五、浏览器测试结果六、…

一文了解什么是车载Tbox

目录 前言一、Tbox是什么呢?二、Tbox架构三、App——TSP——Tbox交互时序图四、汽车混合网关拓扑结构示例五、Tbox功能 前言 强烈建议提前阅读一下这篇文章&#xff1a;车机Tbox介绍 一、Tbox是什么呢? Tbox是汽车上的一个盒子&#xff0c;指的是Telematics BOX&#xff0c…

Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol

Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol 0. 版本0.1 ERC1967Upgrade.sol 1. 目标合约2. 代码精读2.1 _getImplementation() internal && _upgradeTo(address newImplementation) internal2.2 _upgradeToAndCall(address newImplementation,…

常见反爬及应对

一&#xff0c;特殊混淆的还原 1.1 还原 AAEncode 与 JJEncode AAEncode是一种JavaScript代码混淆算法&#xff0c;利用它&#xff0c;可以将代码转换成 颜文字 表示的JavaScript代码。 去掉代码最后的 (‘‘)&#xff0c;这是函数的自调用&#xff0c;去除后就是函数的声明…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验13 静态路由配置错误导致的路由环路问题

一、实验目的 1.验证静态路由配置错误导致的路由环路问题&#xff1b; 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络拓扑&#xff1b; 2.验证路由环路。 四、实验…

探囊取物之多形式登录页面(基于BootStrap4)

基于BootStrap4的登录页面&#xff0c;支持手机验证码登录、账号密码登录、二维码登录、其它统一登录 低配置云服务器&#xff0c;首次加载速度较慢&#xff0c;请耐心等候&#xff1b;演练页面可点击查看源码 预览页面&#xff1a;http://www.daelui.com/#/tigerlair/saas/pr…

【AI提升】如何使用大模型:本机离线和FastAPI服务调用

大模型本身提供的功能&#xff0c;类似于windows中的一个exe小工具&#xff0c;我们可以本机离线调用然后完成具体的功能&#xff0c;但是别的机器需要访问这个exe是不可行的。常见的做法就是用web容器封装起来&#xff0c;提供一个http接口&#xff0c;然后接口在后端调用这个…

electron教程(二)控制应用程序的事件生命周期

1.will-finish-launching 当应用程序完成基础的启动的时候被触发&#xff0c;在 Windows 和 Linux 中, will-finish-launching 事件与 ready 事件是相同的; 在 macOS 中&#xff0c;这个事件相当于 NSApplication 中的 applicationWillFinishLaunching 提示。 app.on(will-fi…

Andrej Karpathy提出未来计算机2.0构想: 完全由神经网络驱动!网友炸锅了

昨天凌晨&#xff0c;知名人工智能专家、OpenAI的联合创始人Andrej Karpathy提出了一个革命性的未来计算机的构想&#xff1a;完全由神经网络驱动的计算机&#xff0c;不再依赖传统的软件代码。 嗯&#xff0c;这是什么意思&#xff1f;全部原生LLM硬件设备的意思吗&#xff1f…

机械设备制造企业MES系统解决方案介绍

机械设备制造行业涵盖了各类工业设备、工程机械、农业机械等多个领域&#xff0c;对生产精度、质量控制和效率提出了较高要求。为了提升生产效率、保证产品质量并满足客户需求&#xff0c;越来越多的机械设备制造企业引入了MES系统。本文将详细介绍MES系统在机械设备制造行业的…

魔镜魔镜,我要变得更漂亮!按需搭配一键叠穿,效果拿下新SOTA!中山大学字节智创数字人团队提出虚拟试穿新框架

魔镜魔镜,我要变得更漂亮!按需搭配一键叠穿,效果拿下新SOTA!中山大学&字节智创数字人团队提出虚拟试穿新框架。 多件衣服按指定穿法一键虚拟试穿! 中山大学&字节智创数字人团队提出了一个名为MMTryon的虚拟试穿框架,可以通过输入多个服装图像及指定穿法的文本指…

COB封装的LED显示屏是什么?

COB&#xff08;Chip on Board&#xff09;封装的LED显示屏&#xff0c;是一种采用先进倒装COB封装技术的显示屏&#xff0c;其中LED芯片是直接被安装并封装在PCB电路板上&#xff0c;而不是先对单个封装再焊接至电路板&#xff0c;与SMD&#xff08;Surface Mount Device&…

怎么快速给他人分享图片?扫描二维码看图的简单做法

现在通过二维码来查看图片是一种很常见的方法&#xff0c;通过二维码来查看图片不仅能够减少对手机存储空间的占用&#xff0c;而且获取图片变得更加方便快捷&#xff0c;只需要扫码就能够查看图片&#xff0c;有利于图片的展现。很多的场景中都有图片二维码的应用&#xff0c;…

2024软件设计师经验贴(一考就过)

2024软件设计师经验贴&#xff08;一考就过&#xff09; 第一阶段、基础积累&#xff1a;把书读厚 这一阶段可以跟着视频、书籍或文章进行基础知识的学习。 推荐的视频系列&#xff1a; 「软件设计师」 上午题 #1 计算机系统_哔哩哔哩_bilibili 40–14.3设计模式 推荐的文…