7.YooAsset资源加密解密

7.YooAsset资源加密解密


7.1 知识点

加密资源对象

在Editor目录下实现一个继承IEncryptionServices接口的类。

搜索TestBundleEncryption,在YooAsset的包中可以搜到这样的示例

我们修改代码只对Image目录下的资源进行加密,加密偏移设置为32

using System;
using System.IO;
using YooAsset;

/// <summary>
/// 文件流加密方式:针对资源文件的二进制数据进行逐字节异或加密
/// </summary>
public class FileStreamEncryption : IEncryptionServices
{
    /// <summary>
    /// 对传入的文件数据进行加密操作
    /// </summary>
    /// <param name="fileInfo">包含文件加载路径和包名等信息</param>
    /// <returns>返回加密结果,包括是否加密和加密后的数据</returns>
    public EncryptResult Encrypt(EncryptFileInfo fileInfo)
    {
        // 只对包名包含 "Image" 字符串的资源进行加密
        // 注意:包名默认采用的是收集器的父目录名称, 并且大写要转换为下划线_
        if (fileInfo.BundleName.Contains("_image"))
        {
            // 读取文件所有字节数据
            var fileData = File.ReadAllBytes(fileInfo.FileLoadPath);

            // 对每个字节执行异或运算,异或因子为32,实现简单加密
            for (int i = 0; i < fileData.Length; i++)
            {
                //每个字节与密钥进行按位异或(XOR)运算。
                //异或加密的特点:解密时,只需再次异或同一个密钥即可恢复原始数据。
                fileData[i] ^= BundleStream.KEY; //KEY是32
            }

            // 构建加密结果对象,标记为已加密并返回加密后的数据
            EncryptResult result = new EncryptResult();
            result.Encrypted = true;
            result.EncryptedData = fileData;
            return result;
        }
        else
        {
            // 如果包名不符合条件,不进行加密,返回未加密的结果
            EncryptResult result = new EncryptResult();
            result.Encrypted = false;
            return result;
        }
    }
}

/// <summary>
/// 文件偏移加密方式:在原始文件数据前增加一定长度的偏移数据,实现简单的加密处理
/// </summary>
public class FileOffsetEncryption : IEncryptionServices
{
    /// <summary>
    /// 对传入的文件数据进行加密处理(数据偏移)
    /// </summary>
    /// <param name="fileInfo">包含文件加载路径和包名等信息</param>
    /// <returns>返回加密结果,包括是否加密和加密后的数据</returns>
    public EncryptResult Encrypt(EncryptFileInfo fileInfo)
    {
        // 只对包名包含 "Image" 字符串的资源进行加密
        // 注意:包名默认采用的是收集器的父目录名称, 并且大写要转换为下划线_
        if (fileInfo.BundleName.Contains("_image")) //判断资源包是否包含 该字符串
        {
            // 设置偏移长度(字节数)
            int offset = 32;

            // 读取原始文件所有字节数据
            byte[] fileData = File.ReadAllBytes(fileInfo.FileLoadPath);

            // 创建一个新的字节数组,新数组长度为原数据长度加上偏移长度
            var encryptedData = new byte[fileData.Length + offset];

            // 将原始文件数据拷贝到新数组中,目标位置从偏移量开始(前面的偏移部分为空)
            //拷贝原始数据到加密数组中, 参数 源数据 源数据起始位置, 目标数组, 目标数组的起始位置, 拷贝的字节数
            //相当于前面空出了32字节, 剩下的全部往后挪 
            Buffer.BlockCopy(fileData, 0, encryptedData, offset, fileData.Length);

            // 构建加密结果对象,标记为已加密并返回带有偏移的新数据
            EncryptResult result = new EncryptResult();
            result.Encrypted = true;
            result.EncryptedData = encryptedData;
            return result;
        }
        else
        {
            // 如果包名不符合条件,不进行加密,返回未加密的结果
            EncryptResult result = new EncryptResult();
            result.Encrypted = false;
            return result;
        }
    }
}

打包时会多出两个选项,因为YooAsset会自动检查继承IEncryptionServices接口的类。现在测试使用偏移加密进行打包。

打包后把打的包拷贝到本地资源服务器

解密资源对象

同样是复制TestBundleEncryption中的资源文件解密流BundleStream,以及资源文件流解密类FileStreamDecryption和资源文件偏移解密类

using System.IO;

