Skip to content

Instantly share code, notes, and snippets.

@benloong
Created April 11, 2017 02:23
Show Gist options
  • Save benloong/23cff5efca16f09a3f6e7856882d9c28 to your computer and use it in GitHub Desktop.
Save benloong/23cff5efca16f09a3f6e7856882d9c28 to your computer and use it in GitHub Desktop.
Unity4.x Build AssetBundle and generate dependence
using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
using System.Linq;
public class AssetBundleManifest
{
public string AssetPath;
public HashSet<AssetBundleManifest> Parents = new HashSet<AssetBundleManifest>();
public HashSet<AssetBundleManifest> Children = new HashSet<AssetBundleManifest>();
public int pushCount = 0;
public bool IsAtlas = false;
public string AtlasName = string.Empty;
public bool IsScene = false;
/// <summary>
/// 是否需要重新导出
/// 根据timestamp比较导出的AssetBundle和资源是否需要重新导出
/// </summary>
public bool NeedExport
{
get;
set;
}
public bool Push()
{
if (pushCount == 0)
{
BuildPipeline.PushAssetDependencies();
Debug.Log("Push :" + AssetPath);
}
pushCount++;
return pushCount == 1;
}
public void Pop()
{
pushCount--;
if (pushCount == 0)
{
BuildPipeline.PopAssetDependencies();
Debug.Log("Pop : " + AssetPath);
}
}
public long GetTimeStamp()
{
return 0;
}
}
public class AssetBundleEditorHelper
{
#if UNITY_ANDROID
static BuildTarget target = BuildTarget.Android;
#elif UNITY_IPHONE
static BuildTarget target = BuildTarget.iPhone;
#else
static BuildTarget target = BuildTarget.StandaloneWindows;
#endif
[MenuItem("Tools/Atlas/ShowUsedAtlasName")]
public static void FilterSelectionAtlas()
{
if (Selection.gameObjects == null)
{
return;
}
var allAssets = Selection.gameObjects.Select(go => AssetDatabase.GetAssetPath(go));
List<string> packedSprites = new List<string>();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
HashSet<string> atlas = new HashSet<string>();
foreach (var item in allAssets)
{
var dependencies = AssetDatabase.GetDependencies(new string[] { item });
foreach (var path in dependencies)
{
var sprite = AssetDatabase.LoadAssetAtPath(path, typeof(Sprite)) as Sprite;
if (sprite)
{
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer && !string.IsNullOrEmpty(importer.spritePackingTag))
{
sb.Append(importer.spritePackingTag);
sb.Append(" ").Append(path);
sb.Append("\n");
atlas.Add(importer.spritePackingTag);
packedSprites.Add(path);
}
}
}
}
Debug.Log(sb.ToString());
Debug.Log("all used atlas:\n" + string.Join("\n", atlas.ToArray()));
}
static BuildAssetBundleOptions options = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.DeterministicAssetBundle;
public static Dictionary<string, AssetBundleManifest> BuildDependencyTree(List<string> allAssetPathList)
{
Dictionary<string, AssetBundleManifest> bundles = new Dictionary<string, AssetBundleManifest>();
//var allAssets = AssetDatabase.FindAssets("l:AssetBundle").Select(p => AssetDatabase.GUIDToAssetPath(p));
var allAssets = allAssetPathList;
List<string> packedSprites = new List<string>();
foreach (var item in allAssets)
{
var dependencies = AssetDatabase.GetDependencies(new string[] { item });
foreach (var path in dependencies)
{
var sprite = AssetDatabase.LoadAssetAtPath(path, typeof(Sprite)) as Sprite;
if (sprite)
{
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer && !string.IsNullOrEmpty(importer.spritePackingTag))
{
packedSprites.Add(path);
}
}
}
}
foreach (var asset in allAssets)
{
AssetBundleManifest ab = new AssetBundleManifest();
ab.AssetPath = asset;
bundles[asset] = ab;
var obj = AssetDatabase.LoadAssetAtPath(asset, typeof(Object));
if (obj && obj.ToString().Contains("UnityEngine.SceneAsset"))
{
ab.IsScene = true;
}
}
foreach (var path in packedSprites)
{
AssetBundleManifest ab = new AssetBundleManifest() { AssetPath = path };
bundles[path] = ab;
}
foreach (var path in bundles)
{
var dependencies = AssetDatabase.GetDependencies(new string[] { path.Value.AssetPath });
UpdateDependencyLink(bundles, path.Value.AssetPath, dependencies);
}
// 合并同图集的Sprite到一个AssetBundle中
// 找到所有的Packed Sprite
List<AssetBundleManifest> allPackedSprites = new List<AssetBundleManifest>();
foreach (var item in bundles.Values)
{
var sprite = AssetDatabase.LoadAssetAtPath(item.AssetPath, typeof(Sprite)) as Sprite;
if (sprite)
{
TextureImporter importer = AssetImporter.GetAtPath(item.AssetPath) as TextureImporter;
if (importer && !string.IsNullOrEmpty(importer.spritePackingTag))
{
allPackedSprites.Add(item);
}
}
}
// 图集AssetBundle
Dictionary<string, AssetBundleManifest> packingTagDict = new Dictionary<string, AssetBundleManifest>();
foreach (var item in allPackedSprites)
{
var sprite = AssetDatabase.LoadAssetAtPath(item.AssetPath, typeof(Sprite)) as Sprite;
if (sprite)
{
TextureImporter importer = AssetImporter.GetAtPath(item.AssetPath) as TextureImporter;
if (importer && !string.IsNullOrEmpty(importer.spritePackingTag))
{
if (!packingTagDict.ContainsKey(importer.spritePackingTag))
{
packingTagDict[importer.spritePackingTag] = new AssetBundleManifest();
packingTagDict[importer.spritePackingTag].AssetPath = importer.spritePackingTag;
packingTagDict[importer.spritePackingTag].IsAtlas = true;
packingTagDict[importer.spritePackingTag].AtlasName = importer.spritePackingTag;
}
var atlasAssetBundle = packingTagDict[importer.spritePackingTag];
foreach (var child in item.Children)
{
// 子对象改变依赖
child.Parents.Remove(item);
child.Parents.Add(atlasAssetBundle);
// 图集增加被依赖项
atlasAssetBundle.Children.Add(child);
}
}
}
}
// 移除原有的Packed Sprite
foreach (var item in allPackedSprites)
{
bundles.Remove(item.AssetPath);
}
// 添加所有的Atlas
foreach (var kv in packingTagDict)
{
bundles.Add(kv.Key, kv.Value);
}
/// TODO:检测所有的AssetBundle是否是需要重新导出
return bundles;
}
private static void UpdateDependencyLink(Dictionary<string, AssetBundleManifest> allAssets, string asset, string[] dependencies)
{
AssetBundleManifest abManifest;
if (allAssets.TryGetValue(asset, out abManifest))
{
foreach (var dependent in dependencies)
{
if (dependent == asset)
{
continue;
}
AssetBundleManifest dependentManifest;
if (allAssets.TryGetValue(dependent, out dependentManifest))
{
abManifest.Parents.Add(dependentManifest);
dependentManifest.Children.Add(abManifest);
}
}
}
}
public static string savePath = string.Empty;
public static void BuildAllAssetBundle()
{
savePath = AssetBundleTool.SelectSaveFolderPath();
if (string.IsNullOrEmpty(savePath))
{
return;
}
var pathList = AssetDatabase.FindAssets("l:AssetBundle").Select(p => AssetDatabase.GUIDToAssetPath(p)).ToList();
BuildAssetBundle(pathList);
}
public static void BuildAssetBundle(List<string> pathList)
{
var allAssets = BuildDependencyTree(pathList);
var leafs = allAssets.Values.Where(a => a.Children.Count == 0);
/// 打包所有的AssetBundle
foreach (var leaf in leafs)
{
BuildAssetBundle(leaf);
}
var metaDB = System.IO.Path.Combine(savePath, "AssetBundleMetaList.txt");
SimpleJSON.JSONClass jsonClass = new SimpleJSON.JSONClass();
if (System.IO.File.Exists(metaDB))
{
var reader = System.IO.File.OpenText(metaDB);
string json = reader.ReadToEnd();
reader.Close();
jsonClass = SimpleJSON.JSON.Parse(json).AsObject;
}
foreach (var item in allAssets)
{
var assetMeta = new SimpleJSON.JSONClass();
foreach (var dependent in item.Value.Parents)
{
assetMeta["dependencies"].Add(dependent.AssetPath);
}
jsonClass[item.Key] = assetMeta;
}
var temp = System.IO.Path.Combine(Application.dataPath, "../Temp/" + System.Guid.NewGuid().ToString("n"));
using (var fs = System.IO.File.OpenWrite(temp))
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(jsonClass.ToString());
fs.Write(data, 0, data.Length);
}
if (System.IO.File.Exists(metaDB))
{
System.IO.File.Delete(metaDB);
}
System.IO.File.Move(temp, metaDB);
}
private static bool BuildAssetBundle(AssetBundleManifest manifest)
{
foreach (var item in manifest.Parents)
{
BuildAssetBundle(item);
}
string path = savePath + manifest.AssetPath + ".ab";
bool exist = System.IO.Directory.GetParent(path).Exists;
if (!exist)
{
System.IO.Directory.CreateDirectory(System.IO.Directory.GetParent(path).FullName);
}
if (manifest.pushCount == 0)
{
foreach (var item in manifest.Children)
{
manifest.Push();
}
if (manifest.Children.Count == 0)
{
Debug.Log("Push :" + manifest.AssetPath);
BuildPipeline.PushAssetDependencies();
}
Debug.Log("Build assetbundle: " + manifest.AssetPath);
if (manifest.IsAtlas)
{
BuildAtlasAssetBundle(manifest.AssetPath, path);
}
else if (manifest.IsScene)
{
string[] levels = new string[] { manifest.AssetPath };
BuildPipeline.BuildStreamedSceneAssetBundle(levels, path, target, BuildOptions.BuildAdditionalStreamedScenes);
}
else
{
bool result = BuildAssetBundle(manifest.AssetPath, path);
//if (!result)
//{
// return result;
//}
}
if (manifest.Children.Count == 0)
{
Debug.Log("Pop :" + manifest.AssetPath);
BuildPipeline.PopAssetDependencies();
}
}
foreach (var item in manifest.Parents)
{
item.Pop();
}
return true;
}
private static Sprite[] GetAllSpriteInPackingTag(string packingTag)
{
var allTextures = AssetDatabase.FindAssets("t:Sprite").Select(guid=>AssetDatabase.GUIDToAssetPath(guid));
var sprites = (from path in allTextures select AssetImporter.GetAtPath(path) as TextureImporter into importer where importer.spritePackingTag == packingTag select AssetDatabase.LoadAssetAtPath(importer.assetPath, typeof(Sprite)) as Sprite).ToArray();
return sprites;
}
private static bool BuildAtlasAssetBundle(string atlasName, string savePath)
{
var sprites = GetAllSpriteInPackingTag(atlasName);
Debug.Log(string.Join(" ", sprites.Select(s => s.name).ToArray()));
var allTextures = AssetDatabase.FindAssets("t:Sprite").Select(guid => AssetDatabase.GUIDToAssetPath(guid));
var atlasTextures = (from path in allTextures select AssetImporter.GetAtPath(path) as TextureImporter into importer where importer.spritePackingTag == atlasName select AssetDatabase.LoadAssetAtPath(importer.assetPath, typeof(Sprite)) as Object).ToArray();
return BuildPipeline.BuildAssetBundle(atlasTextures[0], atlasTextures, savePath, options, target);
}
private static bool BuildAssetBundle(string assetPath, string savePath)
{
var asset = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
if (asset.ToString().Contains("UnityEngine.DefaultAsset"))
{
var oldActive = Selection.activeObject;
Selection.activeObject = asset;
var allAssets = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets);
Selection.activeObject = oldActive;
return BuildPipeline.BuildAssetBundle(null, allAssets, savePath, options, target);
}
else
{
return BuildPipeline.BuildAssetBundle(asset, null, savePath, options, target);
}
}
private static long GetTimeStampAtPath(string path)
{
return 0;
}
}
public class AssetBundleTool : Editor
{
[MenuItem("Tools/BuildAssetbundle")]
static public void BuildAssetbundle()
{
string savepath = SelectSaveFolderPath();
if (string.IsNullOrEmpty(savepath))
{
return;
}
BuildTarget target = BuildTarget.StandaloneWindows;
#if UNITY_ANDROID
target = BuildTarget.Android;
#elif UNITY_IPHONE
target = BuildTarget.iPhone;
#endif
List<Object> assets = new List<Object>();
foreach (Object o in Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets))
{
assets.Add(o);
}
BuildPipeline.BuildAssetBundle(Selection.activeObject, assets.ToArray(), savepath + "/" + Selection.activeObject.name + ".ab", BuildAssetBundleOptions.CollectDependencies, target);
}
[MenuItem("Tools/生成AssetBundle依赖")]
public static void BuildDependencies()
{
var allAssets = AssetDatabase.FindAssets("l:AssetBundle").Select(p => AssetDatabase.GUIDToAssetPath(p)).ToList();
var dependency = AssetBundleEditorHelper.BuildDependencyTree(allAssets);
SimpleJSON.JSONClass json = new SimpleJSON.JSONClass();
foreach (var item in dependency.Values)
{
var newAsset = new SimpleJSON.JSONClass();
foreach (var parent in item.Parents)
{
newAsset["dependencies"].Add(parent.AssetPath);
}
json[item.AssetPath] = newAsset;
}
Debug.Log("Dependencies :" + json.ToString());
}
[MenuItem("Tools/生成所有AssetBundle")]
public static void BuildAllAssetBundle()
{
AssetBundleEditorHelper.BuildAllAssetBundle();
}
[MenuItem("Tools/生成选中的AssetBundle")]
public static void BuildAssetBundleSelected()
{
string savepath = SelectSaveFolderPath();
if (string.IsNullOrEmpty(savepath))
{
return;
}
AssetBundleEditorHelper.savePath = savepath;
var path = AssetDatabase.GetAssetPath(Selection.activeObject);
bool isMarkedAssetBundle = AssetDatabase.GetLabels(Selection.activeObject).Contains("AssetBundle");
if (!isMarkedAssetBundle)
{
Debug.LogError("选择的对象没有被标记为assetbundle, " + path);
return;
}
List<string> pathList = new List<string> { path };
AssetBundleEditorHelper.BuildAssetBundle(pathList);
}
public static string SelectSaveFolderPath()
{
string lastSavePath = EditorPrefs.GetString("LastAssetBundleSavePath", "");
string path = EditorUtility.SaveFolderPanel("保存到", lastSavePath, "");
EditorPrefs.SetString("LastAssetBundleSavePath", path);
if (string.IsNullOrEmpty(path))
{
return string.Empty;
}
return path + "/";
}
[MenuItem("Tools/BuildStreamingScene")]
public static void BuildStreamingScene()
{
var path = SelectSaveFolderPath();
if (string.IsNullOrEmpty(path))
{
return;
}
BuildTarget target = BuildTarget.StandaloneWindows;
#if UNITY_ANDROID
target = BuildTarget.Android;
#elif UNITY_IPHONE
target = BuildTarget.iPhone;
#endif
List<string> scenePaths = new List<string>();
var objs = Selection.objects;
for (int i = 0; i < objs.Length; i++)
{
if (objs[i] && objs[i].ToString().Contains("UnityEngine.SceneAsset"))
{
scenePaths.Add(AssetDatabase.GetAssetOrScenePath(objs[i]));
}
}
if (scenePaths.Count > 0)
{
BuildPipeline.BuildStreamedSceneAssetBundle(scenePaths.ToArray(), path + "Scenes.ab", target, BuildOptions.BuildAdditionalStreamedScenes);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment