初学者,自用笔记

热更完整链路:标记 → 构建 → 上传 → 下载 → 依赖解析 → 加载 → 卸载 → 热更

YooAsset

YooAsset官网

核心管理类

整个热更 / 资源管理的总入口,全局唯一,静态调用

功能:初始化、检查下载、下载、加载 / 卸载资源、场景管理、清理缓存

public static class YooAssetManager
{
    // 默认包名
    public const string DefaultPackageName = "DefaultPackage";
    // 资源版本
    public static string ReVersion { get; private set; }
    // 是否初始化完成
    public static bool IsInitialized { get; private set; }

    private static ResourcePackage _defaultPackage;
    private static ResourceDownloaderOperation _downloader;

    // 资源引用结构(用于引用计数)
    private class AssetRef
    {
        public AssetHandle Handle; // 资源句柄
        public int RefCount;       // 引用次数
    }

    // 资源引用池
    private static readonly Dictionary<string, AssetRef> _assetRefs = new();
    // 场景句柄池
    private static readonly List<SceneHandle> _sceneHandles = new();


    // 1. 初始化YooAsset(只初始化,不下载)
    public static async UniTask<bool> InitializeAsync(EPlayMode playMode)
    {
        if (IsInitialized) return true;

        try
        {
            // 初始化YooAsset底层
            YooAssets.Initialize();

            // 获取或创建默认包
            _defaultPackage = YooAssets.TryGetPackage(DefaultPackageName) ?? YooAssets.CreatePackage(DefaultPackageName);
            YooAssets.SetDefaultPackage(_defaultPackage);

            // 初始化对应运行模式
            var initializer = PackageInitializerRegistry.Get(playMode);
            await initializer.InitializeAsync(_defaultPackage);

            // 更新资源清单
            await UpdatePackageManifestAsync();

            IsInitialized = true;
            Debug.Log("初始化完成");
            return true;
        }
        catch
        {
            Debug.LogError("初始化失败");
            return false;
        }
    }

    // 2. 检查需要下载的资源总大小
    public static async UniTask<long> CheckDownloadSizeAsync()
    {
        if (!IsInitialized) return 0;

        // 创建下载器
        _downloader = _defaultPackage.CreateResourceDownloader(10, 3);
        await _downloader;

        // 没有可下载资源
        if (_downloader.TotalDownloadCount <= 0)
            return 0;

        return _downloader.TotalDownloadBytes;
    }

    // 3. 开始下载资源
    public static async UniTask<bool> DownloadAsync(Action<long, long> onProgress, Action<bool> onComplete, Action<string> onError)
    {
        try
        {
            // 无需要下载
            if (_downloader == null || _downloader.TotalDownloadCount <= 0)
            {
                onComplete?.Invoke(true);
                return true;
            }

            // 注册下载进度回调
            _downloader.DownloadUpdateCallback = data => onProgress?.Invoke(data.CurrentDownloadBytes, data.TotalDownloadBytes);
            // 注册错误回调
            _downloader.DownloadErrorCallback = data => onError?.Invoke(data.ErrorInfo);

            // 开始下载并等待完成
            _downloader.BeginDownload();
            await _downloader;

            bool success = _downloader.Status == EOperationStatus.Succeed;
            onComplete?.Invoke(success);
            return success;
        }
        catch (Exception e)
        {
            onError?.Invoke(e.Message);
            return false;
        }
    }

    // 加载资源(带引用计数)
    public static async UniTask<T> LoadAssetAsync<T>(string assetPath) where T : UnityEngine.Object
    {
        // 如果已加载,引用+1
        if (_assetRefs.TryGetValue(assetPath, out var assetRef))
        {
            assetRef.RefCount++;
            return assetRef.Handle.AssetObject as T;
        }

        // 异步加载资源
        var handle = _defaultPackage.LoadAssetAsync<T>(assetPath);
        await handle;

        if (handle.Status != EOperationStatus.Succeed)
        {
            Debug.LogError("加载失败:" + assetPath);
            return null;
        }

        // 存入引用池
        _assetRefs[assetPath] = new AssetRef
        {
            Handle = handle,
            RefCount = 1
        };

        return handle.AssetObject as T;
    }

    // 卸载资源(引用-1,为0时真正释放)
    public static void UnloadAsset(string assetPath)
    {
        if (!_assetRefs.TryGetValue(assetPath, out var assetRef))
            return;

        assetRef.RefCount--;

        // 没人使用了,释放资源
        if (assetRef.RefCount <= 0)
        {
            assetRef.Handle.Release();
            _assetRefs.Remove(assetPath);
        }
    }