/// <summary>
/// 资源文件解密流:通过继承 FileStream,实现读取数据时自动对数据进行异或解密处理
/// </summary>
public class BundleStream : FileStream
{
    // 异或解密的密钥常量,值为 32
    public const byte KEY = 32;

    /// <summary>
    /// 构造函数:以指定的路径、文件模式、文件访问权限和文件共享选项初始化 BundleStream 实例
    /// </summary>
    /// <param name="path">文件路径</param>
    /// <param name="mode">文件操作模式</param>
    /// <param name="access">文件访问权限</param>
    /// <param name="share">文件共享选项</param>
    public BundleStream(string path, FileMode mode, FileAccess access, FileShare share)
        : base(path, mode, access, share)
    {
    }

    /// <summary>
    /// 构造函数:以指定的路径和文件模式初始化 BundleStream 实例,使用默认访问权限和共享选项
    /// </summary>
    /// <param name="path">文件路径</param>
    /// <param name="mode">文件操作模式</param>
    public BundleStream(string path, FileMode mode)
        : base(path, mode)
    {
    }

    /// <summary>
    /// 重写 Read 方法:读取指定数量的字节到数组中,并对读取到的每个字节进行异或解密操作
    /// </summary>
    /// <param name="array">存储读取数据的字节数组</param>
    /// <param name="offset">数组中开始写入数据的偏移位置</param>
    /// <param name="count">要读取的最大字节数</param>
    /// <returns>实际读取的字节数</returns>
    public override int Read(byte[] array, int offset, int count)
    {
        // 调用基类 FileStream 的 Read 方法,将数据读取到数组中
        var index = base.Read(array, offset, count);

        // 对数组中的所有字节进行异或解密操作(还原原始数据)
        for (int i = 0; i < array.Length; i++)
        {
            array[i] ^= KEY;
        }

        // 返回实际读取的字节数
        return index;
    }
}
using System.IO;
using UnityEngine;
using YooAsset;

/// <summary>
/// 资源文件流解密类
/// 该类实现了 IDecryptionServices 接口,通过自定义流(BundleStream)对加密的资源包进行解密操作。
/// </summary>
public class FileStreamDecryption : IDecryptionServices
{
    /// <summary>
    /// 同步方式加载解密的资源包对象
    /// 注意:加载的流对象会在资源包对象释放时自动释放,无需手动管理流生命周期
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含解密流和加载结果的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo)
    {
        // 创建自定义的 BundleStream 对象,用于解密文件数据
        BundleStream bundleStream =
            new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);

        // 构建解密结果对象
        DecryptResult decryptResult = new DecryptResult();
        // 保存解密流,便于后续自动释放
        decryptResult.ManagedStream = bundleStream;
        // 从解密流中同步加载资源包,传入 CRC 校验码和自定义的读取缓冲区大小
        decryptResult.Result =
            AssetBundle.LoadFromStream(bundleStream, fileInfo.FileLoadCRC, GetManagedReadBufferSize());
        return decryptResult;
    }

    /// <summary>
    /// 异步方式加载解密的资源包对象
    /// 注意:加载的流对象会在资源包对象释放时自动释放,无需手动管理流生命周期
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含解密流和异步加载请求的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo)
    {
        // 创建自定义的 BundleStream 对象,用于解密文件数据
        BundleStream bundleStream =
            new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);

        // 构建解密结果对象
        DecryptResult decryptResult = new DecryptResult();
        // 保存解密流,便于后续自动释放
        decryptResult.ManagedStream = bundleStream;
        // 从解密流中异步加载资源包,传入 CRC 校验码和自定义的读取缓冲区大小
        decryptResult.CreateRequest =
            AssetBundle.LoadFromStreamAsync(bundleStream, fileInfo.FileLoadCRC, GetManagedReadBufferSize());
        return decryptResult;
    }

    /// <summary>
    /// 获取解密后的字节数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的字节数组</returns>
    byte[] IDecryptionServices.ReadFileData(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取解密后的文本数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的文本字符串</returns>
    string IDecryptionServices.ReadFileText(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取用于 AssetBundle 加载时的读取缓冲区大小
    /// </summary>
    /// <returns>缓冲区大小(单位:字节)</returns>
    private static uint GetManagedReadBufferSize()
    {
        return 1024;
    }
}

/// <summary>
/// 资源文件偏移解密类
/// 该类实现了 IDecryptionServices 接口,通过文件偏移的方式加载资源包,无需对流数据进行解密处理
/// </summary>
public class FileOffsetDecryption : IDecryptionServices
{
    /// <summary>
    /// 同步方式加载资源包对象
    /// 注意:该方式直接通过文件路径加载资源包,加载时会跳过文件开头的偏移数据
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含加载结果的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo)
    {
        // 构建解密结果对象,ManagedStream 设为 null(无自定义流)
        DecryptResult decryptResult = new DecryptResult();
        decryptResult.ManagedStream = null;
        // 从文件中同步加载资源包,跳过文件偏移部分
        decryptResult.Result = AssetBundle.LoadFromFile(fileInfo.FileLoadPath, fileInfo.FileLoadCRC, GetFileOffset());
        return decryptResult;
    }

    /// <summary>
    /// 异步方式加载资源包对象
    /// 注意:该方式直接通过文件路径加载资源包,加载时会跳过文件开头的偏移数据
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含异步加载请求的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo)
    {
        // 构建解密结果对象,ManagedStream 设为 null(无自定义流)
        DecryptResult decryptResult = new DecryptResult();
        decryptResult.ManagedStream = null;
        // 从文件中异步加载资源包,跳过文件偏移部分
        decryptResult.CreateRequest =
            AssetBundle.LoadFromFileAsync(fileInfo.FileLoadPath, fileInfo.FileLoadCRC, GetFileOffset());
        return decryptResult;
    }

    /// <summary>
    /// 获取解密后的字节数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的字节数组</returns>
    byte[] IDecryptionServices.ReadFileData(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取解密后的文本数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的文本字符串</returns>
    string IDecryptionServices.ReadFileText(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取文件偏移量,表示资源包文件开头需要跳过的字节数
    /// </summary>
    /// <returns>偏移字节数</returns>
    private static ulong GetFileOffset()
    {
        return 32;
    }
}

使用解密对象解密加密的资源

在MyYooAssetTest,创建联机模式参数,并设置内置及缓存文件系统参数时传入偏移解密对象FileOffsetDecryption

// 创建联机模式参数,并设置内置及缓存文件系统参数
HostPlayModeParameters createParameters = new HostPlayModeParameters
{
    //创建内置文件系统参数
    BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(),
    //创建缓存系统参数
    CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices,new FileOffsetDecryption())
};

