using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; namespace VEngine.Editor.Builds { public class BuildTask { private readonly string[] EXCLUDE_EXTS = new string[] { ".meta" }; private readonly BuildAssetBundleOptions buildAssetBundleOptions; private readonly List bundledAssets = new List(); private readonly string bundleExtension; public readonly string name; private readonly Dictionary pathWithAssets = new Dictionary(); public BuildTask() { name = nameof(Manifest); buildAssetBundleOptions = BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.AppendHashToAssetBundleName; bundleExtension = ".unity3d"; } public Record record { get; private set; } private static string GetRecordsPath(string buildName) { return Settings.GetBuildPath($"build_records_for_{buildName}.json"); } private static void WriteRecord(Record record) { var records = GetRecords(record.build); records.data.Insert(0, record); File.WriteAllText(GetRecordsPath(record.build), JsonUtility.ToJson(records)); } private static Records GetRecords(string build) { var records = ScriptableObject.CreateInstance(); var path = GetRecordsPath(build); if (File.Exists(path)) JsonUtility.FromJsonOverwrite(File.ReadAllText(path), records); return records; } private static void DisplayProgressBar(string title, string content, int index, int max) { EditorUtility.DisplayProgressBar($"{title}({index}/{max}) ", content, index * 1f / max); } public void BuildBundles() { var assetBundleNames = AssetDatabase.GetAllAssetBundleNames(); for (var i = 0; i < assetBundleNames.Length; i++) { var assetBundleName = assetBundleNames[i]; DisplayProgressBar("采集资源", assetBundleName, i, assetBundleNames.Length); var assetNames = AssetDatabase.GetAssetPathsFromAssetBundle(assetBundleName); bundledAssets.AddRange(Array.ConvertAll(assetNames, input => new Asset { path = input, bundle = assetBundleName })); } CheckAssets(); EditorUtility.ClearProgressBar(); FinishBuild(); } public void BuildCustomBundles(string[] resRootDirNames) { foreach(var resRootDirName in resRootDirNames) { CreateBundles(resRootDirName); } CheckAssets(); EditorUtility.ClearProgressBar(); FinishBuild(); } private void CreateBundles(string resRootDirName) { var path = Path.Combine(Application.dataPath, resRootDirName); var buildSetting = GFGEditor.BuildSetting.GetBuildSetting(); var dirBundleList = buildSetting.dirBundleList; GFGEditor.FileUtil.ForeachFileInDir(path, dirBundleList, (string file) => { var ext = Path.GetExtension(file); if (Array.IndexOf(EXCLUDE_EXTS, ext) < 0) { file = file.Replace('\\', '/'); string curDir = Environment.CurrentDirectory.Replace("\\", "/"); string filePath = file.Replace(curDir + "/", ""); EditorUtility.DisplayProgressBar("采集资源", filePath, 1f); var bundle = filePath.Replace($"Assets/{resRootDirName}/", ""); bundle = bundle.Replace('/', '_'); var i = bundle.IndexOf("."); bundle = bundle.Substring(0, i); bundle = bundle.ToLower(); bundledAssets.Add(new Asset { path = filePath, bundle = bundle }); } }); foreach (var dir in dirBundleList) { var dirPath = Path.Combine(Environment.CurrentDirectory, dir); if(!GFGEditor.FileUtil.CheckPathInParent(dirPath, path)) { continue; } GFGEditor.FileUtil.ForeachDirInDir(dirPath, (string subDirPath) => { var targetDirPath = subDirPath.Replace('\\', '/'); string curDirPath = Environment.CurrentDirectory.Replace("\\", "/"); string subDir = targetDirPath.Replace(curDirPath + "/", ""); var bundle = subDir.Replace($"Assets/{resRootDirName}/", ""); bundle = bundle.Replace('/', '_'); bundle = bundle.ToLower(); GFGEditor.FileUtil.ForeachFileInDir(subDirPath, null, (string file) => { var ext = Path.GetExtension(file); file = file.Replace('\\', '/'); string curDir = Environment.CurrentDirectory.Replace("\\", "/"); string filePath = file.Replace(curDir + "/", ""); EditorUtility.DisplayProgressBar("采集资源", filePath, 1f); bundledAssets.Add(new Asset { path = filePath, bundle = bundle }); }); }); } } private void CheckAssets() { for (var i = 0; i < bundledAssets.Count; i++) { var asset = bundledAssets[i]; if (!pathWithAssets.TryGetValue(asset.path, out var ba)) { pathWithAssets[asset.path] = asset; } else { bundledAssets.RemoveAt(i); i--; Debug.LogWarningFormat("{0} can't pack with {1}, because already pack to {2}", asset.path, asset.bundle, ba.bundle); } } } private void FinishBuild() { var bundles = new List(); var dictionary = new Dictionary>(); foreach (var asset in bundledAssets) { if (!dictionary.TryGetValue(asset.bundle, out var assets)) { assets = new List(); dictionary.Add(asset.bundle, assets); bundles.Add(new ManifestBundle { name = asset.bundle, assets = assets }); } assets.Add(asset.path); } var outputPath = Settings.PlatformBuildPath; if (bundles.Count <= 0) return; var manifest = BuildPipeline.BuildAssetBundles(outputPath, bundles.ConvertAll(bundle => new AssetBundleBuild { assetNames = bundle.assets.ToArray(), assetBundleName = bundle.name }).ToArray(), buildAssetBundleOptions | BuildAssetBundleOptions.AppendHashToAssetBundleName, EditorUserBuildSettings.activeBuildTarget); if (manifest == null) { Debug.LogErrorFormat("Failed to build {0}.", name); return; } AfterBuildBundles(bundles, manifest); } private string GetOriginBundle(string assetBundle) { var pos = assetBundle.LastIndexOf("_", StringComparison.Ordinal) + 1; var hash = assetBundle.Substring(pos); if (!string.IsNullOrEmpty(bundleExtension)) hash = hash.Replace(bundleExtension, ""); var originBundle = $"{assetBundle.Replace("_" + hash, "")}"; return originBundle; } private void AfterBuildBundles(List bundles, AssetBundleManifest manifest) { var nameWithBundles = new Dictionary(); for (var i = 0; i < bundles.Count; i++) { var bundle = bundles[i]; bundle.id = i; nameWithBundles[bundle.name] = bundle; } if (manifest != null) { var assetBundles = manifest.GetAllAssetBundles(); foreach (var assetBundle in assetBundles) { var originBundle = GetOriginBundle(assetBundle); var dependencies = Array.ConvertAll(manifest.GetAllDependencies(assetBundle), GetOriginBundle); if (nameWithBundles.TryGetValue(originBundle, out var manifestBundle)) { manifestBundle.nameWithAppendHash = assetBundle; manifestBundle.dependencies = Array.ConvertAll(dependencies, input => nameWithBundles[input].id); var file = Settings.GetBuildPath(assetBundle); if (File.Exists(file)) using (var stream = File.OpenRead(file)) { manifestBundle.size = stream.Length; manifestBundle.crc = Utility.ComputeCRC32(stream); } else Debug.LogErrorFormat("File not found: {0}", file); } else { Debug.LogErrorFormat("Bundle not exist: {0}", originBundle); } } } CreateManifest(bundles); } private void CreateManifest(List bundles) { var manifest = Settings.GetManifest(); manifest.version++; manifest.appVersion = UnityEditor.PlayerSettings.bundleVersion; var getBundles = manifest.GetBundles(); var newFiles = new List(); var newSize = 0L; foreach (var bundle in bundles) if (!getBundles.TryGetValue(bundle.name, out var value) || value.nameWithAppendHash != bundle.nameWithAppendHash) { newFiles.Add(bundle.nameWithAppendHash); newSize += bundle.size; } manifest.bundles = bundles; var newFilesSize = Utility.FormatBytes(newSize); newFiles.AddRange(WriteManifest(manifest)); // write upload files var filename = Settings.GetBuildPath($"upload_files_for_{manifest.name}_{manifest.version}.txt"); File.WriteAllText(filename, string.Join("\n", newFiles.ToArray())); record = new Record { build = name, version = manifest.version, files = newFiles, size = newSize, time = DateTime.Now.ToFileTime() }; WriteRecord(record); Debug.LogFormat("Build bundles with {0}({1}) files with version {2} for {3}.", newFiles.Count, newFilesSize, manifest.version, manifest.name); } private static IEnumerable WriteManifest(Manifest manifest) { var newFiles = new List(); var filename = $"{manifest.name}"; var version = manifest.version; WriteJson(manifest, filename, newFiles); var path = Settings.GetBuildPath(filename); var crc = Utility.ComputeCRC32(path); var info = new FileInfo(path); WriteJson(manifest, $"{filename}_v{version}_{crc}", newFiles); // for version file var manifestVersion = ScriptableObject.CreateInstance(); manifestVersion.crc = crc; manifestVersion.size = info.Length; manifestVersion.version = version; manifestVersion.appVersion = manifest.appVersion; WriteJson(manifestVersion, Manifest.GetVersionFile(filename), newFiles); WriteJson(manifestVersion, $"{filename}_v{version}_{crc}.version", newFiles); return newFiles; } private static void WriteJson(ScriptableObject so, string file, List newFiles) { newFiles.Add(file); var json = JsonUtility.ToJson(so); File.WriteAllText(Settings.GetBuildPath(file), json); } } }