Unity | 资源热更(YooAsset AB)

news2024/11/15 21:39:00

目录

一、AssetBundle 

1. 插件AssetBundle Browser 打AB包

(1)Unity(我用的版本是2020.3.8)导入AssetBundle Browser

(2)设置Prefab

(3)AssetBundleBrowser面板

2. 代码打AB包

二、YooAsset

1. 准备工作

(1)官方文档

(2) 通过Packages清单安装YooAsset

2.全局配置

3.资源配置

(1)Enable Addressable

(2)Pack Rule打包规则

4.资源构建

(1)Build Mode

(2)Encryption

 5.YooAsset运行时

(1)初始化

(2)资源系统的运行模式

(3)获取资源版本

(4)更新资源清单

(5)资源包下载

(6)资源加载

(7)完整TestLoad

6.YooAsset测试

三、参考资料


一、AssetBundle 

        AB包可以存储绝大部分Unity资源但无法直接存储C#脚本,所以代码的热更新需要使用Lua或者存储编译后的DLL文件。

        AB包不能重复进行加载,当AB包已经加载进内存后必须卸载后才能重新加载。

        多个资源分布在不同的AB包可能会出现一个预制体的贴图等部分资源不在同一个包下,直接加载会出现部分资源丢失的情况,即AB包之间是存在依赖关系的,在加载当前AB包时需要一并加载其所依赖的包。

        打包完成后,会自动生成一个主包(主包名称随平台不同而不同),主包的manifest下会存储有版本号、校验码(CRC)、所有其它包的相关信息(名称、依赖关系)。

1. 插件AssetBundle Browser 打AB包

(1)Unity(我用的版本是2020.3.8)导入AssetBundle Browser

        从github下载插件,将下载后的安装包解压到Unity工程的Packages文件夹下,如果报错,删除Tests即可。  

(2)设置Prefab

(3)AssetBundleBrowser面板

         正确获取到并安装完插件后,通过 Windows/AssetBundle Browser下打开AB包管理面板 一共有三个面板。利用AssetBundleBrowser打包时,我们用的是Build面板,设置好之后点击[build]即可。

  • Configure面板 :能查看当前AB包及其内部资源的基本情况(大小,资源,依赖情况等)。

  • Build面板:负责AssetBundle打包的相关设置。 

  • Inspect面板:用来查看已经打包后的AB包文件的一些详细情况(大小,资源路径等)。

2. 代码打AB包

        通过代码打AB包,不需要设置prefab的AssetBundle名称。

private static void BuildAssetBundles(string tempDir, string outputDir, BuildTarget target)
 {
     Debug.Log("AB outputDir:"+ outputDir);
     Directory.CreateDirectory(tempDir);
     Directory.CreateDirectory(outputDir);
     
     List<AssetBundleBuild> abs = new List<AssetBundleBuild>();
     {
         var prefabAssets = new List<string>();
         string testPrefab = $"{Application.dataPath}/Prefabs/Cube.prefab";
         prefabAssets.Add(testPrefab);
         AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
         abs.Add(new AssetBundleBuild
         {
             assetBundleName = "prefabs",
             assetNames = prefabAssets.Select(s => ToRelativeAssetPath(s)).ToArray(),
         });
     }

     BuildPipeline.BuildAssetBundles(outputDir, abs.ToArray(), BuildAssetBundleOptions.None, target);
     AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
 }

二、YooAsset

        AssetBundle 的管理和使用相对复杂,需要处理依赖关系、版本控制、加载策略等问题。因此,很多开发者会选择使用如 YooAsset 这样的资源管理框架来简化 AssetBundle 的使用,自动处理这些复杂的任务。

1. 准备工作

(1)官方文档

  • Introduce | YooAsset  

(2) 通过Packages清单安装YooAsset

        直接修改Packages文件夹下的清单文件manifest.json:

{
  "dependencies": {
    "com.tuyoogame.yooasset": "2.1.0",
    ......
  },
  "scopedRegistries": [
    {
      "name": "package.openupm.cn",
      "url": "https://package.openupm.cn",
      "scopes": [
        "com.tuyoogame.yooasset"
      ]
    }
  ]
}

         如果出现以下报错,说明Unity没有安装Windows Build Support(IL2CPP)模块,安装即可。

2.全局配置

        Project窗体内右键 -> Create -> YooAsset -> Create YooAsset Setting可创建YooAssetSettings全局配置文件,该文件需放在Resources文件夹下:

  • Manifest File Name : 清单文件名称
  • DefaultYooFolderName:Yoo文件夹名称

3.资源配置

        Project窗体内右键 -> Create -> YooAsset -> Create AssetBundle Collector Setting可创建该文件,需要注意的是一个工程只能有一个AssetBundleCollectorSetting。

重点关注Enable Addressable及Pack Rule打包规则:

(1)Enable Addressable

        启用可寻址资源定位系统。启用后加载资源时可以不写全路径,只根据资源名称即可加载:

 YooAssets.LoadSceneAsync("scene_home");

(2)Pack Rule打包规则

        规则可以自定义扩展。下面是内置规则:

  • PackSeparately 以文件路径作为资源包名,每个资源文件单独打包(比如场景文件)。
  • PackDirectory 以文件所在的文件夹路径作为资源包名,该文件夹下所有文件打进一个资源包。
  • PackTopDirectory 以收集器下顶级文件夹为资源包名,该文件夹下所有文件打进一个资源包。
  • PackCollector 以收集器路径作为资源包名,收集的所有文件打进一个资源包。
  • PackGroup 以分组名称作为资源包名,收集的所有文件打进一个资源包。
  • PackRawFile 目录下的资源文件会被处理为原生资源包。

4.资源构建

(1)Build Mode

  • ForceRebuild:强制构建模式,会删除指定构建平台下的所有构建记录,重新构建所有资源包。
  • IncrementalBuild:增量构建模式,以上一次构建结果为基础,对于发生变化的资源进行增量构建。

(2)Encryption

        加密类列表。Build时选的加密类型要和加载资源时的解密类型一致,否则加载资源时会报错。比如Build时选择的加密方式为FileOffsetEncryption,则文件解密服务接口也需要为FileOffset类型:

    {
        ...
        var initParameters = new HostPlayModeParameters();//HostPlayModeParameters继承自    InitializeParameters
        initParameters.BuildinQueryServices = new GameQueryServices();//内置资源查询服务接口
        initParameters.DecryptionServices = new FileOffsetDecryption();//如果资源包在构建的时候有加密,需要提供实现IDecryptionServices接口的实例类。
        ...
    }
    /// <summary>
    /// 资源文件偏移加载解密类
    /// </summary>
    private class FileOffsetDecryption : IDecryptionServices
    {
        /// <summary>
        /// 同步方式获取解密的资源包对象
        /// 注意:加载流对象在资源包对象释放的时候会自动释放
        /// </summary>
        AssetBundle IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo, out Stream managedStream)
        {
            managedStream = null;
            return AssetBundle.LoadFromFile(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
        }

        /// <summary>
        /// 异步方式获取解密的资源包对象
        /// 注意:加载流对象在资源包对象释放的时候会自动释放
        /// </summary>
        AssetBundleCreateRequest IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo, out Stream managedStream)
        {
            managedStream = null;
            return AssetBundle.LoadFromFileAsync(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
        }

        private static ulong GetFileOffset()
        {
            return 32;
        }

    }

 5.YooAsset运行时

        以官方提供的Space Shooter这个Demo为例,重新编写YooAsset相关的流程。先确保Demo能正常运行:

  • Space Shooter在导入完成后,打开YooAsset->AssetBundle Collector窗口。
  • 点击修复按钮,然后点击Save按钮保存配置,最后关闭窗口。
  • 找到Boot.scene场景启动游戏。

        打开Boot.cs文件,注释掉 IEnumerator Start方法,在TestLoad方法中一步一步进行YooAsset资源的加载:

    private void Start()
    {
        StartCoroutine(TestLoad());
    }
    IEnumerator TestLoad()
    {
        ...
    }

