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 = Path.Combine(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 _assetPath3 = _source.Substring(Application.dataPath.Length + 1);

Debug.Log("_assetPath is " + _assetPath);

Debug.Log("_assetPath3 is " + _assetPath3);


//在代码中给资源设置AssetBundleName

AssetImporter assetImporter = AssetImporter.GetAtPath(_assetPath);

string assetName = _assetPath3.Substring(_assetPath3.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)

{

Hash228 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值

Hash228 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 System.Runtime.CompilerServices;


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打包的使用案例,本人能力有限,有错误和不足的地方,还请不吝赐教,万分感激!