AssetBundle依赖关系
原地址:http://www.cnblogs.com/realtimepixels/p/3652086.html
Unity AssetBundle Dependencies
In the last few weeks I’ve spent quite a lot of time with Unity’s Asset Bundle system. Understanding how dependencies were tracked. What determines GC cleanup of assets and understanding why the editor profiler produces particular results has been a bit of a struggle. I’m lucky enough to work for a group that allows me to have access to the Unity source code however so this has probably been slightly less painful than it has been for others going down this same path.
First, I’d like to acknowledge what appears to be the most useful piece of Unity Answers information that I’ve come across, located here. This seems to explain the general mechanics of how to include dependencies using the push and then pop method in an editor script.
For my purposes, I created a test project that contained several prefabs all containing simple GUI elements. These elements all used the same fonts and referenced the same textures. This was to verify that, in the editor profiler and in instruments, assets were in fact being shared.
I’ve included my example editor script below to demonstrate some of the methods I used for packing the bundles. In my case, I am traversing all assets in my prefab folder and treating elements with the word “Global” in their name as shared. If I were to have many dependency bundles instead of the one that I have in this example I would have to sort the order in which I am packing these bundles.

using UnityEngine;
using UnityEditor; using System.IO;
using System.Collections.Generic; public class AssetBundleBuild : MonoBehaviour
{
[MenuItem("AssetBundle/Build Bundles")]
private static void BuildBundles()
{
DirectoryInfo directory = new DirectoryInfo(Application.dataPath + "/Prefabs");
FileInfo[] files = directory.GetFiles("*.prefab", SearchOption.AllDirectories); bool shouldPush; BuildPipeline.PushAssetDependencies();
foreach (FileInfo f in files)
{
shouldPush = !f.Name.Contains("Global");
if (shouldPush)
{
BuildPipeline.PushAssetDependencies();
} // Get each asset path
string path = Application.dataPath + "/Bundles/" + f.Name.Substring(0, f.Name.LastIndexOf(".")) + ".bundle";
string assetPath = f.FullName.Substring(f.FullName.IndexOf("Assets", f.FullName.Length - f.FullName.IndexOf("Assets"))); Object asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
//Debug.Log("Asset to pack " + asset + " , " + asset.name); BuildAssetBundleOptions options =
BuildAssetBundleOptions.DisableWriteTypeTree |
BuildAssetBundleOptions.CollectDependencies |
BuildAssetBundleOptions.CompleteAssets |
BuildAssetBundleOptions.DeterministicAssetBundle; if (!shouldPush)
{
Object[] d = EditorUtility.CollectDependencies(new Object[] { asset }); List<Object> dSource = new List<Object>();
List<string> dNames = new List<string>(); // In this case I'm attempting to manually collect dependencies for tracking purposes
// however this does not always seem to be necessary unless you have complex prefab heirarchies
foreach (Object o in d)
{
if (o != null && !dSource.Contains(o))
{
Debug.Log(" -- d " + o + " , " + o.name + " , " + o.GetType()); dSource.Add(o);
dNames.Add(o.name);
}
} Debug.Log("::BUILDING DEPENDENCY BUNDLE:: " + asset.name + " , " + dSource.Count);
BuildPipeline.BuildAssetBundleExplicitAssetNames(
dSource.ToArray(),
dNames.ToArray(),
path,
options,
EditorUserBuildSettings.activeBuildTarget);
}
else
{
Debug.Log("::NON DEPENDENCY:: " + asset.name);
BuildPipeline.BuildAssetBundleExplicitAssetNames(
new Object[] { asset },
new string[] { asset.name },
path,
options,
EditorUserBuildSettings.activeBuildTarget); if (shouldPush)
{
BuildPipeline.PopAssetDependencies();
}
}
} BuildPipeline.PopAssetDependencies(); Debug.Log("[AssetBundleBuild] Complete.");
}
}

Now, from the standpoint of packing shared dependencies, this seemed to work with most asset types such as textures or prefabs but when I included fonts, while they wouldn’t be duplicated across multiple bundles, I would always see two copies of my fonts. One set would be flagged as being ‘used by scripts and native code’ and the other would only be flagged as ‘used by native code’. After performing numerous tests, I discovered that upon opening the dependency bundle containing the font I was interested in, if there was a copy of the font in the project directory as well, both versions would be loaded into Resources. I haven’t been able to verify whether this happens on device as well but my inclination is that it only occurs in the editor.
The Problem of the Decompression Buffer
Another problem that became visible when working with large quantities of bundles that (based off of conversations with Unity technical representatives and the 4.2.0b3 beta client) may have changed in such a way as to render the problem less dire is that Unity, as of 4.1.5, allocates an 8mb decompression buffer per each asset bundle being opened. If there is an attempt to parallelize these requests, each request will allocate it’s own decompression buffer. This can be rather disconcerting as one can see how the allocation amount (especially on restricted memory devices) can really get a guy down.
Although 4.2.0b3 seems to be reducing the decompression buffer size down to 0.5mb per bundle the problem of parallelization still persists. The only immediate solution for individuals loading any quantity of bundles seems to be amortizing the requests in such a way as to prevent too much overlap. If someone out there has a suggestion to mitigate this problem otherwise please drop me a line mcelroy.jon[at]gmail.com
Please explain BuildPipeline.PushAssetDependencies
I've read the docs on BuildPipeline.PushAssetDependencies and gone through the example code several times and it just doesn't make sense to me. Can someone explain it in more detail and clearer language?
For instance, when you call PushAssetDependencies, what gets pushed, and onto where?
In the example code athttp://unity3d.com/support/documentation/ScriptReference/BuildPipeline.PushAssetDependencies.html what does that first call to PushAssetDependencies do? Does it cause Lerpz.unity3d, explosive.unity3d, and AdditiveScene.unity3d to depend on shared.unity3d? (i.e. if you didn't call PushAssetDependencies then lerpzuv.tif would get duplicated into each asset bundle?)
If so, why is every build after that preceded by a push and followed by a pop? I read the comment that says, "By pushing and popping around the asset bundle, this file will share resources but later asset bundles will not share assets in this resource" but I don't understand that sentence. This file will share which resources? Does share mean "make available" or "take from somewhere else"? How does the push and pop accomplish that?
Are there some unnecessary calls to push and pop in the example?
asked Mar 30 '11 at 05:05 PM
duggulous
148 ● 3 ● 4 ● 11
I'll give a shot at answering this based on my own understanding, though please chime in if I have this wrong in some way...
Push and PopAssetDependencies let you control which asset bundles are dependent on another (ie which assets can be excluded). When you call PushAssetDependencies, it instructs BuildPipeline to exclude assets in the next bundle it builds that were already included in the previous one. Dependent asset bundles have to be built in order and then loaded in order. For PushAssetDependencies to work you have to build all of your asset bundles in one script as there's no way (as far as I know) to build an asset bundle with dependencies to a pre-existing bundle.
Here's an example. Suppose you want to create 2 bundles (Bundle1 and Bundle2) and you want Bundle2 to reference assets in Bundle1, instead of including them twice separately. For the sake of visualization, let's say that there's a huge texture file "humungous.png" used by both bundles, but you really only need to have it included in Bundle1. Here's how you would build the bundles with an editor script:
var mode : int = BuildAssetBundleOptions.CollectDependencies |
BuildAssetBundleOptions.CompleteAssets |
BuildAssetBundleOptions.DeterministicAssetBundle;
var path : String = Application.dataPath+"/Bundle1.assetbundle";
BuildPipeline.PushAssetDependencies();
BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Prefabs/myObject1.prefab"), null, path, mode, BuildTarget.iPhone);
path = Application.dataPath+"/Bundle2.assetbundle";
BuildPipeline.PushAssetDependencies();
BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Prefabs/myObject2.prefab"), null, path, mode, BuildTarget.iPhone);
BuildPipeline.PopAssetDependencies();
BuildPipeline.PopAssetDependencies();
Because PushAssetDependencies was called before building Bundle2, the "humungous.png" file would be in Bundle1, and Bundle2 would only store a reference to it.
Even if you don't need to redistribute Bundle1, it needs to be rebuilt in the script to correctly prepare Bundle2's dependencies. Each call to PushAssetDependencies tells the BuildPipeline to exclude the shared dependencies of the previous asset bundle in the one created next. While PopAssetDependencies removes the exclusion. The Push and Pop commands are hierarchical, meaning you can nest dependencies. For example, consider if you had a complex project with a bundle for base assets, then other sets of bundles of shared assets for specific environments. Using Push and Pop you can create 'layers' of dependencies.
The use of DeterministicAssetBundle means that each time asset bundles are created, the same internal IDs for objects will be used. This ensures that when you build bundles, the assets are referenced the same way every time. If you don't use DeterministicAssetBundle, then there's a risk that there will be conflicts with new bundles and previously distributed bundles.
Hopefully that helps some.
We have prepared a tutorial about building asset bundles with our uTomate build tool. This tutorial also covers the asset bundle dependency stack and how Pushing and Popping from it affect what ends up in your asset bundles, so it's probably going to help you understand the PushAssetDependencies and PopAssetDependencies commands even if you don't use uTomate for building:http://docs.ancientlightstudios.com/display/UT/How+to+build+Asset+Bundles+with+uTomate
How to build asset bundles with uTomate
What are Asset Bundles and why would I want to use them?
The simplest way to answer this question is having a look at the Unity manual.
AssetBundles are a collection of assets, packaged for loading at runtime. With Asset Bundles, you can dynamically load and unload new content into your application. AssetBundles can be used to implement post-release DLC. They can also be used to reduce the amount of space on disk used by your game, when first deployed. It can also be used to add new content to an already published game.
Asset Dependencies - The Theory
When you build asset bundles you usually want them to be of minimal size. Therefore you don’t want to include all dependencies of a scene or an asset into each asset bundle, as this will duplicate all the assets and will unnecessarily add to the size of your bundles.
Unity provides a concept called Dependency Stack for this. This concept is not necessarily intuitive (see the Unity manual for the gory details) but it allows for a great deal of flexibility. The basic idea of the dependency stack is that all bundles, which are on the same or higher stack level as previously built bundles, will share assets from previously built bundles. Now that is probably somewhat abstract so let’s use an example for this.
Let’s assume you got a level called "Level1" from which you can access two versions of another level, called "Level 2a" and "Level 2b". Let’s also assume that in Level 1 you need a robot mesh and textures and Levels 2a and 2b require the same robot mesh and texture but additionally require a car model and textures. Now we want to build asset bundles for Levels 1, 2a and 2b. If you simply build asset bundles without using the dependency stack, each bundle will contain all the dependencies of the level, that is, you get three bundles looking like this:

This is very inefficient because it includes the robot model and textures into Levels 2a and 2b even though it is already known since Level 1 has been loaded before Levels 2a and 2b. To change this, you can use the dependency stack. You enable the dependency stack by callingBuildPipeline.PushAssetDependencies
before building a bundle. This will create a new level on the dependency stack.
Now each bundle that is built will share it’s dependencies with previously built bundles that are on the same or a lower stack level. Let’s see how the dependency makes the bundles smaller:

As you can see, the Level 2a bundle now no longer contains the robot model and textures but shares them with the Level 1 bundle. Unity will do no magic for you though, so you are responsible for loading these bundles in the correct order in your application. Level 2b get’s even smaller as it shares the car model and textures with Level 2a (as 2a got built before 2b on the same stack level).
Now while this yielded some nice savings in bundle space it isn’t exactly what we need. Using this structure makes Level 2b depend on Level 2a. So you will always have to load Level 2a before you can switch to Level 2b otherwise the car model and textures will be missing. Since you can go to either Level 2a or 2b from Level 1 this setup is not useable for our example.
Basically we need the car model and texture in both bundles, the one for Level 2a and the one for Level 2b. To do this, you can use BuildPipeline.PushAssetDependencies
once before you build the bundle for Level 2a. This will move this onto a higher dependency stack level. It will still share resources from the Level 1 bundle (as this is on a lower stack level). After you have built the bundle for Level 2a, you call BuildPipeline.PopAssetDependencies
. This will remove the higher dependency stack level and therefore future bundles will not share resources with the Level 2a bundle (remember, bundles only share dependencies with bundles on the same or a lower stack level).

This approach works for streamed scene asset bundles as well as for simple asset bundles and players.
How to do it with uTomate
Usually when you want to build asset bundles there is no way around scripting this. Unity provides a simple UI for building asset bundles which lets you select a few files and build an asset bundle from the selection. But this is not sufficient in two ways. First, if you happen to forget to select a certain file that should be in your asset bundle you will notice that only when you test your game online - which less than optimal. Second, the Unity UI does not support modifying the dependency stack, so building asset bundles that depend on each other is not possible using the UI. So scripting this is the way to go (and even the Unity documentation recommends that you build a function for creating your asset bundles).
With uTomate you can do all of this without scripting with the familiar Unity inspector UI. uTomate provides two actions that can interact with asset bundles - the obvious link:/utomate/documentation/actiondocs/build_asset_bundle .html[Build Asset Bundle] action and the Build Player action. Both actions provide options to modify the dependency stack:

Basically if you tick Push Dependencies uTomate will put a new level on the dependency stack (by calling PushAssetDependencies
). When you tick Pop Dependencies uTomate will drop a level from the dependency stack (by calling PopAssetDependencies
). The Pop All Dependencies flag will clear the dependency stack.
So to build the asset bundles from the example, we need three actions that build the asset bundle for the Levels 1, 2a and 2b. Let’s start with the action for Level1
:

Its building an asset bundle for the full Level1
scene and all it’s dependencies. To make sure that the dependency stack is opened, we have ticked the Push Dependencies flag. Now lets continue with the bundle for Level2a
:

To make sure that everything that is included in this bundle will not be skipped by the Level2b
bundle (since we want both bundles to be independent of each other) we open a new level on the dependency stack by ticking Push Dependencies. We also tick Pop Dependencies, to make sure that new level on the dependency stack is cleared once we finish building the bundle. Apart from this, the setup is basically the same as for Level1
, except that we use a different scene here. Finally the action for Level2b
:

Here we tick Pop Dependencies to clean up the dependency stack. This is just good practice to ensure that other actions that might come afterwards don’t accidentally reference to asset bundles to which they should not reference. The rest of the setup is the same as for Level2a
, except again we use a different scene here.
Now that our actions are set up, all we need to do is putting them into an automation plan:

I have added a few notes and arranged the actions to visualize the dependency stack levels. This is just a visual help to show how the plan is supposed to work, it is not required to format it this way to make it work. However having a good documentation on your automation plans makes it easier for everyone on the team to understand how it’s supposed to work (see also our article on building robust automation plans). |
Now with the plan set up, all you need to do is to run the plan and have your asset bundles built. Since you automated this you will get repeatable, consistent results as opposed to the error-prone manual building of asset bundles. Automating this with uTomate instead of scripting also provides a few bonuses:
You don’t need any scripting experience to do it, so every team member can understand and modify the building process.
The automation plan provides a nice visual documentation on what asset bundles you build and how they depend on each other.
If you would like to give us feedback on this tutorial shoot us a mail. We’d also like to hear from you if you would like to see a tutorial about a special topic here.
AssetBundle依赖关系的更多相关文章
- 处理Assetbundle依赖关系时想到的一道题
在处理unit3d的assetbundle依赖关系的时候,想到了一道有趣的题目: 给定一堆数据,例如{A = {1, 3, 4}, B = {3, 4}, C = {5, 6}, D = {6, 7, ...
- 【Unity3D技术文档翻译】第1.4篇 AssetBundle 依赖关系
上一章:[Unity3D技术文档翻译]第1.3篇 创建 AssetBundles 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced Develo ...
- [Unity3d][NGUI]两种思路解决AssetBundle的依赖关系.
接上文. 使用上文中的AssetBundle打包方式生成的文件包括了依赖关系中的文件. 一般的使用中并不会发现什么问题. 可是当配合NGUI的时候,使用dynamicFont时打包AssetBundl ...
- Unity -- AssetBundle(本地资源加载和加载依赖关系)
1.本地资源加载 1).建立Editor文件夹 2).建立StreamingAssets文件夹和其Windows的子文件夹 将下方第一个脚本放入Editor 里面 脚本一 资源打包AssetBund ...
- Unity进阶----AssetBundle_02(加载依赖关系及网络资源)(2018/10/31)
网络资源加载: string path ="file://"+ Application.streamingAssetsPath + "\\windows\\123&quo ...
- AssetBundle依赖
[Managing asset dependencies] 一个Asset会依赖其它Asset.可以把一个Asset所依赖的Asset也打包进自己的AssetBundle.可是多个Asset可能依赖同 ...
- Unity5-ABSystem(四):AssetBundle依赖
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/lodypig/article/detai ...
- Ambari服务依赖关系图生成脚本
1. 生成服务依赖关系 #!/usr/bin/python import sys import commands import json def genDependString(ip): url=&q ...
- 解决安装rpm包依赖关系的烦恼 - yum工具介绍及本地源配置方法
版权声明:本文发布于http://www.cnblogs.com/yumiko/,版权由Yumiko_sunny所有,欢迎转载.转载时,请在文章明显位置注明原文链接.若在未经作者同意的情况下,将本文内 ...
随机推荐
- STM32F4_TIM输出PWM波形(可调频率、占空比)
Ⅰ.概述 上一篇文章关于STM32基本的计数原理明白之后,该文章是在其基础上进行拓展,讲述关于STM32比较输出的功能,以输出PWM波形为实例来讲述. 提供实例工程中比较实用的函数:只需要调用该函数, ...
- 转:OK6410内存及启动流程
一.内存 只是从大体上介绍,并没有涉及寄存器的操作 6410的系统资源为:256MB DDR .2GB NANDFlash 如下图所示: ROM是只读存储器,RAM是随机存储器. 区别: 1.ROM( ...
- 第五节:什么导致Finalize方法被调用
Finalize方法在垃圾回收结束时被调用,下面有5种事件会导致开始垃圾回收 1.第0代已满 第0代已满,垃圾回收会自动开始.该事件是目前导致Finalize方法被调用的最常见的一种方式,因为虽 ...
- Swing基础
Swing基础 JFrame JPanel 绘图:paint 监听事件: ActionListener KeyListener Listener和Adapter 计时器:Timer Time ...
- gravity、layout_gravity、ayout_weight 区别及用法
layout_gravity 表示组件自身在父组件中的位置. gravity 表示组件的子组件在组件中的位置. weight 意思是权重.比重,即当前控件在画布中所占的空间 ...
- android 连续点击退出程序
package com.test.twiceexit; import java.util.Timer; import android.app.Activity;import android.os.Bu ...
- AppCan移动平台,开发者是这样用的……
随着生活节奏的加快,每天各种压力山大,难免产生心理问题.然而穷的都要吃土了,又不想面对陌生人,怎么办? 近日,AppCan开发者樊星阳“一夜爆红”,引起猎云网的持续关注.起因是这样的,樊星阳利用App ...
- Partition分组使用和行列转换
CREATE TABLE score ( name NVARCHAR(20), subject NVARCHAR(20), score INT ) --2.插入测试数据 INSERT INTO sco ...
- Golang与MySQL
1. 在golib下载go-sql-driver/mysql go get github.com/go-sql-driver/mysql 2. 代码引入 import ( "database ...
- sharepoint 2010 中获取system账号的真实账号
在使用sharepoint的时候有的时候需要在后台获取当前登录用户的登录名,一般的时候使用SPContext.Current.Web.CurrentUser就可以了,但是有一个特殊的用“系统账户”,获 ...