Assetbundle is the unity of the recommended resource management, the official list such as hot update, compression, flexibility and so on, but the Assetbundle pit is very deep, many hidden details so that you need to use very carefully, carelessly will fall into the deep pit, packing is not planned well, 20MB of resources "compressed" to 30MB, or a large number of packages lead to packaging and loading of various inefficiencies, or inexplicably lost the association, or memory burst, as well as a variety of loading failures, online research on a lot of assetbundle articles, but after each read, still have a lot of questions, So only through the practice to answer the question in mind, in order to ensure the accuracy of the results, the following test under the editor, Windows,ios under the test comparison.
First Why do you choose Assetbundle, even if he has thousands of benefits, but the general choice Assetbundle reason is, to do hot update, dynamic update game resources, Or do you have more resources to resource than its limits (2GB or 4GB?). If you do not have such a demand, then suggest you do not use this bad thing, annoying ~ ~
When you choose Assetbundle, and before I start spraying assetbundle, We need to make a simple introduction to the workflow of Assetbundle:
Assetbundle can be divided into packaged assetbundle and Assetbundle
Packaging requires that you write some simple code under Unityeditor to take out the resources you want to package, and then call the packaging method to package the
Object obj = Assetdatabase.loadmainassetatpath (" Assets/test.png ");
Buildpipeline.buildassetbundle (obj, null,
Application.streamingassetspath + "/test.assetbundle",
buildassetbundleoptions.collectdependencies | Buildassetbundleoptions.completeassets
| Buildassetbundleoptions.deterministicassetbundle, buildtarget.standalonewindows);
In use, we need to load bundle with WWW, and then load resources with bundle.
www w = new www ("file://" + Application.streamingassetspath + "/test.assetbundle");
Mytexture = W.assetbundle.load ("Test");
"One, pack"
Next, let's take a look at packing:
1. Collection of resources
We can automate packaging by traversing the directory before packing. You can selectively package some directories into a bundle, which can also manage resources with a variety of configuration files, or you can use directory specifications to manage my side is a large classification of resources with a directory specification, divided into public and game , several large modules outside the game, a simple naming convention is then used to guide packaging, such as using OBO (OneByOne) as the directory suffix to guide all resources in the directory to be packaged independently, by default to a package, with a base prefix to indicate that this belongs to the public package, and other directories under the sibling directory to depend on it
Using the GetFiles and getdirectories of your directory makes it easy to get to the directory and the files in the directory
Directory.GetFiles ("Assets/mydirs", "*.*", searchoption.topdirectoryonly);
Directory.getdirectories (Application.datapath + "/resources/game", "*.*", searchoption.alldirectories);
2. Resource Reading
The resource path that the GetFiles collects can be loaded, before loading to determine if the suffix is. Meta, if so, do not remove the resource, then convert the path to the relative path at the beginning of assets, and then load the resource
String NewPath = "Assets" + mypath. Replace (Application.datapath, "");
NewPath = Newpath.replace ("\", "/");
Object obj = Assetdatabase.loadmainassetatpath (NewPath);
3. Packing function
We call Buildpipeline.buildassetbundle to package:
Buildpipeline.buildassetbundle has 5 parameters, the first is the primary resource, the second is the resource array, the two parameters must have a NOT NULL, if the master resource exists in the resource array, there is no relationship, if the primary resource is set, you can pass the Bundle.mainasse T to use it directly
The third parameter is the path, and generally we set the target path and bundle name of the Application.streamingassetspath + bundle
The fourth parameter has four options, Buildassetbundleoptions.collectdependencies will look for dependencies, Buildassetbundleoptions.completeassets will force the entire resource to be included, BuildAssetBundleOptions . Deterministicassetbundle makes sure that a unique ID is generated, which is useful when packing dependencies, and that other options don't make sense
The fifth parameter is the platform, in the Android, IOS,PC, we need to pass the different platform identity, to play different platform to apply the package, note that the Windows platform for the package, can not be used for IOS
You should select the corresponding platform before you pack the corresponding package
4. Packaging decisions
In packaging, we need to balance the size and quantity of the package, all the resources into a package, a resource to play a package, are more extreme practices, their problems are obvious, more often we need to flexibly combine them
The disadvantage of playing a package is that the package is loaded, and what we don't need is loaded in, takes up extra memory, and is not conducive to hot update
The disadvantage of playing multiple packages is that it is easy to create redundancy, first affecting the packet read speed, and then the content between the packages may be duplicated, and too many packages are not good for resource management
Which modules are in a package and which modules are in multiple packages need to be based on actual situations, for example, each monster in the game needs to be made into a package, because each monster is independent, such as the game's underlying UI, which can be made into a package because they appear on every interface
PS. To be packaged into a binary file in Assetbundle, the suffix of the filename must be ". Bytes"
"Two, unpack."
Unpack the first step is to load the bundle, a new www to upload a URL to load bundle, we can pass in a bundle URL, download from the network, can also be passed in the local package path, generally we use file://start +bundle path, To specify the local bundle, use the http://or https://+bundle URL to specify the network bundle
String. Format ("File://{0}/{1}", Application.streamingassetspath, Bundlepath);
In Android, the path is not the same, if the Android platform's local bundle, the need to use jar:file://as a prefix, and need to set a special path to load
String. Format ("Jar:file://{0}!/assets/{1}", Application.datapath, Bundlepath);
After passing in the specified URL, we can use the WWW to load the bundle, loading the bundle takes some time, so we usually load the bundle in collaboration, if the load fails, you can get the reason of failure in Www.error
IEnumerator loadbundle (string url)
{
www www = = new www (URL);
yield return www;
if (www.error!= null)
{
Debug.logerror ("Load Bundle faile" + URL + "Error is" + www.error);
Yield break;
}
Do something ...
}
In addition to creating a WWW, there is another way to load bundle,www. Loadfromcacheordownload (URL, version), the use of this function on the memory is much smaller, but each repackaging will need to update the version number of the bundle (the second parameter version), or you may use the previous package, Rather than the latest package, Loadfromcacheordownload will extract bundle from the network or program resources to a disk cache, which can generally be understood to extract to the local disk, and use the extracted resources directly if the local disk already exists. For Assetbundle all memory footprint, there is a section that describes it specifically
Loadfromcacheordownload records the use of all bundle and, when appropriate, deletes the most recently infrequently used resource bundle, which allows for two different version numbers with the same name as the resource bundle, which means that after you update the resource bundle, if you do not update the version number in the code , you might get an older version of the resource bundle, resulting in some other bugs. In addition, when your disk space is low (hard drive burst), Loadfromcacheordownload is just an ordinary new www! The following section on memory introduction will also introduce the exclamation point.
After we get the bundle, we need the resources in the load, the Load,loadall and Loadasyn to choose from.
Load all objects into a resource
Object[] Objs = bundle. Loadall ();
To load a resource named obj
Object obj = bundle. Load ("obj");
Asynchronously load a resource named Resname with type
Assetbundlerequest res = bundle. LoadAsync (resname, type);
Yield return res;
var obj = Res.asset;
We often make a variety of game objects into a prefab, then prefab will also be a common resource in our bundle, we need to pay attention to the use of prefab, the bundle loaded in the prefab is not directly used, it needs to be instantiated, before use, And for this prefab, after instantiation, this bundle can be released
Need to instantiate first
Gameobject obj = gameobject.instantiate (bundle. Load ("Myprefab")) as Gameobject;
For the prefab loaded from the bundle, it can be understood that a public variable that we drag directly from the resource directory to the script is not instantiated prefab, just a template
If you use the above code to load resources, when your resources slowly up, you may find a very bad dad problem, you have to load the resources load failed, such as you want to load a gameobject, but the entire loading process did not error, And when you're going to use this gameobject, there's a mistake, and the same code that we probably didn't find on the PC, when we hit Android or the iOS package, a resource failed to load.
This magical question, first of all, is the question of packing, too big? Delete some content, no! Make a new one? Or not! Then found to and fro, is this a gameobject newspaper fault, is this gameobject some of the resources have problems? To this gameobject various analysis, put it to unload eight pieces, handle into a very simple gameobject, still can't! Is it a question of names? Change the name of this gameobject, OK!
This is the end of things, but it is too baffling! Moreover, the most important thing is, the elder brother likes the original name!! Change this resource to a new name, how to see how to twist, how to see the original name is not good-looking, so continue to toss up ~
First step tracking to the resources of the load, the resource was successfully load out, but the load out of something a bit strange, obviously not a gameobject, but a baffling thing, may be unity generated an intermediate object, perhaps an Index object, It's not what I want, but how does a packed gameobject become this thing? So in the loading bundle place, the bundle loadall a bit, and then look at the contents of the bundle inside
Here we can see that there is a gameobject called Roomhallview and Roommainview, and that the resources after loadall are much more than the resources I have packaged, and it seems that all the associated resources are automatically packaged. The 427 of the array is the gameobject of the Roomhallview, and 431 is the gameobject of the Roommainview. You can see that the objects named Roommainview and Roomhallview have several, Gameobject,transform, and a name-only object whose type is a referencedata.
A closer look at what you can find is that the gameobject of Roomhallview is in the top of all the Roomhallview objects in the array, and Roommainview is in front of the Referencedata, When we load or Loadasyn, it's an array traversal, and when we traverse the object that matches the name, the object is returned and Loadasyn matches the type, but as we pass in the object, and almost all objects are object, So the result returned is the first name to match the object
in the load and Loadasyn, in addition to the name of the object to load the type is also passed in, and then debug, the original name can also be read to the normal, this detail is very pit, because at the official website does not remind, And the sample does not say that you should pay attention to this place, and there is little chance of problems. So once it appears, it's dead.
Bundle. Load ("Myprefab", typeof (Gameobject))
In addition, do not test assetbundle on the iOS emulator, you will receive a bad URL error
Three, Dependencies
dependencies are tied to packaging, because it's too much of a pit to separate dependencies.
1. Package dependent
&NBSP
When we pack up two of resources into separate packages, the resources shared by two resources are packaged into two, which creates redundancy, so we need to pull out the public resources, and To make a bundle, then the next two resources, depending on the public package, then there is another way to put them three into a package, but this is not conducive to late maintenance
We use Buildpipeline.pushassetdependencies () and buildpipeline.popassetdependencies () to open the dependencies between bundle, When we call Pushassetdependencies, will open the dependency mode, when we package a B C in turn, if a contains the resources of B, B will not include the resource, but directly depends on a, if A and B contains the resources of C, then C's this resource will not be packaged in the old , but rely on a and B. At this point, as long as the same resources, we will rely on forward, when we hope that B and C rely on a, but B and C do not depend on each other, you need to nest push pop, when we call Popassetdependencies will end dependency
string path = Application.streamingassetspath;
Buildpipeline.pushassetdependencies ();
Buildtarget target = buildtarget.standalonewindows;
Buildpipeline.buildassetbundle (Assetdatabase.loadmainassetatpath ("assets/ui_tck_icon_houtui.png"), NULL,
Path + "/package1.assetbundle",
buildassetbundleoptions.collectdependencies | Buildassetbundleoptions.completeassets
| Buildassetbundleoptions.deterministicassetbundle, Target);
Buildpipeline.buildassetbundle (Assetdatabase.loadmainassetatpath ("Assets/new material.mat"), NULL,
Path + "/package2.assetbundle",
buildassetbundleoptions.collectdependencies | Buildassetbundleoptions.completeassets
| Buildassetbundleoptions.deterministicassetbundle, Target);
Buildpipeline.pushassetdependencies ();
Buildpipeline.buildassetbundle (Assetdatabase.loadmainassetatpath ("Assets/cube.prefab"), NULL,
Path + "/package3.assetbundle",
buildassetbundleoptions.collectdependencies | Buildassetbundleoptions.completeassets
| Buildassetbundleoptions.deterministicassetbundle, buildtarget.standalonewindows);
Buildpipeline.popassetdependencies ();
Buildpipeline.pushassetdependencies ();
Buildpipeline.buildassetbundle (Assetdatabase.loadmainassetatpath ("Assets/cubes.prefab"), NULL,
Path + "/package4.assetbundle",
buildassetbundleoptions.collectdependencies | Buildassetbundleoptions.completeassets
| Buildassetbundleoptions.deterministicassetbundle, Target);
Buildpipeline.popassetdependencies ();
Buildpipeline.popassetdependencies ();
The above code demonstrates how to use dependencies, this test uses a texture, a material, a cube prefab, and two cubes of prefab, textures are used, and both groups of squares use this material, and the code above uses a push to turn on dependencies, to package textures, Then package the material (the material automatically relies on the texture), then a push is nested, the cube is packaged (the cube relies on the front material and texture), then pops, then a push is nested, and the cube is packaged (not dependent on the front cube, depending on the material and texture).
If we only open the outermost push pop, not the push pop, then the two cube prefab will depend on the prefab of a single cube, a double-edged sword that can remove redundancy, but sometimes we need a little redundancy
"2. Reliance lost"
When there is a dependency between our bundle, it is not as simple as the previous load corresponding to the bundle, we need to bundle rely on the bundle first loaded in, this load is only www or loadfromcacheordownload, There is no need to load on this bundle, if Bundleb relies on Bundlea, when we want to load Bundleb resources, suppose Bundlea is not loaded, or has been unload, Then the part that Bundleb relies on Bundlea will be lost, for example, a script is hung on every cube, and when we do not nest a push pop, the bundle of a single cube is not loaded or unloaded, and the script on the cube we load is lost, and the script is a resource, When a script has been packaged and relies on the resources of the package, it will not be hit again.
Cubes and Cube Both mount the same script, testobje,cubes depend on the cube, the bundle Unload of the cube, the cubes of the load Bundle,cubes script lost, script, texture, material and so on all resources, are so
"3. Update Dependencies"
When packing, we need to specify the Buildassetbundleoptions.deterministicassetbundle option, which generates a unique ID for each resource, and when the resource is repackaged, it is determined that the ID will not change, and that the packet dependency is based on this ID. , the advantage of using this option is that when resources need to be updated, other resources that depend on that resource do not need to be repackaged
A-> B-> C
When a relies on B for C, b update, the need to repackage c,b, and a does not need to move, the reason for packing C is because B depends on C, if not packaged C, directly packaged B, then C's resources will be repeated packaging, and B and C dependencies will be broken off
"Four, Memory"
When the bundle is loaded with www, a piece of memory is opened, which is the memory after the bundle file is uncompressed, which means that the memory is large, and the memory can be freed by Bundle.unload, Unload true and Unload false Will release this memory, and this bundle can not be used again, if you want to reuse, need to reload bundle, it is necessary to pay attention to this bundle of other bundle, in the load time, will be the error
After getting bundle, We use Bundle.load to load resources, which are copied from bundle memory and put into memory as asset, which means that this memory, too, is large, asset the release of memory, as well as the release mechanism of other resources in unity, through Resources.unloadunus Easset to release resources that are not referenced, or you can force the release of asset by Bundle.unload (true), which causes all objects referenced to this resource to lose the resource
The above two paragraphs can draw a conclusion, in the new WWW (URL), will open up a memory storage after decompression bundle, and in the resource is load out, and will open up a piece of memory to store asset resources, WWW. The Loadfromcacheordownload (URL) function is the same as the new WWW (URL), but loadfromcacheordownload is to extract bundle to disk space instead of memory. So the WWW object returned by Loadfromcacheordownload itself does not consume too much memory (just some index information, a disk path for each resource, removed from disk at load), and for small memory on the phone, Using WWW.LoadFromCacheOrDownload instead of new www can save memory efficiently. But Loadfromcacheordownload Dafa also has a time when it does not work, and when it does not work, the WWW object that Loadfromcacheordownload returns will occupy the same memory as the new WWW, so no matter how your bundle is created, All need to unload in time when not in use.
Another issue to be noted for using Loadfromcacheordownload is the second parameter, version number, bundle, after repackaging, the version number is not updated, and the old version of the bundle is removed. And there may be multiple older versions of the bundle in one bundle cache, such as 1,2,3 three versions of bundle
After the bundle load, no longer need to use the bundle, to unload, if there are other bundle dependent on the bundle, then should be dependent on the bundle bundle no longer need to load, unload this bundle, Usually occurs when the big scene is switched.
We know that when packing bundle, one of the parameters is Mainasset, and if this argument is passed in, then the resource is considered to be packaged as a primary resource, and after the bundle is obtained, Can be used directly with Assetbundle.mainasset, then whether in the WWW to obtain bundle, it has been mainasset the pre-load out of it? No! When we call Assetbundle.mainasset to remove the Mainasset, its get method blocks to the load Mainasset and returns, Assetbundle.mainasset equivalent to the load (" Mainassetname ")
PS. Repeat load the same resource does not open up new memory to store this resource
"Five, other"
In the use of assetbundle development process, we often adjust the resources, after the adjustment needs to be packaged to the resources to take effect, the development efficiency has a great impact, so in development we use resource and bundle compatible way
The first is to encapsulate resource management in a manager, which is determined by the load resource from the bundle or from the resource, so that the upper logic code does not need to be concerned with the current resource management type
Of course, all of the objects we want to package are in the resource directory, and we use the Strict directory specification, then we use the script object to record each resource's bundle, and the corresponding resource directory, update the script object when the resource changes, The manager uses the configuration information for the script object at run time, and the script object here is generated automatically using code, of course, you can also use the configuration table, the effect is the same
Versioning can also be implemented by the script object, each time the resource is packaged, it needs to be version number +1, the script object can store the version number of all resources, the version number can be used for loadfromcacheordownload, or the configuration table can be written manually, in the script object that I designed, Each resource will have a relative path under Bundle,resource, version number, etc. three attributes
At release time, you need to pack a bundle first, and then change the resource directory to another name, and then pack it up to ensure that the resources in the resource directory are not repackaged, and if you want to make a resource version, You need to delete the bundle file under Streamingassets
The script object is used as follows:
1. Design the storage structure first
2. Write a class that inherits from ScriptObject, storing data structures (lists or arrays) in a serializable container, and dictionary containers that cannot be serialized, after public
[Serializable]
public class Resconfigdata
{
public string Resname; Resource Name
public string Bundlename; Package Name
public string Path; Resource Path
public int vesrion; Version number
}
[System.serializable]
public class Resconfig:scriptableobject
{
Public list<resconfigdata> Configdatas = new list<resconfigdata> ();
}
4. To read the object in the specified path and not read to create the object
Resconfig obj = (resconfig) assetdatabase.loadassetatpath (Path, typeof (Resconfig));
if (obj = null)
{
obj = scriptableobject.createinstance<resconfig> ();
Assetdatabase.createasset (obj, path);
}
3. Write data, directly modify obj's array, and save (do not save the next start Unity data will be lost)
Editorutility.setdirty (obj);
Because of the inconvenient array operation, we can convert the data into a dictionary container store that facilitates various additions and deletions, and writes it to the persisted container at the time of retention.
Unity in the question of Assetbundle,shader lost
Unity's assetbundle is a way of managing resources, but there are a few things to be aware of, and the following is a problem that relies on packaging for mobile devices:
Dependencies are lost when loading, but the texture information is still there. As shown in figure:
Found a mistake for shader.
The present error is the use of unity of unlit/transparent
Cutout, usually our solution is to add the shader to the shader list of the Editor->graphics settings and then package it in the following script:
public class Fixshader:monobehaviour {
Private list<material> thismaterial;
Private list<string> shaders;
void Start ()
{
thismaterial = new list<material> (6);
Shaders = new list<string> (6);
meshrenderer[] Meshrenderer = getcomponentsinchildren<meshrenderer> ();
int length = Meshrenderer.length;
for (int i = 0; i < length; i++)
{
int count = Meshrenderer[i].materials. Length;
for (int j = 0; J < Count; J +)
{
Material _mater = Meshrenderer[i].materials[j];
Thismaterial.add (_mater);
Shaders. ADD (_mater.shader.name);
}
}
skinnedmeshrenderer[] Meshskinrenderer = getcomponentsinchildren<skinnedmeshrenderer> ();
length = Meshskinrenderer.length;
for (int i = 0; i < length; i++)
{
int count = Meshskinrenderer[i].materials. Length;
for (int j = 0; J < Count; J +)
{
Material _mater = Meshskinrenderer[i].materials[j];
Thismaterial.add (_mater);
Shaders. ADD (_mater.shader.name);
}
}
for (int i = 0; i < Thismaterial.count; i++)
{
Thismaterial[i].shader = Shader.find (Shaders[i]);
}
}
}
This solves the problem of losing, but when you write a shader or use a shader, you can't always use it every time editor->graphics
Settings to add shader, and then in the contract. If you do not add or forget to add, the publication will be lost after the mobile device.
The shader solution for editing is
1. Dependent packaging
2.shader to the Resources folder, it must be in the Resources folder.