(1)初始化

    IEnumerator TestLoad()
    {
        // 初始化资源系统
        YooAssets.Initialize();

        // 创建默认的资源包
        var package = YooAssets.CreatePackage("DefaultPackage");

        // 设置该资源包为默认的资源包,可以使用YooAssets相关加载接口加载该资源包内容。
        YooAssets.SetDefaultPackage(package);
        ...    
    }

(2)资源系统的运行模式

...
if (PlayMode == EPlayMode.EditorSimulateMode)
{
    var initParameters = new EditorSimulateModeParameters();//EditorSimulateModeParameters继承自InitializeParameters
    string simulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild(EDefaultBuildPipeline.BuiltinBuildPipeline.ToString(), "DefaultPackage");
    initParameters.SimulateManifestFilePath = simulateManifestFilePath;
    yield return package.InitializeAsync(initParameters);
}
else if (PlayMode == EPlayMode.OfflinePlayMode)
{
    var initParameters = new OfflinePlayModeParameters();//OfflinePlayModeParameters继承自InitializeParameters
    initParameters.DecryptionServices = new FileOffsetDecryption();//需要补充这个
    yield return package.InitializeAsync(initParameters);
}
else if (PlayMode == EPlayMode.HostPlayMode)
{
    // 注意:GameQueryServices.cs 太空战机的脚本类,详细见StreamingAssetsHelper.cs
    string defaultHostServer = "https://static0.xesimg.com/project-yooasset/0130Offset";
    string fallbackHostServer = "https://static0.xesimg.com/project-yooasset/0130Offset";
    var initParameters = new HostPlayModeParameters();//HostPlayModeParameters继承自InitializeParameters
    initParameters.BuildinQueryServices = new GameQueryServices();//内置资源查询服务接口
    initParameters.DecryptionServices = new FileOffsetDecryption();//如果资源包在构建的时候有加密,需要提供实现IDecryptionServices接口的实例类。
    initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);//远端服务器查询服务接口
    var initOperation = package.InitializeAsync(initParameters);
    yield return initOperation;

    if (initOperation.Status == EOperationStatus.Succeed)
    {
        Debug.Log("资源包初始化成功!");
    }
    else
    {
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
    }
}
else if (PlayMode == EPlayMode.WebPlayMode)
{
    string defaultHostServer = "http://127.0.0.1/CDN/WebGL/V1.0";
    string fallbackHostServer = "http://127.0.0.1/CDN/WebGL/V1.0";
    var initParameters = new WebPlayModeParameters();//WebPlayModeParameters继承自InitializeParameters
    initParameters.BuildinQueryServices = new GameQueryServices();
    initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
    var initOperation = package.InitializeAsync(initParameters);
    yield return initOperation;

    if (initOperation.Status == EOperationStatus.Succeed)
    {
        Debug.Log("资源包初始化成功!");
    }
    else
    {
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
    }
}

补充两个类:

    /// <summary>
    /// 资源文件偏移加载解密类
    /// </summary>
    private class FileOffsetDecryption : IDecryptionServices
    {
        /// <summary>
        /// 同步方式获取解密的资源包对象
        /// 注意:加载流对象在资源包对象释放的时候会自动释放
        /// </summary>
        AssetBundle IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo, out Stream managedStream)
        {
            managedStream = null;
            return AssetBundle.LoadFromFile(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
        }

        /// <summary>
        /// 异步方式获取解密的资源包对象
        /// 注意:加载流对象在资源包对象释放的时候会自动释放
        /// </summary>
        AssetBundleCreateRequest IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo, out Stream managedStream)
        {
            managedStream = null;
            return AssetBundle.LoadFromFileAsync(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
        }

        private static ulong GetFileOffset()
        {
            return 32;
        }

    }


    /// <summary>
    /// 远端资源地址查询服务类
    /// </summary>
    private class RemoteServices : IRemoteServices
    {
        private readonly string _defaultHostServer;
        private readonly string _fallbackHostServer;

        public RemoteServices(string defaultHostServer, string fallbackHostServer)
        {
            _defaultHostServer = defaultHostServer;
            _fallbackHostServer = fallbackHostServer;
        }
        string IRemoteServices.GetRemoteMainURL(string fileName)
        {
            return $"{_defaultHostServer}/{fileName}";
        }
        string IRemoteServices.GetRemoteFallbackURL(string fileName)
        {
            return $"{_fallbackHostServer}/{fileName}";
        }
    }

(3)获取资源版本

    ...
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.UpdatePackageVersionAsync();
    yield return operation;

    if (operation.Status == EOperationStatus.Succeed)
    {
        //更新成功
        string packageVersion = operation.PackageVersion;
        Debug.Log($"Updated package Version : {packageVersion}");
    }
    else
    {
        //更新失败
        Debug.LogError(operation.Error);
    }

(4)更新资源清单

        对于联机运行模式,在获取到资源版本号之后,就可以利用UpdatePackageManifestAsync更新资源清单了:

    ...    
    bool savePackageVersion = true;
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.UpdatePackageManifestAsync(packageVersion, savePackageVersion);
    yield return operation;

    if (operation.Status == EOperationStatus.Succeed)
    {
        //更新成功
    }
    else
    {
        //更新失败
        Debug.LogError(operation.Error);
    }

(5)资源包下载

    IEnumerator Download()
    {
        int downloadingMaxNum = 10;
        int failedTryAgain = 3;
        var package = YooAssets.GetPackage("DefaultPackage");

        //创建资源下载器,下载所有资源
        var downloader = package.CreateResourceDownloader(downloadingMaxNum, failedTryAgain);

        //没有需要下载的资源
        if (downloader.TotalDownloadCount == 0)
        {
            Debug.Log("TotalDownloadCount = 0");
          
            yield break;
        }

        //需要下载的文件总数和总大小
        int totalDownloadCount = downloader.TotalDownloadCount;
        long totalDownloadBytes = downloader.TotalDownloadBytes;

        //注册回调方法
        downloader.OnDownloadErrorCallback = OnDownloadErrorFunction;
        downloader.OnDownloadProgressCallback = OnDownloadProgressUpdateFunction;
        downloader.OnDownloadOverCallback = OnDownloadOverFunction;
        downloader.OnStartDownloadFileCallback = OnStartDownloadFileFunction;

        //开启下载
        downloader.BeginDownload();
        yield return downloader;

        //检测下载结果
        if (downloader.Status == EOperationStatus.Succeed)
        {
            Debug.Log("Finish");
        }
        else
        {
            Debug.Log("DownLoad Failed");
        }
    }

    private void OnStartDownloadFileFunction(string fileName, long sizeBytes)
    {
        Debug.Log("fileName:" + fileName + ",sizeBytes:" + sizeBytes);
    }

    private void OnDownloadOverFunction(bool isSucceed)
    {
        Debug.Log("isSucceed");
    }

    private void OnDownloadProgressUpdateFunction(int totalDownloadCount, int currentDownloadCount, long totalDownloadBytes, long currentDownloadBytes)
    {
        Debug.Log("totalDownloadCount:" + totalDownloadCount + ",currentDownloadCount" + currentDownloadCount + ",totalDownloadBytes:" + totalDownloadBytes + ",currentDownloadBytes" + currentDownloadBytes);
    }

    private void OnDownloadErrorFunction(string fileName, string error)
    {
        Debug.Log("DownloadError:" + fileName + ",error:" + error);
    }