还是用之前的加载小鸡图片的代码,可以成功加载

//图片子对象加载
SubAssetsHandle handle =
    _package.LoadSubAssetsAsync<Sprite>("Assets/AB/Image/Farms");
await handle.Task;
var sprite = handle.GetSubAssetObject<Sprite>("hen");
new GameObject().AddComponent<SpriteRenderer>().sprite = sprite;
Debug.Log("Sprite name: " + sprite.name);

如果改了偏移量,比如加密用的32,解密用的64。加密解密的秘钥对不上,加载失败。

/// <summary>
/// 获取文件偏移量,表示资源包文件开头需要跳过的字节数
/// </summary>
/// <returns>偏移字节数</returns>
private static ulong GetFileOffset()
{
    return 64;
}


7.2 知识点代码

BundleStream.cs

using System.IO;

/// <summary>
/// 资源文件解密流:通过继承 FileStream,实现读取数据时自动对数据进行异或解密处理
/// </summary>
public class BundleStream : FileStream
{
    // 异或解密的密钥常量,值为 32
    public const byte KEY = 32;

    /// <summary>
    /// 构造函数:以指定的路径、文件模式、文件访问权限和文件共享选项初始化 BundleStream 实例
    /// </summary>
    /// <param name="path">文件路径</param>
    /// <param name="mode">文件操作模式</param>
    /// <param name="access">文件访问权限</param>
    /// <param name="share">文件共享选项</param>
    public BundleStream(string path, FileMode mode, FileAccess access, FileShare share)
        : base(path, mode, access, share)
    {
    }

    /// <summary>
    /// 构造函数:以指定的路径和文件模式初始化 BundleStream 实例,使用默认访问权限和共享选项
    /// </summary>
    /// <param name="path">文件路径</param>
    /// <param name="mode">文件操作模式</param>
    public BundleStream(string path, FileMode mode)
        : base(path, mode)
    {
    }

