using UnityEditor; using UnityEngine; using System; using System.IO; using System.Text; using System.Collections.Generic; using System.Security.Cryptography; using System.Diagnostics; //using XGame; using UnityEditor.Build.Player; using HybridCLR.Editor; using Debug = UnityEngine.Debug; #if !UNITY_WEBGL using HybridCLR; using UnityEditor.Build.Content; using UnityEditor.Build.Reporting; #if UNITY_EDITOR using HybridCLR.Editor.Commands; #endif #endif public class Packager { //#if UNITY_STANDALONE_WIN static public BuildTarget buildTaget = BuildTarget.StandaloneWindows; //其他平台。。。。。。 //#endif public static string OutputName = "LocalTest"; public static string platform = string.Empty; static Dictionary m_mapResNameDic = new Dictionary(); static List maps = new List(); static List paths = new List(); static List files = new List(); static long UpdateDT = 0; static string sPlatform = "pc"; static Dictionary m_dicFilelist = new Dictionary(); static string m_strDllFileMD5; static string m_strResFileMD5; public static string ProjectDir => Directory.GetParent(Application.dataPath).ToString(); public static string HybridCLRDataDir { get; } = $"{ProjectDir}/HybridCLRData"; public static string LocalIl2CppDir => $"{HybridCLRDataDir}/LocalIl2CppData/il2cpp"; public static string AssembliesPostIl2CppStripDir => $"{HybridCLRDataDir}/AssembliesPostIl2CppStrip"; public static string GetAssembliesPostIl2CppStripDir(BuildTarget target) { return $"{AssembliesPostIl2CppStripDir}/{target}"; } //需要导出的aot文件,运行的时候会全部更新 public static List AOTMetaAssemblyNames { get; } = new List() { "mscorlib.dll", "System.dll", "System.Core.dll", // 如果使用了Linq,需要这个 }; static Dictionary DicAssetsMap = new Dictionary(); public static string[] HotfixDir = { "res/scene", "res/character","res/effect", "res/ui", "arts"};//shader单独打 public static string GetUpdatePath() { #if UNITY_WEBGL return $"{XGame.AppConst.WebGL}/StreamingAssets/"; #else return $"{XGame.AppConst.UpdatePath}{sPlatform}\\";//$"d:/HTTP/update/{sPlatform}/";// #endif } public static string DataPath { get { return Application.dataPath + "/StreamAssets/"; } } public static string md5(string source) { MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); byte[] data = System.Text.Encoding.UTF8.GetBytes(source); byte[] md5Data = md5.ComputeHash(data, 0, data.Length); md5.Clear(); string destString = ""; for (int i = 0; i < md5Data.Length; i++) { destString += System.Convert.ToString(md5Data[i], 16).PadLeft(2, '0'); } destString = destString.PadLeft(32, '0'); return destString; } public static string md5file(string file) { long size = 0; return md5file(file, out size); } public static string md5file(string file, out long size) { try { FileStream fs = new FileStream(file, FileMode.Open); System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); byte[] retVal = md5.ComputeHash(fs); size = fs.Length; 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); } } public static void CopyLuaBytesFiles(string sourceDir, string destDir, bool appendext = true, string searchPattern = "*.lua", SearchOption option = SearchOption.AllDirectories) { if (!Directory.Exists(sourceDir)) { return; } string[] files = Directory.GetFiles(sourceDir, searchPattern, option); int len = sourceDir.Length; if (sourceDir[len - 1] == '/' || sourceDir[len - 1] == '\\') { --len; } for (int i = 0; i < files.Length; i++) { string str = files[i].Remove(0, len); string dest = destDir + "/" + str; if (appendext) dest += ".bytes"; string dir = Path.GetDirectoryName(dest); Directory.CreateDirectory(dir); File.Copy(files[i], dest, true); } } ///----------------------------------------------------------- static string[] exts = { ".txt", ".xml", ".lua", ".assetbundle", ".json" }; static bool CanCopy(string ext) { //能不能复制 foreach (string e in exts) { if (ext.Equals(e)) return true; } return false; } /// /// 载入素材 /// static UnityEngine.Object LoadAsset(string file) { if (file.EndsWith(".lua")) file += ".txt"; return AssetDatabase.LoadMainAssetAtPath("Assets/" + file); } public static void BuildDLL() { UnityEngine.Debug.LogFormat("编译DLL"); } public static void BuildBasePack(string OutputPath, BuildTargetGroup targetGroup, BuildTarget target) { //生成最终底包 string[] SCENES = FindEnabledEditorScenes(); if (EditorUserBuildSettings.activeBuildTarget != target) EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, target); CSObjectWrapEditor.Generator.ClearAll(); CSObjectWrapEditor.Generator.GenAll(); HybridCLR.Editor.Commands.LinkGeneratorCommand.GenerateLinkXml(); PlayerSettings.Android.keystoreName = "Android/android.keystore"; PlayerSettings.Android.keyaliasName = "xw"; PlayerSettings.keystorePass = "abcd12"; PlayerSettings.keyaliasPass = "abcd12"; PlayerSettings.Android.androidIsGame = true; PlayerSettings.SetIncrementalIl2CppBuild(BuildTargetGroup.Android, true); EditorUserBuildSettings.buildScriptsOnly = false; EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; //Release EditorUserBuildSettings.development = false; EditorUserBuildSettings.allowDebugging = false; EditorUserBuildSettings.androidBuildType = AndroidBuildType.Release; AssetDatabase.Refresh(); //保证这个打出来的使用设置好的路径 //PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroup, "LOCAL"); BuildPipeline.BuildPlayer(SCENES, OutputPath, target, BuildOptions.None); //CSObjectWrapEditor.Generator.ClearAll(); UnityEngine.Debug.Log("Build OK!"); } public static bool BuildAPK(string apkBuildName = "", bool bHotfix = true, bool bRelease = true) { //HybridCLR.BuildConfig.Setup(); //设置il2cpp的环境变量 string[] args = Environment.GetCommandLineArgs(); Debug.LogFormat("CommandLineArgs:{0}", string.Join(",", args)); if (bHotfix) { HybridCLR.Editor.SettingsUtil.Enable = true; } else { HybridCLR.Editor.SettingsUtil.Enable = false; } //切换到目标平台 if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android) EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); CSObjectWrapEditor.Generator.ClearAll(); CSObjectWrapEditor.Generator.GenAll(); PlayerSettings.allowUnsafeCode = true; PlayerSettings.SetManagedStrippingLevel(BuildTargetGroup.Android, ManagedStrippingLevel.Low); PlayerSettings.SplashScreen.show = false; PlayerSettings.SplashScreen.showUnityLogo = false; PlayerSettings.Android.keystoreName = "Assets/60s.keystore"; PlayerSettings.Android.keyaliasName = "60s"; PlayerSettings.keystorePass = "abcd12"; PlayerSettings.keyaliasPass = "abcd12"; PlayerSettings.Android.preferredInstallLocation = AndroidPreferredInstallLocation.Auto; // s = GetCommandLineValue(args, "-svn_version"); // PlayerSettings.Android.bundleVersionCode = string.IsNullOrEmpty(s) ? 1 : int.Parse(s); // 上传符号表文件时需要该值在一次打包中不改变 //PlayerSettings.Android.bundleVersionCode = int.Parse(BuildSettings.VersionCode.Replace(".", "")); PlayerSettings.Android.startInFullscreen = true; PlayerSettings.Android.androidIsGame = true; PlayerSettings.SetIncrementalIl2CppBuild(BuildTargetGroup.Android, true); EditorUserBuildSettings.buildScriptsOnly = false; EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; BuildOptions buildOptions = BuildOptions.None; if (bRelease) { EditorUserBuildSettings.development = false; EditorUserBuildSettings.allowDebugging = false; EditorUserBuildSettings.androidBuildType = AndroidBuildType.Release; //IL2CPP C++ 编译配置默认是 Debug(带 -g 调试符号),对 HybridCLR 生成的超大 lump 做 codegen 时 //易触发 NDK clang 段错误(clang frontend command failed due to signal)。发布包统一用 Release 去掉 -g。 PlayerSettings.SetIl2CppCompilerConfiguration(BuildTargetGroup.Android, Il2CppCompilerConfiguration.Release); Debug.Log("开始打包安卓发布版本"); } else { EditorUserBuildSettings.development = true; EditorUserBuildSettings.allowDebugging = true; EditorUserBuildSettings.androidBuildType = AndroidBuildType.Debug; PlayerSettings.SetIl2CppCompilerConfiguration(BuildTargetGroup.Android, Il2CppCompilerConfiguration.Debug); buildOptions = BuildOptions.Development; Debug.Log("开始打包安卓测试版本"); } // 获取当前的宏定义 string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android); { PlayerSettings.stripEngineCode = true; } AssetDatabase.Refresh(); //打包前自检:底包里随包发布的加密 AB 必须是"已加密"状态(运行时 LoadDll 会无条件 AES 解密)。 //若 a.unity3d 等是原始未加密 AB(头部为 UnityFS / 长度非16倍数),真机会在 StartGame 解密时 //抛 "Offset and length were out of bounds" 并 "Decrypt Failed"。这里提前拦截,避免出坏包。 if (!VerifyEncryptedBaseBundles()) { Debug.LogError("APK 打包中断:底包加密 AB 自检未通过(见上条日志)。请用『打包/生成底包/Android最小包(或完整包)』重新生成(内部会加密 a.unity3d),且不要在其后再点『更新dll(Android)』(那会删掉 a.unity3d)。"); return false; } try { string[] LauncherSceneNames; //build一下link.xml HybridCLR.Editor.Commands.LinkGeneratorCommand.GenerateLinkXml(); LauncherSceneNames = FindEnabledEditorScenes(); BuildReport report = BuildPipeline.BuildPlayer(LauncherSceneNames, apkBuildName, BuildTarget.Android, buildOptions); //CSObjectWrapEditor.Generator.ClearAll();//如果不清 可能源码参数修改导致导致编译错误 打完包默认清理一下 if (report.summary.result == BuildResult.Succeeded) { Debug.Log("安卓打包成功!"); return true; } else { Debug.LogFormat("安卓打包失败,totalErrors:{0}!", report.summary.totalErrors); return false; } } catch (Exception e) { Debug.Log(e.ToString()); EditorApplication.Exit(1); } return false; } //校验随底包发布的加密 AB 是否真的处于"已加密"状态。 //判定:已加密数据不应以 AssetBundle 魔数(UnityFS/Unity)开头,且 AES-CBC 产物长度必为16倍数。 //a.unity3d 为必需(缺失或未加密都拦截);h.unity3d / bs.unity3d 仅在存在时校验。 static bool VerifyEncryptedBaseBundles() { bool ok = true; // name -> 是否必需存在 var targets = new Dictionary { { "a.unity3d", true }, { "h.unity3d", false }, { "bs.unity3d", false }, }; foreach (var kv in targets) { string file = $"{AppDataPath}/StreamingAssets/{kv.Key}"; if (!File.Exists(file)) { if (kv.Value) { Debug.LogError($"加密 AB 缺失:{file} 不存在。a.unity3d(AOT元数据包)须先经『生成底包/Android…』(PackAotFile)产出并加密。"); ok = false; } continue; } byte[] head = new byte[5]; int n; long size; using (var fs = File.OpenRead(file)) { size = fs.Length; n = fs.Read(head, 0, head.Length); } bool isRawAB = n >= 5 && head[0] == (byte)'U' && head[1] == (byte)'n' && head[2] == (byte)'i' && head[3] == (byte)'t' && head[4] == (byte)'y'; bool blockAligned = (size % 16) == 0; if (isRawAB || !blockAligned) { Debug.LogError($"加密 AB 自检失败:{kv.Key} 看起来未加密(头部RawAB={isRawAB}, size={size}, size%16={size % 16})。" + "运行时会无条件 AES 解密它 → 真机必崩。请走会加密的底包菜单重新生成。"); ok = false; } } return ok; } //[MenuItem("打包/增量编译LocalTest底包(包含C#,Config,Script,UI)", false, 101)] public static void BuildWindowsRuntime() { //生成最终pc底包 string[] SCENES = FindEnabledEditorScenes(); //保证这个打出来的使用设置好的路径 //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL"); BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.None); //增量更新 string UpdateFile = AppDataPath + "/../FileTime.txt"; //获得上回时间 FileInfo fi = new FileInfo(UpdateFile); if (fi.Exists) { UpdateDT = fi.LastWriteTime.ToFileTime(); } else { File.Create(UpdateFile).Dispose(); UpdateDT = 0; } //脚本和配置 //源文件编译 string srcPath, outPath; //Script srcPath = AppDataPath + "/../../Scripts/"; outPath = AppDataPath + "/../../Scripts_c/"; //不转luac CopyPath(srcPath, outPath); //CompileScript(srcPath, outPath); //Config srcPath = AppDataPath + "/../../Config/Config/"; outPath = AppDataPath + "/../../Scripts_c/Base/Config/"; //配置不转luac CopyPath(srcPath, outPath); //CompileScript(srcPath, outPath); UpdateDT = DateTime.Now.ToFileTime(); using (StreamWriter sw = new StreamWriter(UpdateFile)) { sw.Write(UpdateDT); } CopyScript_c();//复制到目标端 //UI 资源 //BuildUI(BuildTarget.StandaloneWindows64); UnityEngine.Debug.Log("Local Version OK!"); } #if !UNITY_WEBGL [MenuItem("打包/生成底包/Win64最小包", false, 101)] public static void BuildWindowsBase() { string outPath = $"{AppDataPath}/StreamingAssets/"; ClearDir(outPath); //生成最终pc底包 string[] SCENES = FindEnabledEditorScenes(); //保证这个打出来的使用设置好的路径 //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL"); //PrebuildCommand.GenerateAll(); //AssetDatabase.Refresh(); BuildReport buildReport = BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.CompressWithLz4); if (buildReport == null || buildReport.summary.result != BuildResult.Succeeded) { UnityEngine.Debug.LogError("Build Base Failed!"); } else { UnityEngine.Debug.Log("Build Base OK!"); } AssetDatabase.Refresh(); //BuildUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64); //AssetDatabase.Refresh(); } [MenuItem("打包/生成底包/Win64完整包", false, 101)] public static void BuildWindowsFull() { string outPath = $"{AppDataPath}/StreamingAssets/"; ClearDir(outPath); BuildUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64); //生成最终pc底包 string[] SCENES = FindEnabledEditorScenes(); //保证这个打出来的使用设置好的路径 //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL"); PrebuildCommand.GenerateAll(); BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.CompressWithLz4); UnityEngine.Debug.Log("Build Full OK!");//*/ } [MenuItem("打包/生成底包/Android最小包", false, 102)] public static void BuildAndroidBase() { //ring outPath = $"{AppDataPath}/StreamingAssets/"; //earDir(outPath); if (true == HybridCLRBuild.PackAotFile(BuildTargetGroup.Android, BuildTarget.Android, "a.unity3d", $"{Application.dataPath.ToLower()}/StreamingAssets/xaot/")) { BuildAPK("Client.apk", true, true); } //BuildBasePack("Client.apk", BuildTargetGroup.Android, BuildTarget.Android); //HybridCLRBuild.BuildDllUpdateAndroid(); //BuildUpdate(BuildTargetGroup.Android, BuildTarget.Android); //earDir(outPath); } [MenuItem("打包/生成底包/Android完整包", false, 102)] public static void BuildAndroidFull() { string outPath = $"{AppDataPath}/StreamingAssets/"; HybridCLRBuild.ClearDir(outPath); //BuildUpdate(BuildTargetGroup.Android, BuildTarget.Android); //HybridCLRBuild.BuildDllUpdateAndroid(); HybridCLRBuild.BuildHotFix(BuildTargetGroup.Android, BuildTarget.Android, "client", false); if (true == HybridCLRBuild.PackAotFile(BuildTargetGroup.Android, BuildTarget.Android, "a.unity3d", $"{Application.dataPath.ToLower()}/StreamingAssets/xaot/")) { BuildAPK("Client.apk", true, true); } } [MenuItem("打包/生成基础更新Win64", false, 201)] public static void BuildUpdateWin64() { //BuildUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64); HybridCLRBuild.BuildHotFix(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64); } [MenuItem("打包/生成基础更新Android", false, 202)] public static void BuildUpdateAndroid() { //BuildUpdate(BuildTargetGroup.Android, BuildTarget.Android); HybridCLRBuild.BuildDllUpdateAndroid(); } [MenuItem("打包/生成游戏更新", false, 1030)] public static void BuildCurGameUpdate() { //加载../../XDevNode/xid.txt string xid = null; if(File.Exists("../XDevNode/xid.txt")) { xid = File.ReadAllText("../XDevNode/xid.txt"); } if(string.IsNullOrEmpty(xid)) { Debug.LogWarning("./../XDevNode/xid.txt 不存在。自动生成"); ulong id = Generate64BitId(); byte[] bytes = BitConverter.GetBytes(id); // 如果是大端系统需要反转数组(如网络传输场景) if (BitConverter.IsLittleEndian == false) { Array.Reverse(bytes); } // 转换为Base64字符串 xid = Convert.ToBase64String(bytes); File.WriteAllText("../XDevNode/xid.txt", xid); } if(xid.Length > 24) { xid = xid.Substring(0, 24); } //以xid为目录打包资源 string outPath = $"../XDevNode/{xid}/"; //根据平台打更新 ClearDir(outPath); BuildGameUpdate(BuildTargetGroup.Android, BuildTarget.Android, xid); } public static ulong Generate64BitId() { RandomNumberGenerator rng = RandomNumberGenerator.Create(); DateTime Epoch1601 = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc); Span randomBytes = stackalloc byte[8]; rng.GetBytes(randomBytes); var timestamp = (ulong)((DateTime.UtcNow - Epoch1601).Ticks / 10); return ((timestamp >> 22) & 0x3FFFFFFFFFF) << 22 | (BitConverter.ToUInt64(randomBytes) & 0x3FFFFF); } public static void BuildGameUpdate(BuildTargetGroup targetGroup, BuildTarget target, string xid) { if (EditorUserBuildSettings.activeBuildTarget != target) { EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, target); } if (target == BuildTarget.Android) { platform = "android"; } else if (target == BuildTarget.iOS) { platform = "ios"; } else if (target == BuildTarget.WebGL) { platform = "webgl"; //bLuaPackage = true; } else { platform = "pc"; } string outPath = $"../XDevNode/{xid}/{platform}/"; ClearDir(outPath); if (!Directory.Exists(outPath)) Directory.CreateDirectory(outPath); AssetDatabase.Refresh(); maps.Clear(); //lua 打包 BuildLuaAB("s.unity3d"); //渲染shader ab //shader也跟下面的资源一起打吧 //PackAllPathFile("game_shader.unity3d", $"Assets/Game/Shader", "Shadervariants"); AddBuildMap("game_shader.unity3d", "*.*", "Assets/Game/Shader"); //资源 ab string[] HotfixRes = { "Game/Art" };//热更资源目录, 按目录打资源 string namePre = $"Game_Art"; foreach (string respath in HotfixRes) { string tempPath = $"{ AppDataPath}/{respath}/"; string buildPath = $"Assets/{respath}/";//路径必须以Assets开始 string[] dirs = Directory.GetDirectories(tempPath, "*", SearchOption.AllDirectories); for (int i = 0; i < dirs.Length; i++) { string path = dirs[i]; if (path.Contains(".svn")) continue; string pathname = dirs[i].Replace(tempPath, string.Empty); string name = pathname.Replace('\\', '_').Replace('/', '_');//Lua ab文件使用.替换目录分隔符 name = name.ToLower() + ".unity3d"; AddBuildMap($"{namePre}_{name}", "*.*", buildPath + pathname); //AssetDatabase.Refresh(); } AddBuildMap($"{namePre}_{respath}.unity3d", "*.*", $"Assets/{respath}/"); //AssetDatabase.Refresh(); } //打ab包 BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression/*| BuildAssetBundleOptions.DisableWriteTypeTree*/, target); AssetDatabase.Refresh(); //改名资源依赖文件,加个后缀 if (File.Exists($"{outPath}{platform}")) { File.Move($"{outPath}{platform}", $"{outPath}abdepend.unity3d"); } //打索引文件和版本文件 m_dicFilelist.Clear(); BuildFileIndex($"{outPath}"); string resmd5 = md5file($"{outPath}files.txt"); m_strResFileMD5 = GetFixedMd5(resmd5); File.WriteAllText($"{outPath}/updatever.txt", $"{m_strResFileMD5}"); //加密 string[] EncryptFile = { "abdepend.unity3d", "s.unity3d" }; foreach (string f in EncryptFile) { string file = $"{outPath}{f}"; if (File.Exists(file)) { byte[] srcdata = File.ReadAllBytes(file); byte[] encryptdata = AESManager.Encrypt(srcdata); byte[] retdata = AESManager.Decrypt(encryptdata); File.WriteAllBytes(file, encryptdata); } } //根据md5列表修改ab包名称 foreach (var file in m_dicFilelist) { string destFile = GetFileMd5Name($"{outPath}{file.Key}", GetFixedMd5(file.Value)); string srcFile = $"{outPath}{file.Key}"; if (!File.Exists(srcFile)) continue; if (!File.Exists(destFile)) File.Move(srcFile, destFile); } //文件列表添加MD5 string newFilePath = GetFileMd5Name($"{outPath}files.txt", GetFixedMd5(m_strResFileMD5)); if (File.Exists(newFilePath)) File.Delete(newFilePath); File.Move($"{outPath}files.txt", newFilePath); } #else //[MenuItem("打包/生成WebGL端", false, 301)] public static void BuildWebGL() { string outPath = $"{AppDataPath}/StreamingAssets/"; ClearDir(outPath); BuildBasePack("Client", BuildTargetGroup.WebGL, BuildTarget.WebGL); //BuildUpdate(BuildTargetGroup.WebGL, BuildTarget.WebGL); //放到AppConst.WebGL CopyPath($"{System.Environment.CurrentDirectory}/Client", XGame.AppConst.WebGL); } //[MenuItem("打包/生成WebGL更新(旧)", false, 302)] public static void BuildUpdateWebGL() { BuildUpdate(BuildTargetGroup.WebGL, BuildTarget.WebGL); } #endif public static void BuildUpdate(BuildTargetGroup targetGroup, BuildTarget target) { m_mapResNameDic.Clear(); if (EditorUserBuildSettings.activeBuildTarget != target) { EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, target); } if (target == BuildTarget.Android) { sPlatform = "android"; } else if (target == BuildTarget.iOS) { sPlatform = "ios"; } else if (target == BuildTarget.WebGL) { sPlatform = "webgl"; } else { sPlatform = "pc"; } maps.Clear(); string outPath = $"{AppDataPath}/StreamingAssets/"; //if (targetGroup != BuildTargetGroup.Standalone) //{ // outPath = $"{AppDataPath}/StreamingAssets/{sPlatform}/"; //} ClearDir(outPath); if (!Directory.Exists(outPath)) Directory.CreateDirectory(outPath); #if !UNITY_WEBGL //生成一次临时包,以便正确生成dll //string[] SCENES = FindEnabledEditorScenes(); //保证这个打出来的使用设置好的路径 //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL"); //BuildPipeline.BuildPlayer(SCENES, "Client", target, BuildOptions.None); //打入更新的资源,dll //dll update //BuildAssetsCommand.BuildAndCopyABAOTHotUpdateDlls(); #endif //runtime 编译 ScriptCompilationSettings scriptCompilationSettings = default; scriptCompilationSettings.group = targetGroup; scriptCompilationSettings.target = target; PlayerSettings.stripEngineCode = true; PlayerSettings.allowUnsafeCode = true; string BasePath = System.Environment.CurrentDirectory; PlayerSettings.SetScriptingBackend(targetGroup, ScriptingImplementation.IL2CPP); ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, $"{BasePath}/client/"); //HybridCLR dll update BuildAssetsCommand.BuildAndCopyABAOTHotUpdateDlls(); //CopyScriptsFile($"{AppDataPath}/lua/", $"{AppDataPath}/luabytes/", "bytes"); BuildLuaAB("bs.unity3d"); //目录列表 string[] res = HotfixDir;//{ "res", "ui", "luabytes"};//必须是Assets目录下的目录 //看是否三种资源要分开特殊处理打包 //美术资源基于目录和预设相关, shader完全打包,ui需要打图集,lua脚本完全打包 //加密dll并把dll打入unity ab 包 PackDllFile("h.unity3d", $"{AppDataPath}/StreamingAssets/xhotfix/"); PackDllFile("a.unity3d", $"{AppDataPath}/StreamingAssets/xaot/"); //打包基础代码入口资源,这个为挂载GameLuncher脚本的预设 //PackDirFile($"{AppDataPath}/res/base", "res.unity3d"); AddBuildMap($"res/res.unity3d", "*.*", $"Assets/res/base/"); //打包shader到单一文件,方便开始一次加载 if (target == BuildTarget.WebGL) PackAllPathFile("res/res_shader.unity3d", $"Assets/res/shader", "Shadervariants"); else PackAllPathFile("res/res_shader.unity3d", $"Assets/res/shader", "Shadervariants_webgl"); foreach (string respath in res) { string tempPath = $"{ AppDataPath}/{respath}/"; string buildPath = $"Assets/{respath}/";//路径必须以Assets开始 string[] dirs = Directory.GetDirectories(tempPath, "*", SearchOption.AllDirectories); for (int i = 0; i < dirs.Length; i++) { string path = dirs[i]; string pathname = dirs[i].Replace(tempPath, string.Empty); string name = pathname.Replace('\\', '_').Replace('/', '_');//Lua ab文件使用.替换目录分隔符 name = name.ToLower() + ".unity3d"; AddBuildMap($"{respath}/{name}", "*.*", buildPath + pathname); } AddBuildMap($"{respath}/{respath}.unity3d", "*.*", $"Assets/{respath}/"); AssetDatabase.Refresh(); } //统一打ab包 BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, target); AssetDatabase.Refresh(); //复制res.unity3d基础游戏开始模块到xhotfix目录,此目录为开始强制更新dll即有 string resfile = $"{outPath}xhotfix/res.unity3d"; if (File.Exists(resfile)) File.Delete(resfile); string dir = $"{outPath}xhotfix"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); Directory.CreateDirectory($"{outPath}xaot"); } File.Copy($"{outPath}res/res.unity3d", resfile); string platform = ""; //if (targetGroup != BuildTargetGroup.Standalone) // platform = sPlatform; //生成更新列表 m_dicFilelist.Clear(); m_strDllFileMD5 = ""; m_strResFileMD5 = ""; BuildFileIndex(platform);//StreamingAssets 里的除了\\xaot\\ 和 \\xhotfix (dll目录)外所有文件 //添加dll更新列表 BuildDllsFileIndex(platform); SaveUpdateVer(platform); //复制到目标地址 //先清理之前的 if (Directory.Exists(GetUpdatePath())) { ClearDir($"{GetUpdatePath()}xaot"); ClearDir($"{GetUpdatePath()}xhotfix"); ClearDir($"{GetUpdatePath()}arts");//原来的资源目录 ClearDir($"{GetUpdatePath()}res"); string[] files = Directory.GetFiles(GetUpdatePath(), "*.*"); for (int i = 0; i < files.Length; i++) { File.Delete(files[i]); } } //加密 string[] EncryptFile = {"a.unity3d", "h.unity3d", "bs.unity3d" }; foreach (string f in EncryptFile) { byte[] srcdata = File.ReadAllBytes($"{AppDataPath}/StreamingAssets/{f}"); byte[] encryptdata = AESManager.Encrypt(srcdata); byte[] retdata = AESManager.Decrypt(encryptdata); File.WriteAllBytes($"{AppDataPath}/StreamingAssets/{f}", encryptdata); } CopyPath(outPath, GetUpdatePath()); //CopyPath($"{outPath}xaot", $"{GetUpdatePath()}xaot"); //CopyPath($"{outPath}xhotfix", $"{GetUpdatePath()}xhotfix"); //File.Copy($"{outPath}dllfiles.txt", $"{GetUpdatePath()}dllfiles.txt", true); //File.Copy($"{outPath}updatever.txt", $"{GetUpdatePath()}updatever.txt", true); if (target == BuildTarget.WebGL) {//给文件加上md5后缀,以便方便webgl小程序使用UnityWebRequest自动缓存 foreach (var file in m_dicFilelist) { File.Move($"{GetUpdatePath()}{file.Key}", $"{GetUpdatePath()}{GetFileMd5Name(file.Key,file.Value)}");//{file.Key}_{file.Value} } File.Move($"{GetUpdatePath()}dllfiles.txt", $"{GetUpdatePath()}{GetFileMd5Name("dllfiles.txt", m_strDllFileMD5)}"); File.Move($"{GetUpdatePath()}files.txt", $"{GetUpdatePath()}{GetFileMd5Name("files.txt", m_strResFileMD5)}"); //File.Move($"{GetUpdatePath()}updatever.txt", $"{GetUpdatePath()}update.ver"); } //删除临时luabytes文件 //ClearDir($"{AppDataPath}/luabytes/"); } static string GetFileMd5Name(string filename, string md5) { int pos = filename.LastIndexOf('.'); if (pos != -1) { return $"{filename.Substring(0, pos)}_{md5}{filename.Substring(pos)}"; } else return $"{filename}_{md5}"; } static void BuildLuaAB(string bundleName) { string luapath = $"{Application.dataPath}/l/"; ClearDir(luapath); CopyScriptsFile($"{AppDataPath}/../../lua/", luapath, "bytes"); var files = Directory.GetFiles($"Assets/l", "*.bytes", SearchOption.AllDirectories); for (int i = 0; i < files.Length; i++) { files[i] = files[i].Replace('\\', '/'); } AssetBundleBuild build = new AssetBundleBuild(); build.assetBundleName = bundleName; build.assetNames = files; maps.Add(build); } public static void ClearDir(string path) { if (!Directory.Exists(path)) return; if (Directory.Exists(path)) { Directory.Delete(path, true); if(path.EndsWith("/")) path = path.Substring(0, path.Length - 1); path = path + ".meta"; if (Directory.Exists(path)) File.Delete(path); } } public static void CompileConfig() { string srcPath, outPath; //Config UpdateDT = 0; srcPath = AppDataPath + "/../../Config/Config/"; outPath = AppDataPath + "/../../Scripts_c/Base/Config/"; CompileScript(srcPath, outPath); } //[MenuItem("打包/完整编译Script", false, 104)] public static void CompileScript() { string srcPath, outPath; //Script UpdateDT = 0; srcPath = AppDataPath + "/../../Scripts/"; outPath = AppDataPath + "/../../Scripts_c/"; CompileScript(srcPath, outPath); } //[MenuItem("打包/增量编译Script,Config", false, 105)] public static void CompileConfigScript() { //增量更新 string UpdateFile = AppDataPath + "/../FileTime.txt"; //获得上回时间 FileInfo fi = new FileInfo(UpdateFile); if (fi.Exists) { UpdateDT = fi.LastWriteTime.ToFileTime(); } else { File.Create(UpdateFile).Dispose(); UpdateDT = 0; } string srcPath, outPath; //Config srcPath = AppDataPath + "/../../Config/Config/"; outPath = AppDataPath + "/../../Scripts_c/Base/Config/"; CompileScript(srcPath, outPath); //Script srcPath = AppDataPath + "/../../Scripts/"; outPath = AppDataPath + "/../../Scripts_c/"; CompileScript(srcPath, outPath); UpdateDT = DateTime.Now.ToFileTime(); using (StreamWriter sw = new StreamWriter(UpdateFile)) { sw.Write(UpdateDT); } } //[MenuItem("打包/编译c#底包", false, 104)] public static void CompileCSharp() { //生成最终pc底包 string[] SCENES = FindEnabledEditorScenes(); //保证这个打出来的使用设置好的路径 //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL"); BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.None); } //[MenuItem("打包/生成Windows UI 资源ab", false, 102)] public static void CreateWindowsUI() { string OutputPath = AppDataPath + "/../pc/base/ui"; BuildUI(BuildTarget.StandaloneWindows64, OutputPath); } //[MenuItem("打包/生成Android UI 资源ab", false, 103)] public static void CreateAndroidUI() { string OutputPath = AppDataPath + "/../android/base/ui"; BuildUI(BuildTarget.Android, OutputPath); } //public static void BuildUI(UnityEditor.BuildTarget target) //[MenuItem("打包/生成测试UI 资源ab", false, 102)] public static void BuildUI(UnityEditor.BuildTarget target, string OutputPath = "") { maps.Clear(); string resPath = AppDataPath + "/UI/"; string outPath; if (OutputPath == null || OutputPath == "" ) { outPath = AppDataPath + "/../../" + OutputName + "/Client_Data/UI/"; } else outPath = OutputPath; if (!Directory.Exists(outPath)) { Directory.CreateDirectory(outPath); } string[] dirs = Directory.GetDirectories(resPath, "*", SearchOption.AllDirectories); for (int i = 0; i < dirs.Length; i++) { string path = dirs[i]; string pathname = dirs[i].Replace(resPath, string.Empty); string name = pathname.Replace('\\', '_').Replace('/', '_'); name = name.ToLower() + ".unity3d"; AddBuildMap(name, "*.*", "Assets/UI/" + pathname);//路径必须以Assets开始 } AssetDatabase.Refresh(); BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, target); AssetDatabase.Refresh(); } static void AddBuildMap(string bundleName, string pattern, string path, bool bDirPack = true) { var files = Array.FindAll(Directory.GetFiles(path, pattern), x => !x.EndsWith(".meta")); if (files.Length == 0) return; for (int i = 0; i < files.Length; i++) { files[i] = files[i].Replace('\\', '/'); } if (bDirPack) { AssetBundleBuild build = new AssetBundleBuild(); build.assetBundleName = bundleName; build.assetNames = files; maps.Add(build); } else { string[] singlefile; string abname = bundleName.Substring(0, bundleName.IndexOf(".unity3d")); abname += "_"; for (int i = 0; i < files.Length; i++) { AssetBundleBuild build = new AssetBundleBuild(); singlefile = new string[] { files[i] }; build.assetBundleName = abname + Path.GetFileNameWithoutExtension(files[i]).ToLower() + ".unity3d";// bundleName; build.assetNames = singlefile; maps.Add(build); } } } //[MenuItem("打包/生成Android Scripts资源ab", false, 106)] public static void CreateAndroidScriptsAB() { string OutputPath = AppDataPath + "/../android/base/scripts"; BuildScriptsAB(BuildTarget.Android, OutputPath); } public static void BuildScriptsAB(UnityEditor.BuildTarget target, string OutputPath = "") { string fixname = "bytes";//"txt"; //"bytes";// //Main.lua Agent.lua test.lua + Base目录下文件独一份 其他打包 Base目录下其他文件 maps.Clear(); string resPath = AppDataPath + "/../../Scripts_c/Base/";//Base/Config/ string tempPath = AppDataPath + "/lua/";// string outPath; if (OutputPath == null || OutputPath == "") { outPath = AppDataPath + "/../../" + OutputName + "/Client_Data/lua/"; } else outPath = OutputPath; if (Directory.Exists(outPath)) { DirectoryInfo dir = new DirectoryInfo(outPath); dir.Delete(true); } if (!Directory.Exists(outPath)) { Directory.CreateDirectory(outPath); } //复制大部分lua 文件到 tempPath CopyScriptsFile(resPath, tempPath, fixname); //复制最后三个文件 Main.lua Agent.lua test.lua File.Copy(AppDataPath + "/../../Scripts_c/Main.lua", tempPath + "Main." + fixname, true); File.Copy(AppDataPath + "/../../Scripts_c/Agent.lua", tempPath + "Agent." + fixname, true); File.Copy(AppDataPath + "/../../Scripts_c/Test.lua", tempPath + "Test." + fixname, true); string[] dirs = Directory.GetDirectories(tempPath, "*", SearchOption.AllDirectories); for (int i = 0; i < dirs.Length; i++) { string path = dirs[i]; string pathname = dirs[i].Replace(tempPath, string.Empty); string name = pathname.Replace('\\', '_').Replace('/', '_');//Lua ab文件使用.替换目录分隔符 name = name.ToLower() + ".unity3d"; AddBuildMap(name, "*.*", "Assets/lua/" + pathname);//路径必须以Assets开始 } AddBuildMap("bs.unity3d", "*.*", "Assets/lua/"); AssetDatabase.Refresh(); BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, target); AssetDatabase.Refresh(); //删除临时lua目录,此处不应删除,以免unity重新生成每个文件的一些索引,导致每次复制进来相同的文件也会产生打包变化 //DirectoryInfo di = new DirectoryInfo(tempPath); //di.Delete(true); } static void CopyScriptsFile(string srcPath, string outPath, string fixname) { if (!Directory.Exists(outPath)) { Directory.CreateDirectory(outPath); } string[] luaPaths = { srcPath };//AppDataPath + "/../../Scripts_c/" }; for (int i = 0; i < luaPaths.Length; i++) { paths.Clear(); files.Clear(); string luaDataPath = luaPaths[i].ToLower(); Recursive(luaDataPath); int n = 0; foreach (string f in files) { if (f.EndsWith(".meta")) continue; string newfile = f.Replace(luaDataPath, ""); string newpath; if (newfile.IndexOf(".lua") < 0)//转2进制文件 { newpath = outPath + newfile + ".bytes"; } else { newpath = outPath + newfile.Replace(".lua", "." + fixname); } string path = Path.GetDirectoryName(newpath); if (!Directory.Exists(path)) Directory.CreateDirectory(path); if (File.Exists(newpath)) { File.Delete(newpath); } File.Copy(f, newpath, true); UpdateProgress(n++, files.Count, newpath); } } EditorUtility.ClearProgressBar(); } static public void CopyPath(string srcPath, string outPath) { srcPath = srcPath.Replace("\\", "/"); ; UnityEngine.Debug.Log($"{srcPath} => {outPath}"); if (!Directory.Exists(outPath)) { Directory.CreateDirectory(outPath); } string[] luaPaths = { srcPath };//AppDataPath + "/../../Scripts_c/" }; for (int i = 0; i < luaPaths.Length; i++) { paths.Clear(); files.Clear(); string luaDataPath = luaPaths[i];//.ToLower(); Recursive(luaDataPath); int n = 0; foreach (string f in files) { if (f.EndsWith(".meta") || f.EndsWith(".manifest")) continue; string newfile = f.Replace(luaDataPath, ""); string newpath = outPath + newfile; string path = Path.GetDirectoryName(newpath); if (!Directory.Exists(path)) { UnityEngine.Debug.Log($"{path}"); Directory.CreateDirectory(path); } if (File.Exists(newpath)) { File.Delete(newpath); } File.Copy(f, newpath, true); UpdateProgress(n++, files.Count, newpath); } } EditorUtility.ClearProgressBar(); } static void CopyScript_c() { string resPath = AppDataPath + "/../../" + OutputName + "/Client_Data"; string luaPath = resPath + "/Scripts_c/"; //----------复制Lua文件---------------- if (!Directory.Exists(luaPath)) { Directory.CreateDirectory(luaPath); } string[] luaPaths = { AppDataPath + "/../../Scripts_c/" }; for (int i = 0; i < luaPaths.Length; i++) { paths.Clear(); files.Clear(); string luaDataPath = luaPaths[i].ToLower(); Recursive(luaDataPath); int n = 0; foreach (string f in files) { if (f.EndsWith(".meta")) continue; string newfile = f.Replace(luaDataPath, ""); string newpath = luaPath + newfile; string path = Path.GetDirectoryName(newpath); if (!Directory.Exists(path)) Directory.CreateDirectory(path); if (File.Exists(newpath)) { File.Delete(newpath); } //if (AppConst.LuaByteMode) //{ // EncodeLuaFile(f, newpath);//luajit //} //else //{ File.Copy(f, newpath, true); //} UpdateProgress(n++, files.Count, newpath); } } EditorUtility.ClearProgressBar(); } //把原始script脚本和config 转为统一的luac版 static void CompileScript(string srcPath, string outPath) {// AppDataPath appname_Data/ Client_Data/ //string resPath = AppDataPath + "/../../" + OutputName + "/Client_Data"; //string luaPath = resPath + "/Scripts_c/"; //----------复制Lua文件---------------- if (!Directory.Exists(outPath)) { Directory.CreateDirectory(outPath); } string[] luaPaths = { srcPath };//AppDataPath + "/../../Scripts_c/" }; for (int i = 0; i < luaPaths.Length; i++) { paths.Clear(); files.Clear(); string luaDataPath = luaPaths[i].ToLower(); Recursive(luaDataPath); int n = 0; foreach (string f in files) { if (f.EndsWith(".meta")) continue; //时间判定 FileInfo fi = new FileInfo(f); if (fi.Exists && fi.LastWriteTime.ToFileTime() < UpdateDT && fi.CreationTime.ToFileTime() < UpdateDT) { continue; } string newfile = f.Replace(luaDataPath, ""); string newpath = outPath + newfile; string path = Path.GetDirectoryName(newpath); if (!Directory.Exists(path)) Directory.CreateDirectory(path); if (File.Exists(newpath)) { File.Delete(newpath); } EncodeLuac(f, newpath); UpdateProgress(n++, files.Count, newpath); } } EditorUtility.ClearProgressBar(); } private static string[] FindEnabledEditorScenes() { List EditorScenes = new List(); foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { if (!scene.enabled) continue; EditorScenes.Add(scene.path); } return EditorScenes.ToArray(); } static void PackDirFile(string path, string bundleName) { var filelist = Directory.GetFiles(path); AssetBundleBuild build = new AssetBundleBuild(); build.assetBundleName = bundleName; build.assetNames = filelist; maps.Add(build); } static private List GetAllDependencies(string mainAssetPath) { List result = new List(); string[] depends = AssetDatabase.GetDependencies(mainAssetPath, true); foreach (string assetPath in depends) { // 注意:排除主资源对象 if (assetPath != mainAssetPath) result.Add(assetPath); } return result; } static void PackDllFile(string bundleName, string dllpath) { //string resPath = $"{AppDataPath}/StreamingAssets/xhotfix/"; List files = new List(); files.AddRange(Directory.GetFiles(dllpath)); //files.AddRange(Directory.GetFiles($"{AppDataPath}/StreamingAssets/xaot/")); //int nPos = resPath.IndexOf("assets/StreamingAssets/", StringComparison.OrdinalIgnoreCase); int nPos = bundleName.IndexOf('.'); //加密 List DllFiles = new List(); //byte[] buffer, aesdata; string dirname = bundleName.Substring(0, nPos); string temppath = $"{AppDataPath}/{dirname}"; if (!Directory.Exists(temppath)) { Directory.CreateDirectory(temppath); } else { ClearDir(temppath); } foreach (var file in files) { if (file.EndsWith(".dll")) { //buffer = File.ReadAllBytes(file); //aesdata = buffer;// AESManager.Encrypt(buffer); string name = Path.GetFileNameWithoutExtension(file);//file.Replace(".dll", ".bytes"); string filepath = $"{temppath}/{name}.bytes"; //File.WriteAllBytes(filepath, aesdata); File.Move(file, filepath); name = $"assets/{dirname}/{name}.bytes"; DllFiles.Add(name); } } AssetBundleBuild build = new AssetBundleBuild(); build.assetBundleName = bundleName; build.assetNames = DllFiles.ToArray();// filelist; maps.Add(build); } static void PackAllPathFile(string bundleName, string path, string nopath = null) { paths.Clear(); files.Clear(); Recursive(path); string[] filelist = files.ToArray(); List total = new List(filelist); foreach (var file in filelist) { if (!m_mapResNameDic.ContainsKey(file)) { m_mapResNameDic[file] = "1"; } } //依赖文件 foreach (var file in filelist) { List depends = GetAllDependencies(file); foreach (var fd in depends) { if (fd.EndsWith(".hlsl")) continue; if (!m_mapResNameDic.ContainsKey(fd)) { m_mapResNameDic[fd] = "1"; total.Add(fd); } } } if (nopath != null) { for (int i = total.Count - 1; i >= 0; i--) { var file = total[i]; if (file.Contains(nopath)) { string[] pathParts = file.Replace('\\','/').Split('/'); foreach (var p in pathParts) { if(string.Equals(p, nopath, StringComparison.OrdinalIgnoreCase))//if (p == nopath) { //删除不合理的 total.RemoveAt(i); break; } } } } } AssetBundleBuild build = new AssetBundleBuild(); build.assetBundleName = bundleName; build.assetNames = total.ToArray();// filelist; maps.Add(build); UnityEngine.Debug.Log($"ab:{bundleName} has {filelist.Length} file"); } //生成更新文件 static void BuildDllsFileIndex(string dir = "") { List files = new List(); string resPath = $"{AppDataPath}/StreamingAssets/{dir}/"; ///----------------------创建文件列表----------------------- string newFilePath = resPath + "dllfiles.txt"; if (File.Exists(newFilePath)) File.Delete(newFilePath); files.AddRange(Directory.GetFiles($"{resPath}xaot/")); files.AddRange(Directory.GetFiles($"{resPath}xhotfix/")); FileStream fs = new FileStream(newFilePath, FileMode.CreateNew); StreamWriter sw = new StreamWriter(fs); foreach (var dllfile in files) { if (!dllfile.EndsWith(".dll") && !dllfile.EndsWith(".unity3d")) continue; long size = 0; string md5 = GetFixedMd5(md5file(dllfile, out size)); string value = dllfile.Replace(resPath, string.Empty); sw.WriteLine($"{{{value},{md5},{size}}}"); m_dicFilelist[value] = md5; } sw.Close(); fs.Close(); } static string GetFixedMd5(string md5) { return md5.Substring(0, 8);//只取8位 } static void BuildFileIndex(string dir = "") { string resPath; if (dir != "") { resPath = dir; } else { resPath = $"{AppDataPath}/StreamingAssets/"; } ///----------------------创建文件列表----------------------- string newFilePath = resPath + "files.txt"; if (File.Exists(newFilePath)) File.Delete(newFilePath); paths.Clear(); files.Clear(); Recursive(resPath);//此处去掉了dll热更相关的目录 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") || file.EndsWith(".manifest")) continue; long size = 0; string md5 = GetFixedMd5(md5file(file, out size)); string value = file.Replace(resPath, string.Empty); sw.WriteLine($"{{{value},{md5},{size}}}"); m_dicFilelist[value] = md5; } sw.Close(); fs.Close(); //版本文件ver.txt //string vermd5 = md5file(newFilePath); //File.WriteAllText($"{resPath}/updatever.txt", vermd5); } static void SaveUpdateVer(string dir = "") { string resPath; if (dir != "") { resPath = dir; } else { resPath = $"{AppDataPath}/StreamingAssets/"; } string resfilelist = $"{resPath}files.txt"; string dllfilelist = $"{resPath}dllfiles.txt"; if (!File.Exists(dllfilelist) || !File.Exists(resfilelist)) { UnityEngine.Debug.LogError("需要先生成更新列表文件"); return;//需要先 } string resmd5 = md5file(resfilelist); string dllmd5 = md5file(dllfilelist); m_strResFileMD5 = GetFixedMd5(resmd5); m_strDllFileMD5 = GetFixedMd5(dllmd5); File.WriteAllText($"{resPath}/updatever.txt", $"{m_strDllFileMD5},{m_strResFileMD5}"); } /// /// 数据目录 /// static string AppDataPath { get { return Application.dataPath.ToLower(); }//Assets目录 } /// /// 遍历目录及其子目录 /// static void Recursive(string path) { if (path.Contains("\\xaot\\")|| path.Contains("\\xhotfix\\")) {//忽略 return; } 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; if (ext.Equals(".hlsl")) continue; files.Add(filename.Replace('\\', '/')); } foreach (string dir in dirs) { if (dir.IndexOf(".svn") >= 0) { continue; } paths.Add(dir.Replace('\\', '/')); Recursive(dir); } } static void UpdateProgress(int progress, int progressMax, string desc) { string title = "Processing...[" + progress + " - " + progressMax + "]"; float value = (float)progress / (float)progressMax; EditorUtility.DisplayProgressBar(title, desc, value); } public static void EncodeLuajit(string srcFile, string outFile) { if (!srcFile.ToLower().EndsWith(".lua")) { File.Copy(srcFile, outFile, true); return; } bool isWin = true; string luaexe = string.Empty; string args = string.Empty; string exedir = string.Empty; string currDir = Directory.GetCurrentDirectory(); if (Application.platform == RuntimePlatform.WindowsEditor) { isWin = true; luaexe = "luajit.exe"; args = "-b -g " + srcFile + " " + outFile; exedir = AppDataPath.Replace("assets", "") + "LuaEncoder/luajit/"; } else if (Application.platform == RuntimePlatform.OSXEditor) { isWin = false; luaexe = "./luajit"; args = "-b -g " + srcFile + " " + outFile; exedir = AppDataPath.Replace("assets", "") + "LuaEncoder/luajit_mac/"; } Directory.SetCurrentDirectory(exedir); ProcessStartInfo info = new ProcessStartInfo(); info.FileName = luaexe; info.Arguments = args; info.WindowStyle = ProcessWindowStyle.Hidden; info.UseShellExecute = isWin; info.ErrorDialog = true; UnityEngine.Debug.Log(info.FileName + " " + info.Arguments); Process pro = Process.Start(info); pro.WaitForExit(); Directory.SetCurrentDirectory(currDir); } public static void EncodeLuac(string srcFile, string outFile) { if (!srcFile.ToLower().EndsWith(".lua")) { File.Copy(srcFile, outFile, true); return; } bool isWin = true; string luaexe = string.Empty; string args = string.Empty; string exedir = string.Empty; string currDir = Directory.GetCurrentDirectory(); if (Application.platform == RuntimePlatform.WindowsEditor) { isWin = true; luaexe = "luac53_64_compatible.exe";//for /r %%v in (*.lua) do luac -o %%v %%v args = " -o " + outFile + " -s " + srcFile; exedir = AppDataPath.Replace("assets", "") + "LuaEncoder/luac/"; } Directory.SetCurrentDirectory(exedir); ProcessStartInfo info = new ProcessStartInfo(); info.FileName = luaexe; info.Arguments = args; info.WindowStyle = ProcessWindowStyle.Hidden; info.UseShellExecute = isWin; info.ErrorDialog = true; //UnityEngine.Debug.Log(info.FileName + " " + info.Arguments); Process pro = Process.Start(info); pro.WaitForExit(); Directory.SetCurrentDirectory(currDir); } /// /// 打安卓包 /// public static void BuildPlayer() { List levels = new List(); foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { if (!scene.enabled) continue; levels.Add(scene.path); } string[] args = Environment.GetCommandLineArgs(); //File.WriteAllLines("log0.txt", args); List currentArgs = new List(); bool flag = false; for (int i = 0; i < args.Length; i++) { if (args[i] == "-executeMethod") { flag = true; continue; } else { if (args[i].StartsWith("-")) { flag = false; } } if (flag) { currentArgs.Add(args[i]); } } UnityEditor.PlayerSettings.productName = "G2Online"; UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2Online"; string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1]; bool isDebugAPK = currentArgs.Count > 2 ? (int.Parse(currentArgs[2]) > 0) : false;//开发者调试模式包 EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP); UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64; BuildOptions options = BuildOptions.CompressWithLz4; if (isDebugAPK) { options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection | BuildOptions.Development | BuildOptions.ConnectWithProfiler; } UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options); } public static void BuildAndroidLocal() { List levels = new List(); foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { if (!scene.enabled) continue; levels.Add(scene.path); } string[] args = Environment.GetCommandLineArgs(); //File.WriteAllLines("log0.txt", args); List currentArgs = new List(); bool flag = false; for (int i = 0; i < args.Length; i++) { if (args[i] == "-executeMethod") { flag = true; continue; } else { if (args[i].StartsWith("-")) { flag = false; } } if (flag) { currentArgs.Add(args[i]); } } UnityEditor.PlayerSettings.productName = "G2Local"; UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2local"; string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1]; bool isDebugAPK = currentArgs.Count > 2 ? (int.Parse(currentArgs[2]) > 0) : false;//开发者调试模式包 EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP); UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64; BuildOptions options = BuildOptions.CompressWithLz4; if (isDebugAPK) { options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection | BuildOptions.Development | BuildOptions.ConnectWithProfiler; } UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options); } public static void BuildAndroidAPK() { List levels = new List(); foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { if (!scene.enabled) continue; levels.Add(scene.path); } string[] args = Environment.GetCommandLineArgs(); //File.WriteAllLines("log0.txt", args); List currentArgs = new List(); bool flag = false; for (int i = 0; i < args.Length; i++) { if (args[i] == "-executeMethod") { flag = true; continue; } else { if (args[i].StartsWith("-")) { flag = false; } } if (flag) { currentArgs.Add(args[i]); } } string packFileName = currentArgs.Count < 2 ? UnityEditor.PlayerSettings.productName : currentArgs[1]; if (!string.IsNullOrEmpty(packFileName) && packFileName.ToLower().IndexOf("online")>-1) { UnityEditor.PlayerSettings.productName = "G2Online"; UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2Online"; } else { UnityEditor.PlayerSettings.productName = "G2Local"; UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2local"; } bool isDebugAPK = currentArgs.Count > 2 ? (int.Parse(currentArgs[2]) > 0) : false;//开发者调试模式包 EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP); UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64; BuildOptions options = BuildOptions.CompressWithLz4; if (isDebugAPK) { options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection | BuildOptions.Development | BuildOptions.ConnectWithProfiler; } UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("{0}.apk", packFileName), BuildTarget.Android, options); } /// /// 打包UWA包 /// public static void BuildAndroidUWA() { List levels = new List(); foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { if (!scene.enabled) continue; levels.Add(scene.path); } string[] args = Environment.GetCommandLineArgs(); List currentArgs = new List(); bool flag = false; for (int i = 0; i < args.Length; i++) { if (args[i] == "-executeMethod") { flag = true; continue; } else { if (args[i].StartsWith("-")) { flag = false; } } if (flag) { currentArgs.Add(args[i]); } } string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1]; EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); BuildOptions options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.Development; UnityEditor.PlayerSettings.productName = "G2"; UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2"; string defs = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android); PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, "UWA;INSTANCE_OC"); UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.Mono2x); UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7; UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options); PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defs); } /// /// 打包UWA包 /// public static void BuildAndroidUWAAUTO() { List levels = new List(); foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { if (!scene.enabled) continue; levels.Add(scene.path); } string[] args = Environment.GetCommandLineArgs(); List currentArgs = new List(); bool flag = false; for (int i = 0; i < args.Length; i++) { if (args[i] == "-executeMethod") { flag = true; continue; } else { if (args[i].StartsWith("-")) { flag = false; } } if (flag) { currentArgs.Add(args[i]); } } string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1]; EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); BuildOptions options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.Development; UnityEditor.PlayerSettings.productName = "G2"; UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2"; string defs = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android); PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, "UWA_AUTO"); UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.Mono2x); UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7; UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options); PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defs); } //在这里找出你当前工程所有的场景文件,假设你只想把部分的scene文件打包 那么这里可以写你的条件判断 总之返回一个字符串数组。 static string[] GetBuildScenes() { List names = new List(); foreach (EditorBuildSettingsScene e in EditorBuildSettings.scenes) { if (e == null) continue; if (e.enabled) names.Add(e.path); } return names.ToArray(); } //得到项目的名称 public static string projectName { get { //在这里分析shell传入的参数, 还记得上面我们说的哪个 project-$1 这个参数吗? //这里遍历所有参数,找到 project开头的参数, 然后把-符号 后面的字符串返回, //这个字符串就是 91 了。。 foreach (string arg in System.Environment.GetCommandLineArgs()) { if (arg.StartsWith("project")) { return arg.Split("-"[0])[1]; } } return "test"; } } //[MenuItem("打包/生成ios工程", false, 106)] //shell脚本直接调用这个静态方法 public static void BuildForIOS() { //这里就是构建xcode工程的核心方法了, //参数1 需要打包的所有场景 //参数2 需要打包的名子, 这里取到的就是 shell传进来的字符串 91 //参数3 打包平台 BuildOptions options = BuildOptions.None;//BuildOptions.CompressWithLz4; BuildPipeline.BuildPlayer(GetBuildScenes(), "G2", BuildTarget.iOS, options); } }