(6)资源加载

//加载场景,启用可寻址功能(Enable Addressable)后,不用写全路径,直接写资源名称即可
string location = "scene_home";
var sceneMode = UnityEngine.SceneManagement.LoadSceneMode.Single;
bool suspendLoad = false;
SceneHandle handle = package.LoadSceneAsync(location, sceneMode, suspendLoad);
yield return handle;
Debug.Log($"Scene name is {handle.SceneObject.name}");

//加载预制体
AssetHandle handle0 = package.LoadAssetAsync<GameObject>("UIHome");//不用加后缀
yield return handle0;
GameObject go = handle0.InstantiateSync();
Debug.Log($"Prefab name is {go.name}");

//加载音频
AssetHandle handle1 = package.LoadAssetAsync<AudioClip>("music_background");
yield return handle1;
AudioClip audioClip = handle1.AssetObject as AudioClip;
GameObject audio = new GameObject("AudioSource");
audio.AddComponent<AudioSource>().clip= audioClip;
audio.GetComponent<AudioSource>().Play();

(7)完整TestLoad

    IEnumerator TestLoad()
    {
        //0.别忘初始化项目中这两个事件相关的类
        // 游戏管理器 注册场景事件
        GameManager.Instance.Behaviour = this;
        // 初始化事件系统
        UniEvent.Initalize();


        //1.初始化
        Debug.Log("1. 初始化");
        // 初始化资源系统
        YooAssets.Initialize();

        // 创建默认的资源包
        var package = YooAssets.CreatePackage("DefaultPackage");

        // 设置该资源包为默认的资源包,可以使用YooAssets相关加载接口加载该资源包内容。
        YooAssets.SetDefaultPackage(package);

        //2.资源系统的运行模式
        Debug.Log("2. 资源系统的运行模式");
        if (PlayMode == EPlayMode.EditorSimulateMode)
        {
            var initParameters = new EditorSimulateModeParameters();//EditorSimulateModeParameters继承自InitializeParameters
            string simulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild(EDefaultBuildPipeline.BuiltinBuildPipeline.ToString(), "DefaultPackage");
            initParameters.SimulateManifestFilePath = simulateManifestFilePath;
            yield return package.InitializeAsync(initParameters);
        }
        else if (PlayMode == EPlayMode.OfflinePlayMode)
        {
            var initParameters = new OfflinePlayModeParameters();//OfflinePlayModeParameters继承自InitializeParameters
            initParameters.DecryptionServices = new FileOffsetDecryption();//需要补充这个
            yield return package.InitializeAsync(initParameters);
        }
        else if (PlayMode == EPlayMode.HostPlayMode)
        {
            // 注意:GameQueryServices.cs 太空战机的脚本类,详细见StreamingAssetsHelper.cs
            string defaultHostServer = "https://static0.xesimg.com/project-yooasset/0130Offset";
            string fallbackHostServer = "https://static0.xesimg.com/project-yooasset/0130Offset";
            var initParameters = new HostPlayModeParameters();//HostPlayModeParameters继承自InitializeParameters
            initParameters.BuildinQueryServices = new GameQueryServices();//内置资源查询服务接口
            initParameters.DecryptionServices = new FileOffsetDecryption();//如果资源包在构建的时候有加密,需要提供实现IDecryptionServices接口的实例类。
            initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);//远端服务器查询服务接口
            var initOperation = package.InitializeAsync(initParameters);
            yield return initOperation;

            if (initOperation.Status == EOperationStatus.Succeed)
            {
                Debug.Log("资源包初始化成功!");
            }
            else
            {
                Debug.LogError($"资源包初始化失败:{initOperation.Error}");
            }
        }
        else if (PlayMode == EPlayMode.WebPlayMode)
        {
            string defaultHostServer = "http://127.0.0.1/CDN/WebGL/V1.0";
            string fallbackHostServer = "http://127.0.0.1/CDN/WebGL/V1.0";
            var initParameters = new WebPlayModeParameters();//WebPlayModeParameters继承自InitializeParameters
            initParameters.BuildinQueryServices = new GameQueryServices();
            initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
            var initOperation = package.InitializeAsync(initParameters);
            yield return initOperation;

            if (initOperation.Status == EOperationStatus.Succeed)
            {
                Debug.Log("资源包初始化成功!");
            }
            else
            {
                Debug.LogError($"资源包初始化失败:{initOperation.Error}");
            }
        }

        //3.获取资源版本:UpdatePackageVersionAsync
        Debug.Log("3. 获取资源版本");
        var operation = package.UpdatePackageVersionAsync();
        yield return operation;

        if (operation.Status == EOperationStatus.Succeed)
        {

            string packageVersion = operation.PackageVersion;
            Debug.Log($"Updated package Version : {packageVersion}");

            //4.更新资源清单:对于联机运行模式,在获取到资源版本号之后,就可以更新资源清单了:UpdatePackageManifestAsync
            //联机运行模式
            //通过传入的清单版本,优先比对当前激活清单的版本,如果相同就直接返回成功。如果有差异就从缓存里去查找匹配的清单,如果缓存里不存在,就去远端下载并保存到沙盒里。最后加载沙盒内匹配的清单文件。
            Debug.Log("4. 更新资源清单");
            bool savePackageVersion = true;
            var operation2 = package.UpdatePackageManifestAsync(packageVersion, savePackageVersion);
            yield return operation2;

            if (operation2.Status == EOperationStatus.Succeed)
            {
                //5.资源包下载
                Debug.Log("5. 资源包下载");
                yield return Download();
                //6.加载场景,启用可寻址功能(Enable Addressable)后,不用写全路径,直接写资源名称即可
                //YooAssets.LoadSceneAsync("scene_home");
                string location = "scene_home";
                var sceneMode = UnityEngine.SceneManagement.LoadSceneMode.Single;
                bool suspendLoad = false;
                SceneHandle handle = package.LoadSceneAsync(location, sceneMode, suspendLoad);
                yield return handle;
                Debug.Log($"Scene name is {handle.SceneObject.name}");
                //7.加载预制体
                AssetHandle handle0 = package.LoadAssetAsync<GameObject>("UIHome");//不用加后缀
                yield return handle0;
                GameObject go = handle0.InstantiateSync();
                Debug.Log($"Prefab name is {go.name}");
                //8.加载音频
                AssetHandle handle1 = package.LoadAssetAsync<AudioClip>("music_background");
                yield return handle1;
                AudioClip audioClip = handle1.AssetObject as AudioClip;
                GameObject audio = new GameObject("AudioSource");
                audio.AddComponent<AudioSource>().clip= audioClip;
                audio.GetComponent<AudioSource>().Play();

                //9.资源释放
                handle0.Release();
                package.UnloadUnusedAssets();
            }
            else
            {
                //更新失败
                Debug.LogError(operation.Error);
            }
        }
        else
        {
            //更新失败
            Debug.LogError(operation.Error);
        }

    }

