Search code examples
c#unity-game-engineassetbundleanimator

Instantiate game object loaded from assetbundle lost animation clip


I Had all my Npc's prefab packed into assetbundle with all dependencies,but when I finished loading all dependencies of one npc and instantiate it into scene, some times the animation clip on it can't be played. I print animator.GetCurrentAnimatorClipInfo(0).Length and I found it is 0.So it seems that I had lost all animation clip of this animator. The funny thing is that it doesn't happed on all npc,and I can't find any differences between them.

I used following codes to attached assetbundle name to all assets.

private void SelectionAndAssetsListChange()
{
    EditorUtility.ClearProgressBar();
    selection = Selection.objects;
    //assets = new AssetImporter[selection.Length];
    if (selection.Length > 0)
    {
        for (int i = 0; i < selection.Length; i++)
        {
            string assetPath = AssetDatabase.GetAssetPath(selection[i]);                
           
            string[] dps = AssetDatabase.GetDependencies(assetPath);
            int length = dps.Length;                 
            int index = 0;
            foreach (var dp in dps)
            {
                if (EditorUtility.DisplayCancelableProgressBar("Changing Assets's ab name ", "Changing No." + index + "/" + length, (float)index / length))
                {
                    EditorUtility.ClearProgressBar();
                    return;
                }

                if (dp.EndsWith(".cs"))
                    continue;
                AssetImporter dpAsset = AssetImporter.GetAtPath(dp);                    
                string assetNameDPs = dp.Substring("Assets".Length + 1);                                        
                assetNameDPs = assetNameDPs.Replace(Path.GetExtension(assetNameDPs), ".data");
                assetNameDPs = Path.Combine("assetbundle", assetNameDPs);
                assetNameDPs = assetNameDPs.Replace("\\", "/");
                dpAsset.assetBundleName = assetNameDPs;
                index++;
            }
            EditorUtility.ClearProgressBar();
        }
    }
}

Following Codes are used to load assetbundle ,i used 2 dictionaries TempABGO_Dict and TempDPSAB_Dict to make sure make sure it doesn't double load.

IEnumerator SingleTempGOABLoad(string abName)
{
   
        string path = Application.streamingAssetsPath + "/" + abName;

        if (!File.Exists(path))
        {
            Debug.Log("Can't find path:" + path);
            yield break;
        }

        Debug.Log(" 1 SingleTempGOABLoad :" + abName);
        
        if (TempABGO_Dict.ContainsKey(abName))
            yield break;

        string[] allDps = mainManifest.GetAllDependencies(abName);

        
        for (int i = 0; i < allDps.Length; i++)
            if (TempDPSAB_Dict.ContainsKey(allDps[i]))
            {
                TempDPSAB_Dict[allDps[i]].refCount++;
            }
            else
            {
                DpsContainer dc = new DpsContainer(null, allDps[i]);
             
                TempDPSAB_Dict[allDps[i]] = dc;
                StartCoroutine(SingleDpsABLoad(allDps[i]));
                yield return 0;
            }
                
         for (int i = 0; i < allDps.Length; i++)
        yield return new WaitUntil(() => TempDPSAB_Dict[allDps[i]].finishLoad);

        yield return 0;
        AssetBundleCreateRequest ab = AssetBundle.LoadFromFileAsync(path);

        yield return ab;
        GameObject go = null;

        AssetBundleRequest abReq = ab.assetBundle.LoadAllAssetsAsync<GameObject>();
        yield return abReq;
        go = (GameObject)abReq.asset;

        TempABGO_Dict[abName] = new TempPrefabABPair(ab.assetBundle, go, allDps);
    
}
IEnumerator SingleDpsABLoad(string abName)
{
    DpsContainer dc = TempDPSAB_Dict[abName];
        AssetBundleCreateRequest ab = null;
        ab = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + abName);
        yield return ab;
        dc.AB = ab.assetBundle;
        AssetBundleRequest rq = ab.assetBundle.LoadAllAssetsAsync();
        yield return rq;
        dc.finishLoad = true;        
}

This is what I used to build BuildAssetBundles.

BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, compressMode, EditorUserBuildSettings.activeBuildTarget);

Solution

  • First more a general note, you can use

    yield return null;

    and you can also probably reduce the amount of those a lot e.g. before and after

    AssetBundleCreateRequest ab = AssetBundle.LoadFromFileAsync(path);
    

    within the for loop etc.

    And then within in your SingleDpsABLoadyou do not wait for the

    ab = AssetBundle.LoadFromFileAsync(....);
    

    to actually finish! You just continue to immediately set

    dc.finihed = true
    

    so you might just be lucky that sometimes the rest of the redundant

    yield return 0;
    

    in the for loop etc are just delay enough to actually manage to load it in time ^^


    So you probably could rather do something like

    IEnumerator SingleDpsABLoad(DpsContainer dpsContainer)
    {
        var path = Path.Combine(Application.streamingAssetsPath, dpsContainer.abName);
    
        if (!File.Exists(path))
        {
            Debug.LogError("Can't find path:" + path);
            yield break;
        }
    
        var ab = AssetBundle.LoadFromFileAsync(path);  
    
        // actually wait until the async operation is done
        yield return ab;
    
        // can also make that one async
        yield return ab.assetBundle.LoadAllAssetsAsync();
    
        dpsContainer.AB = ab.assetBundle;
        dpsContainer.finishLoad = true;
    }
    
    IEnumerator SingleTempGOABLoad(string abName)
    {
        Debug.Log(" 1 SingleTempGOABLoad :" + abName);
    
        // personally I would start with that check here already
        var path = Path.Combine(Application.streamingAssetsPath, abName);
    
        if (!File.Exists(path))
        {
            Debug.Log("Can't find path:" + path);
            yield break;
        }
        
        if (TempABGO_Dict.ContainsKey(abName)) yield break;
    
        var allDps = mainManifest.GetAllDependencies(abName);
             
        foreach (var dps in allDps)
        {
            if (TempDPSAB_Dict.TryGetValue(dps, out var dpsContainer))
            {
                dpsContainer.refCount++;
            }
            else
            {
                dpsContainer = new DpsContainer(null, dps);
                
                TempDPSAB_Dict[dps] = dpsContainer;
                StartCoroutine(SingleDpsABLoad(dps));
            }
        }
        
        // using System.Linq
        yield return new WaitUntil(() => TempDPSAB_Dict.All(kvp => kvp.Value.finishLoad));
       
    
        var ab = AssetBundle.LoadFromFileAsync(path);
        yield return ab;
    
        var abReq = ab.assetBundle.LoadAllAssetsAsync<GameObject>();
        yield return abReq;
    
        var go = (GameObject)abReq.asset;
       
        TempABGO_Dict[abName] = new TempPrefabABPair(ab.assetBundle, go, allDps));
    }