Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二)

Unity 之 Addressable可寻址系统 -- HybridCLR+Addressable实现资源脚本全热更 -- 实战

  • 前言
  • 实现原理
  • 一,HybridCLR相关操作
    • 1.1 安装HybridCLR
    • 1.2 打包程序集
    • 1.2 设置面板
    • 1.3 补充元数据
    • 1.4 代码加载流程
  • 二,Addressable资源管理
    • 2.1 生成热更代码资源
    • 2.2 创建资源组
    • 2.3 设置资源更新
  • 三,实现代码
    • 3.1 包内逻辑
    • 3.2 热更代码
    • 3.3 打包工具类
  • 四,示例工程源码分享

前言

在Unity中,结合Addressable Asset System(简称:AA)和HybridCLR来实现热更新资源和脚本的控制。AA是Unity的一个强大的资源管理系统,可以用于动态加载和更新资源。HybridCLR是一个用于在Unity中运行C#脚本的工具,可以实现热更新脚本的功能。


实现原理

使用版本:

  • Unity 2022.3.8
  • Addressables 1.21.19
  • HybridCLR 3.4.0

发布WebGL平台:从Unity 2021.3.4+、2022.3.0+版本起,不再需要全局安装,也就是webgl平台的构建过程与其他平台完全相同。

实现思路:

  1. 创建两个代码文件夹
    • 一个用于热更(游戏逻辑代码)
    • 一个用于加载(检测热更,资源下载,加载热更程序集,启动热更代码)
  2. 将在热更文件夹下创建程序集,用于热更游戏逻辑代码
  3. 通过HybridCLR将热更程序集打包成资源包,并将资源包复制到游戏内命名后缀添加“.bytes”
  4. 将3步骤中资源包“HotUptare.dll.btyes”添加到AA的资源管理中
  5. 在加载逻辑中,使用AA的资源更新机制,校验是否需要下载更新(所有AA包,包括代码程序集)
  6. 更新检测完成后,使用AA读取热更代码资源(若有会使用新的),进入游戏流程。

原理解析:

  • 通过使用AA,你可以将游戏中的资源打包成独立的AssetBundle,并在运行时根据需要加载和卸载这些资源。这样,你就可以实现资源的热更新,不需要重新构建和发布整个应用程序。

  • HybridCLR可以帮助你实现热更新脚本的功能。它允许你在Unity中加载和运行C#脚本代码,而无需重新编译整个项目。通过将脚本代码打包成DLL文件,并使用HybridCLR加载和执行这些DLL文件,你可以在游戏运行时动态更新脚本逻辑,实现脚本的热更新。

  • 结合AA和HybridCLR,可以实现资源和脚本的全部热更新控制。我们将资源和脚本分别打包成独立的AssetBundle和DLL文件,然后在游戏运行时根据需要下载和加载这些文件。这样,你可以实现资源和脚本的全部热更新流程。


一,HybridCLR相关操作

1.1 安装HybridCLR

主菜单中点击Windows/Package Manager打开包管理器。如下图所示点击Add package from git URL...,填入https://gitee.com/focus-creative-games/hybridclr_unity.githttps://github.com/focus-creative-games/hybridclr_unity.git

没看过官方文档的,推荐去看官方文档的快速上手

按照官方快速上手的文档将实现:

  1. 创建热更新程序集
  2. 加载热更新程序集,并执行其中热更新代码,打印 Hello, HybridCLR
  3. 修改热更新代码,打印 Hello, World

1.2 打包程序集

设计思路: 推荐将所有需要热更的脚本都放到一个程序集中。这样方便会减少后面游戏热更限制。

创建热更程序集

实现步骤:

  1. 创建程序集作为热更程序集,并命名为“HotUpdate”
  2. 在程序集中添加需要的其他程序集的引用,如:“Unity.Addressable”等(在热更程序集中的代码引用不到Unity或者插件的命名空间,都需要在这里添加)
  3. 程序集的作用域为:所在目录集齐子目录

1.2 设置面板

将热更程序集添加到热更新Assembly Defintions中:

配置PlayerSettings:

  • 如果你用的hybridclr包低于v4.0.0版本,需要关闭增量式GC(Use Incremental GC) 选项
  • Scripting Backend 切换为 IL2CPP
  • Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或 .Net Framework(Unity 2021+)