6.YooAsset测试

         无论是通过增量构建还是强制构建,都会生成一个以Build Version命名的文件夹,我们把这个文件夹统称为补丁包。补丁包里包含了游戏运行需要的所有资源,我们可以无脑的将补丁包内容覆盖到CDN目录下。

        Host Play Mode下,YooAsset资源加载顺序是:先检查StreamingAsset目录,再检查同Library目录的的Yoo缓存目录(_data),最后才Host服务器下载。

        Offline Play Mode下:检查StreamingAsset目录。

三、参考资料

  • 【用Unity搭建自己的游戏框架】YooAsset资源管理-01.安装和配置_哔哩哔哩_bilibili

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

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

相关文章

算法:积木游戏学习数学

一、算法描述 小华和小微一起通过玩积木游戏学习数学。 他们有很多积木&#xff0c;每个积木块上都有一个数字&#xff0c;积木块上的数字可能相同。 小华随机拿一些积木挨着排成一排&#xff0c;请小微找到这排积木中数字相同且所处位置最远的2块积木块&#xff0c;计算他们的…

【LeetCode】每日一题 2024_1_30 使循环数组所有元素相等的最少秒数(哈希、贪心、扩散)

文章目录 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01;题目&#xff1a;使循环数组所有元素相等的最少秒数题目描述代码与解题思路 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 今天的题目类型差不多是第一次见到&#xff0c;原来题目描述…

【大数据】Flink 架构(四):状态管理

《Flink 架构》系列&#xff08;已完结&#xff09;&#xff0c;共包含以下 6 篇文章&#xff1a; Flink 架构&#xff08;一&#xff09;&#xff1a;系统架构Flink 架构&#xff08;二&#xff09;&#xff1a;数据传输Flink 架构&#xff08;三&#xff09;&#xff1a;事件…

GPIO中断

1.EXTI简介 EXTI是External Interrupt的缩写&#xff0c;指外部中断。在嵌入式系统中&#xff0c;外部中断是一种用于处理外部事件的机制。当外部事件发生时&#xff08;比如按下按钮、传感器信号变化等&#xff09;&#xff0c;外部中断可以立即打断正在执行的程序&#xff0…

异常——浅析

异常 本人不才&#xff0c;对于异常只能做基本的介绍&#xff0c;无法给出自己的体会以及无法指明易错点 C异常 c语言处理异常的方式有 assert——直接中断程序 返回错误码——需要查找错误码表确定错误 这两种操作都不是很好&#xff0c;如果一个大型程序&#xff0c;为了找…

UE5动画源码剖析

重点剖析的类&#xff1a; UAnimationInstanceFAnimInstanceProxy 参考&#xff1a;https://zhuanlan.zhihu.com/p/405437842 参考&#xff1a;https://blog.csdn.net/qq_23030843/article/details/109103433 参考&#xff1a;https://ikrima.dev/ue4guide/gameplay-programm…

Opencv——霍夫变换

霍夫直线变换 霍夫直线变换(Hough Line Transform)用来做直线检测 为了加升大家对霍夫直线的理解,我在左图左上角大了一个点,然后在右图中绘制出来经过这点可能的所有直线 绘制经过某点的所有直线的示例代码如下,这个代码可以直接拷贝运行 import cv2 as cv import matplot…

自建DNS劫持服务器,纯内网劫持PS5,屏蔽更新,自动hen

背景&#xff1a;目前PS5首次折腾必须要连外网&#xff0c;还要改DNS&#xff0c;除非使用ESP8266/32&#xff0c; 本文的方法是完全不改DNS&#xff0c;不使用ESP8266,不连接外网的情况下自动折腾 能实现什么&#xff1a; 1.折腾全程不连接外网 2.完全自建hen服务器&#xff…

Vue(十九):ElementUI 扩展实现树形结构表格组件的勾父选子、半勾选、过滤出半勾选节点功能

效果 原理分析 从后端获取数据后,判断当前节点是否勾选,从而判断是否勾选子节点勾选当前节点时,子节点均勾选全勾选与半勾选与不勾选的样式处理全勾选和全取消勾选的逻辑筛选出半勾选的节点定义变量 import {computed, nextTick, reactive, ref} from vue; import {tree} f…

我该坚持纯正原创?还是随波逐流做搬运作者?

本文可能不是一个热点文章&#xff0c;甚至可能不是一个网创者该关心的文章&#xff0c;但是阿阳真心希望&#xff0c;大家可以静下心来看看。 阿阳在网赚领域混了差不多十年&#xff0c;这么多年也经历不少。我不想吹嘘什么&#xff0c;可能是因为年纪大了&#xff0c;觉得低调…

