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.
answered May 10 '11 at 02:31 AM
Steven Walker
1.1k ● 30 ● 34 ● 50
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
answered Apr 19 '13 at 07:03 AM
kork
101 ● 1 ● 2
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所有,欢迎转载.转载时,请在文章明显位置注明原文链接.若在未经作者同意的情况下,将本文内 ...
随机推荐
- SQLite中命令行程序(CLP)的使用
SQLite CLP是使用和管理SQLite数据库最常用的工具.它在所有平台上的操作方式相同.CLP其实是两个程序,它可以运行在Shell模式下以交互的方式执行查询操作,也可以运行在命令行模式下完成各 ...
- ruby 笔记
symbol 不能有- 'data-turbolinks-track' => true stop rails –s kill -INT $(cat tmp/pids/server.pid) cl ...
- matlab封装DLL混合编程总结
最近做了个项目要用到matlab做些算法处理,然后用.net项目调用这个类,我把这个matlab封装dll总结了下如下: matlab是商业数学软件,优势是在算法开发上面有很强的功能,提供了很多数学算 ...
- 仿SDWebImage
仿SDWebImage 目标:模拟 SDWebImage 的实现 说明:整体代码与之前博客上的演练代码的基本一致,只是编写顺序会有变化! 在模仿 SDWebImage 之前,首先需要补充一个知识点:N ...
- Javascript是一个事件驱动语言
面向原型这种说法我没在网上找到
- 在ASP.NET中实现OAuth2.0(二)之打造自己的API安全策略
1.场景介绍 公司开发了一款APP产品,前期提供的api接口都是裸奔状态 举个例子:想要获取某一个用户的数据,只需要传递该用户的ID就可以拿走数据(说多了都是泪) 现在想给这些接口穿个衣服,加个壳(对 ...
- As.net WebAPI CORS, 开启跨源访问,解决错误No 'Access-Control-Allow-Origin' header is present on the requested resource
默认情况下ajax请求是有同源策略,限制了不同域请求的响应. 例子:http://localhost:23160/HtmlPage.html 请求不同源API http://localhost:228 ...
- C++变量的存储类别与作用域
总结一下C++中变量的存储类别以及变量的作用域. (1)标示符的存储类别决定了标示符在内存中存在的时间(我们可以理解标示符就是确定一个变量的符号,也就是我们所说的变量名) 二:存储类别 (1)静态存储 ...
- OC-通讯录
要求描述:用OC语言完成简易通讯录(实现增删改查)功能.(注:使用MRC) 1.创建AddressBook类. 1)使用字典作为容器,字典的Key值为分组名(姓名首字母),value值为数组,数组中存 ...
- vi/vim编辑器
vi / vim是Unix / Linux上最常用的文本编辑器而且功能非常强大.