在游戏项目开发制作过程中,开发者都需要考虑游戏中资源的的动态加载问题,为此unity提供了AssetBundle技术来满足开发者的需求。什么是AssetBundle,它是Unity引擎提供的一个存储资源的文件格式,它可以存储任意一种unity引擎能够识别的资源文件。
一、创建AssetBundle
unity提供了一个简单的AssetBundle的ui界面,可以让用户快速的将Asset标记到AssetBundle中。我们先创建一个 cube预制体:
图中我们可以看到有两个参数设置项,第一个表示资源所标记的AssetBundle名称,第二个参数表示用于设置AssetBundle Variant,主要应用在不同版本资源的使用和动态替换AssetBundle,然后我们将测试的预制体设置成为相应的信息,然后在Editor文件中创建脚本实现资源打包:
[MenuItem("Tools/Build AssetBundle")]
static void CreatAssetBundle()
{
//先删除之前旧的资源
string streamPath = Application.streamingAssetsPath;
if (Directory.Exists(streamPath))
{
Directory.Delete(streamPath, true);
}
Directory.CreateDirectory(streamPath);
//重新生成新的资源
BuildPipeline.BuildAssetBundles(streamPath, BuildAssetBundleOptions.None, BuildTarget.Android);
}
其中打包函数,第一个参数就是保存路径,第二个参数就是下面详细介绍,第三个参数就是目标平台 我们以安卓为例,注意各个平台之间是不会相互兼容的,安卓和ios也不能相互兼容,是要区别打包的。
第二个参数就是涉及到AssetBundle的打包方式:
//
// 摘要:
// ///
// Build assetBundle without any special option.
// ///
None = 0,
//
// 摘要:
// ///
// Don't compress the data when creating the asset bundle.
// ///
UncompressedAssetBundle = 1,
//
// 摘要:
// ///
// Includes all dependencies.
// ///
CollectDependencies = 2,
//
// 摘要:
// ///
// Forces inclusion of the entire asset.
// ///
CompleteAssets = 4,
//
// 摘要:
// ///
// Do not include type information within the AssetBundle.
// ///
DisableWriteTypeTree = 8,
//
// 摘要:
// ///
// Builds an asset bundle using a hash for the id of the object stored in the asset
// bundle.
// ///
DeterministicAssetBundle = 16,
//
// 摘要:
// ///
// Force rebuild the assetBundles.
// ///
ForceRebuildAssetBundle = 32,
//
// 摘要:
// ///
// Ignore the type tree changes when doing the incremental build check.
// ///
IgnoreTypeTreeChanges = 64,
//
// 摘要:
// ///
// Append the hash to the assetBundle name.
// ///
AppendHashToAssetBundleName = 128,
//
// 摘要:
// ///
// Use chunk-based LZ4 compression when creating the AssetBundle.
// ///
ChunkBasedCompression = 256,
//
// 摘要:
// ///
// Do not allow the build to succeed if any errors are reporting during it.
// ///
StrictMode = 512,
//
// 摘要:
// ///
// Do a dry run build.
// ///
DryRunBuild = 1024,
//
// 摘要:
// ///
// Disables Asset Bundle LoadAsset by file name.
// ///
DisableLoadAssetByFileName = 4096,
//
// 摘要:
// ///
// Disables Asset Bundle LoadAsset by file name with extension.
// ///
DisableLoadAssetByFileNameWithExtension = 8192
然后我们运行打出资源包:
这就是创建的AssetBundle文件,你会发现每个资源都会产生一个对应的mainfest文件,这个文件主要是记录当前的资源信息和依赖关系,下面我们具体看下里面内容
下面介绍其中重要的几个参数:
还有最后我们会发现还多了一个StreamingAssets文件,这是总的一个Mainfest文件,文件名和当前文件夹名字相同,然后记录当前所有的打包信息。
二、资源依赖关系
在打包中我们发现实际上我们cube和sphere是公用一个材质球的,这样打包就会在cube.unity3d和sphere.unity3d中分别打包一份这个材质球资源,造成资源浪费,现在你会发现两个文件都是153kb,那么怎么避免这个问题呢,官方提供的就是讲三个资源分别打包,他们之间就会建立对应的资源依赖关系,下面我们来测试一下:
现在你会发现,打包成三个资源,文件大小反而都减小了,现在三者总和才156kb,所以这样就可以减小文件大小,同时文件也会自动生成依赖关系,我们现在再看cube文件信息:
这时候发现在资源依赖一栏多了一个资源依赖信息,就是公用材质球的资源信息。
三、AssetBundle资源的加载和卸载:
打包完成之后,我们说下如何加载AssetBundle,并从中取出Asset资源,同时卸载加载完成的AssetBundle信息。
我们这里以从本地加载为例,和从服务器加载方式是一样的 ,我们加载出其中的cube为例进行讲解:
//变量定义
public const string mainfestName = "StreamingAssets"; //总的Mainfest文件名字
private string assetBundleName; //要加载的资源名称
private const string suffixName = ".unity3d"; //后缀名
private string url; //资源加载路径
void Start()
{
assetBundleName = "cube"; //添加要加载的资源名
url = "file://" + Application.streamingAssetsPath + "/"; //路径赋值
}
再具体加载过程中,如果AssetBundle之间存在依赖关系,就要先加载总的Manifest文件,然后再加载AssetBundle。
从上图看出,如果需要加载cube,就需要先加载common.unity3d资源信息,具体代码如下:
//加载Manifest资源文件
WWW www = WWW.LoadFromCacheOrDownload(url + mainfestName, 0);
yield return www;
if (www.error != null)
{
Debug.LogError("资源下载失败:" + www.error);
yield break;
}
//获取总的Manifest资源信息
AssetBundle mainfestBundle = www.assetBundle;
AssetBundleManifest mainfest = (AssetBundleManifest)mainfestBundle.LoadAsset("AssetBundleManifest");
mainfestBundle.Unload(false);
获取当前资源的依赖关系,例如cube就会获取到common的依赖的关系:
//获取资源依赖列表
string[] dependentAssetBundles = mainfest.GetAllDependencies(assetBundleName + suffixName);
AssetBundle[] abs = new AssetBundle[dependentAssetBundles.Length];
for (int i = 0; i < dependentAssetBundles.Length; i++)
{
//加载所有依赖关系
WWW www_re = WWW.LoadFromCacheOrDownload(url + dependentAssetBundles[i], 0);
yield return www_re;
abs[i] = www_re.assetBundle;
}
然后加载cube资源信息:
WWW www1 = WWW.LoadFromCacheOrDownload(url + assetBundleName + suffixName, 0);
yield return www1;
AssetBundle assetBundle = www1.assetBundle;
//从AssetBundle中加载Asset资源
AssetBundleRequest request = assetBundle.LoadAssetAsync(assetBundleName, typeof(GameObject));
yield return request;
GameObject go = request.asset as GameObject;
GameObject cube = Instantiate(go) as GameObject;
cube.transform.localPosition = Vector2.zero;
ResetShader(cube);
//卸载资源
assetBundle.Unload(false);
现在就是所有的资源加载以及卸载流程,下面是全部代码信息:
IEnumerator LoadResource()
{
WWW www = WWW.LoadFromCacheOrDownload(url + mainfestName, 0);
yield return www;
if (www.error != null)
{
Debug.LogError("资源下载失败:" + www.error);
yield break;
}
//获取总的Manifest资源信息
AssetBundle mainfestBundle = www.assetBundle;
AssetBundleManifest mainfest = (AssetBundleManifest)mainfestBundle.LoadAsset("AssetBundleManifest");
mainfestBundle.Unload(false);
//获取资源依赖列表
string[] dependentAssetBundles = mainfest.GetAllDependencies(assetBundleName + suffixName);
AssetBundle[] abs = new AssetBundle[dependentAssetBundles.Length];
for (int i = 0; i < dependentAssetBundles.Length; i++)
{
//加载所有依赖关系
WWW www_re = WWW.LoadFromCacheOrDownload(url + dependentAssetBundles[i], 0);
yield return www_re;
abs[i] = www_re.assetBundle;
}
WWW www1 = WWW.LoadFromCacheOrDownload(url + assetBundleName + suffixName, 0);
yield return www1;
AssetBundle assetBundle = www1.assetBundle;
//从AssetBundle中加载Asset资源
AssetBundleRequest request = assetBundle.LoadAssetAsync(assetBundleName, typeof(GameObject));
yield return request;
GameObject go = request.asset as GameObject;
GameObject cube = Instantiate(go) as GameObject;
cube.transform.localPosition = Vector2.zero;
ResetShader(cube);
//卸载资源
assetBundle.Unload(false);
}
实现效果:
四、关于部分shader丢失问题的解决
在实际项目中,可能有时候会发现加载的资源是粉色的,也就是材质丢失了,如下图:
针对这种情况,就是需要在加载资源之后,对shader重新复制刷新一下就OK了,具体分为两步:
1.需要将对应的shader加载到资源中去,具体就是讲用到的shader放到设置中去,具体操作就是将用到的Shader加Edit->Graphics Settings的Shader列表里再进行打包即可:
2.就是对当前的材质重新赋值:
///
/// 重置 shader信息
///
///
public static void ResetShader(UnityEngine.Object obj)
{
List listMat = new List();
if (obj is Material)
{
Material m = obj as Material;
listMat.Add(m);
}
else if (obj is GameObject)
{
GameObject go = obj as GameObject;
Renderer[] rends = go.GetComponentsInChildren(true);
if (null != rends)
{
foreach (Renderer item in rends)
{
Material[] materialsArr = item.sharedMaterials;
foreach (Material m in materialsArr)
listMat.Add(m);
}
}
}
else if (obj is Transform)
{
GameObject go = (obj as Transform).gameObject;
Renderer[] rends = go.GetComponentsInChildren(true);
if (null != rends)
{
foreach (Renderer item in rends)
{
Material[] materialsArr = item.sharedMaterials;
foreach (Material m in materialsArr)
listMat.Add(m);
}
}
}
else if (obj is RectTransform)
{
GameObject go = (obj as RectTransform).gameObject;
Renderer[] rends = go.GetComponentsInChildren(true);
if (null != rends)
{
foreach (Renderer item in rends)
{
Material[] materialsArr = item.sharedMaterials;
foreach (Material m in materialsArr)
listMat.Add(m);
}
}
}
for (int i = 0; i < listMat.Count; i++)
{
Material m = listMat[i];
if (null == m)
continue;
var shaderName = m.shader.name;
var newShader = Shader.Find(shaderName);
if (newShader != null)
{
m.shader = newShader;
}
}
}
通过上述两步操作,就可以恢复加载资源对应的shader信息。
工程链接:https://pan.baidu.com/s/1K2YVAL03bozg_5HfPqqA2Q 提取码:frc9
如果想学习更多unity相关知识可以关注下方公众号哦: