当前位置: 首页 > news >正文

【Unity3D】实现2D小地图效果

目录

一、玩家脚本Player

二、Canvas组件设置

三、小地图相关

四、GameLogicMap脚本修改


  

基于:【Unity3D】Tilemap俯视角像素游戏案例-CSDN博客

2D玩家添加Dotween移动DOPath效果,移动完成后进行刷新小地图(小地图会顺便刷新大地图)

一、玩家脚本Player

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class Player : MonoBehaviour
{void Update(){if (Input.GetMouseButtonDown(0)){Vector2 pos = Input.mousePosition;Ray ray = Camera.main.ScreenPointToRay(pos);RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);if (hit.collider != null){Vector2 hitPos = hit.point;Vector3Int v3Int = new Vector3Int(Mathf.FloorToInt(hitPos.x), Mathf.FloorToInt(hitPos.y), 0);List<Vector3Int> pathPointList = GameLogicMap.Instance.PlayAstar(v3Int);Vector3[] pathArray = new Vector3[pathPointList.Count];int offset = pathPointList.Count - 1;for (int i = pathPointList.Count - 1; i >= 0; i--){                    Vector3Int pointPos = pathPointList[i];Vector3 worldPos = GameLogicMap.Instance.GetWorldPos(pointPos);pathArray[offset - i] = worldPos;}transform.DOPath(pathArray, 1f).OnComplete(() =>{Debug.Log("移动完成 更新小地图");GameLogicMap.Instance.DrawSmallMap();}).SetAutoKill(true);}}}public Vector3Int GetPos(){Vector3 pos = transform.position;return new Vector3Int(Mathf.FloorToInt(pos.x - 0.5f), Mathf.FloorToInt(pos.y - 0.5f), 0);}
}

二、Canvas组件设置

三、小地图相关

原理:利用2D游戏为了实现寻路而创建的二维数组去生成一张Texture2D纹理图,大地图是直接用int[,]map二维数组去创建,map[x,y]等于1是空地,不等于1是障碍物或不可穿越地形;
大地图上的玩家绿点是直接拿到玩家在map的坐标点直接绘制。

小地图是以玩家为中心的[-smallSize/2, smallSize/2]范围内进行绘制;小地图上的玩家绿点是直接绘制到中心点(smallSize.x/2, smallSize.y/2);

注意:绘制到Texture2D的像素点坐标是[0, size]范围的,则小地图的绘制是SetPixel(i, j, color),传递i, j是[0,size]范围的,而不要传递x, y,这个x,y的取值是以玩家点为中心的[-size/2, size/2]范围坐标值,如下取法:

x = 玩家点.x - size.x/2 + i;
y = 玩家点.y - size.y/2 + j;

用2层for遍历来看的话就是从(玩家点.x - size.x/2, 玩家点.y - size.y/2)坐标点,从下往上,从左往右依次遍历每个map[x,y]点生成对应颜色的像素点,构成一张Texture2D图片。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class SmallMapFor2D : MonoBehaviour
{public Image smallImage;public Vector2Int smallSize;private Vector2Int lastSmallSize;Texture2D smallImageTexture2D;public Image bigImage;Vector2Int bigSize;Texture2D bigImageTexture2D;private Vector3Int playerPoint;//环境二维数组地图[静态] 若是动态需改为自定义类数组,此时是int值类型数组private int[,] map;//初始化大地图整体public void InitBigMap(int[,] map, Vector2Int size){//已初始化则直接退出if (bigImageTexture2D != null)return;this.map = map;this.bigSize = size;bigImageTexture2D = new Texture2D(bigSize.x, bigSize.y);bigImageTexture2D.filterMode = FilterMode.Point;DrawBigMap();}public void DrawBigMap(){int mapWidth = map.GetLength(0);int mapHeight = map.GetLength(1);for (int i = 0; i < bigSize.x; i++){for (int j = 0; j < bigSize.y; j++){//判断是否在map范围内if (i < mapWidth && j < mapHeight){if (map[i, j] == 1) //空地{bigImageTexture2D.SetPixel(i, j, new Color(0.6f, 0.6f, 0.6f, 0.5f));}else //障碍物{bigImageTexture2D.SetPixel(i, j, new Color(0.3f, 0.3f, 0.3f, 0.5f));}}else{//非map范围内bigImageTexture2D.SetPixel(i, j, new Color(0.1f, 0.1f, 0.1f, 0.8f));}}}if (playerPoint != null){//更新大地图玩家点bigImageTexture2D.SetPixel(playerPoint.x, playerPoint.y, Color.green);}bigImageTexture2D.Apply();//bigImage.material.SetTexture("_MainTex", bigImageTexture2D);bigImage.material.mainTexture = bigImageTexture2D;bigImage.SetMaterialDirty();}//动态绘制小地图 时机:玩家移动完成后public void DrawSmallMap(Vector3Int playerPoint){//中途可换小地图大小if (this.lastSmallSize != null && this.lastSmallSize.x != smallSize.x && this.lastSmallSize.y != smallSize.y){if (smallImageTexture2D != null){Destroy(smallImageTexture2D);smallImageTexture2D = null;}smallImageTexture2D = new Texture2D(smallSize.x, smallSize.y);smallImageTexture2D.filterMode = FilterMode.Point;}this.lastSmallSize = smallSize;this.playerPoint = playerPoint;int mapWidth = map.GetLength(0);int mapHeight = map.GetLength(1);        for (int i = 0; i < smallSize.x; i++){for (int j = 0; j < smallSize.y; j++){//中心点是人物点 故绘制点为如下int x = playerPoint.x - smallSize.x / 2 + i;int y = playerPoint.y - smallSize.y / 2 + j;//判断是否在map范围内if (x >= 0 && x < mapWidth && y >= 0 && y < mapHeight){if (map[x, y] == 1) //空地{smallImageTexture2D.SetPixel(i, j, new Color(0.6f, 0.6f, 0.6f, 0.5f));}else //障碍物{smallImageTexture2D.SetPixel(i, j, new Color(0.3f, 0.3f, 0.3f, 0.5f));}}else{//非map范围内smallImageTexture2D.SetPixel(i, j, new Color(0.8f, 0f, 0f, 0.8f));}}}smallImageTexture2D.SetPixel(smallSize.x / 2, smallSize.y / 2, Color.green);        smallImageTexture2D.Apply();//smallImage.material.SetTexture("_MainTex", smallImageTexture2D);smallImage.material.mainTexture = smallImageTexture2D;smallImage.SetMaterialDirty();//更新大地图 因为玩家位置变化//(可优化 不需要重新整张地图再绘制 只更新上一个玩家点和新玩家点,只是要保存的大地图原始数据、旧玩家点更新即可)DrawBigMap();}
}

 小地图2张Image图片的材质球要使用不同的材质球实例

四、GameLogicMap脚本修改

主要变化新增和修改:

初始化绘制大地图和小地图
smallMapFor2D.InitBigMap(map, new Vector2Int(map.GetLength(0), map.GetLength(1)));
DrawSmallMap();