毕业设计----Ajax请求遇到的问题Uncaught TypeError: Cannot read properties of undefi

目录 问题 问题 总的来说&#xff0c;就是我在form表单新增了一个字段&#xff0c;在表单的验证规则中添加了一个名为 code 的自定义验证规则&#xff0c;但是没有提供该规则的实现代码&#xff0c;validate.js提示报错。 所以为了使验证规则生效&#xff0c;添加自定义验证方…

BODIPY FL NHS,BODIPY FL NHS 活化酯,可以实现对特定生物分子的可视化追踪和定位

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;BODIPY FL NHS ester&#xff0c;BODIPY FL NHS&#xff0c;BODIPY FL NHS 活化酯 一、基本信息 产品简介&#xff1a;BODIPY FL NHS ester, by combining BODIPY with NHS ester, this reagent is able to react w…

【CSS】常见

一. 溢出隐藏 1.1 单行文本溢出 .content{max-width:200px; /* 定义容器最大宽度 */overflow:hidden; /* 隐藏溢出的内容 */text-overflow:ellipsis; /* 溢出部分...表示 */white-space: nowrap; /* 确保文本在一行内显示 */ }问题&#xff1a;display:flex 和 ellipsis 冲…

Java学习之基础语法

Java学习之基础语法 本文主要是对于有了其他语言基础的人总结的资料&#xff0c;因此本文只写出了Java与C语言&#xff0c;C等语言的区别之处与部分重点。 1.基础语法&#xff1a; 1.1.包与类&#xff1a; 1.1.1.包&#xff1a; 在Java中&#xff0c;包&#xff08;packag…

Asp.net移除Server, X-Powered-By, 和X-AspNet-Version头

移除X-AspNet-Version很简单,只需要在Web.config中增加这个配置节: <httpRuntime enableVersionHeader"false" />移除Server在Global.asax文件总增加&#xff1a; //隐藏IIS版本 protected void Application_PreSendRequestHeaders() {HttpContext.Current.Res…

【题解 拓扑思维】 C - Building Company

题目描述: 分析&#xff1a; 对于每一个项目&#xff0c;需要满足几个条件&#xff0c;对于每一个条件&#xff0c;表示为第i项工作需要有几个人做。 这几个条件全部满足后&#xff0c;这个项目就可以收入囊下&#xff0c;同时获得新的员工 对于每一个项目的几个条件&#xf…

vxe-table3.0的表格树如何做深层查找,返回搜索关键字的树形结构

vxe-table2.0版本是提供深层查找功能的&#xff0c;因为他的数据源本身就是树形结构&#xff0c;所以深层查找查询出来也是树形结构。 但是vxe-table3.0版本为了做虚拟树功能&#xff0c;将整个数据源由树形垂直结构变成了扁平结构&#xff0c;便不提供深层查询功能&#xff0c…

代码随想录 Leetcode108. 将有序数组转换为二叉搜索树

题目&#xff1a; 代码(首刷自解 2024年1月31日&#xff09;&#xff1a; class Solution { public:TreeNode* recursion(vector<int>& nums, int left, int right) {if (left > right) return nullptr;int mid left (right-left)/2;TreeNode* node new TreeN…

Linux 入门基础知识(一)—— Linux的基本使用

Linux 入门基础知识 一、Linux的基本使用和配置1.1、终端1.2、消耗内存1.3、运行级别1.6、登录前欢迎语1.5、登录后欢迎语1.6、shell1.7、ps aux1.8、设置主机名1.9、whoami和who am i1.10、命令提示符 二、Linux执行命令的过程详解和命令类型2.1、命令执行2.2、hash缓存表2.3、…

在线摸头GIF生成系统源码

在线摸头GIF在线生成器html网页源码&#xff0c;可以点击选择文件按钮&#xff0c;或者直接将图片拖入&#xff0c;即可生成导出