// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using UnityEditor; using UnityEditor.Build.Reporting; using UnityEngine; namespace XRTool.Util { public enum BuildTargets { NoHand = 0, Hand = 1, Both = 2, None = 3 } /// /// 打包窗口 /// public class BuildDeployWindow : EditorWindow { private Vector2 scrollPos; private static BuildConfig buildConf; /// public IEnumerable Scenes { get; set; } public string outputPath; public bool isAutoUpdateVersion = true; private bool isOpenOutPath = true; private string realPath; private string serverPath; public BuildOptions buildOptions = BuildOptions.None; private string[] suffix = new string[] { ".exe", ".apk", "Project", }; public string appName; private int buildIndex = 2; public const string initVersion = "1.0.0"; private string allSymbol; private bool isAutoInstall = true; private string androidSDPath = "sdcard/Android/data/"; private BuildTargets buildTargets; [MenuItem("XRTools/Build Window", false, 0)] public static void OpenWindow() { // Dock it next to the Scene View. var window = GetWindow(typeof(SceneView)); window.titleContent = new GUIContent("Build Window"); window.Show(); } private void OnEnable() { buildTargets = BuildTargets.None; PlayerSettings.companyName = "shadowcreator"; buildIndex = 2; if (buildConf == null) { if (BuildConfigMgr.Instance.IsInit) { buildConf = BuildConfig.Instance; } else { UnityLog.LogError("配置初始化失败!"); } //buildConf = AssetDatabase.LoadAssetAtPath("Assets/GameData/BuildConf/BuildConfig.asset"); } titleContent = new GUIContent("Build Window"); minSize = new Vector2(512, 256); CheckVersion(); if (buildConf == null) { UnityLog.LogError("buildConf is null"); } //UnityLog.Log(buildConf); } private void CheckVersion() { string version = PlayerSettings.bundleVersion; string[] curversion = version.Split('.'); if (curversion.Length < 3) { PlayerSettings.bundleVersion = initVersion; Debug.Log("版本号初始化:" + initVersion); } } /// /// 更新版本号,在打包时调用此函数 /// public string UpdateGameVersion() { string version = PlayerSettings.bundleVersion; string[] curversion = version.Split('.'); if (curversion.Length < 3) { Debug.LogError("请规范版本号:" + initVersion); return PlayerSettings.bundleVersion; } ///测试版本,程序自测版本 if (buildConf.buildType == BuildType.Test) { return PlayerSettings.bundleVersion; } int startIndex = (int)buildConf.buildVersion; int ver = int.Parse(curversion[startIndex]) + 1; curversion[startIndex] = ver.ToString(); if (buildConf.buildVersion != BuildVersion.Build) { for (int i = startIndex + 1; i < curversion.Length; i++) { curversion[i] = "0"; } } string newVersion = curversion[0]; for (int i = 1; i < curversion.Length; i++) { newVersion += "." + curversion[i]; } return newVersion; } private void ChangeHand() { if (buildTargets == BuildTargets.Hand) { PlayerSettings.productName = BuildConfig.Instance.appProName + "WithHand"; BuildConfig.Instance.useHand = true; } else if (buildTargets == BuildTargets.NoHand) { PlayerSettings.productName = BuildConfig.Instance.appProName + "NoHand"; BuildConfig.Instance.useHand = false; } else { PlayerSettings.productName = BuildConfig.Instance.appProName; BuildConfig.Instance.useHand = false; } if (buildTargets != BuildTargets.None) { PlayerSettings.applicationIdentifier = "com." + PlayerSettings.companyName + "." + PlayerSettings.productName; } BuildConfig.Instance.appPackName = PlayerSettings.applicationIdentifier; } /// /// 属性展示和同步 /// private void OnGUI() { //buildConf = EditorGUI.ObjectField(new Rect(0, 5, 600, 20), "BuildSetting", buildConf, typeof(BuildConfig), true) as BuildConfig; if (!buildConf) { return; } scrollPos = GUI.BeginScrollView(new Rect(0, 30, position.width, position.height), scrollPos, new Rect(0, 0, 1920, 2160)); BuildConfig.Instance.appProName = EditorGUILayout.TextField("应用名称", BuildConfig.Instance.appProName); BuildConfig.Instance.appPackName = EditorGUILayout.TextField("包名", BuildConfig.Instance.appPackName); buildConf.buildVersion = (BuildVersion)EditorGUILayout.EnumPopup("版本类型", buildConf.buildVersion); buildConf.buildType = (BuildType)EditorGUILayout.EnumPopup("发布模式", buildConf.buildType); buildConf.assetsType = (AssetsType)EditorGUILayout.EnumPopup("资源访问方式", buildConf.assetsType); buildConf.buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("运行的平台", buildConf.buildTarget); buildConf.playType = (PlayType)EditorGUILayout.EnumPopup("运行的模式", buildConf.playType); //buildConf.buildTargetGroup = (BuildTargetGroup)EditorGUILayout.EnumPopup("发布的平台", buildConf.buildTargetGroup); buildConf.otherSymbol = EditorGUILayout.TextField("宏", buildConf.otherSymbol); allSymbol = EditorGUILayout.TextField("全部宏", allSymbol); if (string.IsNullOrEmpty(allSymbol)) { allSymbol = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildConf.buildTargetGroup); } isAutoUpdateVersion = EditorGUILayout.Toggle("更新版本", isAutoUpdateVersion); isOpenOutPath = EditorGUILayout.Toggle("打开输出路径", isOpenOutPath); if (buildConf.buildTarget == BuildTarget.Android) { isAutoInstall = EditorGUILayout.Toggle("自动安装", isAutoInstall); } EditorGUILayout.LabelField("Version", PlayerSettings.bundleVersion); EditorGUILayout.LabelField("发布时间", buildConf.buildTime); outputPath = EditorGUILayout.TextField("输出路径", outputPath); if (string.IsNullOrEmpty(outputPath)) { string path = Application.dataPath; path = path.Substring(0, path.LastIndexOf("/")); outputPath = path; } realPath = EditorGUILayout.TextField("完整路径", realPath); serverPath = EditorGUILayout.TextField("服务器地址", serverPath); androidSDPath = EditorGUILayout.TextField("安卓sd卡路径", androidSDPath); if (string.IsNullOrEmpty(realPath)) { realPath = Path.Combine(outputPath, buildConf.buildTarget.ToString(), buildConf.playType.ToString(), Application.productName); } buildOptions = (BuildOptions)EditorGUILayout.EnumPopup("发布设置", buildOptions); var bd = buildTargets; buildTargets = (BuildTargets)EditorGUILayout.EnumPopup("发布操作方式", buildTargets); if (bd != buildTargets) { ChangeHand(); } if (GUILayout.Button("重置版本号", GUILayout.Width(200))) { buildConf.appVersion = initVersion; PlayerSettings.bundleVersion = buildConf.appVersion; } if (GUILayout.Button("强制升级版本", GUILayout.Width(200))) { PlayerSettings.bundleVersion = UpdateGameVersion(); } if (GUILayout.Button("同步设置", GUILayout.Width(200))) { SetDef(); } if (GUILayout.Button("清空编辑器日志", GUILayout.Width(200))) { UnityLog.ClearAllLog(); } if (GUILayout.Button("打开编辑器缓存路径", GUILayout.Width(200))) { OpenDirectory(Application.persistentDataPath); } if (buildConf.buildTarget == BuildTarget.Android && GUILayout.Button("安装", GUILayout.Width(200))) { if (!string.IsNullOrEmpty(appName) && !string.IsNullOrEmpty(realPath)) { InstallApp(Path.Combine(realPath, appName)); } } if (buildConf.buildTarget == BuildTarget.Android && GUILayout.Button("卸载", GUILayout.Width(200))) { if (!string.IsNullOrEmpty(appName) && !string.IsNullOrEmpty(realPath)) { UnInstallApp(); } } if (GUILayout.Button("清空Androd日志", GUILayout.Width(200))) { if (Directory.Exists(realPath + "/LogData")) { Directory.Delete(realPath + "/LogData", true); } } if (GUILayout.Button("拷贝缓存数据", GUILayout.Width(150))) { string resPath = "sdcard/ShadowCreate/MRStores/"; string logPath = realPath + "/ShadowCreate/MRStores/"; string[] files = new string[] { "DeviceDownInfo.xml" }; if (!Directory.Exists(logPath)) { Directory.CreateDirectory(logPath); } for (int i = 0; i < files.Length; i++) { string tmpPath = logPath + "/" + files[i]; if (File.Exists(tmpPath)) { File.Delete(tmpPath); } Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd)); string adb = "adb pull " + resPath + files[i] + " " + logPath; newThread.Start(adb); } OpenDirectory(logPath); } if (GUILayout.Button("拷贝日志到本地", GUILayout.Width(150))) { string resPath = androidSDPath + Application.identifier + "/files/LogData/"; string logPath = realPath + "/LogData/" + buildConf.buildType.ToString() + "_" + PlayerSettings.bundleVersion + "/" + Application.version; string[] files = new string[] { "error.txt", "except.txt", "log.txt" }; if (!Directory.Exists(logPath)) { Directory.CreateDirectory(logPath); } for (int i = 0; i < files.Length; i++) { string tmpPath = logPath + "/" + files[i]; if (File.Exists(tmpPath)) { File.Delete(tmpPath); } Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd)); string adb = "adb pull " + resPath + files[i] + " " + logPath; newThread.Start(adb); } OpenDirectory(logPath); } if (GUILayout.Button("推送服务器地址", GUILayout.Width(150))) { string targetPath = androidSDPath + Application.identifier + "/files/"; Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd)); string adb = "adb push " + serverPath + " " + targetPath; newThread.Start(adb); } if (GUILayout.Button("发布", GUILayout.Width(150))) { if (buildTargets != BuildTargets.Both) { DisPlayBuild(buildTargets); } else { ///先安装手势版本,再安装无手势版本 DisPlayBuild(BuildTargets.Hand); isAutoUpdateVersion = false; DisPlayBuild(BuildTargets.NoHand); } } GUI.EndScrollView(); //GUI.EndScrollView(); if (GUI.changed) { UpdateSetting(); } } public void DisPlayBuild(BuildTargets buildTargets) { string lastBuildTime = buildConf.buildTime; buildConf.buildTime = DateTime.Now.ToString(); EditorUtility.SetDirty(buildConf); this.buildTargets = buildTargets; ChangeHand(); ///设置宏编译指令 SetDef(); string lastVersion = PlayerSettings.bundleVersion; UpdateSetting(); string newVersion = isAutoUpdateVersion ? UpdateGameVersion() : lastVersion; PlayerSettings.bundleVersion = newVersion; appName = Application.productName + "_" + buildConf.buildType.ToString() + "_" + PlayerSettings.bundleVersion + "_" + DateTime.Now.ToString("MMddHHmm") + suffix[buildIndex]; bool isSucess = PlayerBuild(Path.Combine(realPath, appName)); if (!isSucess) { PlayerSettings.bundleVersion = lastVersion; buildConf.buildTime = lastBuildTime; EditorUtility.SetDirty(buildConf); } else { if (isOpenOutPath) { OpenDirectory(realPath); } if (buildConf.buildTarget == BuildTarget.Android && isAutoInstall) { InstallApp(Path.Combine(realPath, appName)); } } Debug.Log("当前版本" + PlayerSettings.bundleVersion); } private void InstallApp(string realPath) { if (File.Exists(realPath)) { Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd)); string cmd = "adb install -r -d " + realPath; newThread.Start(cmd); } else { UnityLog.LogError(realPath + " 不存在"); } } private void UnInstallApp() { Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd)); string cmd = "adb uninstall " + Application.identifier; newThread.Start(cmd); } private void UpdateSetting() { if (buildConf.buildTarget == BuildTarget.Android) { buildConf.buildTargetGroup = BuildTargetGroup.Android; if (EditorUserBuildSettings.exportAsGoogleAndroidProject) { buildIndex = 2; } else { buildIndex = 1; } } if (buildConf.buildTarget == BuildTarget.iOS) { buildConf.buildTargetGroup = BuildTargetGroup.iOS; buildIndex = 2; } if (buildConf.buildTarget == BuildTarget.StandaloneWindows) { buildConf.buildTargetGroup = BuildTargetGroup.Standalone; buildIndex = 0; } //buildConf.buildType.ToString(), buildConf.playType.ToString(), realPath = Path.Combine(outputPath, Application.productName, buildConf.buildTarget.ToString(), buildConf.playType.ToString()); } private bool PlayerBuild(string path) { if (EditorUserBuildSettings.activeBuildTarget != buildConf.buildTarget) { EditorUserBuildSettings.SwitchActiveBuildTarget(buildConf.buildTargetGroup, buildConf.buildTarget); } BuildReport buildReport = Build(path); bool success = buildReport != null && buildReport.summary.result == BuildResult.Succeeded; return success; } private BuildReport Build(string path) { Scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path); EditorUtility.DisplayProgressBar("Build Pipeline", "Gathering Build Data...", 0.25f); BuildReport buildReport = default; try { buildReport = BuildPipeline.BuildPlayer( Scenes.ToArray(), path, EditorUserBuildSettings.activeBuildTarget, buildOptions); } catch (Exception e) { Debug.LogError($"{e.Message}\n{e.StackTrace}"); } EditorUtility.ClearProgressBar(); return buildReport; } private void SetDef() { string lastSym = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildConf.buildTargetGroup); Debug.Log(lastSym); ///设置指令用;分割 string symbol = string.Format("buildType_{0};assetsType_{1};buildPlatform_{2};playType_{3}", buildConf.buildType, buildConf.assetsType, buildConf.buildTarget, buildConf.playType).ToUpper() + ";" + buildConf.otherSymbol; if (symbol != lastSym) { Debug.Log(lastSym + "当前宏编译配置:" + symbol); PlayerSettings.SetScriptingDefineSymbolsForGroup(buildConf.buildTargetGroup, symbol); } allSymbol = symbol; } /// /// 弹出指定的文件窗口,仅windows和mac使用 /// /// public static void OpenDirectory(string path) { if (string.IsNullOrEmpty(path)) return; if (!Directory.Exists(path)) { UnityEngine.Debug.LogError("No Directory: " + path); return; } //Application.dataPath 只能在主线程中获取 int lastIndex = Application.dataPath.LastIndexOf("/"); string shellPath = Application.dataPath.Substring(0, lastIndex) + "/Shell/"; // 新开线程防止锁死 Thread newThread = new Thread(new ParameterizedThreadStart(CmdOpenDirectory)); newThread.Start(path); } private static void CmdOpenDirectory(object obj) { System.Diagnostics.Process p = new System.Diagnostics.Process(); #if UNITY_EDITOR_WIN p.StartInfo.FileName = "cmd.exe"; p.StartInfo.Arguments = "/c start " + obj.ToString(); #elif UNITY_EDITOR_OSX p.StartInfo.FileName = "bash"; string shPath = shellPath + "openDir.sh"; p.StartInfo.Arguments = shPath + " " + obj.ToString(); #endif //UnityEngine.Debug.Log(p.StartInfo.Arguments); p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.CreateNoWindow = true; p.Start(); p.WaitForExit(); p.Close(); } private static void RunCmd(object command) { //例Process System.Diagnostics.Process p = new System.Diagnostics.Process(); p.StartInfo.FileName = "cmd.exe"; //确定程序名 p.StartInfo.Arguments = "/c " + command.ToString(); //确定程式命令行 p.StartInfo.UseShellExecute = false; //Shell的使用 p.StartInfo.RedirectStandardInput = true; //重定向输入 p.StartInfo.RedirectStandardOutput = true; //重定向输出 p.StartInfo.RedirectStandardError = true; //重定向输出错误 p.StartInfo.CreateNoWindow = false; //设置置不显示示窗口 p.Start(); //return p.StandardOutput.ReadToEnd(); //输出出流取得命令行结果果 } } }