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

Unity C#脚本的热更新

以下内容是根据Unity 2020.1.0f1版本进行编写的

目前游戏开发厂商主流还是使用lua框架来进行热更,如xlua,tolua等,也有的小游戏是直接整包更新,这种小游戏的包体很小,代码是用C#写的;还有的游戏就是通过热更C#代码来实现热更新的。本篇就来学习一下。

1、热更C#代码的方法

AI时代,遇事不决,先问AI,下面就是百度问AI的答案:
在这里插入图片描述
在这里插入图片描述
可以看到,AI给出的答案大部分都是将C#代码编译成dll,然后在需要时动态加载对应的dll来实现代码的热更新的。下面就来尝试一下。

2、使用ILRuntime框架

ILRuntime官方文档:https://ourpalm.github.io/ILRuntime/public/v1/guide/tutorial.html
在这里插入图片描述
“scopedRegistries”: [
{
“name”: “ILRuntime”,
“url”: “https://registry.npmjs.org”,
“scopes”: [
“com.ourpalm”
]
}
],
在这里插入图片描述
如上图一,新建一个unity项目,然后在工程目录Packages下的manifest文件中增加图一框住的代码,保存。然后关闭Unity项目再重新打开。点击菜单栏Window->Package Manager打开PackageManager窗口,切换Packages切换到My Registries,可以看到刚刚加上去的ILRuntime包(如图二)。

选中后点击右下角箭头所指的install按钮就可以导入包体了(这里因为我已经导入过了所以显示的按钮是Remove)。这个包体还有示例Demo,有需要也可以一并导入到工程中。
在这里插入图片描述
导入的Demo用的是unsafe代码,导入后可能会有很多报错,点击菜单栏Edit->Project Settings打开ProjectSettings窗口,设置player页签中的OtherSettings,使工程允许使用unsafe代码。
在这里插入图片描述
接着尝试运行Demo,直接运行会报错,需要生成dll。
先用VisualStudio打开一次项目工程的sln文件,再打开下载的Demo包内工程的sln文件(如上图)。
在这里插入图片描述
在这里插入图片描述
在打开的HotFix_Project中,点击菜单栏生成->生成解决方案按钮,等待VS左下角出现生成成功提示。
在这里插入图片描述
此时回到Unity随便运行一个Examples场景,都不会有报错了。

在这里插入图片描述
接下来简单尝试一下,先做一个简单的界面(如上图),功能是点击下方左右两个按钮,点击哪边的按钮,就在中间的文本显示点击了哪边的按钮。

using UnityEngine;
using System.Collections;
using System.IO;
using System;public class AppCommon : MonoBehaviour
{static AppCommon instance;System.IO.MemoryStream fs;System.IO.MemoryStream p;public bool isLoaded = false;public static AppCommon Instance{get { return instance; }}//AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个//大家在正式项目中请全局只创建一个AppDomainpublic ILRuntime.Runtime.Enviorment.AppDomain appdomain;//在awake方法中先加载好appdomainvoid Awake(){instance = this;StartCoroutine(LoadHotFixAssembly());}IEnumerator LoadHotFixAssembly(){//首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();//正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取,//正式发布的时候需要大家自行从其他地方读取dll//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
#if UNITY_ANDROIDWWW www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.dll");
#elseWWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endifwhile (!www.isDone)yield return null;if (!string.IsNullOrEmpty(www.error))UnityEngine.Debug.LogError(www.error);byte[] dll = www.bytes;www.Dispose();//PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可
#if UNITY_ANDROIDwww = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#elsewww = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endifwhile (!www.isDone)yield return null;if (!string.IsNullOrEmpty(www.error))UnityEngine.Debug.LogError(www.error);byte[] pdb = www.bytes;fs = new MemoryStream(dll);p = new MemoryStream(pdb);try{appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());}catch{Debug.LogError("加载热更DLL失败,请确保已经通过VS打开Assets/Samples/ILRuntime/1.6/Demo/HotFix_Project/HotFix_Project.sln编译过热更DLL");}InitializeILRuntime();OnHotFixLoaded();}private void OnDestroy(){fs.Close();p.Close();}unsafe void InitializeILRuntime(){
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)//由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profilerappdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif//这里做一些ILRuntime的注册appdomain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter());appdomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((act) =>{return new UnityEngine.Events.UnityAction(() =>{((Action)act)();});});ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain);}unsafe void OnHotFixLoaded(){isLoaded = true;Debug.Log("AppDomain Loaded");}
}

