当前位置:   article > 正文

【Unity编辑器扩展】扩展Unity工具栏Toolbar, 一键出包/打热更简化打包流程_unity editor 自定义拓展工具

unity editor 自定义拓展工具

GF_HybridCLR是基于GameFramework + HybridCLR的一款工具链完善,工作流简洁的游戏框架。拥有标准高效的开发工作流,开箱即用,适用于快速研发。

出包时经常遇到忘记刷新配置表、忘记重新打AB包等等,接入HybridCLR每次打热更包也需要重新编译热更dll,新发App时需要生成桥接函数等。各种琐碎的打包准备工作,一旦忘记操作就容易出故障。基于工作中遇到的痛点,迫切需要写一个傻瓜式一键打包/打热更的工具。

为了这个一键打包工具入口突出,就把它放在Unity编辑器的Toolbar栏,如图:

 点击Toolbar栏Build App/Hotfix后打开一键打包/打热更工具:

一,扩展Unity编辑器的菜单栏(Toolbar):

Toolbar扩展方法可参考github开源项目: GitHub - marijnz/unity-toolbar-extender: Extend the Unity Toolbar with your own Editor UI code.

 实现原理,通过反射获取UnityEditor的Toolbar类,扩展GUI出回调。

Toolbar扩展插件源代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using UnityEditor;
  5. using UnityEngine;
  6. #if UNITY_2019_1_OR_NEWER
  7. using UnityEngine.UIElements;
  8. #else
  9. using UnityEngine.Experimental.UIElements;
  10. #endif
  11. namespace UnityToolbarExtender
  12. {
  13. public static class ToolbarCallback
  14. {
  15. static Type m_toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
  16. static Type m_guiViewType = typeof(Editor).Assembly.GetType("UnityEditor.GUIView");
  17. #if UNITY_2020_1_OR_NEWER
  18. static Type m_iWindowBackendType = typeof(Editor).Assembly.GetType("UnityEditor.IWindowBackend");
  19. static PropertyInfo m_windowBackend = m_guiViewType.GetProperty("windowBackend",
  20. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  21. static PropertyInfo m_viewVisualTree = m_iWindowBackendType.GetProperty("visualTree",
  22. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  23. #else
  24. static PropertyInfo m_viewVisualTree = m_guiViewType.GetProperty("visualTree",
  25. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  26. #endif
  27. static FieldInfo m_imguiContainerOnGui = typeof(IMGUIContainer).GetField("m_OnGUIHandler",
  28. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  29. static ScriptableObject m_currentToolbar;
  30. /// <summary>
  31. /// Callback for toolbar OnGUI method.
  32. /// </summary>
  33. public static Action OnToolbarGUI;
  34. public static Action OnToolbarGUILeft;
  35. public static Action OnToolbarGUIRight;
  36. static ToolbarCallback()
  37. {
  38. EditorApplication.update -= OnUpdate;
  39. EditorApplication.update += OnUpdate;
  40. }
  41. static void OnUpdate()
  42. {
  43. // Relying on the fact that toolbar is ScriptableObject and gets deleted when layout changes
  44. if (m_currentToolbar == null)
  45. {
  46. // Find toolbar
  47. var toolbars = Resources.FindObjectsOfTypeAll(m_toolbarType);
  48. m_currentToolbar = toolbars.Length > 0 ? (ScriptableObject)toolbars[0] : null;
  49. if (m_currentToolbar != null)
  50. {
  51. #if UNITY_2021_1_OR_NEWER
  52. var root = m_currentToolbar.GetType().GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
  53. var rawRoot = root.GetValue(m_currentToolbar);
  54. var mRoot = rawRoot as VisualElement;
  55. RegisterCallback("ToolbarZoneLeftAlign", OnToolbarGUILeft);
  56. RegisterCallback("ToolbarZoneRightAlign", OnToolbarGUIRight);
  57. void RegisterCallback(string root, Action cb)
  58. {
  59. var toolbarZone = mRoot.Q(root);
  60. var parent = new VisualElement()
  61. {
  62. style = {
  63. flexGrow = 1,
  64. flexDirection = FlexDirection.Row,
  65. }
  66. };
  67. var container = new IMGUIContainer();
  68. container.style.flexGrow = 1;
  69. container.onGUIHandler += () => {
  70. cb?.Invoke();
  71. };
  72. parent.Add(container);
  73. toolbarZone.Add(parent);
  74. }
  75. #else
  76. #if UNITY_2020_1_OR_NEWER
  77. var windowBackend = m_windowBackend.GetValue(m_currentToolbar);
  78. // Get it's visual tree
  79. var visualTree = (VisualElement) m_viewVisualTree.GetValue(windowBackend, null);
  80. #else
  81. // Get it's visual tree
  82. var visualTree = (VisualElement) m_viewVisualTree.GetValue(m_currentToolbar, null);
  83. #endif
  84. // Get first child which 'happens' to be toolbar IMGUIContainer
  85. var container = (IMGUIContainer) visualTree[0];
  86. // (Re)attach handler
  87. var handler = (Action) m_imguiContainerOnGui.GetValue(container);
  88. handler -= OnGUI;
  89. handler += OnGUI;
  90. m_imguiContainerOnGui.SetValue(container, handler);
  91. #endif
  92. }
  93. }
  94. }
  95. static void OnGUI()
  96. {
  97. var handler = OnToolbarGUI;
  98. if (handler != null) handler();
  99. }
  100. }
  101. [InitializeOnLoad]
  102. public static class UnityEditorToolbar
  103. {
  104. static int m_toolCount;
  105. static GUIStyle m_commandStyle = null;
  106. public static readonly List<Action> LeftToolbarGUI = new List<Action>();
  107. public static readonly List<Action> RightToolbarGUI = new List<Action>();
  108. static UnityEditorToolbar()
  109. {
  110. Type toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
  111. #if UNITY_2019_1_OR_NEWER
  112. string fieldName = "k_ToolCount";
  113. #else
  114. string fieldName = "s_ShownToolIcons";
  115. #endif
  116. FieldInfo toolIcons = toolbarType.GetField(fieldName,
  117. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
  118. #if UNITY_2019_3_OR_NEWER
  119. m_toolCount = toolIcons != null ? ((int)toolIcons.GetValue(null)) : 8;
  120. #elif UNITY_2019_1_OR_NEWER
  121. m_toolCount = toolIcons != null ? ((int) toolIcons.GetValue(null)) : 7;
  122. #elif UNITY_2018_1_OR_NEWER
  123. m_toolCount = toolIcons != null ? ((Array) toolIcons.GetValue(null)).Length : 6;
  124. #else
  125. m_toolCount = toolIcons != null ? ((Array) toolIcons.GetValue(null)).Length : 5;
  126. #endif
  127. ToolbarCallback.OnToolbarGUI = OnGUI;
  128. ToolbarCallback.OnToolbarGUILeft = GUILeft;
  129. ToolbarCallback.OnToolbarGUIRight = GUIRight;
  130. }
  131. #if UNITY_2019_3_OR_NEWER
  132. public const float space = 8;
  133. #else
  134. public const float space = 10;
  135. #endif
  136. public const float largeSpace = 20;
  137. public const float buttonWidth = 32;
  138. public const float dropdownWidth = 80;
  139. #if UNITY_2019_1_OR_NEWER
  140. public const float playPauseStopWidth = 140;
  141. #else
  142. public const float playPauseStopWidth = 100;
  143. #endif
  144. static void OnGUI()
  145. {
  146. // Create two containers, left and right
  147. // Screen is whole toolbar
  148. if (m_commandStyle == null)
  149. {
  150. m_commandStyle = new GUIStyle("CommandLeft");
  151. }
  152. var screenWidth = EditorGUIUtility.currentViewWidth;
  153. // Following calculations match code reflected from Toolbar.OldOnGUI()
  154. float playButtonsPosition = Mathf.RoundToInt((screenWidth - playPauseStopWidth) / 2);
  155. Rect leftRect = new Rect(0, 0, screenWidth, Screen.height);
  156. leftRect.xMin += space; // Spacing left
  157. leftRect.xMin += buttonWidth * m_toolCount; // Tool buttons
  158. #if UNITY_2019_3_OR_NEWER
  159. leftRect.xMin += space; // Spacing between tools and pivot
  160. #else
  161. leftRect.xMin += largeSpace; // Spacing between tools and pivot
  162. #endif
  163. leftRect.xMin += 64 * 2; // Pivot buttons
  164. leftRect.xMax = playButtonsPosition;
  165. Rect rightRect = new Rect(0, 0, screenWidth, Screen.height);
  166. rightRect.xMin = playButtonsPosition;
  167. rightRect.xMin += m_commandStyle.fixedWidth * 3; // Play buttons
  168. rightRect.xMax = screenWidth;
  169. rightRect.xMax -= space; // Spacing right
  170. rightRect.xMax -= dropdownWidth; // Layout
  171. rightRect.xMax -= space; // Spacing between layout and layers
  172. rightRect.xMax -= dropdownWidth; // Layers
  173. #if UNITY_2019_3_OR_NEWER
  174. rightRect.xMax -= space; // Spacing between layers and account
  175. #else
  176. rightRect.xMax -= largeSpace; // Spacing between layers and account
  177. #endif
  178. rightRect.xMax -= dropdownWidth; // Account
  179. rightRect.xMax -= space; // Spacing between account and cloud
  180. rightRect.xMax -= buttonWidth; // Cloud
  181. rightRect.xMax -= space; // Spacing between cloud and collab
  182. rightRect.xMax -= 78; // Colab
  183. // Add spacing around existing controls
  184. leftRect.xMin += space;
  185. leftRect.xMax -= space;
  186. rightRect.xMin += space;
  187. rightRect.xMax -= space;
  188. // Add top and bottom margins
  189. #if UNITY_2019_3_OR_NEWER
  190. leftRect.y = 4;
  191. leftRect.height = 22;
  192. rightRect.y = 4;
  193. rightRect.height = 22;
  194. #else
  195. leftRect.y = 5;
  196. leftRect.height = 24;
  197. rightRect.y = 5;
  198. rightRect.height = 24;
  199. #endif
  200. if (leftRect.width > 0)
  201. {
  202. GUILayout.BeginArea(leftRect);
  203. GUILayout.BeginHorizontal();
  204. foreach (var handler in LeftToolbarGUI)
  205. {
  206. handler();
  207. }
  208. GUILayout.EndHorizontal();
  209. GUILayout.EndArea();
  210. }
  211. if (rightRect.width > 0)
  212. {
  213. GUILayout.BeginArea(rightRect);
  214. GUILayout.BeginHorizontal();
  215. foreach (var handler in RightToolbarGUI)
  216. {
  217. handler();
  218. }
  219. GUILayout.EndHorizontal();
  220. GUILayout.EndArea();
  221. }
  222. }
  223. public static void GUILeft()
  224. {
  225. GUILayout.BeginHorizontal();
  226. foreach (var handler in LeftToolbarGUI)
  227. {
  228. handler();
  229. }
  230. GUILayout.EndHorizontal();
  231. }
  232. public static void GUIRight()
  233. {
  234. GUILayout.BeginHorizontal();
  235. foreach (var handler in RightToolbarGUI)
  236. {
  237. handler();
  238. }
  239. GUILayout.EndHorizontal();
  240. }
  241. }
  242. }

使用方法: 

定义一个静态类添加[UnityEditor.InitializeOnLoad],使其自动执行构造函数。

在Toolbar右侧绘制GUI: UnityEditorToolbar.RightToolbarGUI.Add(OnRightToolbarGUI);
在Toolbar左侧绘制GUI: UnityEditorToolbar.LeftToolbarGUI.Add(OnLeftToolbarGUI);

  1. using UnityEngine;
  2. using UnityEditor;
  3. using UnityToolbarExtender;
  4. using UnityGameFramework.Editor.ResourceTools;
  5. [UnityEditor.InitializeOnLoad]
  6. public static class EditorToolbarExtension
  7. {
  8. private static GUIContent buildBtContent;
  9. static EditorToolbarExtension()
  10. {
  11. buildBtContent = EditorGUIUtility.TrTextContentWithIcon("Build App/Hotfix","打新包/打热更", "UnityLogo");
  12. UnityEditorToolbar.RightToolbarGUI.Add(OnRightToolbarGUI);
  13. UnityEditorToolbar.LeftToolbarGUI.Add(OnLeftToolbarGUI);
  14. }
  15. private static void OnLeftToolbarGUI()
  16. {
  17. //在Toolbar左侧绘制UI
  18. }
  19. private static void OnRightToolbarGUI()
  20. {
  21. //在Toolbar右侧绘制UI
  22. if (GUILayout.Button(buildBtContent,EditorStyles.toolbarButton, GUILayout.MaxWidth(125), GUILayout.Height(EditorGUIUtility.singleLineHeight)))
  23. {
  24. AppBuildEidtor.Open();
  25. GUIUtility.ExitGUI();
  26. }
  27. GUILayout.FlexibleSpace();
  28. }
  29. }

二,打包工具功能设计: 

 先明确工具要解决的问题:

1. 工具界面可配置打资源和打App的相关设置,切配置持久化保存。

2. 可一键打热更资源,一键出包,简化流程。

具体功能设计:

1. 打单机包或增量热更包:

单机包或增量热更包出包时都需要把AB资源打进包里,点击Build App按钮逻辑流程为:若是热更包则生成热更(hotfix)Dll => 自动处理AB包重复依赖资源 =>  打AB包 => 把AB包复制到SteamingAssets目录 => 若是热更包则执行HybridCLR预处理命令(生成link.xml,桥接函数等) => 把AOT泛型补充dll自动复制到Resources目录 => Build出包;

Build出包需要根据目标平台留出一些打包常用的参数设置入口,例如app版本号、Version Code, 打aab(谷歌商店包),开发者模式,安卓密钥等。

2. 打全热更包:

①全热更包是进入游戏后再从热更地址下载资源,所以出包时不用打AB包。点击Build App按钮逻辑流程为:HybridCLR预处理命令(生成link.xml,桥接函数等) => 把AOT泛型补充dll自动复制到Resources目录 => Build出包;

②打热更资源和dll,对于热更包(增量热更/全热更),每次更新只需要点击Build Resources按钮打出热更资源,然后把热更资源上传到资源服务器即可。点击Build Resources按钮逻辑流程为:一生成热更dll => 自动处理AB包重复依赖资源 =>  打AB包;把打出的AB包提交到热更新资源服务器即可。

3. 其它功能:

打资源/出包常用配置项可在界面中配置并持久化保存配置数据;

Resource Mode: 可选择资源模式,单机模式 / 全热更模式 / 部分热更模式(即,需要某部分资源时再热更)

除了上述部分,还需要在各个功能模块区域显示对应的一键跳转按钮,如:

Resource Editor按钮: 打开AB包编辑器

Hotfix Settings按钮:打开HybridCLR Settings界面,配置C#代码热更相关(一般只需要配置一次)

Player Settings按钮:打开Player Setting界面,设置出包参数。

三,具体功能实现:

由于GF框架内置的打AB包工具已经有了打资源的相关配置和功能按钮,索性直接基于GF的Resource Builder工具做修改。

1. Resource Editor按钮, 打开GF的Resource Editor(AB包编辑器):

UnityGameFramework.Editor.ResourceTools.ResourceEditor类有个打开窗口的静态私有方法“Open”, 只需要通过反射调用即可:

  1. private void OpenResourcesEditor()
  2. {
  3. var resEditorClass = Utility.Assembly.GetType("UnityGameFramework.Editor.ResourceTools.ResourceEditor");
  4. resEditorClass?.GetMethod("Open", BindingFlags.Static | BindingFlags.NonPublic)?.Invoke(null, null);
  5. }

2. Resource Mode资源模式切换(单机/全热更/需要时热更):

ResourceComponent留出了SetResourceMode()方法,但运行时调用却报错,原来ResourceComponent在Start回调里根据Resource Mode做一次初始化,不允许初始化之后再修改,即使修改了ResourceMode也是无效的。为了保持低耦合不能改GF源码,只能特殊处理,在其它MonoBehavior脚本的Awake方法中通过反射修改ResourceComponent的私有变量m_ResourceMode,Awake方法早于ResourceComponent的Start,这样设置就能生效了。

  1. private void Awake()
  2. {
  3. var resCom = GameEntry.GetComponent<ResourceComponent>();
  4. if (resCom != null)
  5. {
  6. var resTp = resCom.GetType();
  7. var m_ResourceMode = resTp.GetField("m_ResourceMode", BindingFlags.Instance | BindingFlags.NonPublic);
  8. m_ResourceMode.SetValue(resCom, AppSettings.Instance.ResourceMode);
  9. Log.Info("------------Set ResourceMode:{0}", AppSettings.Instance.ResourceMode);
  10. }
  11. }

其中AppSettings是一个运行时的ScriptableObject,用于保存一些运行时配置,如是否开启debug模式,ResourceMode类型等。

AppSettings配置文件实现:

  1. using GameFramework.Resource;
  2. using UnityEngine;
  3. [CreateAssetMenu(fileName = "AppSettings", menuName = "ScriptableObject/AppSettings")]
  4. public class AppSettings : ScriptableObject
  5. {
  6. private static AppSettings mInstance = null;
  7. public static AppSettings Instance
  8. {
  9. get
  10. {
  11. if (mInstance == null)
  12. {
  13. mInstance = Resources.Load<AppSettings>("AppSettings");
  14. }
  15. return mInstance;
  16. }
  17. }
  18. [Tooltip("debug模式,默认显示debug窗口")]
  19. public bool DebugMode = false;
  20. [Tooltip("资源模式: 单机/全热更/需要时热更")]
  21. public ResourceMode ResourceMode = ResourceMode.Package;
  22. }

AppSettings是全局配置,因此使用单例模式。当打包工具界面打开时,检测Resource目录是否存在AppSettings配置文件,若无则自动创建。工具界面ResourceMode设置实时同步保存到AppSettings, 游戏运行时获取并应用AppSettings中的配置。

Hotfix Settings(热更相关设置):

打热更资源时根据这些配置自动生成version.json文件,其中信息包含热更包hash code, 资源大小、资源版本号、热更下载地址、App是否有新版本、是否强制更新App、当前版本资源适用于哪些App版本等。游戏启动时会先从服务器请求version.json信息检测是否需要更新。

热更基本流程

 Update Prefix Uri: 热更资源下载地址;

Applicable Verison:当前版本资源适用哪些App版本,多版本用‘|’分割;

App Update Url:App下载跳转链接;

Force Update:是否强制更新App;

App Update Description:App更新说明,显示在新版本提示对话框;

Hotfix Settings跳转按钮,跳转到HybridCLR设置界面:

SettingsService.OpenProjectSettings("Project/HybridCLR Settings");

跳转到Player Settings界面:

SettingsService.OpenProjectSettings("Project/Player");

Build App Settings(出包相关设置):

Build App Buindle: 打谷歌商店aab文件;

Development Build: 开发者模式打包;

Debug Mode: 调试模式,true:默认显示GF Debug窗口;

Use Custom Keystore: 使用自定义keystore打安卓包;

选择keystore文件:

  1. if (GUILayout.Button("Select Keystore", GUILayout.Width(160f)))
  2. {
  3. var keystoreDir = string.IsNullOrWhiteSpace(AppBuildSettings.Instance.AndroidKeystoreName) ? Directory.GetParent(Application.dataPath).FullName : Path.GetDirectoryName(AppBuildSettings.Instance.AndroidKeyAliasName);
  4. var openPath = Directory.Exists(keystoreDir) ? keystoreDir : Directory.GetParent(Application.dataPath).FullName;
  5. string path = EditorUtility.OpenFilePanel("Select Keystore", openPath, "keystore,jks,ks");
  6. AppBuildSettings.Instance.AndroidKeystoreName = PlayerSettings.Android.keystoreName = path;
  7. GUIUtility.ExitGUI();
  8. }

一键打热更资源实现:

直接调用GF框架自带的ResourceBuilderController的BuildResources()方法即可;

一键出包:

Unity的Build Settings界面已经有了现成的出包功能,可以直接通过反射调用。

 从Unity开源代码中可以找到具体实现:https://github.com/Unity-Technologies/UnityCsReference

 在BuildPlayerWindow.cs可以看到,Build按钮调用了CallBuildMethods静态方法,但是此方法每次Build都会弹出Build路径选择界面,这绝对是不能接受的。而且我们还需要根据版本号、是否为debug版等信息命名包文件。

查看BuildPlayerWindowBuildMethods.cs源码可以发现,BuildPlayerWindow.RegisterGetBuildPlayerOptionsHandler()函数可以注册打包配置获取参数,这样就可以修打包配置,自动设置打包路径。

自定义打包配置:

  1. private void Awake()
  2. {
  3. BuildPlayerWindow.RegisterGetBuildPlayerOptionsHandler(CustomBuildOptions);
  4. }
  5. private BuildPlayerOptions CustomBuildOptions(BuildPlayerOptions options)
  6. {
  7. var buildTarget = GetSelectedBuildTarget();
  8. BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
  9. int subtarget = (int)Utility.Assembly.GetType("UnityEditor.EditorUserBuildSettings").GetMethod("GetSelectedSubtargetFor", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { buildTarget });
  10. string buildLocation = GetBuildLocation(buildTargetGroup, buildTarget, subtarget, options.options);
  11. bool isDir = !Path.HasExtension(buildLocation);
  12. if (string.IsNullOrWhiteSpace(buildLocation) || (isDir && !Directory.Exists(buildLocation)))
  13. throw new BuildMethodException("Build location for buildTarget " + buildTarget + " is not valid.");
  14. //Check if Lz4 is supported for the current buildtargetgroup and enable it if need be
  15. if ((bool)Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("SupportsLz4Compression", BindingFlags.Static | BindingFlags.Public).Invoke(null, new object[] { buildTargetGroup, buildTarget }))
  16. {
  17. //internal enum Compression
  18. //{
  19. // None = 0,
  20. // Lz4 = 2,
  21. // Lz4HC = 3,
  22. //}
  23. var compression = (int)Utility.Assembly.GetType("UnityEditor.EditorUserBuildSettings").GetMethod("GetCompressionType", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { buildTargetGroup });
  24. if (compression < 0)
  25. compression = (int)Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("GetDefaultCompression", BindingFlags.Static | BindingFlags.Public).Invoke(null, new object[] { buildTargetGroup, buildTarget });
  26. if (compression == 2)//Lz4
  27. options.options |= BuildOptions.CompressWithLz4;
  28. else if (compression == 3)//Lz4HC
  29. options.options |= BuildOptions.CompressWithLz4HC;
  30. }
  31. bool developmentBuild = EditorUserBuildSettings.development;
  32. if (developmentBuild)
  33. options.options |= BuildOptions.Development;
  34. if (EditorUserBuildSettings.allowDebugging && developmentBuild)
  35. options.options |= BuildOptions.AllowDebugging;
  36. if (EditorUserBuildSettings.symlinkSources)
  37. options.options |= BuildOptions.SymlinkSources;
  38. if (EditorUserBuildSettings.connectProfiler && (developmentBuild || buildTarget == BuildTarget.WSAPlayer))
  39. options.options |= BuildOptions.ConnectWithProfiler;
  40. if (EditorUserBuildSettings.buildWithDeepProfilingSupport && developmentBuild)
  41. options.options |= BuildOptions.EnableDeepProfilingSupport;
  42. if (EditorUserBuildSettings.buildScriptsOnly)
  43. options.options |= BuildOptions.BuildScriptsOnly;
  44. string connectID = Utility.Assembly.GetType("UnityEditor.Profiling.ProfilerUserSettings").GetProperty("customConnectionID", BindingFlags.Static | BindingFlags.Public).GetValue(null, null) as string;
  45. if (!string.IsNullOrEmpty(connectID) && developmentBuild)
  46. options.options |= BuildOptions.CustomConnectionID;
  47. var checkFunc = typeof(UnityEditor.BuildPlayerWindow.DefaultBuildMethods).GetMethod("IsInstallInBuildFolderOption", BindingFlags.Static | BindingFlags.NonPublic);
  48. if ((bool)checkFunc.Invoke(null, null))
  49. {
  50. options.options |= BuildOptions.InstallInBuildFolder;
  51. }
  52. options.target = buildTarget;
  53. options.subtarget = subtarget;
  54. options.targetGroup = buildTargetGroup;
  55. options.locationPathName = buildLocation;
  56. options.assetBundleManifestPath = Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("GetStreamingAssetsBundleManifestPath", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null) as string;
  57. // Build a list of scenes that are enabled
  58. ArrayList scenesList = new ArrayList();
  59. EditorBuildSettingsScene[] editorScenes = EditorBuildSettings.scenes;
  60. foreach (EditorBuildSettingsScene scene in editorScenes)
  61. {
  62. if (scene.enabled)
  63. {
  64. scenesList.Add(scene.path);
  65. break;// GF框架只需要把启动场景打进包里,其它场景动态加载
  66. }
  67. }
  68. options.scenes = scenesList.ToArray(typeof(string)) as string[];
  69. return options;
  70. }

 获取不同平台的包文件名,例如Android平台可以打出apk或aab,也可以导出安卓工程,不同设置包文件格式不同,Build出的可能是一个文件,也有可能是一个文件夹。这些Unity内部已经有函数做了处理,要用反射调用Unity内部函数:

  1. //获取Build Location
  2. private static string GetBuildLocation(BuildTargetGroup targetGroup, BuildTarget target, int subtarget, BuildOptions options)
  3. {
  4. string defaultFolder = UtilityBuiltin.ResPath.GetCombinePath(Directory.GetParent(Application.dataPath).FullName, AppBuildSettings.Instance.AppBuildDir, target.ToString());
  5. string defaultName = Utility.Text.Format("{0}_{1}{2}_v{3}", Application.productName, AppSettings.Instance.DebugMode ? "debug" : "release", EditorUserBuildSettings.development ? "Dev" : string.Empty, Application.version);
  6. string extension = Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("GetExtensionForBuildTarget", new Type[] { typeof(BuildTargetGroup), typeof(BuildTarget), typeof(int), typeof(BuildOptions) }).Invoke(null, new object[] { targetGroup, target, subtarget, options }) as string;
  7. string buildPath = defaultFolder;
  8. if (!string.IsNullOrEmpty(extension))
  9. {
  10. string appFileName = Utility.Text.Format("{0}.{1}", defaultName, extension);
  11. buildPath = UtilityBuiltin.ResPath.GetCombinePath(defaultFolder, appFileName);
  12. }
  13. return buildPath;
  14. }

然后就可以通过反射调用UnityEditor.BuildPlayerWindow.CallBuildMethods()方法:

  1. private void CallBuildMethods()
  2. {
  3. #if !DISABLE_HYBRIDCLR
  4. HybridCLR.Editor.Commands.PrebuildCommand.GenerateAll();
  5. #endif
  6. var buildWin = Utility.Assembly.GetType("UnityEditor.BuildPlayerWindow");
  7. if (buildWin != null)
  8. {
  9. var buildFunc = buildWin.GetMethod("CallBuildMethods", System.Reflection.BindingFlags.Static | BindingFlags.NonPublic);
  10. buildFunc?.Invoke(null, new object[] { false, BuildOptions.ShowBuiltPlayer });
  11. }
  12. }
  13. private void BuildApp()
  14. {
  15. if ((m_Controller.OutputPackageSelected || m_Controller.OutputPackedSelected))
  16. {
  17. if (m_Controller.BuildResources())
  18. {
  19. AssetDatabase.Refresh();
  20. CallBuildMethods();
  21. }
  22. }
  23. else if (m_Controller.OutputFullSelected)
  24. {
  25. DeleteStreamingAssets();
  26. CallBuildMethods();
  27. }
  28. }

工具完整代码:

  1. using GameFramework;
  2. using System;
  3. using System.IO;
  4. using System.Reflection;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using GameFramework.Resource;
  8. using Unity.VisualScripting;
  9. using HybridCLR.Editor.Commands;
  10. using System.Text;
  11. using System.Linq;
  12. using static UnityEditor.BuildPlayerWindow;
  13. using System.Collections;
  14. namespace UnityGameFramework.Editor.ResourceTools
  15. {
  16. /// <summary>
  17. /// 资源生成器。
  18. /// </summary>
  19. public class AppBuildEidtor : EditorWindow
  20. {
  21. private readonly string[] keystoreExtNames = { ".keystore", ".jks", ".ks" };
  22. private ResourceBuilderController m_Controller = null;
  23. private bool m_OrderBuildResources = false;
  24. private int m_CompressionHelperTypeNameIndex = 0;
  25. private int m_BuildEventHandlerTypeNameIndex = 0;
  26. private GUIContent hotfixUrlContent;
  27. private GUIContent applicableVerContent;
  28. private GUIContent forceUpdateAppContent;
  29. private GUIContent appUpdateUrlContent;
  30. private GUIContent appUpdateDescContent;
  31. private GUIContent revealFolderContent;
  32. private GUIContent buildResBtContent;
  33. private GUIContent buildAppBtContent;
  34. private GUIContent saveBtContent;
  35. private GUIContent playerSettingBtContent;
  36. private GUIContent hybridclrSettingBtContent;
  37. private Vector2 scrollPosition;
  38. private GUIStyle dropDownBtStyle;
  39. public static void Open()
  40. {
  41. AppBuildEidtor window = GetWindow<AppBuildEidtor>("App Builder", true);
  42. #if UNITY_2019_3_OR_NEWER
  43. window.minSize = new Vector2(800f, 800f);
  44. #else
  45. window.minSize = new Vector2(800f, 750f);
  46. #endif
  47. }
  48. private void Awake()
  49. {
  50. BuildPlayerWindow.RegisterGetBuildPlayerOptionsHandler(CustomBuildOptions);
  51. }
  52. private void OnEnable()
  53. {
  54. hotfixUrlContent = new GUIContent("Update Prefix Uri", "热更新资源服务器地址");
  55. applicableVerContent = new GUIContent("Applicable Version", "资源适用的客户端版本号,多版本用'|'分割");
  56. forceUpdateAppContent = new GUIContent("Force Update", "是否强制更新App");
  57. appUpdateUrlContent = new GUIContent("App Update Url", "App更新下载地址");
  58. appUpdateDescContent = new GUIContent("App Update Description:", "App更新公告,用于显示在对话框(支持TextMeshPro富文本)");
  59. revealFolderContent = new GUIContent("Reveal Folder", "打包完成后打开资源输出目录");
  60. buildResBtContent = EditorGUIUtility.TrTextContentWithIcon("Build Resources", "打AB包/热更", "CloudConnect@2x");
  61. buildAppBtContent = EditorGUIUtility.TrTextContentWithIcon("Build App", "打新包,首次打热更包请使用Full Build", "UnityLogo");
  62. playerSettingBtContent = EditorGUIUtility.TrTextContentWithIcon("Player Settings", "打开Player Settings界面", "Settings");
  63. hybridclrSettingBtContent = EditorGUIUtility.TrTextContentWithIcon("Hotfix Settings", "打开HybridCLR Settings界面", "Settings");
  64. saveBtContent = EditorGUIUtility.TrTextContentWithIcon("Save", "保存设置", "SaveAs@2x");
  65. string tgStyleName = "DropDownToggleButton";
  66. dropDownBtStyle = GUI.skin.FindStyle(tgStyleName) ?? EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector).FindStyle(tgStyleName);
  67. if (AppSettings.Instance == null)
  68. {
  69. AssetDatabase.CreateAsset(CreateInstance<AppSettings>(), "Assets/Resources/AppSettings.asset");
  70. }
  71. RefreshHybridCLREnable();
  72. m_Controller = new ResourceBuilderController();
  73. m_Controller.OnLoadingResource += OnLoadingResource;
  74. m_Controller.OnLoadingAsset += OnLoadingAsset;
  75. m_Controller.OnLoadCompleted += OnLoadCompleted;
  76. m_Controller.OnAnalyzingAsset += OnAnalyzingAsset;
  77. m_Controller.OnAnalyzeCompleted += OnAnalyzeCompleted;
  78. m_Controller.ProcessingAssetBundle += OnProcessingAssetBundle;
  79. m_Controller.ProcessingBinary += OnProcessingBinary;
  80. m_Controller.ProcessResourceComplete += OnProcessResourceComplete;
  81. m_Controller.BuildResourceError += OnBuildResourceError;
  82. m_OrderBuildResources = false;
  83. if (m_Controller.Load())
  84. {
  85. Debug.Log("Load configuration success.");
  86. m_CompressionHelperTypeNameIndex = 0;
  87. string[] compressionHelperTypeNames = m_Controller.GetCompressionHelperTypeNames();
  88. for (int i = 0; i < compressionHelperTypeNames.Length; i++)
  89. {
  90. if (m_Controller.CompressionHelperTypeName == compressionHelperTypeNames[i])
  91. {
  92. m_CompressionHelperTypeNameIndex = i;
  93. break;
  94. }
  95. }
  96. m_Controller.RefreshCompressionHelper();
  97. m_BuildEventHandlerTypeNameIndex = 0;
  98. string[] buildEventHandlerTypeNames = m_Controller.GetBuildEventHandlerTypeNames();
  99. for (int i = 0; i < buildEventHandlerTypeNames.Length; i++)
  100. {
  101. if (m_Controller.BuildEventHandlerTypeName == buildEventHandlerTypeNames[i])
  102. {
  103. m_BuildEventHandlerTypeNameIndex = i;
  104. break;
  105. }
  106. }
  107. m_Controller.RefreshBuildEventHandler();
  108. }
  109. else
  110. {
  111. Debug.LogWarning("Load configuration failure.");
  112. }
  113. if (string.IsNullOrWhiteSpace(m_Controller.OutputDirectory) || !Directory.Exists(m_Controller.OutputDirectory))
  114. {
  115. m_Controller.OutputDirectory = ConstEditor.AssetBundleOutputPath;
  116. }
  117. }
  118. private void Update()
  119. {
  120. if (m_OrderBuildResources)
  121. {
  122. m_OrderBuildResources = false;
  123. BuildResources();
  124. }
  125. }
  126. private void OnGUI()
  127. {
  128. EditorGUILayout.BeginVertical(GUILayout.Width(position.width), GUILayout.Height(position.height));
  129. scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
  130. {
  131. GUILayout.Space(5f);
  132. EditorGUILayout.LabelField("Environment Information", EditorStyles.boldLabel);
  133. EditorGUILayout.BeginVertical("box");
  134. {
  135. EditorGUILayout.BeginHorizontal();
  136. {
  137. EditorGUILayout.LabelField("Product Name", GUILayout.Width(160f));
  138. EditorGUILayout.LabelField(m_Controller.ProductName);
  139. }
  140. EditorGUILayout.EndHorizontal();
  141. EditorGUILayout.BeginHorizontal();
  142. {
  143. EditorGUILayout.LabelField("Company Name", GUILayout.Width(160f));
  144. EditorGUILayout.LabelField(m_Controller.CompanyName);
  145. }
  146. EditorGUILayout.EndHorizontal();
  147. EditorGUILayout.BeginHorizontal();
  148. {
  149. EditorGUILayout.LabelField("Game Identifier", GUILayout.Width(160f));
  150. EditorGUILayout.LabelField(m_Controller.GameIdentifier);
  151. }
  152. EditorGUILayout.EndHorizontal();
  153. EditorGUILayout.BeginHorizontal();
  154. {
  155. EditorGUILayout.LabelField("Game Framework Version", GUILayout.Width(160f));
  156. EditorGUILayout.LabelField(m_Controller.GameFrameworkVersion);
  157. }
  158. EditorGUILayout.EndHorizontal();
  159. EditorGUILayout.BeginHorizontal();
  160. {
  161. EditorGUILayout.LabelField("Unity Version", GUILayout.Width(160f));
  162. EditorGUILayout.LabelField(m_Controller.UnityVersion);
  163. }
  164. EditorGUILayout.EndHorizontal();
  165. EditorGUILayout.BeginHorizontal();
  166. {
  167. EditorGUILayout.LabelField("Applicable Game Version", GUILayout.Width(160f));
  168. EditorGUILayout.LabelField(m_Controller.ApplicableGameVersion);
  169. }
  170. EditorGUILayout.EndHorizontal();
  171. }
  172. EditorGUILayout.EndVertical();
  173. GUILayout.Space(5f);
  174. EditorGUILayout.BeginHorizontal();
  175. {
  176. EditorGUILayout.BeginVertical();
  177. {
  178. EditorGUILayout.LabelField("Platforms", EditorStyles.boldLabel);
  179. EditorGUILayout.BeginHorizontal("box");
  180. {
  181. EditorGUILayout.BeginVertical();
  182. {
  183. DrawPlatform(Platform.Windows, "Windows");
  184. DrawPlatform(Platform.Windows64, "Windows x64");
  185. DrawPlatform(Platform.MacOS, "macOS");
  186. }
  187. EditorGUILayout.EndVertical();
  188. EditorGUILayout.BeginVertical();
  189. {
  190. DrawPlatform(Platform.Linux, "Linux");
  191. DrawPlatform(Platform.IOS, "iOS");
  192. DrawPlatform(Platform.Android, "Android");
  193. }
  194. EditorGUILayout.EndVertical();
  195. EditorGUILayout.BeginVertical();
  196. {
  197. DrawPlatform(Platform.WindowsStore, "Windows Store");
  198. DrawPlatform(Platform.WebGL, "WebGL");
  199. }
  200. EditorGUILayout.EndVertical();
  201. }
  202. EditorGUILayout.EndHorizontal();
  203. }
  204. EditorGUILayout.EndVertical();
  205. }
  206. EditorGUILayout.EndHorizontal();
  207. GUILayout.Space(5f);
  208. EditorGUILayout.LabelField("Compression", EditorStyles.boldLabel);
  209. EditorGUILayout.BeginVertical("box");
  210. {
  211. EditorGUILayout.BeginHorizontal();
  212. {
  213. EditorGUILayout.LabelField("AssetBundle Compression", GUILayout.Width(160f));
  214. m_Controller.AssetBundleCompression = (AssetBundleCompressionType)EditorGUILayout.EnumPopup(m_Controller.AssetBundleCompression);
  215. }
  216. EditorGUILayout.EndHorizontal();
  217. EditorGUILayout.BeginHorizontal();
  218. {
  219. EditorGUILayout.LabelField("Compression Helper", GUILayout.Width(160f));
  220. string[] names = m_Controller.GetCompressionHelperTypeNames();
  221. int selectedIndex = EditorGUILayout.Popup(m_CompressionHelperTypeNameIndex, names);
  222. if (selectedIndex != m_CompressionHelperTypeNameIndex)
  223. {
  224. m_CompressionHelperTypeNameIndex = selectedIndex;
  225. m_Controller.CompressionHelperTypeName = selectedIndex <= 0 ? string.Empty : names[selectedIndex];
  226. if (m_Controller.RefreshCompressionHelper())
  227. {
  228. Debug.Log("Set compression helper success.");
  229. }
  230. else
  231. {
  232. Debug.LogWarning("Set compression helper failure.");
  233. }
  234. }
  235. }
  236. EditorGUILayout.EndHorizontal();
  237. EditorGUILayout.BeginHorizontal();
  238. {
  239. EditorGUILayout.LabelField("Additional Compression", GUILayout.Width(160f));
  240. m_Controller.AdditionalCompressionSelected = EditorGUILayout.ToggleLeft("Additional Compression for Output Full Resources with Compression Helper", m_Controller.AdditionalCompressionSelected);
  241. }
  242. EditorGUILayout.EndHorizontal();
  243. }
  244. EditorGUILayout.EndVertical();
  245. GUILayout.Space(5f);
  246. EditorGUILayout.BeginHorizontal();
  247. {
  248. EditorGUILayout.LabelField("Build Resources Settings", EditorStyles.boldLabel);
  249. if (GUILayout.Button("Resources Editor", GUILayout.Width(160f)))
  250. {
  251. OpenResourcesEditor();
  252. GUIUtility.ExitGUI();
  253. }
  254. }
  255. EditorGUILayout.EndHorizontal();
  256. EditorGUILayout.BeginVertical("box");
  257. {
  258. EditorGUILayout.BeginHorizontal();
  259. {
  260. EditorGUILayout.LabelField("Force Rebuild AssetBundle", GUILayout.Width(160f));
  261. m_Controller.ForceRebuildAssetBundleSelected = EditorGUILayout.Toggle(m_Controller.ForceRebuildAssetBundleSelected);
  262. }
  263. EditorGUILayout.EndHorizontal();
  264. EditorGUILayout.BeginHorizontal();
  265. {
  266. EditorGUILayout.LabelField("Build Event Handler", GUILayout.Width(160f));
  267. string[] names = m_Controller.GetBuildEventHandlerTypeNames();
  268. int selectedIndex = EditorGUILayout.Popup(m_BuildEventHandlerTypeNameIndex, names);
  269. if (selectedIndex != m_BuildEventHandlerTypeNameIndex)
  270. {
  271. m_BuildEventHandlerTypeNameIndex = selectedIndex;
  272. m_Controller.BuildEventHandlerTypeName = selectedIndex <= 0 ? string.Empty : names[selectedIndex];
  273. if (m_Controller.RefreshBuildEventHandler())
  274. {
  275. Debug.Log("Set build event handler success.");
  276. }
  277. else
  278. {
  279. Debug.LogWarning("Set build event handler failure.");
  280. }
  281. }
  282. }
  283. EditorGUILayout.EndHorizontal();
  284. EditorGUILayout.BeginHorizontal();
  285. {
  286. EditorGUILayout.LabelField("Internal Resource Version", GUILayout.Width(160f));
  287. m_Controller.InternalResourceVersion = EditorGUILayout.IntField(m_Controller.InternalResourceVersion);
  288. }
  289. EditorGUILayout.EndHorizontal();
  290. EditorGUILayout.BeginHorizontal();
  291. {
  292. EditorGUILayout.LabelField("Resource Version", GUILayout.Width(160f));
  293. GUILayout.Label(Utility.Text.Format("{0} ({1})", m_Controller.ApplicableGameVersion, m_Controller.InternalResourceVersion.ToString()));
  294. }
  295. EditorGUILayout.EndHorizontal();
  296. EditorGUILayout.BeginHorizontal();
  297. {
  298. EditorGUILayout.LabelField("Output Directory", GUILayout.Width(160f));
  299. m_Controller.OutputDirectory = EditorGUILayout.TextField(m_Controller.OutputDirectory);
  300. if (GUILayout.Button("Browse...", GUILayout.Width(80f)))
  301. {
  302. BrowseOutputDirectory();
  303. }
  304. }
  305. EditorGUILayout.EndHorizontal();
  306. EditorGUILayout.BeginHorizontal();
  307. {
  308. EditorGUILayout.LabelField("Output Resources Path", GUILayout.Width(160f));
  309. GUILayout.Label(GetResourceOupoutPathByMode(AppSettings.Instance.ResourceMode));
  310. EditorGUILayout.LabelField("Resource Mode:", GUILayout.Width(100f));
  311. EditorGUI.BeginChangeCheck();
  312. {
  313. AppSettings.Instance.ResourceMode = (ResourceMode)EditorGUILayout.EnumPopup(AppSettings.Instance.ResourceMode, GUILayout.Width(160f));
  314. }
  315. if (EditorGUI.EndChangeCheck())
  316. {
  317. RefreshHybridCLREnable();
  318. }
  319. if (AppSettings.Instance.ResourceMode != ResourceMode.Unspecified)
  320. {
  321. SetResourceMode(AppSettings.Instance.ResourceMode);
  322. }
  323. AppBuildSettings.Instance.RevealFolder = EditorGUILayout.ToggleLeft(revealFolderContent, AppBuildSettings.Instance.RevealFolder, GUILayout.Width(105f));
  324. }
  325. EditorGUILayout.EndHorizontal();
  326. if (AppSettings.Instance.ResourceMode == ResourceMode.Unspecified)
  327. {
  328. EditorGUILayout.HelpBox("ResourceMode is invalid.", MessageType.Error);
  329. }
  330. EditorGUILayout.BeginHorizontal();
  331. {
  332. EditorGUILayout.LabelField("Working Path", GUILayout.Width(160f));
  333. GUILayout.Label(m_Controller.WorkingPath);
  334. }
  335. EditorGUILayout.EndHorizontal();
  336. EditorGUILayout.BeginHorizontal();
  337. {
  338. EditorGUILayout.LabelField("Build Report Path", GUILayout.Width(160f));
  339. GUILayout.Label(m_Controller.BuildReportPath);
  340. }
  341. EditorGUILayout.EndHorizontal();
  342. }
  343. EditorGUILayout.EndVertical();
  344. string buildMessage = string.Empty;
  345. MessageType buildMessageType = MessageType.None;
  346. GetBuildMessage(out buildMessage, out buildMessageType);
  347. EditorGUILayout.HelpBox(buildMessage, buildMessageType);
  348. if (m_Controller.OutputFullSelected || m_Controller.OutputPackedSelected)
  349. {
  350. DrawHotfixConfigPanel();
  351. }
  352. DrawAppBuildSettingsPanel();
  353. EditorGUILayout.EndScrollView();
  354. EditorGUILayout.BeginHorizontal();
  355. {
  356. EditorGUI.BeginDisabledGroup(m_Controller.Platforms == Platform.Undefined || string.IsNullOrEmpty(m_Controller.CompressionHelperTypeName) || !m_Controller.IsValidOutputDirectory || AppSettings.Instance.ResourceMode == ResourceMode.Unspecified);
  357. {
  358. if (GUILayout.Button(buildResBtContent, GUILayout.Height(35)))
  359. {
  360. BuildHotfix();
  361. }
  362. DrawBuildAppButton();
  363. }
  364. EditorGUI.EndDisabledGroup();
  365. if (GUILayout.Button(saveBtContent, GUILayout.Width(140), GUILayout.Height(35)))
  366. {
  367. SaveConfiguration();
  368. }
  369. }
  370. EditorGUILayout.EndHorizontal();
  371. GUILayout.Space(2f);
  372. }
  373. EditorGUILayout.EndVertical();
  374. }
  375. private void DrawBuildAppButton()
  376. {
  377. Rect buildRect = GUILayoutUtility.GetRect(buildAppBtContent, dropDownBtStyle,
  378. GUILayout.Height(35));
  379. Rect buildRectPopupButton = buildRect;
  380. buildRectPopupButton.x += buildRect.width - 35;
  381. buildRectPopupButton.width = 35;
  382. if (EditorGUI.DropdownButton(buildRectPopupButton, GUIContent.none, FocusType.Passive,
  383. GUIStyle.none))
  384. {
  385. GenericMenu menu = new GenericMenu();
  386. menu.AddItem(new GUIContent("Full Build(Generate AOT Dll)", "Build时生成AOT Dlls"), false,
  387. () =>
  388. {
  389. BuildApp(true);
  390. });
  391. menu.DropDown(buildRect);
  392. }
  393. else if (GUI.Button(buildRect, buildAppBtContent, dropDownBtStyle))
  394. {
  395. BuildApp(false);
  396. GUIUtility.ExitGUI();
  397. }
  398. }
  399. private void RefreshHybridCLREnable()
  400. {
  401. if (AppSettings.Instance.ResourceMode != ResourceMode.Unspecified)
  402. {
  403. if (AppSettings.Instance.ResourceMode == ResourceMode.Package)
  404. {
  405. #if !DISABLE_HYBRIDCLR
  406. MyGameTools.DisableHybridCLR();
  407. #endif
  408. }
  409. else
  410. {
  411. #if DISABLE_HYBRIDCLR
  412. MyGameTools.EnableHybridCLR();
  413. #endif
  414. }
  415. }
  416. }
  417. private string GetResourceOupoutPathByMode(ResourceMode mode)
  418. {
  419. string result = null;
  420. switch (mode)
  421. {
  422. case ResourceMode.Package:
  423. result = m_Controller.OutputPackagePath;
  424. break;
  425. case ResourceMode.Updatable:
  426. result = m_Controller.OutputFullPath;
  427. break;
  428. case ResourceMode.UpdatableWhilePlaying:
  429. result = m_Controller.OutputPackedPath;
  430. break;
  431. }
  432. return result;
  433. }
  434. private void SetResourceMode(ResourceMode mode)
  435. {
  436. m_Controller.OutputPackageSelected = false;
  437. m_Controller.OutputFullSelected = false;
  438. m_Controller.OutputPackedSelected = false;
  439. switch (mode)
  440. {
  441. case ResourceMode.Package:
  442. m_Controller.OutputPackageSelected = true;
  443. break;
  444. case ResourceMode.Updatable:
  445. m_Controller.OutputFullSelected = true;
  446. break;
  447. case ResourceMode.UpdatableWhilePlaying:
  448. m_Controller.OutputPackedSelected = true;
  449. break;
  450. }
  451. }
  452. private void OpenResourcesEditor()
  453. {
  454. var resEditorClass = Utility.Assembly.GetType("UnityGameFramework.Editor.ResourceTools.ResourceEditor");
  455. resEditorClass?.GetMethod("Open", BindingFlags.Static | BindingFlags.NonPublic)?.Invoke(null, null);
  456. }
  457. private void DrawAppBuildSettingsPanel()
  458. {
  459. GUILayout.Space(5f);
  460. EditorGUILayout.BeginHorizontal();
  461. {
  462. EditorGUILayout.LabelField("Build App Settings:", EditorStyles.boldLabel, GUILayout.Width(160));
  463. #if UNITY_ANDROID
  464. AppBuildSettings.Instance.BuildForGooglePlay = EditorUserBuildSettings.buildAppBundle = EditorGUILayout.ToggleLeft("Build App Bundle(GP)", AppBuildSettings.Instance.BuildForGooglePlay);
  465. #endif
  466. AppBuildSettings.Instance.DevelopmentBuild = EditorUserBuildSettings.development = EditorGUILayout.ToggleLeft("Development Build", AppBuildSettings.Instance.DevelopmentBuild);
  467. AppSettings.Instance.DebugMode = EditorGUILayout.ToggleLeft("Debug Mode", AppSettings.Instance.DebugMode);
  468. if (GUILayout.Button(playerSettingBtContent))
  469. {
  470. SettingsService.OpenProjectSettings("Project/Player");
  471. GUIUtility.ExitGUI();
  472. }
  473. }
  474. EditorGUILayout.EndHorizontal();
  475. EditorGUILayout.BeginVertical("box");
  476. {
  477. EditorGUILayout.BeginHorizontal();
  478. {
  479. EditorGUILayout.LabelField("Version", GUILayout.Width(160f));
  480. PlayerSettings.bundleVersion = EditorGUILayout.TextField(PlayerSettings.bundleVersion);
  481. }
  482. EditorGUILayout.EndHorizontal();
  483. #if UNITY_ANDROID
  484. EditorGUILayout.BeginHorizontal();
  485. {
  486. EditorGUILayout.LabelField("Version Code", GUILayout.Width(160f));
  487. PlayerSettings.Android.bundleVersionCode = EditorGUILayout.IntField(PlayerSettings.Android.bundleVersionCode);
  488. }
  489. EditorGUILayout.EndHorizontal();
  490. EditorGUILayout.BeginHorizontal();
  491. {
  492. EditorGUILayout.LabelField("App Build Path", GUILayout.Width(160f));
  493. AppBuildSettings.Instance.AppBuildDir = EditorGUILayout.TextField(AppBuildSettings.Instance.AppBuildDir);
  494. if (GUILayout.Button("Select Path", GUILayout.Width(160f)))
  495. {
  496. string projectRoot = Directory.GetParent(Application.dataPath).FullName;
  497. var appBuildDir = string.IsNullOrWhiteSpace(AppBuildSettings.Instance.AppBuildDir) ? projectRoot : AppBuildSettings.Instance.AppBuildDir;
  498. var openPath = Directory.Exists(appBuildDir) ? appBuildDir : projectRoot;
  499. string path = EditorUtility.OpenFolderPanel("Select App Build Path", openPath, "");
  500. if (!string.IsNullOrWhiteSpace(path))
  501. {
  502. AppBuildSettings.Instance.AppBuildDir = Path.GetRelativePath(projectRoot, path);
  503. }
  504. GUIUtility.ExitGUI();
  505. }
  506. EditorGUILayout.EndHorizontal();
  507. }
  508. EditorGUILayout.BeginHorizontal();
  509. {
  510. PlayerSettings.Android.useCustomKeystore = EditorGUILayout.ToggleLeft("Use Custom Keystore", PlayerSettings.Android.useCustomKeystore, GUILayout.Width(160f));
  511. EditorGUI.BeginDisabledGroup(!PlayerSettings.Android.useCustomKeystore);
  512. {
  513. AppBuildSettings.Instance.AndroidKeystoreName = PlayerSettings.Android.keystoreName = EditorGUILayout.TextField(AppBuildSettings.Instance.AndroidKeystoreName);
  514. if (GUILayout.Button("Select Keystore", GUILayout.Width(160f)))
  515. {
  516. string projectRoot = Directory.GetParent(Application.dataPath).FullName;
  517. var keystoreDir = string.IsNullOrWhiteSpace(AppBuildSettings.Instance.AndroidKeystoreName) ? projectRoot : Path.GetDirectoryName(AppBuildSettings.Instance.AndroidKeyAliasName);
  518. var openPath = Directory.Exists(keystoreDir) ? keystoreDir : projectRoot;
  519. string path = EditorUtility.OpenFilePanel("Select Keystore", openPath, "keystore,jks,ks");
  520. if (!string.IsNullOrWhiteSpace(path))
  521. {
  522. AppBuildSettings.Instance.AndroidKeystoreName = PlayerSettings.Android.keystoreName = Path.GetRelativePath(projectRoot, path);
  523. }
  524. GUIUtility.ExitGUI();
  525. }
  526. }
  527. EditorGUI.EndDisabledGroup();
  528. }
  529. EditorGUILayout.EndHorizontal();
  530. if (PlayerSettings.Android.useCustomKeystore)
  531. {
  532. EditorGUILayout.BeginHorizontal();
  533. {
  534. EditorGUILayout.LabelField("Keystore Password", GUILayout.Width(160f));
  535. AppBuildSettings.Instance.KeystorePass = PlayerSettings.keystorePass = EditorGUILayout.TextField(AppBuildSettings.Instance.KeystorePass);
  536. }
  537. EditorGUILayout.EndHorizontal();
  538. EditorGUILayout.BeginHorizontal();
  539. {
  540. EditorGUILayout.LabelField("KeyAliasName", GUILayout.Width(160f));
  541. AppBuildSettings.Instance.AndroidKeyAliasName = PlayerSettings.Android.keyaliasName = EditorGUILayout.TextField(AppBuildSettings.Instance.AndroidKeyAliasName);
  542. }
  543. EditorGUILayout.EndHorizontal();
  544. EditorGUILayout.BeginHorizontal();
  545. {
  546. EditorGUILayout.LabelField("Alias Password", GUILayout.Width(160f));
  547. AppBuildSettings.Instance.KeyAliasPass = PlayerSettings.keyaliasPass = EditorGUILayout.TextField(AppBuildSettings.Instance.KeyAliasPass);
  548. }
  549. EditorGUILayout.EndHorizontal();
  550. }
  551. #elif UNITY_IOS
  552. EditorGUILayout.BeginHorizontal();
  553. {
  554. EditorGUILayout.LabelField("Build Number", GUILayout.Width(160f));
  555. PlayerSettings.iOS.buildNumber = EditorGUILayout.TextField(PlayerSettings.iOS.buildNumber);
  556. }
  557. EditorGUILayout.EndHorizontal();
  558. #endif
  559. }
  560. EditorGUILayout.EndVertical();
  561. }
  562. private void DrawHotfixConfigPanel()
  563. {
  564. GUILayout.Space(5f);
  565. EditorGUILayout.BeginHorizontal();
  566. {
  567. EditorGUILayout.LabelField("Hotfix Settings:", EditorStyles.boldLabel);
  568. if (GUILayout.Button(hybridclrSettingBtContent, GUILayout.Width(160f)))
  569. {
  570. SettingsService.OpenProjectSettings("Project/HybridCLR Settings");
  571. GUIUtility.ExitGUI();
  572. }
  573. }
  574. EditorGUILayout.EndHorizontal();
  575. EditorGUILayout.BeginVertical("box");
  576. {
  577. EditorGUILayout.BeginHorizontal();
  578. {
  579. EditorGUILayout.LabelField(hotfixUrlContent, GUILayout.Width(160f));
  580. AppBuildSettings.Instance.UpdatePrefixUri = EditorGUILayout.TextField(AppBuildSettings.Instance.UpdatePrefixUri);
  581. }
  582. EditorGUILayout.EndHorizontal();
  583. EditorGUILayout.BeginHorizontal();
  584. {
  585. EditorGUILayout.LabelField(applicableVerContent, GUILayout.Width(160f));
  586. AppBuildSettings.Instance.ApplicableGameVersion = EditorGUILayout.TextField(AppBuildSettings.Instance.ApplicableGameVersion);
  587. }
  588. EditorGUILayout.EndHorizontal();
  589. EditorGUILayout.BeginHorizontal();
  590. {
  591. EditorGUILayout.LabelField(appUpdateUrlContent, GUILayout.Width(160f));
  592. AppBuildSettings.Instance.AppUpdateUrl = EditorGUILayout.TextField(AppBuildSettings.Instance.AppUpdateUrl);
  593. AppBuildSettings.Instance.ForceUpdateApp = EditorGUILayout.ToggleLeft(forceUpdateAppContent, AppBuildSettings.Instance.ForceUpdateApp, GUILayout.Width(100f));
  594. }
  595. EditorGUILayout.EndHorizontal();
  596. EditorGUILayout.Space(5);
  597. EditorGUILayout.LabelField(appUpdateDescContent, GUILayout.Width(160f));
  598. AppBuildSettings.Instance.AppUpdateDesc = EditorGUILayout.TextArea(AppBuildSettings.Instance.AppUpdateDesc, GUILayout.Height(50));
  599. }
  600. EditorGUILayout.EndVertical();
  601. }
  602. private void BuildHotfix()
  603. {
  604. #if !DISABLE_HYBRIDCLR
  605. MyGameTools.CompileTargetDll();
  606. #endif
  607. m_OrderBuildResources = true;
  608. }
  609. private void BuildApp(bool generateAot)
  610. {
  611. #if UNITY_ANDROID
  612. if (AppBuildSettings.Instance.AndroidUseKeystore && !CheckKeystoreAvailable(AppBuildSettings.Instance.AndroidKeystoreName))
  613. {
  614. EditorUtility.DisplayDialog("Build Error!", Utility.Text.Format("Keystore文件不存在或格式错误:{0}", AppBuildSettings.Instance.AndroidKeystoreName), "GOT IT");
  615. return;
  616. }
  617. #endif
  618. if (m_Controller.OutputPackageSelected)
  619. {
  620. if (m_Controller.BuildResources())
  621. {
  622. DeleteAotDlls();//单机模式删除Resource下的AOT dlls
  623. //AssetDatabase.Refresh();
  624. CallBuildMethods(generateAot);
  625. }
  626. }
  627. else if (m_Controller.OutputPackedSelected)
  628. {
  629. #if !DISABLE_HYBRIDCLR
  630. MyGameTools.CompileTargetDll(false);
  631. #endif
  632. if (m_Controller.BuildResources())
  633. {
  634. //AssetDatabase.Refresh();
  635. CallBuildMethods(generateAot);
  636. }
  637. }
  638. else if (m_Controller.OutputFullSelected)
  639. {
  640. DeleteStreamingAssets();
  641. CallBuildMethods(generateAot);
  642. }
  643. }
  644. private bool CheckKeystoreAvailable(string keystore)
  645. {
  646. if (string.IsNullOrWhiteSpace(keystore)) return false;
  647. var ext = Path.GetExtension(keystore).ToLower();
  648. if (File.Exists(keystore) && keystoreExtNames.Contains(ext))
  649. {
  650. return true;
  651. }
  652. return false;
  653. }
  654. private void DeleteAotDlls()
  655. {
  656. string aotSaveDir = UtilityBuiltin.ResPath.GetCombinePath(Application.dataPath, "Resources", ConstBuiltin.AOT_DLL_DIR);
  657. if (Directory.Exists(aotSaveDir))
  658. {
  659. Directory.Delete(aotSaveDir, true);
  660. }
  661. }
  662. private void DeleteStreamingAssets()
  663. {
  664. string streamingAssetsPath = Path.Combine(Application.dataPath, "StreamingAssets");
  665. if (Directory.Exists(streamingAssetsPath))
  666. {
  667. Directory.Delete(streamingAssetsPath, true);
  668. }
  669. string streamMetaFile = streamingAssetsPath + ".meta";
  670. if (File.Exists(streamMetaFile))
  671. {
  672. File.Delete(streamMetaFile);
  673. }
  674. }
  675. private void CallBuildMethods(bool generateAotDll = false)
  676. {
  677. #if !DISABLE_HYBRIDCLR
  678. GenerateHotfixCodeStripConfig(false);
  679. HybridCLRGenerateAll(generateAotDll);
  680. #else
  681. GenerateHotfixCodeStripConfig(true);
  682. #endif
  683. AssetDatabase.Refresh();
  684. var buildWin = Utility.Assembly.GetType("UnityEditor.BuildPlayerWindow");
  685. if (buildWin != null)
  686. {
  687. var buildFunc = buildWin.GetMethod("CallBuildMethods", System.Reflection.BindingFlags.Static | BindingFlags.NonPublic);
  688. buildFunc?.Invoke(null, new object[] { false, BuildOptions.ShowBuiltPlayer });
  689. }
  690. }
  691. /// <summary>
  692. /// 生成或删除热更dlls防裁剪的link.xml
  693. /// 单机模式时需把热更dlls打到包里
  694. /// </summary>
  695. /// <param name="v">true生成; false删除</param>
  696. private void GenerateHotfixCodeStripConfig(bool v)
  697. {
  698. var linkDir = Path.GetDirectoryName(ConstEditor.HotfixAssembly);
  699. var linkFile = UtilityBuiltin.ResPath.GetCombinePath(linkDir, "link.xml");
  700. if (v)
  701. {
  702. var strBuilder = new StringBuilder();
  703. strBuilder.AppendLine("<linker>");
  704. foreach (var dllName in HybridCLR.Editor.SettingsUtil.HotUpdateAssemblyNamesIncludePreserved)
  705. {
  706. strBuilder.AppendLineFormat("\t<assembly fullname=\"{0}\" preserve=\"all\" />", dllName);
  707. }
  708. strBuilder.AppendLine("</linker>");
  709. File.WriteAllText(linkFile, strBuilder.ToString());
  710. }
  711. else
  712. {
  713. if (File.Exists(linkFile)) File.Delete(linkFile);//热更包不需要添加防裁剪
  714. }
  715. }
  716. /// <summary>
  717. /// 生成HybridCLR热更相关
  718. /// </summary>
  719. /// <param name="generateAotDll"></param>
  720. private void HybridCLRGenerateAll(bool generateAotDll)
  721. {
  722. BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
  723. MyGameTools.CompileTargetDll(false);
  724. // 生成裁剪后的aot dll
  725. if (generateAotDll)
  726. {
  727. StripAOTDllCommand.GenerateStripedAOTDlls(target, EditorUserBuildSettings.selectedBuildTargetGroup);
  728. MyGameTools.CopyAotDllsToProject(target);
  729. }
  730. LinkGeneratorCommand.GenerateLinkXml(target);
  731. Il2CppDefGeneratorCommand.GenerateIl2CppDef();
  732. // 这几个生成依赖HotUpdateDlls
  733. // 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll
  734. MethodBridgeGeneratorCommand.GenerateMethodBridge(target);
  735. ReversePInvokeWrapperGeneratorCommand.GenerateReversePInvokeWrapper(target);
  736. AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target);
  737. }
  738. private void BrowseOutputDirectory()
  739. {
  740. string directory = EditorUtility.OpenFolderPanel("Select Output Directory", m_Controller.OutputDirectory, string.Empty);
  741. if (!string.IsNullOrEmpty(directory))
  742. {
  743. m_Controller.OutputDirectory = directory;
  744. }
  745. }
  746. private void GetBuildMessage(out string message, out MessageType messageType)
  747. {
  748. message = string.Empty;
  749. messageType = MessageType.Error;
  750. if (m_Controller.Platforms == Platform.Undefined)
  751. {
  752. if (!string.IsNullOrEmpty(message))
  753. {
  754. message += Environment.NewLine;
  755. }
  756. message += "Platform is invalid.";
  757. }
  758. if (string.IsNullOrEmpty(m_Controller.CompressionHelperTypeName))
  759. {
  760. if (!string.IsNullOrEmpty(message))
  761. {
  762. message += Environment.NewLine;
  763. }
  764. message += "Compression helper is invalid.";
  765. }
  766. if (!m_Controller.IsValidOutputDirectory)
  767. {
  768. if (!string.IsNullOrEmpty(message))
  769. {
  770. message += Environment.NewLine;
  771. }
  772. message += "Output directory is invalid.";
  773. }
  774. if (!string.IsNullOrEmpty(message))
  775. {
  776. return;
  777. }
  778. messageType = MessageType.Info;
  779. if (Directory.Exists(m_Controller.OutputPackagePath))
  780. {
  781. message += Utility.Text.Format("{0} will be overwritten.", m_Controller.OutputPackagePath);
  782. messageType = MessageType.Warning;
  783. }
  784. if (Directory.Exists(m_Controller.OutputFullPath))
  785. {
  786. if (message.Length > 0)
  787. {
  788. message += " ";
  789. }
  790. message += Utility.Text.Format("{0} will be overwritten.", m_Controller.OutputFullPath);
  791. messageType = MessageType.Warning;
  792. }
  793. if (Directory.Exists(m_Controller.OutputPackedPath))
  794. {
  795. if (message.Length > 0)
  796. {
  797. message += " ";
  798. }
  799. message += Utility.Text.Format("{0} will be overwritten.", m_Controller.OutputPackedPath);
  800. messageType = MessageType.Warning;
  801. }
  802. if (messageType == MessageType.Warning)
  803. {
  804. return;
  805. }
  806. message = "Ready to build.";
  807. }
  808. private void BuildResources()
  809. {
  810. if (m_Controller.BuildResources())
  811. {
  812. Debug.Log("Build resources success.");
  813. SaveConfiguration();
  814. }
  815. else
  816. {
  817. Debug.LogWarning("Build resources failure.");
  818. }
  819. }
  820. private void SaveConfiguration()
  821. {
  822. EditorUtility.SetDirty(AppSettings.Instance);
  823. AppBuildSettings.Save();
  824. if (m_Controller.Save())
  825. {
  826. Debug.Log("Save configuration success.");
  827. }
  828. else
  829. {
  830. Debug.LogWarning("Save configuration failure.");
  831. }
  832. }
  833. private void DrawPlatform(Platform platform, string platformName)
  834. {
  835. m_Controller.SelectPlatform(platform, EditorGUILayout.ToggleLeft(platformName, m_Controller.IsPlatformSelected(platform)));
  836. }
  837. private void OnLoadingResource(int index, int count)
  838. {
  839. EditorUtility.DisplayProgressBar("Loading Resources", Utility.Text.Format("Loading resources, {0}/{1} loaded.", index.ToString(), count.ToString()), (float)index / count);
  840. }
  841. private void OnLoadingAsset(int index, int count)
  842. {
  843. EditorUtility.DisplayProgressBar("Loading Assets", Utility.Text.Format("Loading assets, {0}/{1} loaded.", index.ToString(), count.ToString()), (float)index / count);
  844. }
  845. private void OnLoadCompleted()
  846. {
  847. EditorUtility.ClearProgressBar();
  848. }
  849. private void OnAnalyzingAsset(int index, int count)
  850. {
  851. EditorUtility.DisplayProgressBar("Analyzing Assets", Utility.Text.Format("Analyzing assets, {0}/{1} analyzed.", index.ToString(), count.ToString()), (float)index / count);
  852. }
  853. private void OnAnalyzeCompleted()
  854. {
  855. EditorUtility.ClearProgressBar();
  856. }
  857. private bool OnProcessingAssetBundle(string assetBundleName, float progress)
  858. {
  859. if (EditorUtility.DisplayCancelableProgressBar("Processing AssetBundle", Utility.Text.Format("Processing '{0}'...", assetBundleName), progress))
  860. {
  861. EditorUtility.ClearProgressBar();
  862. return true;
  863. }
  864. else
  865. {
  866. Repaint();
  867. return false;
  868. }
  869. }
  870. private bool OnProcessingBinary(string binaryName, float progress)
  871. {
  872. if (EditorUtility.DisplayCancelableProgressBar("Processing Binary", Utility.Text.Format("Processing '{0}'...", binaryName), progress))
  873. {
  874. EditorUtility.ClearProgressBar();
  875. return true;
  876. }
  877. else
  878. {
  879. Repaint();
  880. return false;
  881. }
  882. }
  883. private void OnProcessResourceComplete(Platform platform)
  884. {
  885. EditorUtility.ClearProgressBar();
  886. Debug.Log(Utility.Text.Format("Build resources for '{0}' complete.", platform.ToString()));
  887. if (AppBuildSettings.Instance.RevealFolder)
  888. {
  889. EditorUtility.RevealInFinder(UtilityBuiltin.ResPath.GetCombinePath(GetResourceOupoutPathByMode(AppSettings.Instance.ResourceMode), platform.ToString()));
  890. }
  891. }
  892. private void OnBuildResourceError(string errorMessage)
  893. {
  894. EditorUtility.ClearProgressBar();
  895. Debug.LogWarning(Utility.Text.Format("Build resources error with error message '{0}'.", errorMessage));
  896. }
  897. private BuildTarget GetSelectedBuildTarget()
  898. {
  899. var buildTarget = (BuildTarget)Utility.Assembly.GetType("UnityEditor.EditorUserBuildSettingsUtils").GetMethod("CalculateSelectedBuildTarget", BindingFlags.Static | BindingFlags.Public).Invoke(null, null);
  900. return buildTarget;
  901. }
  902. private BuildPlayerOptions CustomBuildOptions(BuildPlayerOptions options)
  903. {
  904. var buildTarget = GetSelectedBuildTarget();
  905. BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
  906. int subtarget = (int)Utility.Assembly.GetType("UnityEditor.EditorUserBuildSettings").GetMethod("GetSelectedSubtargetFor", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { buildTarget });
  907. string buildLocation = GetBuildLocation(buildTargetGroup, buildTarget, subtarget, options.options);
  908. bool isDir = !Path.HasExtension(buildLocation);
  909. if (string.IsNullOrWhiteSpace(buildLocation) || (isDir && !Directory.Exists(buildLocation)))
  910. throw new BuildMethodException("Build location for buildTarget " + buildTarget + " is not valid.");
  911. //Check if Lz4 is supported for the current buildtargetgroup and enable it if need be
  912. if ((bool)Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("SupportsLz4Compression", BindingFlags.Static | BindingFlags.Public).Invoke(null, new object[] { buildTargetGroup, buildTarget }))
  913. {
  914. //internal enum Compression
  915. //{
  916. // None = 0,
  917. // Lz4 = 2,
  918. // Lz4HC = 3,
  919. //}
  920. var compression = (int)Utility.Assembly.GetType("UnityEditor.EditorUserBuildSettings").GetMethod("GetCompressionType", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { buildTargetGroup });
  921. if (compression < 0)
  922. compression = (int)Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("GetDefaultCompression", BindingFlags.Static | BindingFlags.Public).Invoke(null, new object[] { buildTargetGroup, buildTarget });
  923. if (compression == 2)//Lz4
  924. options.options |= BuildOptions.CompressWithLz4;
  925. else if (compression == 3)//Lz4HC
  926. options.options |= BuildOptions.CompressWithLz4HC;
  927. }
  928. bool developmentBuild = EditorUserBuildSettings.development;
  929. if (developmentBuild)
  930. options.options |= BuildOptions.Development;
  931. if (EditorUserBuildSettings.allowDebugging && developmentBuild)
  932. options.options |= BuildOptions.AllowDebugging;
  933. if (EditorUserBuildSettings.symlinkSources)
  934. options.options |= BuildOptions.SymlinkSources;
  935. if (EditorUserBuildSettings.connectProfiler && (developmentBuild || buildTarget == BuildTarget.WSAPlayer))
  936. options.options |= BuildOptions.ConnectWithProfiler;
  937. if (EditorUserBuildSettings.buildWithDeepProfilingSupport && developmentBuild)
  938. options.options |= BuildOptions.EnableDeepProfilingSupport;
  939. if (EditorUserBuildSettings.buildScriptsOnly)
  940. options.options |= BuildOptions.BuildScriptsOnly;
  941. string connectID = Utility.Assembly.GetType("UnityEditor.Profiling.ProfilerUserSettings").GetProperty("customConnectionID", BindingFlags.Static | BindingFlags.Public).GetValue(null, null) as string;
  942. if (!string.IsNullOrEmpty(connectID) && developmentBuild)
  943. options.options |= BuildOptions.CustomConnectionID;
  944. var checkFunc = typeof(UnityEditor.BuildPlayerWindow.DefaultBuildMethods).GetMethod("IsInstallInBuildFolderOption", BindingFlags.Static | BindingFlags.NonPublic);
  945. if ((bool)checkFunc.Invoke(null, null))
  946. {
  947. options.options |= BuildOptions.InstallInBuildFolder;
  948. }
  949. options.target = buildTarget;
  950. options.subtarget = subtarget;
  951. options.targetGroup = buildTargetGroup;
  952. options.locationPathName = buildLocation;
  953. options.assetBundleManifestPath = Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("GetStreamingAssetsBundleManifestPath", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null) as string;
  954. // Build a list of scenes that are enabled
  955. ArrayList scenesList = new ArrayList();
  956. EditorBuildSettingsScene[] editorScenes = EditorBuildSettings.scenes;
  957. foreach (EditorBuildSettingsScene scene in editorScenes)
  958. {
  959. if (scene.enabled)
  960. {
  961. scenesList.Add(scene.path);
  962. break;// GF框架只需要把启动场景打进包里,其它场景动态加载
  963. }
  964. }
  965. options.scenes = scenesList.ToArray(typeof(string)) as string[];
  966. return options;
  967. }
  968. private static string GetBuildLocation(BuildTargetGroup targetGroup, BuildTarget target, int subtarget, BuildOptions options)
  969. {
  970. string defaultFolder = UtilityBuiltin.ResPath.GetCombinePath(Directory.GetParent(Application.dataPath).FullName, AppBuildSettings.Instance.AppBuildDir, target.ToString());
  971. string defaultName = Utility.Text.Format("{0}_{1}{2}_v{3}", Application.productName, AppSettings.Instance.DebugMode ? "debug" : "release", EditorUserBuildSettings.development ? "Dev" : string.Empty, Application.version);
  972. string extension = Utility.Assembly.GetType("UnityEditor.PostprocessBuildPlayer").GetMethod("GetExtensionForBuildTarget", new Type[] { typeof(BuildTargetGroup), typeof(BuildTarget), typeof(int), typeof(BuildOptions) }).Invoke(null, new object[] { targetGroup, target, subtarget, options }) as string;
  973. string buildPath = defaultFolder;
  974. if (!string.IsNullOrEmpty(extension))
  975. {
  976. string appFileName = Utility.Text.Format("{0}.{1}", defaultName, extension);
  977. buildPath = UtilityBuiltin.ResPath.GetCombinePath(defaultFolder, appFileName);
  978. }
  979. return buildPath;
  980. }
  981. }
  982. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/115324
推荐阅读
相关标签
  

闽ICP备14008679号