using System; using System.Collections.Generic; using System.IO; using GFGEditor; using GFGGame; using UnityEditor; using UnityEngine; namespace VEngine.Editor.Builds { public class BuildTask { private readonly string[] EXCLUDE_EXTS = new string[] { ".meta", ".bat" }; private readonly string[] EXCLUDE_DIRS = new string[] { "Assets/Res/.svn", ImportArtResTool.Md5FilePath }; 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, string setName) { List hideFilePathList = new List(); List hideDirPahtList = new List(); //换装资源打包屏蔽 SqliteController.Instance.dirPath = Path.Combine(Environment.CurrentDirectory, ResPathUtil.CONFIG_DIR_PATH); SqliteController.Instance.Init(false, null); var dressUpItemList = ItemCfgArray.Instance.GetCfgsByitemType(ConstItemType.DRESS_UP); foreach (var itemCfg in dressUpItemList) { if (itemCfg.isHide <= 0) { continue; } if (!string.IsNullOrEmpty(itemCfg.resLayer1)) { HideItemRes(itemCfg, 1, hideFilePathList, hideDirPahtList); } if (!string.IsNullOrEmpty(itemCfg.resLayer2)) { HideItemRes(itemCfg, 2, hideFilePathList, hideDirPahtList); } if (!string.IsNullOrEmpty(itemCfg.resLayer3)) { HideItemRes(itemCfg, 3, hideFilePathList, hideDirPahtList); } } //套装动作资源打包屏蔽 var suitCfgs = SuitCfgArray.Instance.dataArray; foreach(var suitCfg in suitCfgs) { HideSuitActionRes(suitCfg, hideFilePathList, hideDirPahtList); } //卡牌资源打包屏蔽 //var cardCfgs = ItemCfgArray.Instance.GetCfgsByitemType(ConstItemType.CARD); //foreach(var itemCfg in cardCfgs) //{ // HideCardRes(itemCfg, hideFilePathList, hideDirPahtList); //} foreach (var resRootDirName in resRootDirNames) { CreateBundles(resRootDirName, setName, hideFilePathList, hideDirPahtList); } CheckAssets(); EditorUtility.ClearProgressBar(); FinishBuild(); } private void HideItemRes(ItemCfg itemCfg, int layerId, List hideFilePathList, List hideDirPahtList) { string res = DressUpUtil.GetDressUpItemLayerRes(itemCfg, layerId); //部件图 var resPath = ResPathUtil.GetDressUpPath(res, ItemUtil.GetItemResExt(itemCfg.itemType, itemCfg.subType)); hideFilePathList.Add(resPath); //动画 resPath = ResPathUtil.GetDressUpAnimationDirPath(res); hideDirPahtList.Add(resPath); //特效 resPath = ResPathUtil.GetDressUpEffectDirPath(res); hideDirPahtList.Add(resPath); resPath = ResPathUtil.GetDressUpEffectDirPath(res, true); hideDirPahtList.Add(resPath); } private void HideSuitActionRes(SuitCfg suitCfg, List hideFilePathList, List hideDirPahtList) { if(suitCfg.isHide <= 0) { return; } string resPath; //动画 if(!string.IsNullOrEmpty(suitCfg.aniRes)) { resPath = ResPathUtil.GetDressUpAnimationDirPath(suitCfg.aniRes); hideDirPahtList.Add(resPath); } //特效 if (!string.IsNullOrEmpty(suitCfg.effRes)) { resPath = ResPathUtil.GetDressUpEffectDirPath(suitCfg.effRes); hideDirPahtList.Add(resPath); resPath = ResPathUtil.GetDressUpEffectDirPath(suitCfg.effRes, true); hideDirPahtList.Add(resPath); } } //private void HideCardRes(ItemCfg itemCfg, List hideFilePathList, List hideDirPahtList) //{ // //大图 // var resPath = ResPathUtil.GetCardPath(itemCfg.res); // hideFilePathList.Add(resPath); // //小图 // resPath = ResPathUtil.GetCardSmallPath(itemCfg.res); // hideFilePathList.Add(resPath); // //动画 // resPath = ResPathUtil.GetCardAnimationDirPath(itemCfg.res); // hideDirPahtList.Add(resPath); // //特效 // //to do...暂时没有 //} private void CreateBundles(string resRootDirName, string setName, List hideFilePathList, List hideDirPahtList) { var path = Path.Combine(Application.dataPath, resRootDirName); var buildSetting = GFGEditor.BuildSetting.GetBuildSetting(setName); var dirBundleList = buildSetting.dirBundleList; var excludeDirs = buildSetting.dirBundleList.GetRange(0, buildSetting.dirBundleList.Count); excludeDirs.AddRange(EXCLUDE_DIRS); GFGEditor.FileUtil.ForeachFileInDir(path, excludeDirs, (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 + "/", ""); if(hideFilePathList.Contains(filePath)) { return; } 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(); //以文件名标识分组 for (int j = 0; j < buildSetting.dirTypeList.Count; j++) { string str = buildSetting.dirTypeList[j]; if (bundle.IndexOf(str) >= 0) { bundle = str.Substring(0, str.Length - 1); break; } } 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 + "/", ""); if(hideDirPahtList.Contains(subDir)) { return; } var bundle = subDir.Replace($"Assets/{resRootDirName}/", ""); bundle = bundle.Replace('/', '_'); bundle = bundle.ToLower(); GFGEditor.FileUtil.ForeachFileInDir(subDirPath, null, (string 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 unityBuildPath = Settings.PlatformBuildPath; if (!string.IsNullOrEmpty(GFGGame.LauncherConfig.resKey)) { unityBuildPath = Settings.UnityBuildPath; } if (bundles.Count <= 0) return; var manifest = BuildPipeline.BuildAssetBundles(unityBuildPath, 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; } //if (!string.IsNullOrEmpty(GFGGame.LauncherConfig.resKey)) //{ // string buildPath = Settings.PlatformBuildPath; // CreateEncryptAssets(unityBuildPath, buildPath, manifest, GFGGame.LauncherConfig.resKey); //} AfterBuildBundles(bundles, manifest); } /// /// 创建加密的AssetBundle /// //public static void CreateEncryptAssets(string bundlePackagePath, string encryptAssetPath, AssetBundleManifest manifest, string secretKey) //{ // if (!Directory.Exists(encryptAssetPath)) // { // Directory.CreateDirectory(encryptAssetPath); // } // string[] assetBundles = manifest.GetAllAssetBundles(); // foreach (string assetBundle in assetBundles) // { // string bundlePath = Path.Combine(bundlePackagePath, assetBundle); // byte[] encryptBytes = EncryptHelper.CreateEncryptData(bundlePath, secretKey); // using (FileStream fs = new FileStream(Path.Combine(encryptAssetPath, assetBundle), FileMode.OpenOrCreate)) // { // fs.SetLength(0); // fs.Write(encryptBytes, 0, encryptBytes.Length); // } // } //} /// /// 创建加密的AssetBundle /// public static void CreateEncryptAsset(string unityBundlesPath, string bundlesPath, string unityBundle, string secretKey) { if (!Directory.Exists(bundlesPath)) { Directory.CreateDirectory(bundlesPath); } string unityBundlePath = Path.Combine(unityBundlesPath, unityBundle); byte[] encryptBytes = EncryptHelper.CreateEncryptData(unityBundlePath, secretKey); using (FileStream fs = new FileStream(Path.Combine(bundlesPath, unityBundle), FileMode.OpenOrCreate)) { fs.SetLength(0); fs.Write(encryptBytes, 0, encryptBytes.Length); } } 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); } 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); if (!string.IsNullOrEmpty(GFGGame.LauncherConfig.resKey)) { string buildPath = Settings.PlatformBuildPath; CreateEncryptAsset(Settings.UnityBuildPath, buildPath, bundle.nameWithAppendHash, GFGGame.LauncherConfig.resKey); } } var file = Settings.GetBuildPath(bundle.nameWithAppendHash); if (File.Exists(file)) using (var stream = File.OpenRead(file)) { bundle.size = stream.Length; bundle.crc = Utility.ComputeCRC32(stream); } else Debug.LogErrorFormat("File not found: {0}", file); 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); } } }