1.3 补充元数据

首先安装HybridCLR,点击HybridCLR/Installer弹出面板,再次点击”Install“,等待安装完成即可。

然后HybridCLR/Generate/ALL,此步骤会进行:

  1. 生成依赖HotUpdateDlls
  2. 生成裁剪后的aot dll
  3. 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll。

将需要进行补充的dll添加到Steamingassets,并在HybridCLR Settings/补充元数据AOT dlls将文件名填进去。

需要补充的dll文件生成在,dll编译根目录:HybridCLRData/HotUpdateDlls:

设置后再代码中补充元数据部分的逻辑,就可以顺利通过了:

同样将需要补充元数据的dll名称放到AOTMetaAssemblyFiles

注意:当读取StreamingAssets文件夹下资源时,Android平台是和其他平台不一致的,需要单独处理,处理方法在完整代码中注释里面写了。

1.4 代码加载流程

【逻辑思路简介,详细看后面代码讲解】
在资源更新之后补充元数据,然后读取热更程序集,启动游戏:

这个桥接,可以是从热更加载场景跳转到游戏场景或者加载游戏主预制体都可以。(反正是要启动热更程序集中的代码执行流程)


二,Addressable资源管理

2.1 生成热更代码资源

点击HybridCLR/CompileDll/ActiveBuildTarget,编译目标平台热更程序集代码:

2.2 创建资源组

按照需求创建资源组,并将HybridCLR打包的程序集当做资源包托管的AA:

2.3 设置资源更新

设置远程资源包下载地址:

将需要热更的资源包设置为远程资源包:


三,实现代码

3.1 包内逻辑

不支持热更
要做的事:检测热更,资源下载,加载热更程序集,启动热更代码(桥接)

启动代码挂载如下:

内容如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using HybridCLR;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;/// <summary>
/// Loading页检测更新并下载资源
/// </summary>
public class AADownloadManager : MonoBehaviour
{/// <summary>/// 显示下载状态和进度/// </summary>public Text updateText;public Image progressImage;public Button retryButton;private AsyncOperationHandle downloadDependencies;// 当前下载文件索引private int downLoadIndex = 0;// 下载完成文件个数private int downLoadCompleteCount = 0;// 下载每组资源大小List<long> downLoadSizeList;/// <summary>/// 下载多个文件列表/// </summary>List<string> downLoadKeyList;List<IResourceLocator> resourceLocators;// 总大小long totalSize = 0;// 下载进度float curProgressSize = 0;// 下载到那个资源int progressIndex = 0;// 下载资源总大小private long downLoadTotalSize = 0;// 当前下载大小private float curDownLoadSize = 0;void Start(){Screen.sleepTimeout = SleepTimeout.NeverSleep;Application.targetFrameRate = 60;downLoadIndex = 0;retryButton.gameObject.SetActive(false);InitAddressable();}/// <summary>/// 初始化 --> 加载远端的配置文件/// </summary>private async void InitAddressable(){ShowHintText(0, "正在初始化配置...");var initAddress = Addressables.InitializeAsync(false);await initAddress.Task;if (initAddress.Status == AsyncOperationStatus.Failed){Debug.LogError("初始化失败");ShowHintText(0, "初始化失败");StartGame();return;}CheckUpdateAsset();Addressables.Release(initAddress);}/// <summary>/// 检查是否有更新/// </summary>private async void CheckUpdateAsset(){ShowHintText(0, "正在检测更新配置...");retryButton.gameObject.SetActive(false);var checkCatLogUpdate = Addressables.CheckForCatalogUpdates(false);await checkCatLogUpdate.Task;if (checkCatLogUpdate.Status != AsyncOperationStatus.Succeeded){Debug.LogError("检测更新失败");ShowHintText(0, "检测更新失败");// 展示重试按钮retryButton.gameObject.SetActive(true);retryButton.onClick.RemoveAllListeners();retryButton.onClick.AddListener(CheckUpdateAsset);}downLoadKeyList = checkCatLogUpdate.Result;if (downLoadKeyList.Count <= 0){Debug.Log("无可更新内容,直接进入游戏...");ShowHintText(1, "无可更新内容");StartGame();return;}else{Debug.Log($"有{downLoadKeyList.Count}个资源需要更新");CheckUpdateAssetSize();}Addressables.Release(checkCatLogUpdate);}private async void CheckUpdateAssetSize(){ShowHintText(0, "正在校验更新资源大小...");retryButton.gameObject.SetActive(false);// true:自动清除缓存 ,更新资源列表,是否自动释放var updateCatLog = Addressables.UpdateCatalogs(true, downLoadKeyList, false);await updateCatLog.Task;if (updateCatLog.Status != AsyncOperationStatus.Succeeded){Debug.LogError("更新资源列表失败");ShowHintText(0, "更新资源列表失败");// 展示重试按钮retryButton.gameObject.SetActive(true);retryButton.onClick.RemoveAllListeners();retryButton.onClick.AddListener(CheckUpdateAssetSize);return;}resourceLocators = updateCatLog.Result;Addressables.Release(updateCatLog);AsyncOperationHandle<long> operationHandle = default;foreach (var item in resourceLocators){operationHandle = Addressables.GetDownloadSizeAsync(item.Keys);await operationHandle.Task;downLoadSizeList.Add(operationHandle.Result);totalSize += operationHandle.Result;}Debug.Log($"获取到的下载大小:{totalSize / 1048579f} M");Addressables.Release(operationHandle);if (totalSize <= 0){Debug.Log("无可更新内容");ShowHintText(1, "无可更新内容");StartGame();return;}Debug.Log($"有{downLoadKeyList.Count}个资源需要更新");ShowHintText(0, $"有{downLoadKeyList.Count}个资源需要更新");progressIndex = 0;DownloadAsset();}private async void DownloadAsset(){ShowHintText(0, "正在更新资源...");retryButton.gameObject.SetActive(false);for (int i = progressIndex; i < resourceLocators.Count; i++){var item = resourceLocators[i];AsyncOperationHandle asyncOperationHandle = Addressables.DownloadDependenciesAsync(item.Keys);//await asyncOperationHandle.Task;while (asyncOperationHandle.IsDone){if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded){Debug.Log($"下载成功:{item}...");}else{Debug.LogError($"下载失败:{item},显示重试按钮,下载到第{progressIndex}个资源...");progressIndex = i;retryButton.gameObject.SetActive(true);retryButton.onClick.RemoveAllListeners();retryButton.onClick.AddListener(DownloadAsset);}float progress = asyncOperationHandle.PercentComplete;curProgressSize += downLoadSizeList[i] * progress;Debug.Log($"{item} ;progress:{progress}; downLoadSizeList:{downLoadSizeList[i]}...");ShowHintText(curProgressSize / (totalSize * 1.0f), "正在更新资源...");await Task.Yield();}}Debug.Log("下载完成");StartGame();}private void Update(){if (Input.GetKeyDown(KeyCode.A)){Debug.Log("清理缓存...");// 清理缓存Caching.ClearCache();}}void ShowHintText(float progress, string text){if (updateText != null){updateText.text = text;}if (progressImage != null){progressImage.fillAmount = progress;}}#region CLR -- 进入游戏private Assembly _hotUpdateAss;async void StartGame(){LoadMetadataForAOTAssemblies();
#if UNITY_EDITOR       string hotUpdateName = "HotUpdate";_hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == hotUpdateName);
#else//_hotUpdateAss = Assembly.Load(ReadBytesFromStreamingAssets(hotUpdateName));string hotUpdateName = "HotUpdate.dll.bytes";Debug.Log($"异步加载资源Key路径:{hotUpdateName}");AsyncOperationHandle<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>(hotUpdateName);await handle.Task;Debug.Log($"异步加载资源Key状态:{handle.Status}");if (handle.Status != AsyncOperationStatus.Succeeded){Debug.LogError($"异步加载资源失败,资源Key路径:{hotUpdateName},\n异常 {handle.OperationException}");//throw new Exception($"异步加载资源失败,资源Key路径:{hotUpdateName},\n异常 {handle.OperationException}");}Debug.Log($"异步加载资源大小:{handle.Result.dataSize}");_hotUpdateAss = Assembly.Load(handle.Result.bytes);
#endifawait Task.Yield();Type entryType = _hotUpdateAss.GetType("GameEntry");entryType.GetMethod("Start").Invoke(null, null);}private void OnHotUpdateLoaded(AsyncOperationHandle<TextAsset> handle){Debug.LogError("handle.Status: " + handle.Status);if (handle.Status == AsyncOperationStatus.Succeeded){TextAsset hotUpdateAsset = handle.Result;byte[] assemblyBytes = hotUpdateAsset.bytes;// 将程序集读取到内存中Assembly hotUpdateAssembly = Assembly.Load(assemblyBytes);Type entryType = hotUpdateAssembly.GetType("GameEntry");entryType.GetMethod("Start").Invoke(null, null);}else{Debug.LogError("Failed to load HotUpdate.dll.bytes: " + handle.OperationException);}}private static List<string> AOTMetaAssemblyFiles { get; } = new List<string>(){"mscorlib.dll.bytes","System.dll.bytes","System.Core.dll.bytes","Unity.ResourceManager.dll.bytes",};/// <summary>/// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。/// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行/// </summary>private void LoadMetadataForAOTAssemblies(){// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误HomologousImageMode mode = HomologousImageMode.SuperSet;foreach (var aotDllName in AOTMetaAssemblyFiles){byte[] dllBytes = ReadBytesFromStreamingAssets(aotDllName);// 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}");}}private byte[] ReadBytesFromStreamingAssets(string abName){Debug.Log($"ReadAllBytes name: {abName}");
#if UNITY_ANDROIDAndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.PrivacyActivity");//jc.CallStatic<byte[]>("getFromAssets", name);AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");byte[] oldBytes = jo.Call<byte[]>("getFromAssets", abName);return oldBytes;#region 在Android工程中添加读取方法// import java.io.File;// import java.io.FileInputStream;// import java.io.FileNotFoundException;// import java.io.FileOutputStream;// import java.io.IOException;// import java.io.InputStream;// public byte[] getFromAssets(String fileName) {//     Log.e("****", "getFromAssets:" + fileName);//     try {//         //得到资源中的Raw数据流//         InputStream in = getResources().getAssets().open(fileName);//         //得到数据的大小//         int length = in.available();////         byte[] buffer = new byte[length];//             //读取数据//             in.read(buffer);//             //依test.txt的编码类型选择合适的编码,如果不调整会乱码//             //res = EncodingUtils.getString(buffer, "BIG5");//             //关闭//             in.close();////         return buffer;//     } catch (Exception e) {//         e.printStackTrace();//         return null;//     }// }#endregion#elsebyte[] oldBytes = File.ReadAllBytes(Application.streamingAssetsPath + "/" + abName);return oldBytes;
#endif}#endregion
}

3.2 热更代码

支持热更
要做的事入:桥接热更进入,游戏逻辑

桥接热更入口:切换到新场景后,自动启动游戏逻辑

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.SceneManagement;/// <summary>
/// 游戏入口 -- 热更桥接
/// </summary>
public static class GameEntry
{public static void Start(){Debug.Log("[GameEntry::Start] 热更完成进入游戏场景");//SceneManager.LoadScene("Scenes/MainScene");Addressables.LoadSceneAsync("MainScene");}
}

3.3 打包工具类

拓展编辑器脚本,方便后续打包逻辑:

  1. 编译目标平台热更脚本
  2. 打包AA包资源
  3. 上传资源包到OSS

注意:修改为自己的资源目标路径和远程资源路径

执行后可导出apk或者进行xCode打包

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using HybridCLR.Editor;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;namespace Editor
{public class BuidlEditorTools{[MenuItem("Build/1. 编译目标平台热更脚本", false, 301)]public static void CompileDllActiveBuildTargetCopy(){HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(EditorUserBuildSettings.activeBuildTarget);Debug.Log($"Compile Dll Active Build Target Copy Finished!");CopyDllToAssets();}// 复制热更的DLL到资源目录,以备用AB包导出//[MenuItem("Build/2. 复制热更的DLL到资源目录", false, 301)]public static void CopyDllToAssets(){BuildTarget target = EditorUserBuildSettings.activeBuildTarget;string buildDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);// 项目配置的热更dllfor (int i = 0; i < HybridCLRSettings.Instance.hotUpdateAssemblyDefinitions.Length; i++){string fileName = HybridCLRSettings.Instance.hotUpdateAssemblyDefinitions[i].name + ".dll";string sourcePath = Directory.GetFiles(buildDir).ToList().Find(hotPath => hotPath.Contains(fileName));if (string.IsNullOrEmpty(sourcePath)){Debug.Log($"热更程序集不存在: {buildDir} / {fileName}");Debug.LogError($"热更程序集不存在: {buildDir} / {fileName}");continue;}// 将程序集添加后缀 .bytes 并复制到AB包路径下string newFileName = fileName + ".bytes";//todo... 你的工程:目标目录路径 //Assets/Res/AOTAssembly/HotUpdate.dll.bytesstring targetDirectory = Application.dataPath + "/Res/AOTAssembly";Debug.Log($"目标目录路径:{targetDirectory} ");// 检查源文件是否存在if (File.Exists(sourcePath)){// 构建目标文件的完整路径string destinationPath = Path.Combine(targetDirectory, newFileName);// 检查目标目录是否存在,如果不存在则创建if (!Directory.Exists(targetDirectory)){Directory.CreateDirectory(targetDirectory);}// 如果目标文件已经存在,则删除if (File.Exists(destinationPath)){File.Delete(destinationPath);}// 将源文件复制到目标目录下,并修改名称File.Copy(sourcePath, destinationPath);// 刷新资源,使其在 Unity 编辑器中可见AssetDatabase.Refresh();Debug.Log("File copied successfully!");}else{Debug.LogError("Source file does not exist!");}}Debug.Log("复制热更的DLL到资源目录 完成!!!");}[MenuItem("Build/2. 打包AA包资源", false, 302)]public static void BuildPackageAB(){// AddressableAssetSettings.BuildPlayerContent();Debug.LogError("去用Addressable Group Build 进行打包AB或者热更AB...");}[MenuItem("Build/3. 上传资源包到OSS", false, 303)]public static async void UpLoadABOSS(){#region 注释:通过指定的名称获取组别// AddressableAssetSettings addressableSettings = AssetDatabase.LoadAssetAtPath<AddressableAssetSettings>("Assets/AddressableAssetsData/AddressableAssetSettings.asset");//// if (addressableSettings == null)// {//     Debug.LogError("Addressable Asset Settings not found.");//     return;// }//// List<AddressableAssetGroup> groups = addressableSettings.groups;// foreach (AddressableAssetGroup group in groups)// {//     string groupName = group.Name;//     string profileName = addressableSettings.activeProfileId;//     BundledAssetGroupSchema schema = group.GetSchema<BundledAssetGroupSchema>();////     if (schema != null)//     {//         // Access the configuration data from the profile//         Debug.Log($"Group: {groupName}, Profile: {profileName}");//         Debug.Log($"Remote Build Path: {schema.BuildPath.GetValue(addressableSettings) as string}");//         Debug.Log($"Remote Build Path: {schema.LoadPath.GetValue(addressableSettings) as string}");////         // Access other properties as needed//     }// }// // 获取Profile设置// AddressableAssetProfileSettings profileSettings = addressableSettings.profileSettings;//// if (profileSettings == null)// {//     Debug.LogError("Addressable Profile Settings not found.");//     return;// }//// AddressableAssetGroup remoteGroup = addressableSettings.FindGroup("Prefabs");// BundledAssetGroupSchema bundledAssetGroupSchema = remoteGroup.GetSchema<BundledAssetGroupSchema>();//// if (bundledAssetGroupSchema != null)// {//     string remoteBuildPath = bundledAssetGroupSchema.BuildPath.GetValue(addressableSettings) as string;//     string remoteLoadPath = bundledAssetGroupSchema.LoadPath.GetValue(addressableSettings) as string;//     Debug.Log($"Remote Build Path 111 : {remoteBuildPath}");//     Debug.Log($"Remote Build Path 111 : {remoteLoadPath}");// }#endregion#region 注释:微信小游戏资源地址// var config = UnityUtil.GetEditorConf();// var uploadResCDN = config.ProjectConf.CDN;// if (string.IsNullOrEmpty(config.ProjectConf.DST) || string.IsNullOrEmpty(config.ProjectConf.CDN))// {//     Debug.LogError("请先在设置项目CDN地址");//     return;// }// var fullResPath = config.ProjectConf.DST + "/webgl";#endregion// todo... 修改:打包的资源路径var fullResPath = Application.dataPath.Replace("Assets","")+ "ServerData/" + EditorUserBuildSettings.activeBuildTarget;// todo... 修改:上传CDN的资源路径var uploadResCDN = "wx/Test/"+ EditorUserBuildSettings.activeBuildTarget;Debug.Log($"开始上传 {BuildTarget.WebGL.ToString()} 平台的资源,上传目录:{fullResPath}");await UploadResToOSS.StartUploadOssClient(fullResPath, uploadResCDN);Debug.Log($"上传OSS:{EditorUserBuildSettings.activeBuildTarget.ToString()}平台的完整资源上传成功");}}
}

四,示例工程源码分享

工程目录作用:

源码文件在文章开头链接 或 点击下方卡片,回复“华佗”或“热更”获取资源包

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

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

相关文章

攻防世界——answer_to_everything-writeup

__int64 __fastcall not_the_flag(int a1) {if ( a1 42 )puts("Cipher from Bill \nSubmit without any tags\n#kdudpeh");elseputs("YOUSUCK");return 0LL; } kdudpeh这个东西&#xff0c;根据题目提示sha1加密 import hashlib flagkdudpeh x hashlib…

【Springboot】日志

1.日志的使用 日志主要用于记录程序运行的情况。我们从学习javase的时候就使用System.out.println();打印日志了&#xff0c;通过打印的日志来发现和定位问题&#xff0c;或根据日志来分析程序运行的过程。在Spring的学习中,也经常根据控制台的⽇志来分析和定位问题 。 日志除…

数据分析实战:城市房价分析

流程图&#xff1a; 1.读数据表 首先&#xff0c;读取数据集。 CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATtarget0.00632182.3100.5386.57565.24.09129615.3396.94.98240.0273107.0700.4696.42178.94.9671224217.8396.99.1421.60.0272907.0700.4697.18561.14.9671224217…

大英第四册课后翻译答案

目录 Unit 1Unit 2Unit 3Unit 4小结&#xff1a; Unit 1 中庸思想&#xff08;Doctrine of the Mean&#xff09;是儒家思想的核心内容。孔子所谓的“中”不是指“折中”&#xff0c;而是指在认识和处理客观事物时的一种“适度”和“恰如其分”的方法。孔子主张不仅要把这种思…

街机模拟游戏逆向工程(HACKROM)教程:[14]68K汇编-标志寄存器

在M68K中&#xff0c;有许多条件分支指令&#xff0c;和jmp指令一样也会修改PC达到程序跳转或分支的目的&#xff0c;不过这些会根据一些情况或状态来选择是否跳转。而在M68K中&#xff0c;有一个特别的寄存器来标记这些情况。 CCR(状态标志寄存器) CCR寄存器是用来保存一些对…

微前端-无界wujie

无界微前端方案基于 webcomponent 容器 iframe 沙箱&#xff0c;能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户的核心诉求。 主项目安装无界 vue2项目&#xff1a;npm i wujie-vue2 -S vue3项目…

77. 组合 - 力扣(LeetCode)

题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 输入示例 n 4, k 2输出示例 [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]解题思路 我们使用回溯、深度优先遍历的思想&#xff0c;我们使用一个栈 path…

SpringSecurity+OAuth2.0 搭建认证中心和资源服务中心

目录 1. OAuth2.0 简介 2. 代码搭建 2.1 认证中心&#xff08;8080端口&#xff09; 2.2 资源服务中心&#xff08;8081端口&#xff09; 3. 测试结果 1. OAuth2.0 简介 OAuth 2.0&#xff08;开放授权 2.0&#xff09;是一个开放标准&#xff0c;用于授权第三方应用程序…

Leetcode的AC指南 —— 栈与队列:225.用队列实现栈

摘要&#xff1a; **Leetcode的AC指南 —— 栈与队列&#xff1a;225.用队列实现栈 **。题目介绍&#xff1a;请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 …

简单了解AJAX

文章目录 1、什么是AJAX2、AJAX快速入门3、Axios异步框架3.1、Axios 快速入门3.2、Axios 请求方式别名 1、什么是AJAX 概念&#xff1a;AJAX(Asynchronous JavaScript And XML)&#xff1a;异步的 JavaScript 和 XML AJAX作用&#xff1a; 与服务器进行数据交换&#xff1a;通…

图卷积GCN实战基于网络结构图的网络表示学习实战

下面的是数据&#xff1a; from,to,cost 73,5,352.6 5,154,347.2 154,263,392.9 263,56,440.8 56,96,374.6 96,42,378.1 42,58,364.6 58,95,476.8 95,72,480.1 72,271,419.5 271,68,251.1 134,107,344.0 107,130,862.1 130,129,482.5 227,167,1425.7 167,298,415.7 298,209,42…

Unity3D学习之Unity基础——3D数学

文章目录 1. 前言2 Mathf和Math基础2.1 一般用于只计算一次的函数2.1.1 PI Π PI2.1.2 取绝对值 Abs2.1.3 向上取整 CeilToInt2.1.4 向下取整 FloorToInt2.1.5 钳制函数 Clamp2.1.6 获取最大值 Max2.1.7 获取最小值 Min2.1.8 一个数的n次幂 Pow2.1.9 四舍五入 RoundToInt2.1.10…

Spring Boot实现统一异常处理的技术解析

引言 在软件开发过程中&#xff0c;异常处理是非常重要的一环。一个好的异常处理机制可以帮助我们更好地定位问题&#xff0c;提高代码的可维护性和稳定性。Spring Boot作为一款轻量级的Java开发框架&#xff0c;提供了一种简单而高效的方式来实现统一异常处理。本文将详细介绍…

springboot 3 + mysql8 + flyway 数据库版本管理

1、flyway flyway官方文档地址&#xff1a;https://documentation.red-gate.com/fd 对于不怎么看文档的我来说&#xff1a; 1&#xff09;flyway是个管理数据库版本的工具&#xff0c;可以对不同环境的sql进行迁移操作。 2&#xff09;优点&#xff1a;初始化、后期数据的管理…

java使用jsch处理软链接判断是否文件夹

前言 这一次主要是碰到一个问题。因为使用jsch去读取文件的时候&#xff0c;有一些文件它是使用软链接制作的一个映射。因为这里面有一个问题。如果它是软链接你就无法判断他到底是文件。还是文件夹&#xff1f;因为他没有提供可以直接读取的方法&#xff0c;用权限信息去判断…

Nomogram文献分析:提取数据

前言 今天教大家如何分析Nomogram类型的文章&#xff0c;并使用我们开发的系统零代码提取数据。 系统地址&#xff1a;https://clinicaldata.fun/ 要分析的文章&#xff1a;https://pubmed.ncbi.nlm.nih.gov/36504658/ 。这是一篇典型的mimic-iii数据分析的套路&#xff0c;…

srm-50——攻防世界

可以知道这道题是二类题型&#xff0c;你完成某个事情给你flag 我们输入正确的东西&#xff0c;给“flag” 运行一下可以知道这些关键词 直接关键词在字符串里面 找到运行得到的东西 INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4) {HMODULE Mo…

vue.js js 雪花算法ID生成 vue.js之snowFlake算法

随着前端业务越来越复杂&#xff0c;自定义表单数据量比较大&#xff0c;每条数据的id生成则至关重要。想到前期IOS中实现的雪花算法ID&#xff0c;照着其实现JS版本&#xff0c;供大家学习参考。 一、库的建立引入 在你项目中创建一个snowFlake.js的文件&#xff1a;拷贝以下…

【2020】百度校招Java研发工程师笔试卷(第二批)算法题

贴一下我去年9月份写的博客 三道编程题&#xff0c;一道数学题&#xff0c;两道图论&#xff0c;哎嘿嘿&#xff0c;我就是不会做&#xff0c;哎嘿嘿&#xff0c;哭了。。。 一.最小值 牛牛给度度熊出了一个数学题&#xff0c;牛牛给定数字n,m,k&#xff0c;希望度度熊能找到…

Python使用graphviz绘制模块间数据流

graphviz官方参考链接&#xff1a; http://www.graphviz.org/documentation/ https://graphviz.readthedocs.io/en/stable/index.html 文章目录 需求描述环境配置实现思路代码实现 需求描述 根据各模块之间的传参关系绘制出数据流&#xff0c;如下图所示&#xff1a; 并且生成…