• 【Unity编辑器扩展】GF_HybridCLR自定义Toolbar, 一键出包/打热更扩展工具


    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. ///
    31. /// Callback for toolbar OnGUI method.
    32. ///
    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 LeftToolbarGUI = new List();
    107. public static readonly List RightToolbarGUI = new List();
    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();
    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");
    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. ///
    17. /// 资源生成器。
    18. ///
    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("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(), "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. ///
    692. /// 生成或删除热更dlls防裁剪的link.xml
    693. /// 单机模式时需把热更dlls打到包里
    694. ///
    695. /// true生成; false删除
    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("");
    704. foreach (var dllName in HybridCLR.Editor.SettingsUtil.HotUpdateAssemblyNamesIncludePreserved)
    705. {
    706. strBuilder.AppendLineFormat("\t", dllName);
    707. }
    708. strBuilder.AppendLine("");
    709. File.WriteAllText(linkFile, strBuilder.ToString());
    710. }
    711. else
    712. {
    713. if (File.Exists(linkFile)) File.Delete(linkFile);//热更包不需要添加防裁剪
    714. }
    715. }
    716. ///
    717. /// 生成HybridCLR热更相关
    718. ///
    719. ///
    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. }

  • 相关阅读:
    jumpserver迁移后windowserver资产无法登陆问题排查
    Python中aiohttp和aiofiles模块的安装
    https下载图片
    面试官:讲讲MySql索引失效的几种情况
    pytorch学习(六)——优化器
    2022 CCPC 华为云计算挑战赛 A:95计费法
    Docker基础
    白平衡简介
    【图像分割】图像分割质量分数,如 TP、FP、TN、FN、Accuracy、Sensitivity、Precision、MCC、Dice、Jaccard
    37岁更要坚定,竞争从阿里的Java高并发操作手册开始
  • 原文地址:https://blog.csdn.net/final5788/article/details/128152601