Unity AssetBundle引用计数+时间缓存

该文章介绍了一个Unity开发中用于AssetBundle管理的简单方案,包括了AssetBundle的路径映射、依赖关系、加载与卸载的逻辑。通过LoadBundleSync/LoadBundleAsync方法进行资源加载,并使用IncreaseAssetBundleReferenceCount和DecreaseAssetBundleReferenceCount来跟踪引用计数,确保资源的正确释放。OnTick方法则用于更新加载状态和清理超时的资源缓存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Unity开发过程中,经常需要对各类资源进行内存维护,eg:图片,音效,视频, Prefab 等。

下面给大家提供一个简单的AssetBundle管理方案:

using Game.Utils;
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using static UnityEditor.Progress;

namespace Game.Load
{
    public class BundleCatalog
    {
        public Dictionary<string, string> mPathMapping = new Dictionary<string, string>();

        public Dictionary<string, string[]> mDepensMapping = new Dictionary<string, string[]>();

        public Dictionary<string, string> bundleName2MD5 = new Dictionary<string, string>();
    }

    public class Reference
    {
        public int count;

        public float time;

        public void AddCount() { count++; }

        public void RemoveCount() { count--; }

        public void ResetTime() { time = 0; }
    }

    public class BundleLoader
    {
        public AssetBundleCreateRequest abRequest;

        public string path;

        public bool isDone = false;

        public int cacheCount;

        public List<string> cacheDepens;

        public List<Action<AssetBundle>> actions;

        public bool GetRequestIsDone()
        {
            if (isDone) return true;
            if (abRequest == null) return false;
            return abRequest.isDone;
        }

        public bool GetBundleIsDone()
        {
            if (!GetRequestIsDone()) return false;
            return cacheCount == cacheDepens.Count;
        }
    }

    public class BundleOperation
    {
        public BundleCatalog mBundleCalalog;
        // 记录已加载的AssetBundle引用计数
        private Dictionary<string, Reference> assetBundleReferenceCounts = new Dictionary<string, Reference>();
        // 记录已加载的AssetBundle
        private Dictionary<string, AssetBundle> loadedAssetBundles = new Dictionary<string, AssetBundle>();
        // 正在加载中的AssetBundle
        private Dictionary<string, BundleLoader> AssetBundlesLoader = new Dictionary<string, BundleLoader>();
        // 缓存时间
        private int mCacheTime = 3;

        public BundleOperation()
        {
            mBundleCalalog = JsonMapper.ToObject<BundleCatalog>(
                FileTools.GetFile(
                    AssetUtils.GetRealPath(
                        GameConst.GetAssetBundlePath(GameConst.Catalog)
                        )
                    )
                );
        }

        public string[] FindDepend(string assetBundlePath, List<string> mark = null)
        {
            if (mark == null)
                mark = new List<string>();
            assetBundlePath = GetPathByCatalog(assetBundlePath);
            if (mark.Contains(assetBundlePath))
                return default;
            else
                mark.Add(assetBundlePath);
            if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
            {
                foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
                {
                    FindDepend(depend, mark);
                }
            }
            return mark.ToArray();
        }

        public void LoadBundleAsync(string assetBundlePath, Action<AssetBundle> action = null)
        {
            LoadBundleAsync(assetBundlePath, action, new List<string>());
        }

        private void LoadBundleAsync(string assetBundlePath, Action<AssetBundle> action = null, List<string> mark = null)
        {
            if (mark == null)
                mark = new List<string>();
            assetBundlePath = GetPathByCatalog(assetBundlePath);
            if (mark.Contains(assetBundlePath))
                return;
            else
                mark.Add(assetBundlePath);

            if (loadedAssetBundles.ContainsKey(assetBundlePath))
            {
                action?.Invoke(loadedAssetBundles[assetBundlePath]);
                return;
            }
            if (AssetBundlesLoader.ContainsKey(assetBundlePath))
            {
                AssetBundlesLoader[assetBundlePath].actions.Add(action);
                return;
            }
            if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
            {
                foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
                {
                    LoadBundleAsync(depend, null, mark);
                }
            }
            AssetBundleCreateRequest ab = AssetBundle.LoadFromFileAsync(AssetUtils.GetRealPath(GameConst.GetAssetBundlePath(assetBundlePath)));
            AssetBundlesLoader.Add(assetBundlePath, new BundleLoader()
            {
                cacheDepens = mBundleCalalog.mDepensMapping[assetBundlePath].ToList(),
                isDone = false,
                path = assetBundlePath,
                abRequest = ab,
                actions = new List<Action<AssetBundle>> { action },
                cacheCount = 0
            });
        }

        public AssetBundle LoadBundleSync(string assetBundlePath)
        {
            return LoadBundleSync(assetBundlePath, new List<string>());
        }

        private AssetBundle LoadBundleSync(string assetBundlePath, List<string> mark = null)
        {
            if (mark == null)
                mark = new List<string>();
            assetBundlePath = GetPathByCatalog(assetBundlePath);
            if (mark.Contains(assetBundlePath))
                return default;
            else
                mark.Add(assetBundlePath);
            if (loadedAssetBundles.ContainsKey(assetBundlePath))
            {
                return loadedAssetBundles[assetBundlePath];
            }
            if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
            {
                foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
                {
                    LoadBundleSync(depend, mark);
                }
            }
            AssetBundle ab = AssetBundle.LoadFromFile(
                AssetUtils.GetRealPath(
                    GameConst.GetAssetBundlePath(assetBundlePath)
                )
            );
            loadedAssetBundles.Add(assetBundlePath, ab);
            return loadedAssetBundles[assetBundlePath];
        }

        public string GetPathByCatalog(string assetBundlePath)
        {
            foreach (var item in mBundleCalalog.mPathMapping)
            {
                if (item.Key.Contains(assetBundlePath))
                {
                    return item.Value;
                }
            }
            return assetBundlePath;
        }

        public string GetPathByCatalogKey(string assetBundlePath)
        {
            foreach (var item in mBundleCalalog.mPathMapping)
            {
                if (item.Key.Contains(assetBundlePath))
                {
                    return item.Key;
                }
            }
            return assetBundlePath;
        }

        private void UnloadAssetBundle(string assetBundlePath)
        {
            assetBundlePath = GetPathByCatalog(assetBundlePath);
            if (loadedAssetBundles.ContainsKey(assetBundlePath))
            {
                loadedAssetBundles[assetBundlePath].Unload(true);
                loadedAssetBundles.Remove(assetBundlePath);
            }
        }

        public void IncreaseAssetBundleReferenceCount(string assetBundlePath)
        {
            IncreaseAssetBundleReferenceCount(assetBundlePath, new List<string>());
        }

        private void IncreaseAssetBundleReferenceCount(string assetBundlePath, List<string> mark = null)
        {
            if (mark == null)
                mark = new List<string>();
            assetBundlePath = GetPathByCatalog(assetBundlePath);
            if (mark.Contains(assetBundlePath))
                return;
            else
                mark.Add(assetBundlePath);
            if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
            {
                foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
                {
                    IncreaseAssetBundleReferenceCount(depend, mark);
                }
            }
            if (!assetBundleReferenceCounts.ContainsKey(assetBundlePath))
            {
                assetBundleReferenceCounts.Add(assetBundlePath, new Reference() { count = 0, time = 0 });
            }
            assetBundleReferenceCounts[assetBundlePath].AddCount();
        }

        public void DecreaseAssetBundleReferenceCount(string assetBundlePath)
        {
            DecreaseAssetBundleReferenceCount(assetBundlePath, new List<string>());
        }

        private void DecreaseAssetBundleReferenceCount(string assetBundlePath, List<string> mark = null)
        {
            if (mark == null)
                mark = new List<string>();
            assetBundlePath = GetPathByCatalog(assetBundlePath);
            if (mark.Contains(assetBundlePath))
                return;
            else
                mark.Add(assetBundlePath);
            if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
            {
                foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
                {
                    DecreaseAssetBundleReferenceCount(depend, mark);
                }
            }
            if (assetBundleReferenceCounts.ContainsKey(assetBundlePath))
            {
                assetBundleReferenceCounts[assetBundlePath].RemoveCount();
                if (assetBundleReferenceCounts[assetBundlePath].count < 0)
                {
                    Log.Error("引用计数小于0:", assetBundlePath);
                }
            }
            else
            {
                Log.Error("引用不存在:", assetBundlePath);
            }
        }

        public string GetReferenceCount(string assetBundlePath)
        {
            assetBundlePath = GetPathByCatalog(assetBundlePath);
            if (assetBundleReferenceCounts.ContainsKey(assetBundlePath))
            {
                return "引用:" + assetBundleReferenceCounts[assetBundlePath].count.ToString();
            }
            return "引用:不存在";
        }

        public void OnTick()
        {
            UpdateReference();
            UpdateLoader();
        }

        public void UpdateLoader()
        {
            foreach (var item in AssetBundlesLoader)
            {
                if (item.Value.GetRequestIsDone() && !loadedAssetBundles.ContainsKey(item.Key))
                {
                    foreach (var item2 in AssetBundlesLoader)
                    {
                        if (item2.Value.cacheDepens.Contains(item.Key))
                        {
                            item2.Value.cacheCount++;
                        }
                    }
                }
            }
            List<string> succKey = new List<string>();
            foreach (var item in AssetBundlesLoader)
            {
                if (item.Value.GetBundleIsDone())
                {
                    succKey.Add(item.Key);
                }
            }

            foreach (var item in succKey)
            {
                loadedAssetBundles.Add(item, AssetBundlesLoader[item].abRequest.assetBundle);
                foreach (var action in AssetBundlesLoader[item].actions)
                {
                    action?.Invoke(AssetBundlesLoader[item].abRequest.assetBundle);
                }
                AssetBundlesLoader.Remove(item);
            }
        }

        public void UpdateReference()
        {
            List<string> overtime = new List<string>();
            foreach (var item in assetBundleReferenceCounts)
            {
                if (item.Value.count <= 0)
                {
                    item.Value.time += Time.deltaTime;
                    if (item.Value.time > mCacheTime)
                    {
                        overtime.Add(item.Key);
                    }
                }
                else
                {
                    item.Value.ResetTime();
                }
            }
            foreach (var item in overtime)
            {
                assetBundleReferenceCounts.Remove(item);
                UnloadAssetBundle(item);
            }
        }
    }
}


 简单的说:

在外部调用 LoadBundleSync/LoadBundleAsync 后进行IncreaseAssetBundleReferenceCount

在Destroy时进行DecreaseAssetBundleReferenceCount

OnTick 刷新检测异步加载 以及释放缓存

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值