    // 卸载所有资源
    public static void UnloadAllAssets()
    {
        foreach (var assetRef in _assetRefs.Values)
            assetRef.Handle.Release();

        _assetRefs.Clear();
        Resources.UnloadUnusedAssets();
    }

    // 加载场景
    public static async UniTask LoadSceneAsync(string sceneName, LoadSceneMode mode = LoadSceneMode.Single)
    {
        var handle = _defaultPackage.LoadSceneAsync(sceneName, mode);
        await handle;
        _sceneHandles.Add(handle);
    }

    // 卸载所有场景
    public static void UnloadAllScenes()
    {
        foreach (var handle in _sceneHandles)
            handle.Release();

        _sceneHandles.Clear();
        Resources.UnloadUnusedAssets();
    }

    // 清理所有AB缓存
    public static async UniTask ClearAllCacheAsync()
    {
        var op = _defaultPackage.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles);
        await op;
        Debug.Log(op.Status == EOperationStatus.Succeed ? "缓存清理成功" : "失败");
    }

    // 清理版本AB缓存
    public static async UniTask ClearCacheByVersionAsync(string version)
    {
        var clearOp = _defaultPackage.ClearCacheFilesAsync(version);
        await clearOp;
        Debug.Log(clearOp.Status == EOperationStatus.Succeed ? $"缓存[{version}]清理成功" : "清理失败");
    }

    // 更新资源清单
    private static async UniTask UpdatePackageManifestAsync()
    {
        var versionOp = _defaultPackage.RequestPackageVersionAsync();
        await versionOp;
        ReVersion = versionOp.PackageVersion;
        await _defaultPackage.UpdatePackageManifestAsync(ReVersion);
    }

    // 游戏退出时自动释放
    [RuntimeInitializeOnLoadMethod]
    private static void AutoCleanup()
    {
        Application.quitting += UnloadAllAssets;
    }
}

运行模式初始化器

// 模式初始化接口
public interface IPackageInitializer
{
    UniTask InitializeAsync(ResourcePackage package);
}

// 编辑器模拟模式初始化
public class EditorSimulateModeInitializer : IPackageInitializer
{
    public async UniTask InitializeAsync(ResourcePackage package)
    {
        var buildResult = EditorSimulateModeHelper.SimulateBuild(YooAssetManager.DefaultPackageName);
        var param = FileSystemParameters.CreateDefaultEditorFileSystemParameters(buildResult.PackageRootDirectory);
        await package.InitializeAsync(new EditorSimulateModeParameters { EditorFileSystemParameters = param });
    }
}

// 远端地址配置
public class RemoteServices : IRemoteServices
{
    private readonly string _main, _fallback;
    public RemoteServices(string m, string f) { _main = m; _fallback = f; }
    string IRemoteServices.GetRemoteMainURL(string file) => $"{_main}/{file}";
    string IRemoteServices.GetRemoteFallbackURL(string file) => $"{_fallback}/{file}";
}

// 真机热更模式初始化
public class HostPlayModeInitializer : IPackageInitializer
{
    public async UniTask InitializeAsync(ResourcePackage package)
    {
        // 获取自动适配平台的服务器地址
        string url = GetHotServerURL();
        var remote = new RemoteServices(url, url);

        var param = new HostPlayModeParameters
        {
            BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(),
            CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remote)
        };

        await package.InitializeAsync(param);
    }

    // 根据不同平台,自动拼接热更服务器地址
    private static string GetHotServerURL()
    {
        string host = "http://127.0.0.1";
        string appVersion = Application.version;

#if UNITY_EDITOR
        // 编辑器下根据当前构建目标平台拼接地址
        var target = UnityEditor.EditorUserBuildSettings.activeBuildTarget;
        if (target == UnityEditor.BuildTarget.Android)
            return $"{host}/Android/{appVersion}";
        if (target == UnityEditor.BuildTarget.iOS)
            return $"{host}/iOS/{appVersion}";
        return $"{host}/PC/{appVersion}";
#else
        // 真机运行时根据平台自动选择
        if (Application.platform == RuntimePlatform.Android)
            return $"{host}/Android/{appVersion}";
        if (Application.platform == RuntimePlatform.IPhonePlayer)
            return $"{host}/iOS/{appVersion}";
        return $"{host}/PC/{appVersion}";
#endif
    }
}

// 初始化器注册中心
public static class PackageInitializerRegistry
{
    private static Dictionary<EPlayMode, IPackageInitializer> _dict = new()
    {
        { EPlayMode.EditorSimulateMode, new EditorSimulateModeInitializer() },
        { EPlayMode.HostPlayMode, new HostPlayModeInitializer() }
    };
    public static IPackageInitializer Get(EPlayMode mode) => _dict[mode];
}