打包界面参考的https://github.com/Unity-Technologies/AssetBundles-Browser,逻辑等都是自己写的
上代码:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace AssetBundleBrowser
{
public class BuildPacker
{
private BuildTargetType targetContent = BuildTargetType.Android;
private string outputPath = "Assets/StreamingAssets";
private int compressionOption = 1;
private string rewRootPath = "Assets/SubAssets/Rew";
private string resRootPath = "Assets/SubAssets/Res";
[SerializeField]
internal AssetBundleManageTab m_ManageTab;
private List<AssetBundleBuild> buildList = new List<AssetBundleBuild>();
private List<string> bundleLabelPathList = new List<string>();
private static string filePath = string.Empty;
private bool isRebuild = false;
private EditorWindow parentWindow;
internal enum BuildTargetType
{
MacOS = 2,
iOS = 9,
Android = 13,
Windows = 19,
}
internal enum CompressOptions
{
Uncompressed = 1,
LZMA = 0,
LZ4 = 256,
}
GUIContent[] compressionOptions =
{
new GUIContent("No Compression"),
new GUIContent("LZMA"),
new GUIContent("LZ4")
};
int[] compressionValues = { 1, 0, 256 };
internal void OnEnable(EditorWindow parent)
{
parentWindow = parent;
filePath = Application.dataPath + "/SubAssets/Scripts/GameEditor/Editor/bundleName.txt";
}
internal void OnDisable()
{
}
internal void OnGUI()
{
EditorGUILayout.Space();
GUILayout.BeginVertical();
// 构建平台
targetContent = (BuildTargetType)EditorGUILayout.EnumPopup(new GUIContent("Build Target"), targetContent);
EditorGUILayout.Space();
GUILayout.BeginHorizontal();
// bundle生成路径
outputPath = EditorGUILayout.TextField("Output Path", outputPath);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
// 修改bundle生成路径
if (GUILayout.Button("Browse", GUILayout.MaxWidth(75f)))
BrowseForFolder();
// bungle生成路径复位
if (GUILayout.Button("Reset", GUILayout.MaxWidth(75f)))
ResetPathToDefault();
GUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.Space();
// bundle压缩选择
compressionOption = EditorGUILayout.IntPopup(new GUIContent("CompressOption"),
compressionOption,
compressionOptions,
compressionValues);
EditorGUILayout.Space();
// 是否重置bundle标签
isRebuild = EditorGUILayout.Toggle("重置bundle标签", isRebuild);
EditorGUILayout.Space();
// build.
EditorGUILayout.Space();
if (GUILayout.Button("Build"))
{
EditorApplication.delayCall += ExecuteBuild;
}
GUILayout.EndVertical();
}
private void BrowseForFolder()
{
var newPath = EditorUtility.OpenFolderPanel("Bundle Folder", outputPath, string.Empty);
if (!string.IsNullOrEmpty(newPath))
{
outputPath = newPath;
}
}
private void ResetPathToDefault()
{
outputPath = "Assets/StreamingAssets";
}
private void ExecuteBuild()
{
buildList.Clear();
if ((int)EditorUserBuildSettings.activeBuildTarget != (int)targetContent)
{
if (!SwitchPlatform())
{
Debug.LogError("切换平台失败!");
return;
}
}
CompressAtlas();
CompressTexture();
if (Directory.Exists(outputPath))
DelectDir();
if (!Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
if (isRebuild) ClearBundleFileName();
Building();
WriteFile();
BuildFileIndex();
AssetDatabase.Refresh();
if (parentWindow != null) parentWindow.Close();
Debug.Log("打包完成!");
}
private void DelectDir()
{
string withoutExtensions = ".meta";
string[] filePaths = Directory.GetFiles(outputPath);
for (int i = 0; i < filePaths.Length; i++)
{
string path = filePaths[i];
if (path.Contains(withoutExtensions)) continue;
FileUtil.DeleteFileOrDirectory(path);
}
}
private void WriteFile()
{
if (bundleLabelPathList.Count > 0)
{
File.WriteAllLines(filePath, bundleLabelPathList.ToArray(), Encoding.UTF8);
}
}
private void ClearBundleFileName()
{
if (!File.Exists(filePath)) return;
string[] lines = File.ReadAllLines(filePath);
for (int i = 0; i < lines.Length; i++)
{
AssetImporter import = AssetImporter.GetAtPath(lines[i]);
import.assetBundleName = string.Empty;
if (!string.IsNullOrEmpty(import.assetBundleVariant))
import.assetBundleVariant = string.Empty;
}
}
/// <summary>
/// 构建bundle
/// </summary>
private void Building()
{
BuildAssetBundle();
int target = (int)targetContent;
BuildPipeline.BuildAssetBundles(outputPath, buildList.ToArray(), (BuildAssetBundleOptions)compressionOption, (BuildTarget)target);
}
/// <summary>
/// 设置bundle标签
/// </summary>
private void BuildAssetBundle()
{
BuildRewBundle();
BuildResBundle();
}
/// <summary>
/// 给Rew文件夹下所有文件设置bundle标签
/// </summary>
private void BuildRewBundle()
{
DirectoryInfo directoryInfo = new DirectoryInfo(rewRootPath);
DirectoryInfo[] rewDirectories = directoryInfo.GetDirectories();
for (int i = 0; i < rewDirectories.Length; i++)
{
DirectoryInfo info = rewDirectories[i];
string infoName = info.Name;
if (infoName.Equals("ui") || infoName.Equals("Scenes"))
continue;
else if (infoName.Equals("shaders")) BuildRewShadersBundle(info);
else if (infoName.Equals("Effects")) BuildRewEffectsBundle(info);
else if (infoName.Equals("Character")) BuildRewCharacterBundle(info);
}
}
/// <summary>
/// 给rew文件夹下Character文件夹下文件设置bundle标签
/// </summary>
/// <param name="info">Character文件夹实例</param>
private void BuildRewCharacterBundle(DirectoryInfo info)
{
DirectoryInfo[] dirs = info.GetDirectories();
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
if (dir.Name.Equals("Animations"))
{
string bundleName = GetAnimationsDirBundleName(dir);
BuildBundle(bundleName, dir);
}
else
{
BuildRewCharacterBundle(dir);
}
}
}
/// <summary>
/// 获取Animations文件夹的bundle名
/// </summary>
/// <param name="dir"></param>
private string GetAnimationsDirBundleName(DirectoryInfo dir)
{
string sprChar = "\\";
#if UNITY_EDITOR_OSX
sprChar = "/";
#endif
string fullName = dir.FullName;
string preStr = "";
string tempStr = "";
while (true)
{
tempStr = fullName.Substring(fullName.LastIndexOf(sprChar) + 1);
if (tempStr.Equals("Character")) break;
fullName = fullName.Substring(0, fullName.LastIndexOf(sprChar));
if (string.IsNullOrEmpty(preStr))
{
preStr = string.Format("{0}{1}", preStr, tempStr);
}
else
{
preStr = string.Format("{0}_{1}", preStr, tempStr);
}
}
return preStr;
}
/// <summary>
/// rew文件夹下Effects文件夹下文件设置bundle标签
/// </summary>
/// <param name="info">Effects文件夹实例</param>
private void BuildRewEffectsBundle(DirectoryInfo info)
{
DirectoryInfo[] dirInfos = info.GetDirectories();
for (int i = 0; i < dirInfos.Length; i++)
{
DirectoryInfo dirInfo = dirInfos[i];
if (dirInfo.Name.Equals("Base"))
{
DirectoryInfo[] baseDirs = dirInfo.GetDirectories();
for (int j = 0; j < baseDirs.Length; j++)
{
DirectoryInfo temp = baseDirs[j];
BuildBundle(temp.Name, temp, "eff_base");
}
BuildBundle(dirInfo, "eff_base");
}
else if (dirInfo.Name.Equals("Models"))
{
DirectoryInfo[] baseDirs = dirInfo.GetDirectories();
for (int j = 0; j < baseDirs.Length; j++)
{
DirectoryInfo molInfo = baseDirs[j];
if (molInfo.Name.Equals("Materials"))
{
BuildBundle(molInfo, "eff_models");
}
}
}
}
}
/// <summary>
/// 文件夹内所有文件构成一个bundle
/// </summary>
/// <param name="bunleName">bundle名称</param>
/// <param name="info">文件夹实例</param>
/// <param name="prefix">前缀</param>
private void BuildBundle(string bunleName, DirectoryInfo info, string prefix = null)
{
FileInfo[] fs = info.GetFiles();
List<string> assetNames = new List<string>();
AssetBundleBuild build = new AssetBundleBuild();
if (prefix != null)
{
bunleName = string.Format("{0}_{1}", prefix, bunleName);
}
build.assetBundleName = bunleName.ToLower();
for (int i = 0; i < fs.Length; i++)
{
FileInfo f = fs[i];
if (f.Extension == ".meta")
continue;
string path = f.FullName;
string assetName = path.Substring(path.IndexOf("Assets"));
assetNames.Add(assetName);
}
build.assetNames = assetNames.ToArray();
buildList.Add(build);
//用 AssetImporter 类 修改名称和后缀
string dirPath = info.FullName.Substring(info.FullName.IndexOf("Assets"));
AssetImporter assetImporter = AssetImporter.GetAtPath(dirPath);
assetImporter.assetBundleName = bunleName.ToLower();
assetImporter.SaveAndReimport();
}
/// <summary>
/// 此文件设置为单个bundle
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="info">文件实例</param>
private void BuildBundle(string fileName, FileInfo info)
{
AssetBundleBuild build = new AssetBundleBuild();
string bundleName = fileName.Split(new char[] { '.' })[0];
string path = info.FullName;
string assetName = path.Substring(path.IndexOf("Assets"));
build.assetNames = new string[] { assetName };
build.assetBundleName = bundleName.ToLower();
buildList.Add(build);
//用 AssetImporter 类 修改名称和后缀
AssetImporter assetImporter = AssetImporter.GetAtPath(assetName);
assetImporter.assetBundleName = bundleName.ToLower();
assetImporter.SaveAndReimport();
}
/// <summary>
/// 文件夹内的每个文件都是一个bundle
/// </summary>
/// <param name="info">文件夹实例</param>
/// <param name="prefix">前缀</param>
private void BuildBundle(DirectoryInfo info, string prefix = null)
{
FileInfo[] infos = info.GetFiles();
for (int i = 0; i < infos.Length; i++)
{
FileInfo fileInfo = infos[i];
//忽视unity自身生成的meta文件
if (fileInfo.Extension == ".meta")
continue;
string bunleName = fileInfo.Name;
if (prefix != null)
{
bunleName = string.Format("{0}_{1}", prefix, bunleName);
}
BuildBundle(bunleName, fileInfo);
}
}
/// <summary>
/// 设置rew文件夹下Shaders文件夹下文件的bundle标签
/// </summary>
/// <param name="info">Shaders文件夹实例</param>
private void BuildRewShadersBundle(DirectoryInfo info)
{
BuildBundle(info);
}
/// <summary>
/// 给Res文件夹下所有文件设置bundle标签
/// </summary>
private void BuildResBundle()
{
DirectoryInfo directoryInfo = new DirectoryInfo(resRootPath);
DirectoryInfo[] resDirectories = directoryInfo.GetDirectories();
int dirCount = resDirectories.Length;
for (int i = 0; i < dirCount; i++)
{
DirectoryInfo info = resDirectories[i];
string dirName = info.Name;
if (dirName.Equals("Splat")) BuildResSplatBundle(info);
}
for (int i = 0; i < dirCount; i++)
{
DirectoryInfo info = resDirectories[i];
string dirName = info.Name;
if (dirName.Equals("Prefabs")) BuildResPrefabBundle(info);
}
for (int i = 0; i < dirCount; i++)
{
DirectoryInfo info = resDirectories[i];
string dirName = info.Name;
if (dirName.Equals("ReaderTexture")) BuildResRTBundle(info);
}
for (int i = 0; i < dirCount; i++)
{
DirectoryInfo info = resDirectories[i];
string dirName = info.Name;
if (dirName.Equals("Scenes")) BuildResSceneBundle(info);
}
}
/// <summary>
/// 设置Res文件夹下Prefabs文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildResPrefabBundle(DirectoryInfo info)
{
DirectoryInfo[] dirs = info.GetDirectories();
int dirCount = dirs.Length;
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
string dirName = dir.Name;
if (dirName.Equals("Bullet")) BuildPrefabBullet(dir);
}
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
string dirName = dir.Name;
if (dirName.Equals("Effects")) BuildPrefabEffect(dir);
}
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
string dirName = dir.Name;
if (dirName.Equals("character")) BuildPrefabCharacter(dir);
}
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
string dirName = dir.Name;
if (dirName.Equals("UI")) BuildPrefabUI(dir);
}
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
string dirName = dir.Name;
if (dirName.Equals("Scenes")) BuildPrefabScenes(dir);
}
}
/// <summary>
/// 设置Res文件夹下Prefabs文件夹下Scenes文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildPrefabScenes(DirectoryInfo dir)
{
DirectoryInfo[] dirInfos = dir.GetDirectories();
for (int j = 0; j < dirInfos.Length; j++)
{
DirectoryInfo dirInfo = dirInfos[j];
BuildBundle(dirInfo.Name, dirInfo);
}
}
/// <summary>
/// 设置Res文件夹下Prefabs文件夹下Bullet文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildPrefabBullet(DirectoryInfo info)
{
BuildBundle(info);
}
/// <summary>
/// 设置Res文件夹下Prefabs文件夹下character文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildPrefabCharacter(DirectoryInfo info)
{
// 暂时先直接处理,后面要分成种族等文件夹之后再处理
BuildBundle(info);
}
/// <summary>
/// 设置Res文件夹下Prefabs文件夹下Effects文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildPrefabEffect(DirectoryInfo info)
{
BuildBundle(info);
}
/// <summary>
/// 设置Res文件夹下Prefabs文件夹下UI文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildPrefabUI(DirectoryInfo info)
{
// UI现在还差一个公共图集的文件夹
DirectoryInfo[] dirs = info.GetDirectories();
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
string dirName = dir.Name;
if (dirName.Equals("UIRoot"))
{
BuildBundle(dir);
}
else if (dirName.Equals("UITemplate"))
{
continue;
}
else if (dirName.Equals("UIView"))
{
DirectoryInfo[] dirInfos = dir.GetDirectories();
for (int j = 0; j < dirInfos.Length; j++)
{
DirectoryInfo dirInfo = dirInfos[j];
BuildBundle(dirInfo.Name, dirInfo);
}
}
}
}
/// <summary>
/// 设置Res文件夹下RenderTexture文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildResRTBundle(DirectoryInfo info)
{
BuildBundle(info, "RT");
}
/// <summary>
/// 设置Res文件夹下Scenes文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildResSceneBundle(DirectoryInfo info)
{
// 场景打包暂时这样处理,后面要把场景分割通过配置文件对位置
DirectoryInfo[] dirs = info.GetDirectories();
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
DirectoryInfo[] dirInfos = dir.GetDirectories();
for (int j = 0; j < dirInfos.Length; j++)
{
DirectoryInfo dirInfo = dirInfos[j];
BuildSceneLightMap(dirInfo);
}
BuildBundle(dir, "scene");
}
}
/// <summary>
/// lightMap文件夹比较特殊,要把LightingData.asset文件剔除
/// </summary>
/// <param name="dirInfo"></param>
private void BuildSceneLightMap(DirectoryInfo dirInfo)
{
FileInfo[] infos = dirInfo.GetFiles();
List<string> assetNames = new List<string>();
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = dirInfo.Name.ToLower();
for (int i = 0; i < infos.Length; i++)
{
FileInfo fileInfo = infos[i];
//忽视unity自身生成的meta文件
if (fileInfo.Extension == ".meta" || fileInfo.Extension == ".asset")
continue;
string path = fileInfo.FullName;
string assetName = path.Substring(path.IndexOf("Assets"));
assetNames.Add(assetName);
//用 AssetImporter 类 修改名称和后缀
AssetImporter assetImporter = AssetImporter.GetAtPath(assetName);
assetImporter.assetBundleName = dirInfo.Name.ToLower();
assetImporter.SaveAndReimport();
}
build.assetNames = assetNames.ToArray();
buildList.Add(build);
}
/// <summary>
/// 设置Res文件夹下Splat文件夹下文件bundle标签
/// </summary>
/// <param name="info"></param>
private void BuildResSplatBundle(DirectoryInfo info)
{
DirectoryInfo[] dirs = info.GetDirectories();
for (int i = 0; i < dirs.Length; i++)
{
DirectoryInfo dir = dirs[i];
BuildBundle(dir.Name, dir, "Splat");
}
BuildBundle(info, "Splat");
}
/// <summary>
/// 切换平台
/// </summary>
/// <returns></returns>
private bool SwitchPlatform()
{
int target = (int)targetContent;
BuildTargetGroup group = BuildTargetGroup.Standalone;
switch (targetContent)
{
case BuildTargetType.iOS:
group = BuildTargetGroup.iOS;
break;
case BuildTargetType.Android:
group = BuildTargetGroup.Android;
break;
default:
break;
}
return EditorUserBuildSettings.SwitchActiveBuildTarget(group, (BuildTarget)target);
}
/// <summary>
/// 压缩贴图
/// </summary>
private void CompressTexture()
{
Debug.Log("压缩贴图!");
}
/// <summary>
/// 压缩图集
/// </summary>
private void CompressAtlas()
{
Debug.Log("压缩图集!");
}
private void BuildFileIndex() {
///----------------------创建文件列表-----------------------
string newFilePath = outputPath + "/files.txt";
if (File.Exists(newFilePath)) File.Delete(newFilePath);
List<string> files = Recursive(outputPath);
FileStream fs = new FileStream(newFilePath, FileMode.CreateNew);
StreamWriter sw = new StreamWriter(fs);
for (int i = 0; i < files.Count; i++) {
string file = files[i];
//string ext = Path.GetExtension(file);
if (file.EndsWith(".meta") || file.Contains(".DS_Store")) continue;
string md5 = md5file(file);
string value = file.Replace(outputPath+"/", string.Empty);
sw.WriteLine(value + "|" + md5);
}
sw.Close(); fs.Close();
}
private List<string> Recursive(string path)
{
List<string> files = new List<string>();
string[] names = Directory.GetFiles(path);
string[] dirs = Directory.GetDirectories(path);
foreach (string filename in names)
{
string ext = Path.GetExtension(filename);
if (ext.Equals(".meta")) continue;
files.Add(filename.Replace('\\', '/'));
}
return files;
}
private string md5file(string file)
{
try
{
FileStream fs = new FileStream(file, FileMode.Open);
System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] retVal = md5.ComputeHash(fs);
fs.Close();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < retVal.Length; i++)
{
sb.Append(retVal[i].ToString("x2"));
}
return sb.ToString();
}
catch (Exception ex)
{
throw new Exception("md5file() fail, error:" + ex.Message);
}
}
}
}
AssetBundleTree类是AssetBundleTool提供的类,我在此基础上添加冗余资源检查,如果发现在标签的最左侧显示警告图标,目前显示有点小bug,冗余内容已筛选正确,如果有需要的,下次我在进行分享