AssetBundle管理与加载

本系列文章由抗痘洁面泥 出品,转载请注明出处。  

Assetbundle管理与加载

最近在做项目优化的时候发现公司的项目用的还是老式的WWW去加载assetbundle资源的形式,而且是通过在两个Update里面分开加载AB和Asset的,这样虽然避免了协程的的使用,但是把一件事分开成了两件事,而且是需要每一帧都要在Update里面去检测,这样会加重Update里面的逻辑负担,所以我自己就重新用协程去写了一套资源加载。

1 对比WWW与LoadFromFile

首先WWW是一种以数据流的形式把AB加载到内存里面,他会在初始化的时候去构造网络连接对象,一种类似socket的东西,而且他会在每次初始化的时候去创建WWW对象,如果不做一个对象池去管理的话会造成对象过多而出发GC,严重的话会导致游戏卡顿,而且Unity官网上也推荐用LoadFromFIle的形式去加载AB,我去测了下,WWW与LoadFromFIle在速度上的差别,LoadFromFIle可以节省约1/3的时间,所以果断舍弃掉WWW。

2 使用协程还是使用Update去加载
使用Update去加载的话相当于是把加载Asset与AB分开来,这样我需要在不同的脚本去处理这两件事,会显得逻辑上很复杂,而且每一帧都要在Update检测,这样让逻辑很难被剥离出来。而用协程去实现的话,协程本身会存在一定的开销,而且协程开启过多也会造成GC,但是资源加载本身就不应该一次性加载过多的资源,所以协程的数量是可以控制的,那么协程的开销也可以忽略不计了,其次是协程把加载Asset与AB结合到了一起,变成了一件事,这样让逻辑更清晰,我只需要去关心这个协程是如何加载AB与Asset就行了,而且也很容易剥离开来。
3 AssetBundle的依赖以及引用计数
我们都知道在打包出来的AB中会有两个文件,一个是AB包,一个是manifest文件,而manifest文件中就记录了这个AB包所有的资源的名字等相关信息,以及AB包所引用的资源,这样的话我们就可以做一个工具用来检测所有AB包中是否存在相同的资源,我们就可以把这些重复的资源单独出来打成一个AB包,而运行时加载依赖的话,需要把 BuildPipeline.BuildAssetBundles设置的输出目录下的AB中的资源加载出来,转换为AssetBundleManifest。而AB的引用是会在加载AB的时候用到,主要是为了我们卸载资源,因为如果我们在加载完AB包之后没有做引用计数的话,如果A依赖了B,C也依赖了B,A与C同时加载到场景中,如果没有做引用计数,卸载A的时候会把B也卸载掉,那么C就会出现资源丢失的情况;此外还会出现内存泄露的情况,所以我们需要去维护一个引用计数,来保证正常的卸载。
4 设计思路
首先我们需要明白我们加载AB包需要写什么东西,需要一个AB名,一个Asset名,一个回调函数,一个引用计数,一个依赖数组,一个AB的加载状态,一个Asset加载状态,如下图所示
AssetBundle管理与加载_第1张图片AssetBundle管理与加载_第2张图片
5 AssetBundleManager
Assetbundle不能一次性加载过多,否在会造成异步加载时被Lock,用profiler工具可以看到,所以我设置了一个队列每次从队列中取5个来加载,不同的项目可以根据自己的需要去设置最大加载数量,然后5个加载完了再去检查队列中还有Request没,如果有则等这帧完了再加载。在释放资源的时候提供立即释放与延迟释放的选择。然后剩下是加载场景,因为场景的加载接口是单独的,但是加载AB部分是一样的,所以就留给大家自己去完成了,最后附上代码。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AssetBundleManager : MonoBehaviour
{
    private string mMainManifestPath
    {
        get
        {
            return string.Format("{0}/{1}", Application.streamingAssetsPath, "AssetBundlDir/AssetBundlDir");

        }
    }
    private static AssetBundleManager _instance;
    public static AssetBundleManager Instance
    {
        get
        {
            return _instance;
        }
    }
    private Dictionary mAssetBundleInfoDic = new Dictionary();
    private Queue mAssetRequestQueue = new Queue();
    private List mLoadingAssetReq = new List();
    private List mLoadingAssetFlag = new List(mMaxLoadCount);
    private List mDelayReleaseAssets = new List();
    private const int mMaxLoadCount = 5;
    private const int mMaxReleaseCount = 20;
    private const int mReleaseCountPerFrame = 5;
    private WaitForEndOfFrame mWaitFrameEnd = new WaitForEndOfFrame();
    private float mTime = 0;
    private float mTimeInterval = 50;
    public delegate void AssetReqCallBack(UnityEngine.Object rOriginalRes, string rABName, string rResName);
    public class AssetReq
    {
        public string mABName;
        public string mResName;
        public AssetReqCallBack mCallBack;
        public bool mDelay;
        public AssetReq(string rABName, string rResName, AssetReqCallBack rCallBack, bool rDelay)
        {
            mABName = rABName;
            mResName = rResName;
            mCallBack = rCallBack;
            mDelay = rDelay;
        }
    }
    public class AssetReqBaseInfo
    {
        public string mABName;
        public AssetBundle mAssetBundle;
        public string[] mDependenceAB;
        public int mRefCount;
        public int mVersion;
        public ABLoadState mABState;
        public Dictionary mAsset;
        public AssetReqBaseInfo(string rABName, string[] rDepABName, int rVersion)
        {
            mABName = rABName;
            mDependenceAB = rDepABName;
            mRefCount = 0;
            mAssetBundle = null;
            mVersion = rVersion;
            mAsset = new Dictionary();
            mABState = ABLoadState.None;
        }
    }
    public class AssetInfo
    {
        public UnityEngine.Object mAsset;
        public AssetLodState mState;
        public AssetInfo(UnityEngine.Object rAsset, AssetLodState rAssetState)
        {
            mAsset = rAsset;
            mState = rAssetState;
        }
    }
    public enum AssetLodState
    {
        LoadFailed,
        Loading,
        Loaded,
    }
    public enum ABLoadState
    {
        None,
        Loading,
        Loaded,
        Release,
    }
    private void Awake()
    {
        _instance = this;
        for (int i = 0; i < mMaxLoadCount; i++)
        {
            mLoadingAssetFlag.Add(false);
        }
    }
    private void Start()
    {
        StartCoroutine(LoadAssetBaseInfo(1));
    }
    private void Update()
    {
        if (mTime > mTimeInterval)
            DelayRelease();
        else
            mTime += Time.deltaTime;
    }
    /// 
    /// release assetbundle which refcount is zero 
    /// 
    /// 
    /// 
    /// 
    public void Release(string rABName, bool rDelay, AssetReqCallBack rCallBack)
    {
        AssetReqBaseInfo rBaseInfo = mAssetBundleInfoDic[rABName];
        rBaseInfo.mRefCount--;
        if (rDelay && rBaseInfo.mRefCount == 0 && rBaseInfo.mABState == ABLoadState.Loaded && rBaseInfo.mAssetBundle != null)
        {
            if(!mDelayReleaseAssets.Contains(rABName))
                mDelayReleaseAssets.Add(rABName);
        }
        else if (rBaseInfo.mABState == ABLoadState.Loaded && rBaseInfo.mAssetBundle != null && rBaseInfo.mRefCount == 0)
            UnloadAssetbundle(rABName);

        if (rCallBack != null)
            rCallBack(null, rABName, null);
    }

    private void DelayRelease()
    {
        if (mDelayReleaseAssets.Count > mMaxReleaseCount)
        {
            int rRemoveIndex = 0;
            for (int i = 0; i < mReleaseCountPerFrame; i++)
            {
                if (mDelayReleaseAssets.Count > 0)
                {
                    string rReleaseABName = mDelayReleaseAssets[i];
                    UnloadAssetbundle(rReleaseABName);
                    rRemoveIndex++;
                }
            }
            mDelayReleaseAssets.RemoveRange(0, rRemoveIndex);
        }
    }
    private void UnloadAssetbundle(string rABName)
    {
        AssetReqBaseInfo rBaseInfo = mAssetBundleInfoDic[rABName];
        rBaseInfo.mAsset.Clear();
        rBaseInfo.mAssetBundle.Unload(true);
        rBaseInfo.mAssetBundle = null;
        rBaseInfo.mABState = ABLoadState.None;
    }

    public void Load(string rABName, string rResName, AssetReqCallBack rCallBack)
    {
        AssetReq rReq = new global::AssetBundleManager.AssetReq(rABName, rResName, rCallBack, false);
        mAssetRequestQueue.Enqueue(rReq);
        AddToLoadList();
        LoopLoadAsset();
    }
    private void AddToLoadList()
    {
        if (mAssetRequestQueue.Count > 0 && !mLoadingAssetFlag.Contains(true))
        {
            mLoadingAssetReq.Clear();
            for (int count = 0; count < mMaxLoadCount; count++)
            {
                if (mAssetRequestQueue.Count <= 0)
                    break;
                AssetReq rAssetRequest = mAssetRequestQueue.Dequeue();
                mLoadingAssetReq.Add(rAssetRequest);
            }
        }
    }
    private void LoopLoadAsset()
    {
        for (int req_index = 0; req_index < mLoadingAssetReq.Count; req_index++)
        {
            StartCoroutine(LoadAssetBundleFromFile(mLoadingAssetReq[req_index], null, req_index));
        }
    }
    private bool CheckABName(string rABName)
    {
        if (mAssetBundleInfoDic.ContainsKey(rABName))
            return true;
        return false;
    }
    private IEnumerator LoadAssetBundleFromFile(AssetReq rAssetReq, string rDependABName, int rCurIndex)
    {
        if (rAssetReq == null)
        {
            if (!CheckABName(rDependABName))
            {
                Debug.LogError("not find assetbundle of " + rDependABName);
                yield break;
            }
            AssetReqBaseInfo rAssetReqInfo = mAssetBundleInfoDic[rDependABName];
            if (rAssetReqInfo.mABState == ABLoadState.Loaded && rAssetReqInfo.mAsset != null)
                yield break;
            while (rAssetReqInfo.mABState == ABLoadState.Loading)
            {
                //wait dependency assetbundle load finish;
                yield return null;
            }
            rAssetReqInfo.mABState = ABLoadState.Loading;
            AssetBundleCreateRequest rABCreateRequest = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/AssetBundlDir/" + rAssetReqInfo.mABName);
            yield return rABCreateRequest;
            if (!rABCreateRequest.isDone)
            {
                Debug.LogError(rAssetReqInfo.mABName + " assetbundle load faid ");
                yield break;
            }
            rAssetReqInfo.mABState = ABLoadState.Loaded;
            rAssetReqInfo.mRefCount++;
        }
        else
        {
            //load assetbundle and asset
            if (!CheckABName(rAssetReq.mABName))
            {
                Debug.LogError("not find assetbundle of " + rAssetReq.mABName);
                yield break;
            }
            mLoadingAssetFlag[rCurIndex] = true;
            AssetReqBaseInfo rAssetReqInfo = mAssetBundleInfoDic[rAssetReq.mABName];
            while (rAssetReqInfo.mABState == ABLoadState.Loading)
            {
                //wait dependency assetbundle load finish;
                yield return null;
            }
            if (rAssetReqInfo.mABState == ABLoadState.None)
            {
                //load dependency assetbundle
                rAssetReqInfo.mABState = ABLoadState.Loading;
                mDelayReleaseAssets.Remove(rAssetReq.mABName);
                for (int dep_index = 0; dep_index < rAssetReqInfo.mDependenceAB.Length; dep_index++)
                {
                    yield return StartCoroutine(LoadAssetBundleFromFile(null,  rAssetReqInfo.mDependenceAB[dep_index], -1));
                }
                AssetBundleCreateRequest rABCreateRequest = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/AssetBundlDir/"+rAssetReqInfo.mABName);
                yield return rABCreateRequest;
                if (!rABCreateRequest.isDone)
                {
                    rAssetReqInfo.mABState = ABLoadState.None;
                    Debug.LogError(rAssetReqInfo.mABName + " assetbundle load faid ");
                    yield break;
                }
                else
                {
                    rAssetReqInfo.mABState = ABLoadState.Loaded;
                    rAssetReqInfo.mAssetBundle = rABCreateRequest.assetBundle;
                }
            }
            if(rAssetReqInfo.mABState == ABLoadState.Loaded)
            {
                if (!rAssetReqInfo.mAsset.ContainsKey(rAssetReq.mResName))
                {
                    AssetInfo rAssetInfo = new AssetInfo(null, AssetLodState.Loading);
                    rAssetReqInfo.mAsset.Add(rAssetReq.mResName, rAssetInfo);
                    AssetBundleRequest rABResReq = rAssetReqInfo.mAssetBundle.LoadAssetAsync(rAssetReq.mResName);
                    yield return rABResReq;
                    if (rABResReq.isDone)
                        rAssetInfo.mAsset = rABResReq.asset;
                    else
                    {
                        Debug.LogError("fail load " + rAssetReq.mResName + " from " + rAssetReq.mABName);
                        rAssetInfo.mState = AssetLodState.LoadFailed;
                        yield break;
                    }
                }
                else
                {
                    while (rAssetReqInfo.mAsset[rAssetReq.mResName].mState == AssetLodState.Loading)
                    {
                        yield return null;
                    }
                    if (rAssetReqInfo.mAsset[rAssetReq.mResName].mState == AssetLodState.LoadFailed)
                        yield break;
                }
                rAssetReq.mCallBack(rAssetReqInfo.mAsset[rAssetReq.mResName].mAsset, rAssetReqInfo.mABName, rAssetReq.mResName);
            }
            rAssetReqInfo.mRefCount++;
            mLoadingAssetFlag[rCurIndex] = false;
            yield return mWaitFrameEnd;
            //loop load assetrequest
            if (!mLoadingAssetFlag.Contains(true))
            {
                if (mAssetRequestQueue.Count > 0)
                {
                    AddToLoadList();
                    LoopLoadAsset();
                }
            }
        }
    }
    /// 
    /// Load MainManifest File
    /// 
    /// 
    /// 
    IEnumerator LoadAssetBaseInfo(int rVersion)
    {
        AssetBundleCreateRequest rRequest = AssetBundle.LoadFromFileAsync(mMainManifestPath);
        yield return rRequest;
        if (!rRequest.isDone)
        {
            Debug.LogError("Fail load Mainmanifest file at " + mMainManifestPath);
            yield break;
        }
        else
        {
            if (rRequest.assetBundle != null)
            {
                AssetBundleRequest rABReq = rRequest.assetBundle.LoadAllAssetsAsync();
                yield return rABReq;
                if (rABReq.isDone)
                {
                    AssetBundleManifest rManifest = rABReq.asset as AssetBundleManifest;
                    string[] rAllAssetNames = rManifest.GetAllAssetBundles();
                    for (int asset_index = 0; asset_index < rAllAssetNames.Length; asset_index++)
                    {
                        string[] rDependencsName = rManifest.GetAllDependencies(rAllAssetNames[asset_index]);
                        for (int i = 0; i < rDependencsName.Length; i++)
                        {
                            Debug.LogError(rDependencsName[i]);
                        }
                        AssetReqBaseInfo rBaseInfo = new AssetReqBaseInfo(rAllAssetNames[asset_index], rDependencsName, rVersion);
                        mAssetBundleInfoDic.Add(rAllAssetNames[asset_index], rBaseInfo);
                    }
                }
            }
            else
            {
                Debug.LogError("Fail load Mainmanifest's  all assets at " + mMainManifestPath);
                yield break;
            }
        }
    }
}

你可能感兴趣的:(Unity)