首先在Unity中新建一个名叫AppCommon的脚本,用于定义一些项目内通用的单例类。这里主要是定义一个叫appdomain的类,在Demo中是建议全局只创建一个的。
每个Demo都会有加载这个appdpmain的方法,将代码复制到AppCommon类中,在其InitializeILRuntime方法中注册好全部需要用到的事件等。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using ILRuntime.CLR.TypeSystem;
using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;public class MyView1 : MonoBehaviour
{static MyView1 instance;public static MyView1 Instance{get { return instance; }}void Start(){instance = this;StartCoroutine(LoadHotFixAssembly());}IEnumerator LoadHotFixAssembly(){while(!AppCommon.Instance.isLoaded){yield return 0;}OnHotFixLoaded();}void OnHotFixLoaded(){SetupCLRRedirection();SetupCLRRedirection2();AppCommon.Instance.appdomain.Invoke("HotFix_Project.TestMyView1", "ShowView", null, gameObject);}unsafe void SetupCLRRedirection(){//这里面的通常应该写在InitializeILRuntime,这里为了演示写这里var arr = typeof(GameObject).GetMethods();foreach (var i in arr){if (i.Name == "AddComponent" && i.GetGenericArguments().Length == 1){AppCommon.Instance.appdomain.RegisterCLRMethodRedirection(i, AddComponent);}}}unsafe void SetupCLRRedirection2(){//这里面的通常应该写在InitializeILRuntime,这里为了演示写这里var arr = typeof(GameObject).GetMethods();foreach (var i in arr){if (i.Name == "GetComponent" && i.GetGenericArguments().Length == 1){AppCommon.Instance.appdomain.RegisterCLRMethodRedirection(i, GetComponent);}}}unsafe static StackObject* AddComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj){//CLR重定向的说明请看相关文档和教程,这里不多做解释ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;var ptr = __esp - 1;//成员方法的第一个参数为thisGameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;if (instance == null)throw new System.NullReferenceException();__intp.Free(ptr);var genericArgument = __method.GenericArguments;//AddComponent应该有且只有1个泛型参数if (genericArgument != null && genericArgument.Length == 1){var type = genericArgument[0];object res;if (type is CLRType){//Unity主工程的类不需要任何特殊处理,直接调用Unity接口res = instance.AddComponent(type.TypeForCLR);}else{//热更DLL内的类型比较麻烦。首先我们得自己手动创建实例var ilInstance = new ILTypeInstance(type as ILType, false);//手动创建实例是因为默认方式会new MonoBehaviour,这在Unity里不允许//接下来创建Adapter实例var clrInstance = instance.AddComponent<MonoBehaviourAdapter.Adaptor>();//unity创建的实例并没有热更DLL里面的实例,所以需要手动赋值clrInstance.ILInstance = ilInstance;clrInstance.AppDomain = __domain;//这个实例默认创建的CLRInstance不是通过AddComponent出来的有效实例,所以得手动替换ilInstance.CLRInstance = clrInstance;res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstanceclrInstance.Awake();//因为Unity调用这个方法时还没准备好所以这里补调一次}return ILIntepreter.PushObject(ptr, __mStack, res);}return __esp;}unsafe static StackObject* GetComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj){//CLR重定向的说明请看相关文档和教程,这里不多做解释ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;var ptr = __esp - 1;//成员方法的第一个参数为thisGameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;if (instance == null)throw new System.NullReferenceException();__intp.Free(ptr);var genericArgument = __method.GenericArguments;//AddComponent应该有且只有1个泛型参数if (genericArgument != null && genericArgument.Length == 1){var type = genericArgument[0];object res = null;if (type is CLRType){//Unity主工程的类不需要任何特殊处理,直接调用Unity接口res = instance.GetComponent(type.TypeForCLR);}else{//因为所有DLL里面的MonoBehaviour实际都是这个Component,所以我们只能全取出来遍历查找var clrInstances = instance.GetComponents<MonoBehaviourAdapter.Adaptor>();for (int i = 0; i < clrInstances.Length; i++){var clrInstance = clrInstances[i];if (clrInstance.ILInstance != null)//ILInstance为null, 表示是无效的MonoBehaviour,要略过{if (clrInstance.ILInstance.Type == type){res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstancebreak;}}}}return ILIntepreter.PushObject(ptr, __mStack, res);}return __esp;}
}

然后还是在Unity中新建一个名叫MyView1的脚本,这个脚本的功能其实只是等待上述的appdomain类加载完之后,然后调用加载的dll内部的类以及方法即可。实际逻辑是写到HotFix_Project里的,也只有HotFix_Project里的代码能热更。
这里写的比较简单,大部分代码是抄MonoBehaviourDemo的,实际上就是需要使appdomain注册自定义实现AddComponent方法和GetComponent方法。这样,在热更的代码中就可以通过AddComponent方法来把C#代码以组件的形式挂载到对应的GameObject中。
在这里插入图片描述
在这里插入图片描述
最后就是热更代码部分。
因为要用到Unity UI部分的方法,所以需要将UnityEngine.UI的dll复制过来并加入到HotFix_Project的引用中。(先把dll复制到UnityDlls目录下,然后再在VS上右键添加引用,在打开的窗口中点击右下角浏览,然后选择复制的dll文件即可)

using UnityEngine;
using UnityEngine.UI;namespace HotFix_Project
{class TestMyView1 : MonoBehaviour{private Button btn1;private Button btn2;private Text text;void Start(){btn1 = gameObject.transform.Find("btn1").GetComponent<Button>();btn2 = gameObject.transform.Find("btn2").GetComponent<Button>();btn1.onClick.AddListener(OnClickBtn1);btn2.onClick.AddListener(OnClickBtn2);text = gameObject.transform.Find("Text").GetComponent<Text>();}void OnClickBtn1(){text.text = "点击了左边的按钮";}void OnClickBtn2(){text.text = "点击了右边的按钮";}public static void ShowView(GameObject go){go.AddComponent<TestMyView1>();}}
}

接着在HotFix_Project新建一个名叫TestMyView1的C#脚本,实现上面描述的功能就可以了。这一部分代码就是可热更的。
写好代码后,点击HotFix_Project菜单栏的生成->重新生成解决方案按钮,即可运行Unity。
效果如下:
在这里插入图片描述
最后,如果需要实现热更,就是在AppCommon加载appdomain的协程中,修改一下加载的文件位置(如上图框住的部分,这里我没试)。
所以实际上,C#代码热更就是将代码编译成dll,然后在加载后以反射调用或者委托等方式来调用写在dll内部的类和方法。因此每次热更只需要重新编译生成dll就可以了。
在这里插入图片描述
在这里插入图片描述
此外,该框架还能生成一些CLR绑定的代码,用于减少反射调用的消耗。(实际上这里的做法有点类似于tolua框架)

代码仓库地址:https://gitee.com/chj–project/CSharpHotUpdate

相关文章:

Unity C#脚本的热更新

以下内容是根据Unity 2020.1.0f1版本进行编写的   目前游戏开发厂商主流还是使用lua框架来进行热更&#xff0c;如xlua&#xff0c;tolua等&#xff0c;也有的小游戏是直接整包更新&#xff0c;这种小游戏的包体很小&#xff0c;代码是用C#写的&#xff1b;还有的游戏就是通过…...

监督学习之逻辑回归

逻辑回归&#xff08;Logistic Regression&#xff09; 逻辑回归是一种用于二分类&#xff08;binary classification&#xff09;问题的统计模型。尽管其名称中有“回归”二字&#xff0c;但逻辑回归实际上用于分类任务。它的核心思想是通过将线性回归的输出映射到一个概率值…...

深度优先算法(DFS)洛谷P1683-入门

虽然洛谷是有题解的,但是你如果直接看得懂题解,你也不会来看这篇文章.. 这些代码均是我记录自身成长的记录,有写的不好的地方请谅解&#xff01; 先上代码&#xff1a; #include <iostream> #include <vector> #include<iomanip> #include<cstdio&…...

Python数据分析基础

本文介绍了Python在数据分析中的应用&#xff0c;包括数据读取、清洗、处理和分析的基本操作。通过使用Pandas和Numpy库&#xff0c;我们可以高效地处理大量数据&#xff0c;并利用Matplotlib和Seaborn库进行数据可视化。 1. 引言 Python因其简洁的语法和强大的库支持&#x…...

《企业自设2-软件测试》线下课day3: 006扩展虚拟机

1.win11 修改hosts无权限 分别再cmd终端输入以下两行代码&#xff1a; C:\Windows\System32\drivers\etcnotepad hosts 2.先保存快照&#xff01;&#xff01;&#xff01; 3.关闭虚拟机&#xff0c;将内存&#xff0c;CPU进行修改 就是再这个位置修改&#xff1a; 4.运…...

配置和排查 Lombok 在 IDEA 中使用的详细步骤

在日常开发中&#xff0c;Java 代码常常需要大量的样板代码&#xff0c;比如 getter、setter、toString 等方法。Lombok 是一个 Java 库&#xff0c;可以通过注解的方式&#xff0c;自动生成这些常见的代码&#xff0c;从而让代码更加简洁、清晰。比如&#xff0c;我们可以通过…...

JavaWeb合集18-接口管理Swager

十八、接口管理 1、Swager 使用Swagger你只需要按照它的规范去定义接口及接口相关的信息&#xff0c;就可以做到生成接口文档&#xff0c;以及在线接口调试页面。 官网: https://swagger.io/ Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。 <dependency&g…...

背包九讲——二维费用背包问题

目录 二维费用背包问题 问题描述&#xff1a; 解决方法&#xff1a; 方法一&#xff1a; 代码实现&#xff1a; 方法二&#xff1a; 代码实现&#xff1a; 背包问题第五讲——二维费用背包问题 背包问题是一类经典的组合优化问题&#xff0c;通常涉及在限定容量的背包中…...

【mysql进阶】4-7. 通用表空间

通⽤表空间 - General Tablespace 1 通⽤表空间的作⽤和特性&#xff1f; ✅ 解答问题 通⽤表空间是使⽤ CREATE tablespace 语法创建的共享InnoDB表空间 通⽤表空间能够存储多个表的数据&#xff0c;与系统表空间类似也是共享表空间&#xff1b; 服务器运⾏时会把表空间元数…...

2024 年互联网大厂 1300 多道 JAVA 面试题汇总,包含了程序员的所有技术点

作为一个 Java 程序员&#xff0c;你平时总是陷在业务开发里&#xff0c;每天噼里啪啦忙敲着代码&#xff0c;上到系统开发&#xff0c;下到 Bug 修改&#xff0c;你感觉自己无所不能。然而偶尔的一次聚会&#xff0c;你听说和自己一起出道的同学早已经年薪 50 万&#xff0c;而…...

【开源免费】基于SpringBoot+Vue.JS在线文档管理系统(JAVA毕业设计)

本文项目编号 T 038 &#xff0c;文末自助获取源码 \color{red}{T038&#xff0c;文末自助获取源码} T038&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…...

Linux资源与网络请求

参数说明&#xff1a; d : 改变显示的更新速度&#xff0c;或是在交谈式指令列( interactive command)按 sq : 没有任何延迟的显示速度&#xff0c;如果使用者是有 superuser 的权限&#xff0c;则 top 将会以最高的优先序执行c : 切换显示模式&#xff0c;共有两种模式&#…...

RPA技术重塑企业自动化的未来

1. RPA定义与原理 1.1 机器人流程自动化(RPA)概念 机器人流程自动化&#xff08;Robotic Process Automation&#xff0c;简称RPA&#xff09;是一种软件技术&#xff0c;通过模拟人类用户在计算机界面上的操作来执行重复性的业务流程任务。RPA软件机器人能够自动执行基于规则…...

使用RabbitMQ实现延迟消息的完整指南

在分布式系统中&#xff0c;消息队列通常用于解耦服务&#xff0c;RabbitMQ是一个广泛使用的消息队列服务。延迟消息&#xff08;也称为延时队列或TTL消息&#xff09;是一种常见的场景应用&#xff0c;特别适合处理某些任务在一段时间后执行的需求&#xff0c;如订单超时处理、…...

阿里员工:阿里工作7年至少得P7吧,快的都P8了,年薪100W是正常的,80才算及格...

上一篇&#xff1a;一线体面男的收入 年薪64W的阿里蚂蚁员工爆料&#xff1a;在阿里&#xff0c;工作7年至少得P7&#xff0c;快的都P8了&#xff0c;年薪100W才正常&#xff0c;80分才算及格。 其实&#xff0c;在大厂工作&#xff0c;听起来风光无限&#xff0c;但个中滋味&a…...

Django进一步掌握(10月22日)

一、请求响应对象 请求对象request 响应对象HttpResponse 二、HttpResponse常用属性 status设置HTTP响应状态码 status_code查询HTTP响应状态码 content_type设置响应的类型 write()写入响应内容 三、重定向 1、实现URl访问的重定向 &#xff08;1&#xff09;使用Ht…...

C++从入门到起飞之——红黑树封装map和set 全方位剖析!

目录 1、map和set的整体框架 2、map和set迭代器的实现 3、map支持[] 4、完整源码 set.h map.h RBTree.h 1、map和set的整体框架 因为map和set的底层都是红黑树&#xff0c;所以我们考虑用一个红黑树的类模版去实例化map和set对象&#xff01;不过&#xff0c;map节点中存…...

【javax maven项目缺少_Maven的依赖管理 引入依赖】

javax maven项目缺少_Maven的依赖管理 引入依赖 Maven的依赖管理 - 引入依赖依赖管理(引入依赖)导入依赖 https://blog.csdn.net/weixin_28932089/article/details/112381468 Maven的依赖管理 - 引入依赖 依赖管理(引入依赖) 能够掌握依赖引入的配置方式 导入依赖 导入依赖练…...

手搓一个定时器

目录 1.什么是定时器 2.计时器的使用 3.手搓定时器 3.1定义一个TimerTask类 3.2定义一个Timer类 3.3实现schedule方法 3.4实现Timer的构造方法 3.4.1随时随地查看优先级队列中是否有任务要执行 3.4.2获取队首任务&#xff0c;并判断是否到执行时间 3.4.3到达执行时间…...

AI提示词工程优化Prompt-GPT使用手册(科普一键收藏史上最强攻略)

Prompt(提示)&#xff0c;最初是 NLP 研究者为下游任务设计出来的一种任务专属的输入形式或模板。在 ChatGPT 引发大语言模型新时代之后&#xff0c;Prompt 指与大模型交互输入的代称。 随着大模型的进展&#xff0c;Prompt Engineering是一个持久的探索过程。 目录 什么是提示…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...

02.运算符

目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&&#xff1a;逻辑与 ||&#xff1a;逻辑或 &#xff01;&#xff1a;逻辑非 短路求值 位运算符 按位与&&#xff1a; 按位或 | 按位取反~ …...

从零开始了解数据采集(二十八)——制造业数字孪生

近年来&#xff0c;我国的工业领域正经历一场前所未有的数字化变革&#xff0c;从“双碳目标”到工业互联网平台的推广&#xff0c;国家政策和市场需求共同推动了制造业的升级。在这场变革中&#xff0c;数字孪生技术成为备受关注的关键工具&#xff0c;它不仅让企业“看见”设…...

Vue 3 + WebSocket 实战:公司通知实时推送功能详解

&#x1f4e2; Vue 3 WebSocket 实战&#xff1a;公司通知实时推送功能详解 &#x1f4cc; 收藏 点赞 关注&#xff0c;项目中要用到推送功能时就不怕找不到了&#xff01; 实时通知是企业系统中常见的功能&#xff0c;比如&#xff1a;管理员发布通知后&#xff0c;所有用户…...