    /// <summary>
    /// 重写 Read 方法:读取指定数量的字节到数组中,并对读取到的每个字节进行异或解密操作
    /// </summary>
    /// <param name="array">存储读取数据的字节数组</param>
    /// <param name="offset">数组中开始写入数据的偏移位置</param>
    /// <param name="count">要读取的最大字节数</param>
    /// <returns>实际读取的字节数</returns>
    public override int Read(byte[] array, int offset, int count)
    {
        // 调用基类 FileStream 的 Read 方法,将数据读取到数组中
        var index = base.Read(array, offset, count);

        // 对数组中的所有字节进行异或解密操作(还原原始数据)
        for (int i = 0; i < array.Length; i++)
        {
            array[i] ^= KEY;
        }

        // 返回实际读取的字节数
        return index;
    }
}

FileStreamEncryption.cs

using System;
using System.IO;
using YooAsset;

/// <summary>
/// 文件流加密方式:针对资源文件的二进制数据进行逐字节异或加密
/// </summary>
public class FileStreamEncryption : IEncryptionServices
{
    /// <summary>
    /// 对传入的文件数据进行加密操作
    /// </summary>
    /// <param name="fileInfo">包含文件加载路径和包名等信息</param>
    /// <returns>返回加密结果,包括是否加密和加密后的数据</returns>
    public EncryptResult Encrypt(EncryptFileInfo fileInfo)
    {
        // 只对包名包含 "Image" 字符串的资源进行加密
        // 注意:包名默认采用的是收集器的父目录名称, 并且大写要转换为下划线_
        if (fileInfo.BundleName.Contains("_image"))
        {
            // 读取文件所有字节数据
            var fileData = File.ReadAllBytes(fileInfo.FileLoadPath);

            // 对每个字节执行异或运算,异或因子为32,实现简单加密
            for (int i = 0; i < fileData.Length; i++)
            {
                //每个字节与密钥进行按位异或(XOR)运算。
                //异或加密的特点:解密时,只需再次异或同一个密钥即可恢复原始数据。
                fileData[i] ^= BundleStream.KEY; //KEY是32
            }

            // 构建加密结果对象,标记为已加密并返回加密后的数据
            EncryptResult result = new EncryptResult();
            result.Encrypted = true;
            result.EncryptedData = fileData;
            return result;
        }
        else
        {
            // 如果包名不符合条件,不进行加密,返回未加密的结果
            EncryptResult result = new EncryptResult();
            result.Encrypted = false;
            return result;
        }
    }
}

/// <summary>
/// 文件偏移加密方式:在原始文件数据前增加一定长度的偏移数据,实现简单的加密处理
/// </summary>
public class FileOffsetEncryption : IEncryptionServices
{
    /// <summary>
    /// 对传入的文件数据进行加密处理(数据偏移)
    /// </summary>
    /// <param name="fileInfo">包含文件加载路径和包名等信息</param>
    /// <returns>返回加密结果,包括是否加密和加密后的数据</returns>
    public EncryptResult Encrypt(EncryptFileInfo fileInfo)
    {
        // 只对包名包含 "Image" 字符串的资源进行加密
        // 注意:包名默认采用的是收集器的父目录名称, 并且大写要转换为下划线_
        if (fileInfo.BundleName.Contains("_image")) //判断资源包是否包含 该字符串
        {
            // 设置偏移长度(字节数)
            int offset = 32;

            // 读取原始文件所有字节数据
            byte[] fileData = File.ReadAllBytes(fileInfo.FileLoadPath);

            // 创建一个新的字节数组,新数组长度为原数据长度加上偏移长度
            var encryptedData = new byte[fileData.Length + offset];

            // 将原始文件数据拷贝到新数组中,目标位置从偏移量开始(前面的偏移部分为空)
            //拷贝原始数据到加密数组中, 参数 源数据 源数据起始位置, 目标数组, 目标数组的起始位置, 拷贝的字节数
            //相当于前面空出了32字节, 剩下的全部往后挪 
            Buffer.BlockCopy(fileData, 0, encryptedData, offset, fileData.Length);

            // 构建加密结果对象,标记为已加密并返回带有偏移的新数据
            EncryptResult result = new EncryptResult();
            result.Encrypted = true;
            result.EncryptedData = encryptedData;
            return result;
        }
        else
        {
            // 如果包名不符合条件,不进行加密,返回未加密的结果
            EncryptResult result = new EncryptResult();
            result.Encrypted = false;
            return result;
        }
    }
}

FileStreamDecryption.cs

using System.IO;
using UnityEngine;
using YooAsset;

/// <summary>
/// 资源文件流解密类
/// 该类实现了 IDecryptionServices 接口,通过自定义流(BundleStream)对加密的资源包进行解密操作。
/// </summary>
public class FileStreamDecryption : IDecryptionServices
{
    /// <summary>
    /// 同步方式加载解密的资源包对象
    /// 注意:加载的流对象会在资源包对象释放时自动释放,无需手动管理流生命周期
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含解密流和加载结果的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo)
    {
        // 创建自定义的 BundleStream 对象,用于解密文件数据
        BundleStream bundleStream =
            new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);

        // 构建解密结果对象
        DecryptResult decryptResult = new DecryptResult();
        // 保存解密流,便于后续自动释放
        decryptResult.ManagedStream = bundleStream;
        // 从解密流中同步加载资源包,传入 CRC 校验码和自定义的读取缓冲区大小
        decryptResult.Result =
            AssetBundle.LoadFromStream(bundleStream, fileInfo.FileLoadCRC, GetManagedReadBufferSize());
        return decryptResult;
    }

    /// <summary>
    /// 异步方式加载解密的资源包对象
    /// 注意:加载的流对象会在资源包对象释放时自动释放,无需手动管理流生命周期
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含解密流和异步加载请求的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo)
    {
        // 创建自定义的 BundleStream 对象,用于解密文件数据
        BundleStream bundleStream =
            new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);

        // 构建解密结果对象
        DecryptResult decryptResult = new DecryptResult();
        // 保存解密流,便于后续自动释放
        decryptResult.ManagedStream = bundleStream;
        // 从解密流中异步加载资源包,传入 CRC 校验码和自定义的读取缓冲区大小
        decryptResult.CreateRequest =
            AssetBundle.LoadFromStreamAsync(bundleStream, fileInfo.FileLoadCRC, GetManagedReadBufferSize());
        return decryptResult;
    }

    /// <summary>
    /// 获取解密后的字节数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的字节数组</returns>
    byte[] IDecryptionServices.ReadFileData(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取解密后的文本数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的文本字符串</returns>
    string IDecryptionServices.ReadFileText(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取用于 AssetBundle 加载时的读取缓冲区大小
    /// </summary>
    /// <returns>缓冲区大小(单位:字节)</returns>
    private static uint GetManagedReadBufferSize()
    {
        return 1024;
    }
}

/// <summary>
/// 资源文件偏移解密类
/// 该类实现了 IDecryptionServices 接口,通过文件偏移的方式加载资源包,无需对流数据进行解密处理
/// </summary>
public class FileOffsetDecryption : IDecryptionServices
{
    /// <summary>
    /// 同步方式加载资源包对象
    /// 注意:该方式直接通过文件路径加载资源包,加载时会跳过文件开头的偏移数据
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含加载结果的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo)
    {
        // 构建解密结果对象,ManagedStream 设为 null(无自定义流)
        DecryptResult decryptResult = new DecryptResult();
        decryptResult.ManagedStream = null;
        // 从文件中同步加载资源包,跳过文件偏移部分
        decryptResult.Result = AssetBundle.LoadFromFile(fileInfo.FileLoadPath, fileInfo.FileLoadCRC, GetFileOffset());
        return decryptResult;
    }

    /// <summary>
    /// 异步方式加载资源包对象
    /// 注意:该方式直接通过文件路径加载资源包,加载时会跳过文件开头的偏移数据
    /// </summary>
    /// <param name="fileInfo">包含资源包加载路径、CRC校验码等信息</param>
    /// <returns>返回包含异步加载请求的 DecryptResult 对象</returns>
    DecryptResult IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo)
    {
        // 构建解密结果对象,ManagedStream 设为 null(无自定义流)
        DecryptResult decryptResult = new DecryptResult();
        decryptResult.ManagedStream = null;
        // 从文件中异步加载资源包,跳过文件偏移部分
        decryptResult.CreateRequest =
            AssetBundle.LoadFromFileAsync(fileInfo.FileLoadPath, fileInfo.FileLoadCRC, GetFileOffset());
        return decryptResult;
    }

    /// <summary>
    /// 获取解密后的字节数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的字节数组</returns>
    byte[] IDecryptionServices.ReadFileData(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取解密后的文本数据
    /// 当前未实现该方法,调用时会抛出 NotImplementedException 异常
    /// </summary>
    /// <param name="fileInfo">包含解密文件相关信息</param>
    /// <returns>解密后的文本字符串</returns>
    string IDecryptionServices.ReadFileText(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 获取文件偏移量,表示资源包文件开头需要跳过的字节数
    /// </summary>
    /// <returns>偏移字节数</returns>
    private static ulong GetFileOffset()
    {
        return 32;
    }
}

MyYooAssetTest.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
using YooAsset;

public class MyYooAssetTest : MonoBehaviour
{
    public EPlayMode playMode = EPlayMode.HostPlayMode; //运行模式
    public string packageName = "DefaultPackage"; //默认包名
    public string packageVersion = ""; //服务器资源版本号
    private ResourcePackage _package = null; //资源包对象

    //网络相关
    public string defaultHostServer = "http://127.0.0.1/CDN/PC/v1.0";
    public string fallbackHostServer = "http://127.0.0.1/CDN/PC/v1.0";

    //下载相关
    public int downloadingMaxNum = 10;
    public int filedTryAgain = 3;
    private ResourceDownloaderOperation _downloader;

    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }


    IEnumerator Start()
    {
        yield return null;

        //1.初始化YooAsset
        YooAssets.Initialize();

        // 获取或创建资源包对象
        _package = YooAssets.TryGetPackage(packageName);
        if (_package == null)
        {
            _package = YooAssets.CreatePackage(packageName);
        }

        // 创建远端服务实例,用于资源请求
        IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);

        // 创建联机模式参数,并设置内置及缓存文件系统参数
        HostPlayModeParameters createParameters = new HostPlayModeParameters
        {
            //创建内置文件系统参数
            BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(),
            //创建缓存系统参数
            CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices,new FileOffsetDecryption())
        };

        //执行异步初始化
        InitializationOperation initializationOperation =
            _package.InitializeAsync(createParameters);
        yield return initializationOperation;

        // 处理初始化结果
        if (initializationOperation.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning(initializationOperation.Error);
        }
        else
        {
            Debug.Log("初始化成功-------------------------");
        }


        //2.获取资源版本
        // 发起异步版本请求
        RequestPackageVersionOperation operation = _package.RequestPackageVersionAsync();
        yield return operation;

        // 处理版本请求结果
        if (operation.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning(operation.Error);
        }
        else
        {
            Debug.Log($"请求的版本: {operation.PackageVersion}");
            packageVersion = operation.PackageVersion;
        }


        //3.获取文件清单
        UpdatePackageManifestOperation operationManifest = _package.UpdatePackageManifestAsync(packageVersion);
        yield return operationManifest;

        // 处理文件清单结果
        if (operationManifest.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning(operationManifest.Error);
        }
        else
        {
            Debug.Log("更新资源清单成功-------------------");
        }


        //4.创建下载器
        _downloader = _package.CreateResourceDownloader(downloadingMaxNum, filedTryAgain);
        if (_downloader.TotalDownloadCount == 0)
        {
            Debug.Log("没有需要更新的文件");
            UpdateDone();
            StartCoroutine(UpdateDoneCoroutine());
            yield break;
        }
        else
        {
            int count = _downloader.TotalDownloadCount;
            long bytes = _downloader.TotalDownloadBytes;
            Debug.Log($"需要更新{count}个文件, 大小是{bytes / 1024 / 1024}MB");
        }


        //5.开始下载
        _downloader.DownloadErrorCallback = DownloadErrorCallback; // 单个文件下载失败
        _downloader.DownloadUpdateCallback = DownloadUpdateCallback; // 下载进度更新
        _downloader.BeginDownload(); //开始下载
        yield return _downloader;


        if (_downloader.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning(operationManifest.Error);
            yield break;
        }
        else
        {
            Debug.Log("下载成功-------------------");
        }


        //6.清理文件
        // 清理未使用的文件
        var operationClear = _package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles);
        // 添加清理完成回调
        operationClear.Completed += Operation_Completed;
    }


    //热更新结束
    private async void UpdateDone()
    {
        Debug.Log("热更新结束");

        //跳转场景

        //模拟资源加载
        // Sprite car = _package.LoadAssetSync<Sprite>("Assets/AB/Image/汽车.png")
        //     .AssetObject as Sprite;
        // // Sprite car = _package.LoadAssetSync<Sprite>("Assets/AB/Image/汽车")
        // //     .AssetObject as Sprite;//后缀名可以省略
        // GameObject go = new GameObject();
        // go.AddComponent<SpriteRenderer>().sprite = car;


        //异步委托资源加载
        // AssetHandle handle = _package.LoadAssetAsync<Sprite>("Assets/AB/Image/汽车");
        // handle.Completed += Handle_Completed;


        // task加载资源
        // AssetHandle handle = _package.LoadAssetAsync<Sprite>("Assets/AB/Image/汽车");
        // await handle.Task;
        //
        // Sprite car = handle.AssetObject as Sprite;
        // GameObject go = new GameObject();
        // go.AddComponent<SpriteRenderer>().sprite = car;


        //获取所有信息
        // AssetInfo[] assetInfos = package.GetAssetInfos("hot");
        // foreach (var assetInfo in assetInfos)
        // {
        //     Debug.Log(assetInfo.AssetPath);
        // }

        // 场景加载
        // string scenePath = "Assets/AB/Scene/Main.unity"; //
        // var sceneMode = UnityEngine.SceneManagement.LoadSceneMode.Single;
        // var physicsMode = LocalPhysicsMode.None;
        // bool suspendLoad = false;
        // SceneHandle handle = _package.LoadSceneAsync(scenePath, sceneMode, physicsMode, suspendLoad);
        // await handle.Task;
        // Debug.Log("Scene name is " + handle.SceneName);


        //预制体加载和创建
        // AssetHandle handle = _package.LoadAssetAsync<GameObject>
        //     ("Assets/AB/Prefab/Car.prefab");
        // await handle.Task;
        // GameObject go1 =Instantiate(handle.AssetObject as GameObject);
        // Debug.Log("Prefab go1 name:" + go1.name);
        // GameObject go2 = handle.InstantiateSync();
        // Debug.Log("Prefab go2 name:" + go2.name);


        //图片子对象加载
        SubAssetsHandle handle =
            _package.LoadSubAssetsAsync<Sprite>("Assets/AB/Image/Farms");
        await handle.Task;
        var sprite = handle.GetSubAssetObject<Sprite>("hen");
        new GameObject().AddComponent<SpriteRenderer>().sprite = sprite;
        Debug.Log("Sprite name: " + sprite.name);


        //卸载未使用的资源包
        // var operation = package.UnloadUnusedAssetsAsync();
        // await operation.Task;

        //强制卸载资源包
        // var operation = package.UnloadAllAssetsAsync();
        // await operation.Task;


        //卸载资源包中某个资源 (要未被引用的,否则无效)
        // package.TryUnloadUnusedAsset("Assets/GameRes/Panel/login.prefab");
        //
    }

    //热更新结束协程
    IEnumerator UpdateDoneCoroutine()
    {
        yield return null;
        // 协程方式加载资源
        // AssetHandle handle = _package.LoadAssetAsync<Sprite>("Assets/AB/Image/汽车");
        // yield return handle;
        // Sprite car = handle.AssetObject as Sprite;
        // GameObject go = new GameObject();
        // go.AddComponent<SpriteRenderer>().sprite = car;
    }

    // 单个文件下载失败
    public static void DownloadErrorCallback(DownloadErrorData errorData)
    {
        string fileName = errorData.FileName;
        string errorInfo = errorData.ErrorInfo;
        Debug.Log($"下载失败, 文件名: {fileName}, 错误信息: {errorInfo}");
    }

    // 下载进度更新
    public static void DownloadUpdateCallback(DownloadUpdateData updateData)
    {
        int totalDownloadCount = updateData.TotalDownloadCount;
        int currentDownloadCount = updateData.CurrentDownloadCount;
        long totalDownloadSizeBytes = updateData.TotalDownloadBytes;
        long currentDownloadSizeBytes = updateData.CurrentDownloadBytes;
        Debug.Log($"下载进度: {currentDownloadCount}/{totalDownloadCount}, " +
                  $"{currentDownloadSizeBytes / 1024}KB/{totalDownloadSizeBytes / 1024}KB");
    }

    //文件清理完成
    private void Operation_Completed(AsyncOperationBase obj)
    {
        UpdateDone();
        StartCoroutine(UpdateDoneCoroutine());
    }


    // 异步委托回调
    private void Handle_Completed(AssetHandle handle)
    {
        Sprite car = handle.AssetObject as Sprite;
        GameObject go = new GameObject();
        go.AddComponent<SpriteRenderer>().sprite = car;
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