A*寻路方法返回路径数组,这是一个从终点到起点的数组,所以使用时是倒序遍历的。
public List<Vector3Int> PlayAstar(Vector3Int endPos)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEngine.UI;public class GameLogicMap : MonoBehaviour
{public static GameLogicMap _instance;public static GameLogicMap Instance{get { return _instance; }}public Grid terrainGrid;private Tilemap terrainTilemap;public Grid buildGrid;private Tilemap buildTilemap;public Player player;public int[,] map;private Vector3Int mapOffset;public SmallMapFor2D smallMapFor2D;private const int ConstZ = 0;public class Point{public Vector3Int pos;public Point parent;public float F { get { return G + H; } } //F = G + Hpublic float G; //G = parent.G + Distance(parent,self)public float H; //H = Distance(self, end)public string GetString(){return "pos:" + pos + ",F:" + F + ",G:" + G + ",H:" + H + "\n";}}private List<Point> openList = new List<Point>();private List<Point> closeList = new List<Point>();public LineRenderer lineRenderer;private void Awake(){_instance = this;}void Start(){terrainTilemap = terrainGrid.transform.Find("Tilemap").GetComponent<Tilemap>();buildTilemap = buildGrid.transform.Find("Tilemap").GetComponent<Tilemap>();BoundsInt terrainBound = terrainTilemap.cellBounds;BoundsInt buildBound = buildTilemap.cellBounds;map = new int[terrainBound.size.x, terrainBound.size.y];mapOffset = new Vector3Int(-terrainBound.xMin, -terrainBound.yMin, 0);Debug.Log("mapOffset:" + mapOffset);foreach (var pos in terrainBound.allPositionsWithin){var sprite = terrainTilemap.GetSprite(pos);if (sprite != null){SetMapValue(pos.x, pos.y, 1); //空地1}}foreach (var pos in buildBound.allPositionsWithin){var sprite = buildTilemap.GetSprite(pos);if (sprite != null){SetMapValue(pos.x, pos.y, 2); //障碍2}}smallMapFor2D.InitBigMap(map, new Vector2Int(map.GetLength(0), map.GetLength(1)));DrawSmallMap();}//绘制小地图public void DrawSmallMap(){//传递玩家在Map的坐标smallMapFor2D.DrawSmallMap(ToMapPos(player.GetPos()));}private void SetMapValue(int x, int y, int value){map[x + mapOffset.x, y + mapOffset.y] = value;}private Vector3Int ToMapPos(Vector3Int pos){return pos + mapOffset;}public List<Vector3Int> PlayAstar(Vector3Int endPos){endPos = ToMapPos(endPos);Debug.Log(endPos);openList.Clear();closeList.Clear();Vector3Int playerPos = player.GetPos();playerPos = ToMapPos(playerPos);openList.Add(new Point(){G = 0f,H = GetC(playerPos, endPos),parent = null,pos = playerPos,});List<Vector3Int> resultList = CalculateAstar(endPos);if (resultList != null){lineRenderer.positionCount = resultList.Count;for (int i = 0; i < resultList.Count; i++){Vector3Int pos = resultList[i];lineRenderer.SetPosition(i, GetWorldPos(pos));}}else{Debug.LogError("寻路失败;");}return resultList;}public Vector3 GetWorldPos(Vector3Int pos){pos.x = pos.x - mapOffset.x;pos.y = pos.y - mapOffset.y;return terrainTilemap.GetCellCenterWorld(pos);}private List<Vector3Int> CalculateAstar(Vector3Int endPos){int cnt = 0;while (true){//存在父节点说明已经结束            if (openList.Exists(x => x.pos.Equals(endPos))){Debug.Log("找到父节点~" + endPos + ",迭代次数:" + cnt);List<Vector3Int> resultList = new List<Vector3Int>();Point endPoint = openList.Find(x => x.pos.Equals(endPos));resultList.Add(endPoint.pos);Point parent = endPoint.parent;while (parent != null){resultList.Add(parent.pos);parent = parent.parent;}return resultList;}cnt++;if (cnt > 100 * map.GetLength(0) * map.GetLength(1)){Debug.LogError(cnt);return null;}//从列表取最小F值的Point开始遍历Point currentPoint = openList.OrderBy(x => x.F).FirstOrDefault();string str = "";foreach (var v in openList){str += v.GetString();}Debug.Log("最小F:" + currentPoint.GetString() + "\n" + str);Vector3Int pos = currentPoint.pos;for (int i = -1; i <= 1; i++){for (int j = -1; j <= 1; j++){if (i == 0 && j == 0){continue;}//过滤越界、墙体(map[x,y]不等于1)、已处理节点(存在闭合列表的节点)Vector3Int tempPos = new Vector3Int(i + pos.x, j + pos.y, ConstZ);if (tempPos.x < 0 || tempPos.x >= map.GetLength(0) || tempPos.y < 0 || tempPos.y >= map.GetLength(1)|| map[tempPos.x, tempPos.y] != 1|| closeList.Exists(x => x.pos.Equals(tempPos))){continue;}//判断tempPos该节点是否已经计算,  在openList的就是已经计算的Point tempPoint = openList.Find(x => x.pos.Equals(tempPos));float newG = currentPoint.G + Vector3.Distance(currentPoint.pos, tempPos);if (tempPoint != null){//H固定不变,因此判断旧的G值和当前计算出的G值,如果当前G值更小,需要改变节点数据的父节点和G值为当前的,否则保持原样float oldG = tempPoint.G;if (newG < oldG){tempPoint.G = newG;tempPoint.parent = currentPoint;Debug.Log("更新节点:" + tempPoint.pos + ", newG:" + newG + ", oldG:" + oldG + ",parent:" + tempPoint.parent.pos);}}else{tempPoint = new Point(){G = newG,H = GetC(tempPos, endPos),pos = tempPos,parent = currentPoint};Debug.Log("新加入节点:" + tempPoint.pos + ", newG:" + newG + ", parent:" + currentPoint.pos);openList.Add(tempPoint);}}}//已处理过的当前节点从开启列表移除,并放入关闭列表openList.Remove(currentPoint);closeList.Add(currentPoint);}}private float GetC(Vector3Int a, Vector3Int b){return Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);}
}

相关文章:

【Unity3D】实现2D小地图效果

目录 一、玩家脚本Player 二、Canvas组件设置 三、小地图相关 四、GameLogicMap脚本修改 基于&#xff1a;【Unity3D】Tilemap俯视角像素游戏案例-CSDN博客 2D玩家添加Dotween移动DOPath效果&#xff0c;移动完成后进行刷新小地图&#xff08;小地图会顺便刷新大地图&…...

关联传播和 Python 和 Scikit-learn 实现

文章目录 一、说明二、什么是 Affinity Propagation。2.1 先说Affinity 传播的工作原理2.2 更多细节2.3 传播两种类型的消息2.4 计算责任和可用性的分数2.4.1 责任2.4.2 可用性分解2.4.3 更新分数&#xff1a;集群是如何形成的2.4.4 估计集群本身的数量。 三、亲和力传播的一些…...

https数字签名手动验签

以bing.com 为例 1. CA 层级的基本概念 CA 层级是一种树状结构&#xff0c;由多个层级的 CA 组成。每个 CA 负责为其下一层级的实体&#xff08;如子 CA 或终端实体&#xff09;颁发证书。层级结构的顶端是 根 CA&#xff08;Root CA&#xff09;&#xff0c;它是整个 PKI 体…...

LeetCode:62.不同路径

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;62.不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &…...

如果我想设计一款复古风格的壁纸,应该选什么颜色?

设计复古风格的壁纸时&#xff0c;选择合适的颜色是营造怀旧和经典氛围的关键。复古风格通常使用一些温暖、柔和且带有岁月痕迹的色调。以下是一些适合复古风格壁纸的颜色选择和搭配建议&#xff1a; 一、复古风格的主色调 棕色系&#xff1a; 特点&#xff1a;棕色是复古风格的…...

【数据结构】树的基本:结点、度、高度与计算

树是数据结构中一种重要的非线性结构&#xff0c;广泛应用于计算机科学的各个领域&#xff0c;例如文件系统、数据库索引、编译器等。理解树的各种性质&#xff0c;如结点数、度、高度等&#xff0c;对于解决实际问题至关重要。 本文将会探讨树的基本概念&#xff0c;以及给出几…...

【Pytest】生成html报告中,中文乱码问题解决方案

链接上一篇文章:https://blog.csdn.net/u013080870/article/details/145369926?spm1001.2014.3001.5502 中文乱码问题&#xff0c;python3&#xff0c;Python3.7后&#xff0c;还一个文件就是result.py 因为中文可以在内容中&#xff0c;也可能在文件名&#xff0c;类名&…...

week08_文本匹配任务

1、文本匹配任务概述 狭义&#xff1a; 给定一组文本&#xff0c;判断其是否语义相似 今天天气不错 match 今儿个天不错呀 √ 今天天气不错 match 你的代码有bug 以分值形式给出相似度 今天天气不错 match 今儿个天不错呀 0.9 今天天气不错 match…...

JUC--ConcurrentHashMap底层原理

ConcurrentHashMap底层原理 ConcurrentHashMapJDK1.7底层结构线程安全底层具体实现 JDK1.8底层结构线程安全底层具体实现 总结JDK 1.7 和 JDK 1.8实现有什么不同&#xff1f;ConcurrentHashMap 中的 CAS 应用 ConcurrentHashMap ConcurrentHashMap 是一种线程安全的高效Map集合…...

【2024年华为OD机试】(C卷,200分)- 推荐多样性 (JavaScriptJava PythonC/C++)

一、问题描述 问题描述 我们需要从多个已排序的列表中选取元素,以填充多个窗口。每个窗口需要展示一定数量的元素,且元素的选择需要遵循特定的穿插策略。具体来说,我们需要: 从第一个列表中为每个窗口选择一个元素,然后从第二个列表中为每个窗口选择一个元素,依此类推。…...

【教学类-89-01】20250127新年篇01—— 蛇年红包(WORD模版)

祈愿在2025蛇年里&#xff0c; 伟大的祖国风调雨顺、国泰民安、每个人齐心协力&#xff0c;共同经历这百年未有之大变局时代&#xff08;国际政治、AI技术……&#xff09; 祝福亲友同事孩子们平安健康&#xff08;安全、安全、安全&#xff09;、巳巳如意&#xff01; 背景需…...

[权限提升] 操作系统权限介绍

关注这个专栏的其他相关笔记&#xff1a;[内网安全] 内网渗透 - 学习手册-CSDN博客 权限提升简称提权&#xff0c;顾名思义就是提升自己在目标系统中的权限。现在的操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;我们通过 Web 漏洞拿到的 Web 进程的…...

DeepSeek异军突起,重塑AI格局

DeepSeek异军突起&#xff0c;重塑AI格局这两天AI 圈发生了比过年更令人兴奋的事情&#xff0c;“Meta内部反水事件”、“黄仁勋的底盘问题”&#xff0c;以及AI格局的大动荡&#xff0c;一切都是因为那个叫DeepSeek的“中国自主AI”&#xff01;它由幻方量化开发&#xff0c;以…...

git的理解与使用

本地的git git除了最经典的add commit push用来做版本管理&#xff0c;其实他的分支管理也非常强大 可以说你学好了分支管理&#xff0c;就可以完成团队的配合协作了 git仓库 我们可以使用git init来初始化一个git仓库&#xff0c;只要能看见.git文件夹&#xff0c;就代表这…...

Baklib打造内容中台新模式助力企业智能化升级

内容概要 在如今数字化日渐渗透各个行业的背景下&#xff0c;内容中台逐渐成为推动企业智能化转型的重要工具。内容中台不仅仅是一个信息管理平台&#xff0c;更是一个整合多种内容资源&#xff0c;提升企业反应能力与市场适应力的创新模式。随着数据量的激增&#xff0c;传统…...

STM32完全学习——RT-thread在STM32F407上移植

一、写在前面 关于源码的下载&#xff0c;以及在KEIL工程里面添加操作系统的源代码&#xff0c;这里就不再赘述了。需要注意的是RT-thread默认里面是会使用串口的&#xff0c;因此需要额外的进行串口的初始化&#xff0c;有些人可能会问&#xff0c;为什么不直接使用CubMAX直接…...

基于51单片机和ESP8266(01S)、LCD1602、DS1302、独立按键的WiFi时钟

目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、延时2、定时器03、串口通信4、DS13025、LCD16026、独立按键 四、主函数总结 系列文章目录 前言 之前做了一个WiFi定时器时钟&#xff0c;用八位数码管进行显示&#xff0c;但是定时器时钟的精度较低&#xff0…...

启元世界(Inspir.ai)技术浅析(二):深度强化学习

深度强化学习(Deep Reinforcement Learning, DRL)是启元世界在人工智能领域的一项核心技术,广泛应用于游戏AI、智能决策等领域。 一、状态(State) 1.1 概念与作用 **状态(State)**是指智能体对环境的感知,是智能体进行决策的基础。在深度强化学习中,状态通常是一个高…...

LeetCode100之子集(78)--Java

1.问题描述 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的 子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例1 输入&#xff1a;nums [1,2,3]输出&#xff1a;[[],[1],[2],[1,2],[3],[1…...

React第二十五章(受控组件/非受控组件)

React 受控组件理解和应用 React 受控组件 受控组件一般是指表单元素&#xff0c;表单的数据由React的 State 管理&#xff0c;更新数据时&#xff0c;需要手动调用setState()方法&#xff0c;更新数据。因为React没有类似于Vue的v-model&#xff0c;所以需要自己实现绑定事件…...

使用 Confluent Cloud 的 Elasticsearch Connector 部署 Elastic Agent

作者&#xff1a;来自 Elastic Nima Rezainia Confluent Cloud 用户现在可以使用更新后的 Elasticsearch Sink Connector 与 Elastic Agent 和 Elastic Integrations 来实现完全托管且高度可扩展的数据提取架构。 Elastic 和 Confluent 是关键的技术合作伙伴&#xff0c;我们很…...

嵌入式知识点总结 Linux驱动 (三)-文件系统

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.什么是文件系统&#xff1f; 2.根文件系统为什么这么重要&#xff1f;​编辑 3.可执行映像文件通常由几部分构成&#xff0c;他们有什么特点&#xff1f; 1.什么是文件系统&a…...

【知识】可视化理解git中的cherry-pick、merge、rebase

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 这三个确实非常像&#xff0c;以至于对于初学者来说比较难理解。 总结对比 先给出对比&#xff1a; 特性git mergegit rebasegit cherry-pick功能合并…...

【deepseek】deepseek-r1本地部署-第二步:huggingface.co替换为hf-mirror.com国内镜像

一、背景 由于国际镜像国内无法直接访问&#xff0c;会导致搜索模型时加载失败&#xff0c;如下&#xff1a; 因此需将国际地址替换为国内镜像地址。 二、操作 1、使用vscode打开下载路径 2、全局地址替换 关键字 huggingface.co 替换为 hf-mirror.com 注意&#xff1a;务…...

新站如何快速获得搜索引擎收录?

本文来自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/8.html 新站想要快速获得搜索引擎收录&#xff0c;需要采取一系列有针对性的策略。以下是一些具体的建议&#xff1a; 一、网站内容优化 高质量原创内容&#xff1a; 确保网站内容原创、…...

如何使用tushare pro获取股票数据——附爬虫代码以及tushare积分获取方式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据 总结 一、Tushare 介绍 Tushare 是一个提供中国股市数据的API接口服务&#xff0c;它允许用户…...

解决vsocde ssh远程连接同一ip,不同端口情况下,无法区分的问题

一般服务器会通过镜像分身或者容器的方式&#xff0c;一个ip分出多个端口给多人使用&#xff0c;但如果碰到需要连接同一user&#xff0c;同一个ip,不同端口的情况&#xff0c;vscode就无法识别&#xff0c;如下图所示&#xff0c;vscode无法区分该ip下不同端口的连接&#xff…...

Elasticsearch 自定义分成器 拼音搜索 搜索自动补全 Java对接

介绍 通常用于将文档中的文本数据拆分成易于索引的词项&#xff08;tokens&#xff09;。有时&#xff0c;默认的分词器无法满足特定应用需求&#xff0c;这时就可以创建 自定义分词器 来实现定制化的文本分析。 自定义分词器组成 Char Filters&#xff08;字符过滤器&#x…...

基于物联网设计的疫苗冷链物流监测系统

一、前言 1.1 项目开发背景 随着全球经济的发展和物流行业的不断创新&#xff0c;疫苗和生物制品的运输要求变得越来越高。尤其是疫苗的冷链物流&#xff0c;温度、湿度等环境因素的控制直接关系到疫苗的质量和效力&#xff0c;因此高效、可靠的冷链监控系统显得尤为重要。冷…...

RocketMQ消息是如何存储的?

大家好&#xff0c;我是锋哥。今天分享关于【RocketMQ消息是如何存储的&#xff1f;】面试题。希望对大家有帮助&#xff1b; RocketMQ消息是如何存储的&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 RocketMQ 使用了一个高性能、分布式的消息存储架构…...