unity提供了assetbunlde机制,下面介绍一种方法将指定目录下的所有文件打包成AssetBundle
先说明步骤,再上代码。
步骤一、选择要打包成assetbundle的目录,本案例使用assetbundle_raw
步骤二、把要打包的资源或者目录都放到assetbundle_raw目录,资源可以是
prefab,png,fbx,font,audio,txt等。
步骤三、给assetbundle_raw目录下所有的资源文件设置assetbundle名称,如果是目录,会递归。
名称格式:资源名称.资源后缀.unity3d,
如果有子目录,则资源名称为:子目录/子目录/.../子目录/资源名称.资源后缀.unity3d
在给这些资源设置assetbundle名称的时候会把该资源所依赖的所有资源都设置上
assetbundle名称。编辑器脚本已经写好,稍后附上,这一步你只需要点击
Tools->AssetBundle->Set Asset Bundle Names就可以。
步骤四、选择assetbundle输出目录,本案例使用StreamingAssets目录。只所以要使用这个目录,主要
是考虑到本地测试会比较方便,打包好的assetbundle肯定需要进行一下测试,没问题后再放到
资源服务器上,另外就是可以把StreamingAssets目录作为本地资源测试服务器,还可以打包到
手机设备上进行测试。操作:Tools->AssetBundle->Build Bundles。
步骤五、生成资源版本文件。步骤四执行完毕后,assetbundle已经生成,另外还会生成Manifest文件,
因为assetbundle和平台相关,以android平台为例,会生成android和android.manifest文件,
我们需要根据android文件获取每个bundle的hash值,生成版本文件,用于热更新。废话不多
说,操作:Tools->AssetBundle->Generate Resource Version File。
至此,assetbundle打包完成。
特别需要注意:
放到assetbundle_raw目录下的资源,互相之间不要有任何依赖关系,否则,在步骤三设置名称的阶段,被依赖的资源就会被设置两次或者多次assetbundle名称,这样它只能使用最后设置的名称。如果是这样的话,在加载assetbundle的时候,就可能出现已经加载的assetbundle被再次加载而报错。因为被加载的assetbundle在被释放前不能再次加载。所以,放到assetbundle_raw目录下的资源,互相之间不要有任何依赖关系;放到assetbundle_raw目录下的资源,互相之间不要有任何依赖关系;放到assetbundle_raw目录下的资源,互相之间不要有任何依赖关系。重要的话说三遍。
另外需要注意的地方,在前面的文章中也提到了。
要打包的资源名称不要有空格,空格在pc上支持,在android上不支持
名称用小写,下划线,数字,尽量不要用大写,因为unity打包成assetbundle后的资源名称都是小写
建议unity所有的资源命名的时候用小写,下划线,数字,尽量不要用大写
如果用到一些插件,例如NGUI等,里面的资源,如,shader文件名称中间是有空格的,它会作为依赖资源打包成assetbundle,这样的手机上加载就可能出错。
编辑器代码:
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
using System;
using System.Collections.Generic;
using System.Text;
using LitJson;
public class Builder : Editor
{
public static string sourcePath = Application.dataPath + "/assetbundle_raw";
const string AssetBundlesOutputPath = "Assets/StreamingAssets";
static string m_assetPath = Application.streamingAssetsPath;
static string assetTail = ".unity3d";
static bool canFinish = false;
//生成的bundle的相对路径列表
static List<string> bundleRelativePaths = new List<string>();
[MenuItem("Tools/AssetBundle/Set Asset Bundle Names")]
public static void SetBundleNames()
{
ClearAssetBundlesName();
bundleRelativePaths.Clear();
Pack(sourcePath);
Debug.Log("Bundles 名称设置完成");
}
[MenuItem("Tools/AssetBundle/Build Bundles")]
public static void BuildAssetBundle()
{
string outputPath = bine(AssetBundlesOutputPath, Platform.GetPlatformFolder(EditorUserBuildSettings.activeBuildTarget));
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
//根据BuildSetting里面所激活的平台进行打包
BuildPipeline.BuildAssetBundles(outputPath, 0, EditorUserBuildSettings.activeBuildTarget);
Debug.Log("打包完成");
AssetDatabase.Refresh();
}
[MenuItem("Tools/AssetBundle/Generate Resource Version File")]
public static void GenerateVersionFile()
{
//生成配置文件
GenerateCfgFile();
EditorCoroutineRunner.StartEditorCoroutine(wait());
Debug.Log("成功生成资源版本文件");
AssetDatabase.Refresh();
}
static IEnumerator wait()
{
Debug.Log("wait");
while (!canFinish)
yield return null;
}
/// <summary>
/// 生成资源配置文件resource_version
/// </summary>
static void GenerateCfgFile()
{
Debug.Log("GenerateCfgFile");
getManifest();
}
/// <summary>
/// 清除之前设置过的AssetBundleName,避免产生不必要的资源也打包
/// 之前说过,只要设置了AssetBundleName的,都会进行打包,不论在什么目录下
/// </summary>
static void ClearAssetBundlesName()
{
string[] bundleNames = AssetDatabase.GetAllAssetBundleNames();
int length = bundleNames.Length;
Debug.Log(length);
string[] oldAssetBundleNames = new string[length];
for (int j = 0; j < bundleNames.Length; j++)
{
AssetDatabase.RemoveAssetBundleName(bundleNames[j], true);
}
length = AssetDatabase.GetAllAssetBundleNames().Length;
Debug.Log(length);
}
static void Pack(string source)
{
DirectoryInfo folder = new DirectoryInfo(source);
FileSystemInfo[] files = folder.GetFileSystemInfos();
int length = files.Length;
for (int i = 0; i < length; i++)
{
if (files[i] is DirectoryInfo)
{
Pack(files[i].FullName);
}
else
{
if (!files[i].Name.EndsWith(".meta"))
{
file(files[i].FullName);
Debug.Log("files[i].FullName is " + files[i].FullName);
}
}
}
}
static void file(string source)
{
string _source = Replace(source);
string _assetPath = "Assets" + _source.Substring(Application.dataPath.Length);
string _assetPath2 = _source.Substring(Application.dataPath.Length + 1);
Debug.Log("_assetPath is " + _assetPath);
Debug.Log("_assetPath2 is " + _assetPath2);
//在代码中给资源设置AssetBundleName
AssetImporter assetImporter = AssetImporter.GetAtPath(_assetPath);
string assetName = _assetPath2.Substring(_assetPath2.IndexOf("/") + 1);
//assetName = assetName.Replace(Path.GetExtension(assetName), ".unity3d");
assetName = assetName + ".unity3d";
assetImporter.assetBundleName = assetName;
fileDependences(_assetPath);
Debug.Log("assetName is " + assetName);
//保存bundle的相对路径
if (!bundleRelativePaths.Contains(assetName))
bundleRelativePaths.Add(assetName);
}
static string Replace(string s)
{
return s.Replace("\\", "/");
}
static void fileDependences(string path)
{
try
{
//string path = AssetDatabase.GetAssetPath(0);
Debug.Log("path is " + path);
string[] deps = AssetDatabase.GetDependencies(path);
int counter = 0;
for (int i = 0; i < deps.Length; i++)
{
if (deps[i].Equals(path) || deps[i].EndsWith(".cs"))
continue;
++counter;
}
if (counter == 0)
return;
for (int i = 0; i < deps.Length; i++)
{
Debug.Log("deps " + i + " is " + deps[i]);
if (deps[i].Equals(path) || deps[i].EndsWith(".cs"))
continue;
AssetImporter ai = AssetImporter.GetAtPath(deps[i]);
string assetName = deps[i] + ".unity3d";
assetName = assetName.Substring(assetName.IndexOf("/") + 1);
Debug.Log("assetName is " + assetName);
ai.assetBundleName = assetName;
//保存bundle的相对路径
if (!bundleRelativePaths.Contains(assetName))
bundleRelativePaths.Add(assetName);
fileDependences(deps[i]);
}
}
catch (Exception error)
{
Debug.Log("error is " + error);
}
}
#region LoadAssetBundle
/// <summary>
/// 加载目标资源
/// </summary>
/// <param name="name"></param>
/// <param name="callback"></param>
public static void LoadAssetBundle(string name, Action<UnityEngine.Object> callback)
{
name = name + assetTail;//eg:ui/panel.unity3d
Action<List<AssetBundle>> action = (depenceAssetBundles) =>
{
string realName = GetRuntimePlatform() + "/" + name;//eg:Windows/ui/panel.unity3d
Debug.Log("realName is " + realName);
LoadResReturnWWW(realName, (www) =>
{
int index = realName.LastIndexOf("/");
string assetName = realName.Substring(index + 1);
assetName = assetName.Replace(assetTail, "");
AssetBundle assetBundle = www.assetBundle;
UnityEngine.Object obj = assetBundle.LoadAsset(assetName);//LoadAsset(name),这个name没有后缀,eg:panel
//卸载资源内存
assetBundle.Unload(false);
for (int i = 0; i < depenceAssetBundles.Count; i++)
{
depenceAssetBundles[i].Unload(false);
}
//加载目标资源完成的回调
callback(obj);
});
};
LoadDependenceAssets(name, action);
}
static void writeCfgFile(AssetBundleManifest manifest)
{
if (null == manifest)
return;
StringBuilder sb = new StringBuilder();
JsonWriter js = new JsonWriter(sb);
try
{
js.WriteObjectStart();
js.WritePropertyName("id");
js.Write(0);
js.WritePropertyName("version");
js.Write("1.0");
string platform = Platform.GetPlatformFolder(EditorUserBuildSettings.activeBuildTarget);
js.WritePropertyName("manifest");
js.Write(platform);
js.WritePropertyName("resource");
string[] bundleNames = AssetDatabase.GetAllAssetBundleNames();
js.WriteObjectStart();
foreach (string path in bundleNames)
{
Hash128 hash = manifest.GetAssetBundleHash(path);
//if (hash.isValid)
{
js.WritePropertyName(path);
js.Write(hash.ToString());
}
}
js.WriteObjectEnd();
js.WriteObjectEnd();
}
catch (Exception error)
{
Debug.Log("Write json error : " + error.Message);
}
string strVersion = sb.ToString().ToLower();
try
{
string platform = GetRuntimePlatform();
File.WriteAllText(AssetBundlesOutputPath + "/" + platform + "/resource_version", strVersion);
}
catch (Exception error)
{
Debug.Log("Write Cfg file error : " + error.Message);
}
canFinish = true;
}
/// <summary>
/// 获取总manifest
/// </summary>
static void getManifest()
{
Action<AssetBundleManifest> dependenceAction = (manifest) =>
{
writeCfgFile(manifest);
};
LoadAssetBundleManifest(dependenceAction);
}
/// <summary>
/// 加载目标资源的依赖资源
/// </summary>
/// <param name="targetAssetName"></param>
/// <param name="action"></param>
private static void LoadDependenceAssets(string targetAssetName, Action<List<AssetBundle>> action)
{
Debug.Log("要加载的目标资源:" + targetAssetName);//ui/panel.unity3d
Action<AssetBundleManifest> dependenceAction = (manifest) =>
{
List<AssetBundle> depenceAssetBundles = new List<AssetBundle>();//用来存放加载出来的依赖资源的AssetBundle
string[] dependences = manifest.GetAllDependencies(targetAssetName);
//获取hash值
Hash128 hash = manifest.GetAssetBundleHash(targetAssetName);
Debug.Log(targetAssetName + " hash is " + hash);
Debug.Log("依赖文件个数:" + dependences.Length);
int length = dependences.Length;
int finishedCount = 0;
if (length == 0)
{
//没有依赖
action(depenceAssetBundles);
}
else
{
//有依赖,加载所有依赖资源
for (int i = 0; i < length; i++)
{
string dependenceAssetName = dependences[i];
dependenceAssetName = GetRuntimePlatform() + "/" + dependenceAssetName;//eg:Windows/altas/heroiconatlas.unity3d
//加载,加到assetpool
LoadResReturnWWW(dependenceAssetName, (www) =>
{
int index = dependenceAssetName.LastIndexOf("/");
string assetName = dependenceAssetName.Substring(index + 1);
assetName = assetName.Replace(assetTail, "");
AssetBundle assetBundle = www.assetBundle;
UnityEngine.Object obj = assetBundle.LoadAsset(assetName);
//assetBundle.Unload(false);
depenceAssetBundles.Add(assetBundle);
finishedCount++;
if (finishedCount == length)
{
//依赖都加载完了
action(depenceAssetBundles);
}
});
}
}
};
LoadAssetBundleManifest(dependenceAction);
}
/// <summary>
/// 加载AssetBundleManifest
/// </summary>
/// <param name="action"></param>
private static void LoadAssetBundleManifest(Action<AssetBundleManifest> action)
{
string manifestName = GetRuntimePlatform();
Debug.Log("Application.platform is " + Application.platform);
manifestName = manifestName + "/" + manifestName;//eg:Windows/Windows
Debug.Log("manifestName is " + manifestName);
LoadResReturnWWW(manifestName, (www) =>
{
AssetBundle assetBundle = www.assetBundle;
UnityEngine.Object obj = assetBundle.LoadAsset("AssetBundleManifest");
assetBundle.Unload(false);
AssetBundleManifest manif = obj as AssetBundleManifest;
Debug.Log("(www) " + manif.name);
action(manif);
});
}
#endregion
#region ExcuteLoader
public static void LoadResReturnWWW(string name, Action<WWW> callback)
{
string path = "file://" + m_assetPath + "/" + name;
Debug.Log("m_assetPath is " + m_assetPath);
Debug.Log("name is " + name);
Debug.Log("加载:" + path);
EditorCoroutineRunner.StartEditorCoroutine(LoaderRes(path, callback));
}
static IEnumerator LoaderRes(string path, Action<WWW> callback)
{
WWW www = new WWW(path);
yield return www;
callback(www);
}
#endregion
#region Util
/// <summary>
/// 平台对应文件夹
/// </summary>
/// <returns></returns>
private static string GetRuntimePlatform()
{
string platform = "";
if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor)
{
//platform = "Windows";
platform = "android";
}
else if (Application.platform == RuntimePlatform.Android)
{
platform = "android";
}
else if (Application.platform == RuntimePlatform.IPhonePlayer)
{
platform = "ios";
}
else if (Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor)
{
platform = "osx";
}
return platform;
}
#endregion
}
public class Platform
{
public static string GetPlatformFolder(BuildTarget target)
{
switch (target)
{
case BuildTarget.Android:
return "android";
case BuildTarget.iOS:
return "ios";
case BuildTarget.WebPlayer:
return "webplayer";
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
return "windows";
case BuildTarget.StandaloneOSXIntel:
case BuildTarget.StandaloneOSXIntel64:
case BuildTarget.StandaloneOSXUniversal:
return "osx";
default:
return null;
}
}
}
用到了一个工具类,这是一个在编辑器下可运行的协程类,我也是从网上找的
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using pilerServices;
public static class EditorCoroutineRunner
{
private class EditorCoroutine : IEnumerator
{
private Stack<IEnumerator> executionStack;
public EditorCoroutine(IEnumerator iterator)
{
this.executionStack = new Stack<IEnumerator>();
this.executionStack.Push(iterator);
}
public bool MoveNext()
{
IEnumerator i = this.executionStack.Peek();
if (i.MoveNext())
{
object result = i.Current;
if (result != null && result is IEnumerator)
{
this.executionStack.Push((IEnumerator)result);
}
return true;
}
else
{
if (this.executionStack.Count > 1)
{
this.executionStack.Pop();
return true;
}
}
return false;
}
public void Reset()
{
throw new System.NotSupportedException("This Operation Is Not Supported.");
}
public object Current
{
get { return this.executionStack.Peek().Current; }
}
public bool Find(IEnumerator iterator)
{
return this.executionStack.Contains(iterator);
}
}
private static List<EditorCoroutine> editorCoroutineList;
private static List<IEnumerator> buffer;
public static IEnumerator StartEditorCoroutine(IEnumerator iterator)
{
if (editorCoroutineList == null)
{
// test
editorCoroutineList = new List<EditorCoroutine>();
}
if (buffer == null)
{
buffer = new List<IEnumerator>();
}
if (editorCoroutineList.Count == 0)
{
EditorApplication.update += Update;
}
// add iterator to buffer first
buffer.Add(iterator);
return iterator;
}
private static bool Find(IEnumerator iterator)
{
// If this iterator is already added
// Then ignore it this time
foreach (EditorCoroutine editorCoroutine in editorCoroutineList)
{
if (editorCoroutine.Find(iterator))
{
return true;
}
}
return false;
}
private static void Update()
{
// EditorCoroutine execution may append new iterators to buffer
// Therefore we should run EditorCoroutine first
editorCoroutineList.RemoveAll
(
coroutine => { return coroutine.MoveNext() == false; }
);
// If we have iterators in buffer
if (buffer.Count > 0)
{
foreach (IEnumerator iterator in buffer)
{
// If this iterators not exists
if (!Find(iterator))
{
// Added this as new EditorCoroutine
editorCoroutineList.Add(new EditorCoroutine(iterator));
}
}
// Clear buffer
buffer.Clear();
}
// If we have no running EditorCoroutine
// Stop calling update anymore
if (editorCoroutineList.Count == 0)
{
EditorApplication.update -= Update;
}
}
}
后面我会分享热更新策略,用到了上面生成的资源版本文件。
以上是我用assetbundle打包的使用案例,本人能力有限,有错误和不足的地方,还请不吝赐教,万分感激!
如果觉得《unity 打包AssetBundle》对你有帮助,请点赞、收藏,并留下你的观点哦!