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

news2024/12/26 11:37:57

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扩展插件源代码:

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif

namespace UnityToolbarExtender
{
    public static class ToolbarCallback
    {
        static Type m_toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
        static Type m_guiViewType = typeof(Editor).Assembly.GetType("UnityEditor.GUIView");
#if UNITY_2020_1_OR_NEWER
        static Type m_iWindowBackendType = typeof(Editor).Assembly.GetType("UnityEditor.IWindowBackend");
        static PropertyInfo m_windowBackend = m_guiViewType.GetProperty("windowBackend",
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        static PropertyInfo m_viewVisualTree = m_iWindowBackendType.GetProperty("visualTree",
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
#else
		static PropertyInfo m_viewVisualTree = m_guiViewType.GetProperty("visualTree",
			BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
#endif
        static FieldInfo m_imguiContainerOnGui = typeof(IMGUIContainer).GetField("m_OnGUIHandler",
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        static ScriptableObject m_currentToolbar;

        /// <summary>
        /// Callback for toolbar OnGUI method.
        /// </summary>
        public static Action OnToolbarGUI;
        public static Action OnToolbarGUILeft;
        public static Action OnToolbarGUIRight;

        static ToolbarCallback()
        {
            EditorApplication.update -= OnUpdate;
            EditorApplication.update += OnUpdate;
        }

        static void OnUpdate()
        {
            // Relying on the fact that toolbar is ScriptableObject and gets deleted when layout changes
            if (m_currentToolbar == null)
            {
                // Find toolbar
                var toolbars = Resources.FindObjectsOfTypeAll(m_toolbarType);
                m_currentToolbar = toolbars.Length > 0 ? (ScriptableObject)toolbars[0] : null;
                if (m_currentToolbar != null)
                {
#if UNITY_2021_1_OR_NEWER
                    var root = m_currentToolbar.GetType().GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
                    var rawRoot = root.GetValue(m_currentToolbar);
                    var mRoot = rawRoot as VisualElement;
                    RegisterCallback("ToolbarZoneLeftAlign", OnToolbarGUILeft);
                    RegisterCallback("ToolbarZoneRightAlign", OnToolbarGUIRight);

                    void RegisterCallback(string root, Action cb)
                    {
                        var toolbarZone = mRoot.Q(root);

                        var parent = new VisualElement()
                        {
                            style = {
                                flexGrow = 1,
                                flexDirection = FlexDirection.Row,
                            }
                        };
                        var container = new IMGUIContainer();
                        container.style.flexGrow = 1;
                        container.onGUIHandler += () => {
                            cb?.Invoke();
                        };
                        parent.Add(container);
                        toolbarZone.Add(parent);
                    }
#else
#if UNITY_2020_1_OR_NEWER
					var windowBackend = m_windowBackend.GetValue(m_currentToolbar);

					// Get it's visual tree
					var visualTree = (VisualElement) m_viewVisualTree.GetValue(windowBackend, null);
#else
					// Get it's visual tree
					var visualTree = (VisualElement) m_viewVisualTree.GetValue(m_currentToolbar, null);
#endif

					// Get first child which 'happens' to be toolbar IMGUIContainer
					var container = (IMGUIContainer) visualTree[0];

					// (Re)attach handler
					var handler = (Action) m_imguiContainerOnGui.GetValue(container);
					handler -= OnGUI;
					handler += OnGUI;
					m_imguiContainerOnGui.SetValue(container, handler);
					
#endif
                }
            }
        }

        static void OnGUI()
        {
            var handler = OnToolbarGUI;
            if (handler != null) handler();
        }
    }

    [InitializeOnLoad]
    public static class UnityEditorToolbar
    {
        static int m_toolCount;
        static GUIStyle m_commandStyle = null;

        public static readonly List<Action> LeftToolbarGUI = new List<Action>();
        public static readonly List<Action> RightToolbarGUI = new List<Action>();

        static UnityEditorToolbar()
        {
            Type toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");

#if UNITY_2019_1_OR_NEWER
            string fieldName = "k_ToolCount";
#else
			string fieldName = "s_ShownToolIcons";
#endif

            FieldInfo toolIcons = toolbarType.GetField(fieldName,
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

#if UNITY_2019_3_OR_NEWER
            m_toolCount = toolIcons != null ? ((int)toolIcons.GetValue(null)) : 8;
#elif UNITY_2019_1_OR_NEWER
			m_toolCount = toolIcons != null ? ((int) toolIcons.GetValue(null)) : 7;
#elif UNITY_2018_1_OR_NEWER
			m_toolCount = toolIcons != null ? ((Array) toolIcons.GetValue(null)).Length : 6;
#else
			m_toolCount = toolIcons != null ? ((Array) toolIcons.GetValue(null)).Length : 5;
#endif

            ToolbarCallback.OnToolbarGUI = OnGUI;
            ToolbarCallback.OnToolbarGUILeft = GUILeft;
            ToolbarCallback.OnToolbarGUIRight = GUIRight;
        }

#if UNITY_2019_3_OR_NEWER
        public const float space = 8;
#else
		public const float space = 10;
#endif
        public const float largeSpace = 20;
        public const float buttonWidth = 32;
        public const float dropdownWidth = 80;
#if UNITY_2019_1_OR_NEWER
        public const float playPauseStopWidth = 140;
#else
		public const float playPauseStopWidth = 100;
#endif

        static void OnGUI()
        {
            // Create two containers, left and right
            // Screen is whole toolbar

            if (m_commandStyle == null)
            {
                m_commandStyle = new GUIStyle("CommandLeft");
            }

            var screenWidth = EditorGUIUtility.currentViewWidth;

            // Following calculations match code reflected from Toolbar.OldOnGUI()
            float playButtonsPosition = Mathf.RoundToInt((screenWidth - playPauseStopWidth) / 2);

            Rect leftRect = new Rect(0, 0, screenWidth, Screen.height);
            leftRect.xMin += space; // Spacing left
            leftRect.xMin += buttonWidth * m_toolCount; // Tool buttons
#if UNITY_2019_3_OR_NEWER
            leftRect.xMin += space; // Spacing between tools and pivot
#else
			leftRect.xMin += largeSpace; // Spacing between tools and pivot
#endif
            leftRect.xMin += 64 * 2; // Pivot buttons
            leftRect.xMax = playButtonsPosition;

            Rect rightRect = new Rect(0, 0, screenWidth, Screen.height);
            rightRect.xMin = playButtonsPosition;
            rightRect.xMin += m_commandStyle.fixedWidth * 3; // Play buttons
            rightRect.xMax = screenWidth;
            rightRect.xMax -= space; // Spacing right
            rightRect.xMax -= dropdownWidth; // Layout
            rightRect.xMax -= space; // Spacing between layout and layers
            rightRect.xMax -= dropdownWidth; // Layers
#if UNITY_2019_3_OR_NEWER
            rightRect.xMax -= space; // Spacing between layers and account
#else
			rightRect.xMax -= largeSpace; // Spacing between layers and account
#endif
            rightRect.xMax -= dropdownWidth; // Account
            rightRect.xMax -= space; // Spacing between account and cloud
            rightRect.xMax -= buttonWidth; // Cloud
            rightRect.xMax -= space; // Spacing between cloud and collab
            rightRect.xMax -= 78; // Colab

            // Add spacing around existing controls
            leftRect.xMin += space;
            leftRect.xMax -= space;
            rightRect.xMin += space;
            rightRect.xMax -= space;

            // Add top and bottom margins
#if UNITY_2019_3_OR_NEWER
            leftRect.y = 4;
            leftRect.height = 22;
            rightRect.y = 4;
            rightRect.height = 22;
#else
			leftRect.y = 5;
			leftRect.height = 24;
			rightRect.y = 5;
			rightRect.height = 24;
#endif

            if (leftRect.width > 0)
            {
                GUILayout.BeginArea(leftRect);
                GUILayout.BeginHorizontal();
                foreach (var handler in LeftToolbarGUI)
                {
                    handler();
                }

                GUILayout.EndHorizontal();
                GUILayout.EndArea();
            }

            if (rightRect.width > 0)
            {
                GUILayout.BeginArea(rightRect);
                GUILayout.BeginHorizontal();
                foreach (var handler in RightToolbarGUI)
                {
                    handler();
                }

                GUILayout.EndHorizontal();
                GUILayout.EndArea();
            }
        }

        public static void GUILeft()
        {
            GUILayout.BeginHorizontal();
            foreach (var handler in LeftToolbarGUI)
            {
                handler();
            }
            GUILayout.EndHorizontal();
        }

        public static void GUIRight()
        {
            GUILayout.BeginHorizontal();
            foreach (var handler in RightToolbarGUI)
            {
                handler();
            }
            GUILayout.EndHorizontal();
        }
    }
}

使用方法: 

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

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

using UnityEngine;
using UnityEditor;
using UnityToolbarExtender;
using UnityGameFramework.Editor.ResourceTools;
[UnityEditor.InitializeOnLoad]
public static class EditorToolbarExtension
{
    private static GUIContent buildBtContent;

    static EditorToolbarExtension()
    {
        buildBtContent = EditorGUIUtility.TrTextContentWithIcon("Build App/Hotfix","打新包/打热更", "UnityLogo");
        UnityEditorToolbar.RightToolbarGUI.Add(OnRightToolbarGUI);
        UnityEditorToolbar.LeftToolbarGUI.Add(OnLeftToolbarGUI);
    }

    private static void OnLeftToolbarGUI()
    {
        //在Toolbar左侧绘制UI
    }

    private static void OnRightToolbarGUI()
    {
//在Toolbar右侧绘制UI
        if (GUILayout.Button(buildBtContent,EditorStyles.toolbarButton, GUILayout.MaxWidth(125), GUILayout.Height(EditorGUIUtility.singleLineHeight)))
        {
            AppBuildEidtor.Open();
            GUIUtility.ExitGUI();
        }
        GUILayout.FlexibleSpace();
    }
}

二,打包工具功能设计: 

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

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”, 只需要通过反射调用即可:

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

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

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

private void Awake()
    {
        var resCom = GameEntry.GetComponent<ResourceComponent>();
        if (resCom != null)
        {
            var resTp = resCom.GetType();
            var m_ResourceMode = resTp.GetField("m_ResourceMode", BindingFlags.Instance | BindingFlags.NonPublic);
            m_ResourceMode.SetValue(resCom, AppSettings.Instance.ResourceMode);
            Log.Info("------------Set ResourceMode:{0}", AppSettings.Instance.ResourceMode);
        }
    }

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

AppSettings配置文件实现:

using GameFramework.Resource;
using UnityEngine;

[CreateAssetMenu(fileName = "AppSettings", menuName = "ScriptableObject/AppSettings")]
public class AppSettings : ScriptableObject
{
    private static AppSettings mInstance = null;
    public static AppSettings Instance
    {
        get
        {
            if (mInstance == null)
            {
                mInstance = Resources.Load<AppSettings>("AppSettings");
            }
            return mInstance;
        }
    }
    [Tooltip("debug模式,默认显示debug窗口")]
    public bool DebugMode = false;
    [Tooltip("资源模式: 单机/全热更/需要时热更")]
    public ResourceMode ResourceMode = ResourceMode.Package;
}

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文件:

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

一键打热更资源实现:

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

一键出包:

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

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

 在BuildPlayerWindow.cs可以看到,Build按钮调用了CallBuildMethods静态方法:

private void CallBuildMethods()
        {
#if !DISABLE_HYBRIDCLR
            HybridCLR.Editor.Commands.PrebuildCommand.GenerateAll();
#endif
            var buildWin = Utility.Assembly.GetType("UnityEditor.BuildPlayerWindow");
            if (buildWin != null)
            {
                var buildFunc = buildWin.GetMethod("CallBuildMethods", System.Reflection.BindingFlags.Static | BindingFlags.NonPublic);
                buildFunc?.Invoke(null, new object[] { true, BuildOptions.ShowBuiltPlayer });
            }
        }



private void BuildApp()
        {
            if ((m_Controller.OutputPackageSelected || m_Controller.OutputPackedSelected))
            {
                if (m_Controller.BuildResources())
                {
                    AssetDatabase.Refresh();
                    CallBuildMethods();
                }
            }
            else if (m_Controller.OutputFullSelected)
            {
                DeleteStreamingAssets();
                CallBuildMethods();
            }
        }

工具完整代码:

using GameFramework;
using System;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using GameFramework.Resource;

namespace UnityGameFramework.Editor.ResourceTools
{
    /// <summary>
    /// 资源生成器。
    /// </summary>
    public class AppBuildEidtor : EditorWindow
    {
        private ResourceBuilderController m_Controller = null;
        private bool m_OrderBuildResources = false;
        private int m_CompressionHelperTypeNameIndex = 0;
        private int m_BuildEventHandlerTypeNameIndex = 0;
        private GUIContent hotfixUrlContent;
        private GUIContent applicableVerContent;
        private GUIContent forceUpdateAppContent;
        private GUIContent appUpdateUrlContent;
        private GUIContent appUpdateDescContent;
        private GUIContent revealFolderContent;
        private GUIContent buildResBtContent;
        private GUIContent buildAppBtContent;
        private GUIContent saveBtContent;
        private GUIContent playerSettingBtContent;
        private GUIContent hybridclrSettingBtContent;
        private Vector2 scrollPosition;
        public static void Open()
        {
            AppBuildEidtor window = GetWindow<AppBuildEidtor>("App Builder", true);
#if UNITY_2019_3_OR_NEWER
            window.minSize = new Vector2(800f, 800f);
#else
            window.minSize = new Vector2(800f, 750f);
#endif
        }

        private void OnEnable()
        {
            hotfixUrlContent = new GUIContent("Update Prefix Uri", "热更新资源服务器地址");
            applicableVerContent = new GUIContent("Applicable Version", "资源适用的客户端版本号,多版本用'|'分割");
            forceUpdateAppContent = new GUIContent("Force Update", "是否强制更新App");
            appUpdateUrlContent = new GUIContent("App Update Url", "App更新下载地址");
            appUpdateDescContent = new GUIContent("App Update Description:", "App更新公告,用于显示在对话框(支持TextMeshPro富文本)");
            revealFolderContent = new GUIContent("Reveal Folder", "打包完成后打开资源输出目录");
            buildResBtContent = EditorGUIUtility.TrTextContentWithIcon("Build Resources", "打AB包/热更", "CloudConnect@2x");
            buildAppBtContent = EditorGUIUtility.TrTextContentWithIcon("Build App", "打新包", "UnityLogo");

            playerSettingBtContent = EditorGUIUtility.TrTextContentWithIcon("Player Settings", "打开Player Settings界面", "Settings");
            hybridclrSettingBtContent = EditorGUIUtility.TrTextContentWithIcon("Hotfix Settings", "打开HybridCLR Settings界面", "Settings");
            saveBtContent = EditorGUIUtility.TrTextContentWithIcon("Save", "保存设置", "SaveAs@2x");

            if (AppSettings.Instance == null)
            {
                AssetDatabase.CreateAsset(CreateInstance<AppSettings>(), "Assets/Resources/AppSettings.asset");
            }
            RefreshHybridCLREnable();

            m_Controller = new ResourceBuilderController();
            m_Controller.OnLoadingResource += OnLoadingResource;
            m_Controller.OnLoadingAsset += OnLoadingAsset;
            m_Controller.OnLoadCompleted += OnLoadCompleted;
            m_Controller.OnAnalyzingAsset += OnAnalyzingAsset;
            m_Controller.OnAnalyzeCompleted += OnAnalyzeCompleted;
            m_Controller.ProcessingAssetBundle += OnProcessingAssetBundle;
            m_Controller.ProcessingBinary += OnProcessingBinary;
            m_Controller.ProcessResourceComplete += OnProcessResourceComplete;
            m_Controller.BuildResourceError += OnBuildResourceError;
            m_OrderBuildResources = false;

            if (m_Controller.Load())
            {
                Debug.Log("Load configuration success.");

                m_CompressionHelperTypeNameIndex = 0;
                string[] compressionHelperTypeNames = m_Controller.GetCompressionHelperTypeNames();
                for (int i = 0; i < compressionHelperTypeNames.Length; i++)
                {
                    if (m_Controller.CompressionHelperTypeName == compressionHelperTypeNames[i])
                    {
                        m_CompressionHelperTypeNameIndex = i;
                        break;
                    }
                }

                m_Controller.RefreshCompressionHelper();

                m_BuildEventHandlerTypeNameIndex = 0;
                string[] buildEventHandlerTypeNames = m_Controller.GetBuildEventHandlerTypeNames();
                for (int i = 0; i < buildEventHandlerTypeNames.Length; i++)
                {
                    if (m_Controller.BuildEventHandlerTypeName == buildEventHandlerTypeNames[i])
                    {
                        m_BuildEventHandlerTypeNameIndex = i;
                        break;
                    }
                }

                m_Controller.RefreshBuildEventHandler();
            }
            else
            {
                Debug.LogWarning("Load configuration failure.");
            }

            if (string.IsNullOrWhiteSpace(m_Controller.OutputDirectory) || !Directory.Exists(m_Controller.OutputDirectory))
            {
                m_Controller.OutputDirectory = ConstEditor.AssetBundleOutputPath;
            }
        }

        private void Update()
        {
            if (m_OrderBuildResources)
            {
                m_OrderBuildResources = false;
                BuildResources();
            }
        }

        private void OnGUI()
        {
            scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
            EditorGUILayout.BeginVertical(GUILayout.Width(position.width), GUILayout.Height(position.height));
            {
                GUILayout.Space(5f);
                EditorGUILayout.LabelField("Environment Information", EditorStyles.boldLabel);
                EditorGUILayout.BeginVertical("box");
                {
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Product Name", GUILayout.Width(160f));
                        EditorGUILayout.LabelField(m_Controller.ProductName);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Company Name", GUILayout.Width(160f));
                        EditorGUILayout.LabelField(m_Controller.CompanyName);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Game Identifier", GUILayout.Width(160f));
                        EditorGUILayout.LabelField(m_Controller.GameIdentifier);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Game Framework Version", GUILayout.Width(160f));
                        EditorGUILayout.LabelField(m_Controller.GameFrameworkVersion);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Unity Version", GUILayout.Width(160f));
                        EditorGUILayout.LabelField(m_Controller.UnityVersion);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Applicable Game Version", GUILayout.Width(160f));
                        EditorGUILayout.LabelField(m_Controller.ApplicableGameVersion);
                    }
                    EditorGUILayout.EndHorizontal();
                }
                EditorGUILayout.EndVertical();
                GUILayout.Space(5f);
                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.BeginVertical();
                    {
                        EditorGUILayout.LabelField("Platforms", EditorStyles.boldLabel);
                        EditorGUILayout.BeginHorizontal("box");
                        {
                            EditorGUILayout.BeginVertical();
                            {
                                DrawPlatform(Platform.Windows, "Windows");
                                DrawPlatform(Platform.Windows64, "Windows x64");
                                DrawPlatform(Platform.MacOS, "macOS");
                            }
                            EditorGUILayout.EndVertical();
                            EditorGUILayout.BeginVertical();
                            {
                                DrawPlatform(Platform.Linux, "Linux");
                                DrawPlatform(Platform.IOS, "iOS");
                                DrawPlatform(Platform.Android, "Android");
                            }
                            EditorGUILayout.EndVertical();
                            EditorGUILayout.BeginVertical();
                            {
                                DrawPlatform(Platform.WindowsStore, "Windows Store");
                                DrawPlatform(Platform.WebGL, "WebGL");
                            }
                            EditorGUILayout.EndVertical();
                        }
                        EditorGUILayout.EndHorizontal();
                    }
                    EditorGUILayout.EndVertical();
                }
                EditorGUILayout.EndHorizontal();
                GUILayout.Space(5f);
                EditorGUILayout.LabelField("Compression", EditorStyles.boldLabel);
                EditorGUILayout.BeginVertical("box");
                {
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("AssetBundle Compression", GUILayout.Width(160f));
                        m_Controller.AssetBundleCompression = (AssetBundleCompressionType)EditorGUILayout.EnumPopup(m_Controller.AssetBundleCompression);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Compression Helper", GUILayout.Width(160f));
                        string[] names = m_Controller.GetCompressionHelperTypeNames();
                        int selectedIndex = EditorGUILayout.Popup(m_CompressionHelperTypeNameIndex, names);
                        if (selectedIndex != m_CompressionHelperTypeNameIndex)
                        {
                            m_CompressionHelperTypeNameIndex = selectedIndex;
                            m_Controller.CompressionHelperTypeName = selectedIndex <= 0 ? string.Empty : names[selectedIndex];
                            if (m_Controller.RefreshCompressionHelper())
                            {
                                Debug.Log("Set compression helper success.");
                            }
                            else
                            {
                                Debug.LogWarning("Set compression helper failure.");
                            }
                        }
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Additional Compression", GUILayout.Width(160f));
                        m_Controller.AdditionalCompressionSelected = EditorGUILayout.ToggleLeft("Additional Compression for Output Full Resources with Compression Helper", m_Controller.AdditionalCompressionSelected);
                    }
                    EditorGUILayout.EndHorizontal();
                }
                EditorGUILayout.EndVertical();
                GUILayout.Space(5f);
                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.LabelField("Build Resources Settings", EditorStyles.boldLabel);
                    if (GUILayout.Button("Resources Editor", GUILayout.Width(160f)))
                    {
                        OpenResourcesEditor();
                        GUIUtility.ExitGUI();
                    }
                }
                EditorGUILayout.EndHorizontal();
                EditorGUILayout.BeginVertical("box");
                {
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Force Rebuild AssetBundle", GUILayout.Width(160f));
                        m_Controller.ForceRebuildAssetBundleSelected = EditorGUILayout.Toggle(m_Controller.ForceRebuildAssetBundleSelected);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Build Event Handler", GUILayout.Width(160f));
                        string[] names = m_Controller.GetBuildEventHandlerTypeNames();
                        int selectedIndex = EditorGUILayout.Popup(m_BuildEventHandlerTypeNameIndex, names);
                        if (selectedIndex != m_BuildEventHandlerTypeNameIndex)
                        {
                            m_BuildEventHandlerTypeNameIndex = selectedIndex;
                            m_Controller.BuildEventHandlerTypeName = selectedIndex <= 0 ? string.Empty : names[selectedIndex];
                            if (m_Controller.RefreshBuildEventHandler())
                            {
                                Debug.Log("Set build event handler success.");
                            }
                            else
                            {
                                Debug.LogWarning("Set build event handler failure.");
                            }
                        }
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Internal Resource Version", GUILayout.Width(160f));
                        m_Controller.InternalResourceVersion = EditorGUILayout.IntField(m_Controller.InternalResourceVersion);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Resource Version", GUILayout.Width(160f));
                        GUILayout.Label(Utility.Text.Format("{0} ({1})", m_Controller.ApplicableGameVersion, m_Controller.InternalResourceVersion.ToString()));
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Output Directory", GUILayout.Width(160f));
                        m_Controller.OutputDirectory = EditorGUILayout.TextField(m_Controller.OutputDirectory);
                        if (GUILayout.Button("Browse...", GUILayout.Width(80f)))
                        {
                            BrowseOutputDirectory();
                        }
                    }
                    EditorGUILayout.EndHorizontal();

                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Output Resources Path", GUILayout.Width(160f));
                        GUILayout.Label(GetResourceOupoutPathByMode(AppSettings.Instance.ResourceMode));
                        EditorGUILayout.LabelField("Resource Mode:", GUILayout.Width(100f));
                        EditorGUI.BeginChangeCheck();
                        {
                            AppSettings.Instance.ResourceMode = (ResourceMode)EditorGUILayout.EnumPopup(AppSettings.Instance.ResourceMode, GUILayout.Width(160f));
                        }
                        if (EditorGUI.EndChangeCheck())
                        {
                            RefreshHybridCLREnable();
                        }
                        if (AppSettings.Instance.ResourceMode != ResourceMode.Unspecified)
                        {
                            SetResourceMode(AppSettings.Instance.ResourceMode);
                        }
                        AppBuildSettings.Instance.RevealFolder = EditorGUILayout.ToggleLeft(revealFolderContent, AppBuildSettings.Instance.RevealFolder, GUILayout.Width(105f));
                    }
                    EditorGUILayout.EndHorizontal();
                    if (AppSettings.Instance.ResourceMode == ResourceMode.Unspecified)
                    {
                        EditorGUILayout.HelpBox("ResourceMode is invalid.", MessageType.Error);
                    }
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Working Path", GUILayout.Width(160f));
                        GUILayout.Label(m_Controller.WorkingPath);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Build Report Path", GUILayout.Width(160f));
                        GUILayout.Label(m_Controller.BuildReportPath);
                    }
                    EditorGUILayout.EndHorizontal();
                }

                EditorGUILayout.EndVertical();
                string buildMessage = string.Empty;
                MessageType buildMessageType = MessageType.None;
                GetBuildMessage(out buildMessage, out buildMessageType);
                EditorGUILayout.HelpBox(buildMessage, buildMessageType);
                if (m_Controller.OutputFullSelected || m_Controller.OutputPackedSelected)
                {
                    DrawHotfixConfigPanel();
                }
                DrawAppBuildSettingsPanel();
                GUILayout.Space(2f);
                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUI.BeginDisabledGroup(m_Controller.Platforms == Platform.Undefined || string.IsNullOrEmpty(m_Controller.CompressionHelperTypeName) || !m_Controller.IsValidOutputDirectory || AppSettings.Instance.ResourceMode == ResourceMode.Unspecified);
                    {
                        if (GUILayout.Button(buildResBtContent, GUILayout.Height(35)))
                        {
                            m_OrderBuildResources = true;
                        }
                        if (GUILayout.Button(buildAppBtContent, GUILayout.Height(35)))
                        {
                            BuildApp();
                            GUIUtility.ExitGUI();
                        }
                    }
                    EditorGUI.EndDisabledGroup();
                    if (GUILayout.Button(saveBtContent, GUILayout.Width(140), GUILayout.Height(35)))
                    {
                        SaveConfiguration();
                    }
                }
                EditorGUILayout.EndHorizontal();
            }
            EditorGUILayout.EndVertical();
            EditorGUILayout.EndScrollView();
        }
        private void RefreshHybridCLREnable()
        {
            if (AppSettings.Instance.ResourceMode != ResourceMode.Unspecified)
            {
                if (AppSettings.Instance.ResourceMode == ResourceMode.Package)
                {
#if !DISABLE_HYBRIDCLR
                    MyGameTools.DisableHybridCLR();
#endif
                }
                else
                {
#if DISABLE_HYBRIDCLR
                    MyGameTools.EnableHybridCLR();
#endif
                }
            }
        }
        private string GetResourceOupoutPathByMode(ResourceMode mode)
        {
            string result = null;
            switch (mode)
            {
                case ResourceMode.Package:
                    result = m_Controller.OutputPackagePath;
                    break;
                case ResourceMode.Updatable:
                    result = m_Controller.OutputFullPath;
                    break;
                case ResourceMode.UpdatableWhilePlaying:
                    result = m_Controller.OutputPackedPath;
                    break;
            }
            return result;
        }
        private void SetResourceMode(ResourceMode mode)
        {
            m_Controller.OutputPackageSelected = false;
            m_Controller.OutputFullSelected = false;
            m_Controller.OutputPackedSelected = false;
            switch (mode)
            {
                case ResourceMode.Package:
                    m_Controller.OutputPackageSelected = true;
                    break;
                case ResourceMode.Updatable:
                    m_Controller.OutputFullSelected = true;
                    break;
                case ResourceMode.UpdatableWhilePlaying:
                    m_Controller.OutputPackedSelected = true;
                    break;
            }
        }
        private void OpenResourcesEditor()
        {
            var resEditorClass = Utility.Assembly.GetType("UnityGameFramework.Editor.ResourceTools.ResourceEditor");
            resEditorClass?.GetMethod("Open", BindingFlags.Static | BindingFlags.NonPublic)?.Invoke(null, null);
        }
        private void DrawAppBuildSettingsPanel()
        {
            GUILayout.Space(5f);
            EditorGUILayout.BeginHorizontal();
            {
                EditorGUILayout.LabelField("Build App Settings:", EditorStyles.boldLabel, GUILayout.Width(160));
#if UNITY_ANDROID
                AppBuildSettings.Instance.BuildForGooglePlay = EditorUserBuildSettings.buildAppBundle = EditorGUILayout.ToggleLeft("Build App Bundle(GP)", AppBuildSettings.Instance.BuildForGooglePlay);
#endif
                AppBuildSettings.Instance.DevelopmentBuild = EditorUserBuildSettings.development = EditorGUILayout.ToggleLeft("Development Build", AppBuildSettings.Instance.DevelopmentBuild);
                AppSettings.Instance.DebugMode = EditorGUILayout.ToggleLeft("Debug Mode", AppSettings.Instance.DebugMode);
                if (GUILayout.Button(playerSettingBtContent))
                {
                    SettingsService.OpenProjectSettings("Project/Player");
                    GUIUtility.ExitGUI();
                }
            }
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.BeginVertical("box");
            {
                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.LabelField("Version", GUILayout.Width(160f));
                    PlayerSettings.bundleVersion = EditorGUILayout.TextField(PlayerSettings.bundleVersion);
                }
                EditorGUILayout.EndHorizontal();
#if UNITY_ANDROID
                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.LabelField("Version Code", GUILayout.Width(160f));
                    PlayerSettings.Android.bundleVersionCode = EditorGUILayout.IntField(PlayerSettings.Android.bundleVersionCode);
                }
                EditorGUILayout.EndHorizontal();
                EditorGUILayout.BeginHorizontal();
                {
                    PlayerSettings.Android.useCustomKeystore = EditorGUILayout.ToggleLeft("Use Custom Keystore", PlayerSettings.Android.useCustomKeystore, GUILayout.Width(160f));
                    EditorGUI.BeginDisabledGroup(!PlayerSettings.Android.useCustomKeystore);
                    {
                        AppBuildSettings.Instance.AndroidKeystoreName = PlayerSettings.Android.keystoreName = EditorGUILayout.TextField(AppBuildSettings.Instance.AndroidKeystoreName);
                        if (GUILayout.Button("Select Keystore", GUILayout.Width(160f)))
                        {
                            var keystoreDir = string.IsNullOrWhiteSpace(AppBuildSettings.Instance.AndroidKeystoreName) ? Directory.GetParent(Application.dataPath).FullName : Path.GetDirectoryName(AppBuildSettings.Instance.AndroidKeyAliasName);
                            var openPath = Directory.Exists(keystoreDir) ? keystoreDir : Directory.GetParent(Application.dataPath).FullName;
                            string path = EditorUtility.OpenFilePanel("Select Keystore", openPath, "keystore,jks,ks");
                            AppBuildSettings.Instance.AndroidKeystoreName = PlayerSettings.Android.keystoreName = path;
                            GUIUtility.ExitGUI();
                        }
                    }
                    EditorGUI.EndDisabledGroup();
                }
                EditorGUILayout.EndHorizontal();
                if (PlayerSettings.Android.useCustomKeystore)
                {
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Keystore Password", GUILayout.Width(160f));
                        AppBuildSettings.Instance.KeystorePass = PlayerSettings.keystorePass = EditorGUILayout.TextField(AppBuildSettings.Instance.KeystorePass);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("KeyAliasName", GUILayout.Width(160f));
                        AppBuildSettings.Instance.AndroidKeyAliasName = PlayerSettings.Android.keyaliasName = EditorGUILayout.TextField(AppBuildSettings.Instance.AndroidKeyAliasName);
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("Alias Password", GUILayout.Width(160f));
                        AppBuildSettings.Instance.KeyAliasPass = PlayerSettings.keyaliasPass = EditorGUILayout.TextField(AppBuildSettings.Instance.KeyAliasPass);
                    }
                    EditorGUILayout.EndHorizontal();
                }

#elif UNITY_IOS
                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.LabelField("Build Number", GUILayout.Width(160f));
                    PlayerSettings.iOS.buildNumber = EditorGUILayout.TextField(PlayerSettings.iOS.buildNumber);
                }
                EditorGUILayout.EndHorizontal();
#endif
            }
            EditorGUILayout.EndVertical();
        }
        private void DrawHotfixConfigPanel()
        {
            GUILayout.Space(5f);
            EditorGUILayout.BeginHorizontal();
            {
                EditorGUILayout.LabelField("Hotfix Settings:", EditorStyles.boldLabel);
                if (GUILayout.Button(hybridclrSettingBtContent, GUILayout.Width(160f)))
                {
                    SettingsService.OpenProjectSettings("Project/HybridCLR Settings");
                    GUIUtility.ExitGUI();
                }
            }
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.BeginVertical("box");
            {
                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.LabelField(hotfixUrlContent, GUILayout.Width(160f));
                    AppBuildSettings.Instance.UpdatePrefixUri = EditorGUILayout.TextField(AppBuildSettings.Instance.UpdatePrefixUri);
                }
                EditorGUILayout.EndHorizontal();

                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.LabelField(applicableVerContent, GUILayout.Width(160f));
                    AppBuildSettings.Instance.ApplicableGameVersion = EditorGUILayout.TextField(AppBuildSettings.Instance.ApplicableGameVersion);
                }
                EditorGUILayout.EndHorizontal();

                EditorGUILayout.BeginHorizontal();
                {
                    EditorGUILayout.LabelField(appUpdateUrlContent, GUILayout.Width(160f));
                    AppBuildSettings.Instance.AppUpdateUrl = EditorGUILayout.TextField(AppBuildSettings.Instance.AppUpdateUrl);
                    AppBuildSettings.Instance.ForceUpdateApp = EditorGUILayout.ToggleLeft(forceUpdateAppContent, AppBuildSettings.Instance.ForceUpdateApp, GUILayout.Width(100f));
                }
                EditorGUILayout.EndHorizontal();
                EditorGUILayout.Space(5);
                EditorGUILayout.LabelField(appUpdateDescContent, GUILayout.Width(160f));
                AppBuildSettings.Instance.AppUpdateDesc = EditorGUILayout.TextArea(AppBuildSettings.Instance.AppUpdateDesc, GUILayout.Height(50));
            }
            EditorGUILayout.EndVertical();
        }
        private void BuildApp()
        {
            if ((m_Controller.OutputPackageSelected || m_Controller.OutputPackedSelected))
            {
                if (m_Controller.BuildResources())
                {
                    AssetDatabase.Refresh();
                    CallBuildMethods();
                }
            }
            else if (m_Controller.OutputFullSelected)
            {
                DeleteStreamingAssets();
                CallBuildMethods();
            }
        }
        private void DeleteStreamingAssets()
        {
            string streamingAssetsPath = Path.Combine(Application.dataPath, "StreamingAssets");
            if (Directory.Exists(streamingAssetsPath))
            {
                Directory.Delete(streamingAssetsPath, true);
            }
            string streamMetaFile = streamingAssetsPath + ".meta";
            if (File.Exists(streamMetaFile))
            {
                File.Delete(streamMetaFile);
            }
        }
        private void CallBuildMethods()
        {
#if !DISABLE_HYBRIDCLR
            HybridCLR.Editor.Commands.PrebuildCommand.GenerateAll();
#endif
            var buildWin = Utility.Assembly.GetType("UnityEditor.BuildPlayerWindow");
            if (buildWin != null)
            {
                var buildFunc = buildWin.GetMethod("CallBuildMethods", System.Reflection.BindingFlags.Static | BindingFlags.NonPublic);
                buildFunc?.Invoke(null, new object[] { true, BuildOptions.ShowBuiltPlayer });
            }
        }
        private void BrowseOutputDirectory()
        {
            string directory = EditorUtility.OpenFolderPanel("Select Output Directory", m_Controller.OutputDirectory, string.Empty);
            if (!string.IsNullOrEmpty(directory))
            {
                m_Controller.OutputDirectory = directory;
            }
        }

        private void GetBuildMessage(out string message, out MessageType messageType)
        {
            message = string.Empty;
            messageType = MessageType.Error;
            if (m_Controller.Platforms == Platform.Undefined)
            {
                if (!string.IsNullOrEmpty(message))
                {
                    message += Environment.NewLine;
                }

                message += "Platform is invalid.";
            }

            if (string.IsNullOrEmpty(m_Controller.CompressionHelperTypeName))
            {
                if (!string.IsNullOrEmpty(message))
                {
                    message += Environment.NewLine;
                }

                message += "Compression helper is invalid.";
            }

            if (!m_Controller.IsValidOutputDirectory)
            {
                if (!string.IsNullOrEmpty(message))
                {
                    message += Environment.NewLine;
                }

                message += "Output directory is invalid.";
            }

            if (!string.IsNullOrEmpty(message))
            {
                return;
            }

            messageType = MessageType.Info;
            if (Directory.Exists(m_Controller.OutputPackagePath))
            {
                message += Utility.Text.Format("{0} will be overwritten.", m_Controller.OutputPackagePath);
                messageType = MessageType.Warning;
            }

            if (Directory.Exists(m_Controller.OutputFullPath))
            {
                if (message.Length > 0)
                {
                    message += " ";
                }

                message += Utility.Text.Format("{0} will be overwritten.", m_Controller.OutputFullPath);
                messageType = MessageType.Warning;
            }

            if (Directory.Exists(m_Controller.OutputPackedPath))
            {
                if (message.Length > 0)
                {
                    message += " ";
                }

                message += Utility.Text.Format("{0} will be overwritten.", m_Controller.OutputPackedPath);
                messageType = MessageType.Warning;
            }

            if (messageType == MessageType.Warning)
            {
                return;
            }

            message = "Ready to build.";
        }

        private void BuildResources()
        {
            if (m_Controller.BuildResources())
            {
                Debug.Log("Build resources success.");
                SaveConfiguration();
            }
            else
            {
                Debug.LogWarning("Build resources failure.");
            }
        }

        private void SaveConfiguration()
        {
            EditorUtility.SetDirty(AppSettings.Instance);
            AppBuildSettings.Save();
            if (m_Controller.Save())
            {
                Debug.Log("Save configuration success.");
            }
            else
            {
                Debug.LogWarning("Save configuration failure.");
            }
        }

        private void DrawPlatform(Platform platform, string platformName)
        {
            m_Controller.SelectPlatform(platform, EditorGUILayout.ToggleLeft(platformName, m_Controller.IsPlatformSelected(platform)));
        }

        private void OnLoadingResource(int index, int count)
        {
            EditorUtility.DisplayProgressBar("Loading Resources", Utility.Text.Format("Loading resources, {0}/{1} loaded.", index.ToString(), count.ToString()), (float)index / count);
        }

        private void OnLoadingAsset(int index, int count)
        {
            EditorUtility.DisplayProgressBar("Loading Assets", Utility.Text.Format("Loading assets, {0}/{1} loaded.", index.ToString(), count.ToString()), (float)index / count);
        }

        private void OnLoadCompleted()
        {
            EditorUtility.ClearProgressBar();
        }

        private void OnAnalyzingAsset(int index, int count)
        {
            EditorUtility.DisplayProgressBar("Analyzing Assets", Utility.Text.Format("Analyzing assets, {0}/{1} analyzed.", index.ToString(), count.ToString()), (float)index / count);
        }

        private void OnAnalyzeCompleted()
        {
            EditorUtility.ClearProgressBar();
        }

        private bool OnProcessingAssetBundle(string assetBundleName, float progress)
        {
            if (EditorUtility.DisplayCancelableProgressBar("Processing AssetBundle", Utility.Text.Format("Processing '{0}'...", assetBundleName), progress))
            {
                EditorUtility.ClearProgressBar();
                return true;
            }
            else
            {
                Repaint();
                return false;
            }
        }

        private bool OnProcessingBinary(string binaryName, float progress)
        {
            if (EditorUtility.DisplayCancelableProgressBar("Processing Binary", Utility.Text.Format("Processing '{0}'...", binaryName), progress))
            {
                EditorUtility.ClearProgressBar();
                return true;
            }
            else
            {
                Repaint();
                return false;
            }
        }

        private void OnProcessResourceComplete(Platform platform)
        {
            EditorUtility.ClearProgressBar();
            Debug.Log(Utility.Text.Format("Build resources for '{0}' complete.", platform.ToString()));

            if (AppBuildSettings.Instance.RevealFolder)
            {
                EditorUtility.RevealInFinder(UtilityBuiltin.ResPath.GetCombinePath(GetResourceOupoutPathByMode(AppSettings.Instance.ResourceMode), platform.ToString()));
            }
        }

        private void OnBuildResourceError(string errorMessage)
        {
            EditorUtility.ClearProgressBar();
            Debug.LogWarning(Utility.Text.Format("Build resources error with error message '{0}'.", errorMessage));
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/66066.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Span进阶之路——ClickableSpan

一、前言 在Android中&#xff0c;可以使用强大的标记&#xff08;Span&#xff09;对象来实现富文本展示&#xff0c;相比 HTML 而言更高效实用。关于 Android Span 的入门篇可以阅读 Android中强大的标记对象-Span。本文将对 ClickableSpan &#xff08;可点击的Span&#xf…

WebDAV之葫芦儿·派盘 + Koder

Koder 支持WebDAV方式连接葫芦儿派盘。 一款可以让你在iPhone、iPad上写各种编程语言代码的app,码农不要错过。 Koder是iPad和iPhone的代码编辑器。它确实具有许多功能,包括语法突出显示,代码段管理器,选项卡式编辑,查找和替换代码,编辑器主题,远程和本地文件连接等等…

OpenCV入门(C++/Python)- 使用OpenCV标注图像(六)

使用OpenCV标注图像用颜色线标注图像绘制圆绘制实心圆绘制矩阵绘制椭圆绘制带轮廓和填充半椭圆使用文本注释图像为图像和视频添加标注的目的不止一个&#xff0c;包含&#xff1a;向视频中添加信息在对象检测的情况下&#xff0c;在对象周围绘制边界框&#xff0c;用不同颜色的…

并查集介绍

文章目录&#xff1a;并查集原理并查集实现并查集的类结构并查集的合并统计集合数量并查集原理 在一些应用问题中&#xff0c;需要将 n 个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按照一定的规律将归于同一组元素的集…

《找对英语学习方法的第一本书》

简 述: 此书写于二十年前&#xff0c;结合我自身情况参照&#xff0c;有了一种理论指导&#xff0c;可在众多学习方法中有效抉择&#xff0c;亦能在不同阶段更换不同策略。本文为读后的一个简要归纳和札记。 文章目录第一章&#xff1a;爱之愈深、误之愈切第二章&#xff1a;我…

李宏毅2022《机器学习/深度学习》——学习笔记(5)

文章目录优化方法CNNCNN和全连接神经网络的区别感受野共享参数CNN和全连接神经网络的总结PoolingCNN流程自注意力机制自注意力机制解决的问题输入是一组向量的例子输入是一组向量时输出的可能自注意力机制核心思想自注意力机制具体细节Self-attention和CNN的关系参考资料优化方…

网络安全之从原理看懂XSS

01、XSS的原理和分类 跨站脚本攻击XSS(Cross Site Scripting)&#xff0c;为了不和层叠样式表(Cascading Style Sheets&#xff0c;CSS)的缩写混淆 故将跨站脚本攻击缩写为XSS&#xff0c;恶意攻击者往Web页面里插入恶意Script代码&#xff0c;当用户浏览该页面时&#xff0c…

七周成为数据分析师 | 业务

为什么业务重要&#xff1f; 唯有理解业务&#xff0c;才能建立业务数据模型 一.经典业务分析指标 模型未动&#xff0c;指标先行 如果你不能衡量它&#xff0c;你就无法增长它 指标建立的要点 ①核心指标 ②好的指标应该是比率 ③好的指标应该能带来显著效果 ④好的指…

民办二本程序员阿里、百度、平安等五厂面经,5 份 offer(含真题)

昨天小休&#xff0c;一位高中同学联系了我&#xff0c;说是要请我吃饭&#xff0c;有这种好事&#xff0c;我当然是毫不犹豫的答应了啦&#xff01; 等等...会不会是找我借钱的&#xff1f; 好慌&#xff0c;怎么办&#xff1f;已经答应过去了。 在后面的交谈中&#xff0c;…

Word控件Spire.Doc 【图像形状】教程(12) 如何在C#中旋转word文档上的形状

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

【C语言程序设计】实验 3

目录 1. 水仙花数 2. 五位回文数 3. 输入x&#xff0c;计算y 4. 百分制改为等级制 5. 同构数 6. 月份天数 7. 加一天后日期&#xff08;条件&#xff09; 8. 计算服装款&#xff08;条件&#xff09; 1. 水仙花数 【问题描述】输入一个3位正整数&#xff0c;判断该…

数据可视化之基础图表

一 前言 数据图表则是用来表现数据的一类图表&#xff0c;用来帮助用户理解数据。在这类图表中&#xff0c;以三大类图表最为常用 —— 柱状图&#xff08;条形图&#xff09;、折线图、饼图。据非官方统计&#xff0c;数据图表使用率占所有图表的类型的62%。所以&#xff0c;…

Docker之MySQL_GROUP_REPLICATION组复制(MGR)、宕机节点恢复和Spirngboot整合

三台服务器修改hosts文件 vim /etc/hosts追加内容 192.168.1.11 node1 192.168.1.12 node2 192.168.1.13 node3修改hostname vim /etc/hostname重启网络使配置文件生效 systemctl restart network三台服务器拉取MySQL镜像 docker pull mysql:8.0.23创建配置文件夹 …

PDF文档转TXT怎么转?你不知道的几种方法

PDF文档转TXT怎么转&#xff1f;我们经常需要处理PDF文件&#xff0c;根据不同的要求&#xff0c;我们经常需要将PDF文件进行转换&#xff0c;虽然PDF文件相对于其他大多数文件来说体积已经很小了&#xff0c;但是TXT文件会比PDF文件体积更小一些&#xff0c;这样我们不仅可以节…

Python工程师Java之路(w)数据库连接池Druid

概述 初阶数据库访问的步骤是【创建连接>执行SQL>关闭连接】&#xff0c;有如下不足&#xff1a; 1、创建数据库连接会浪费时间 2、大量访问时&#xff0c;频繁 GC 会导致CPU负载过高 3、如果改为不关闭连接&#xff0c;则会长期占用内存对此&#xff0c;引入“缓冲池”…

物联网各类数据如何轻松获取?秘诀就在定制文件推送服务

当前&#xff0c;数字经济已成为我国经济发展的重要驱动力。随着物联网的蓬勃发展&#xff0c;海量数据伴随着终端联网在各行各业涌现&#xff0c;越来越多的企业已然察觉隐藏在数字中的金矿&#xff0c;加入到数字化转型行列中&#xff0c;通过数据挖掘实现精细化运营&#xf…

高蛋白过敏我们该如何缓解?教你几招远离过敏吃喝无忌

许多朋友回应说&#xff0c;吃海鲜.牛肉、羊肉等高蛋白食物会发生过敏反应&#xff0c;要么脸红肿&#xff0c;要么长痘痘。看着他们贪婪的食物&#xff0c;他们只能避免吃真的很痛苦。为什么现在人们的生活条件越来越好&#xff0c;生活环境也显著改善&#xff0c;但过敏性疾病…

USB插座外壳接地的处理和emi,esd考虑

外壳是否接地&#xff0c;从理想电路环境&#xff08;没有干扰&#xff0c;也不释放干扰&#xff09;和电路原理来说&#xff0c;接和不接没有任何差异&#xff0c;也不会影响正常功能。 但是实际的电子产品的工作环境&#xff0c;是一个处于被各种干扰包围的复杂的电磁场环境&…

[附源码]JAVA毕业设计人才库构建研究(系统+LW)

[附源码]JAVA毕业设计人才库构建研究&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&a…

使用icacls命令设置目录及其子目录、文件的所有权限

以前一直使用cacls命令来设置权限&#xff0c;前两天输入这个命令的时候&#xff0c;却发现了一行提示&#xff1a; “注意: 不推荐使用 Cacls&#xff0c;请使用 Icacls。” 如图&#xff1a; 于是研究了一下 Icacls 这个命令。。 先放上微软官方文档&#xff1a; https:/…