Unity编辑器工具制作(二)——制作一键打包工具

前言

前一篇主要将如何建立自己的工具箱,此篇主要讲如何往工具箱里添加工具,并以一键打包为例,探寻一种通用的工具制作流程

1.明确数据对象

要做工具首先要明确我们工具要处理的对象是谁,即数据是谁,数据的形式是怎样的,数据的量,数据的来源,甚至于数据的结构,就像是厨子要做一道菜,第一件事是选择食材,处理食材。

对数据的处理不是一下子就完成的,可能在工具制作过程中,我们会对数据有新的要求,比如命名,数据结构,数据路径要求等等,这些要求是对数据的规范,只有统一的,有规律的,有秩序的数据才能被工具批处理。

以一键打包为例,我们首先确定我们都有哪些数据,一键打包不需要处理数据,但是需要获得数据,我们需要获取打包平台,打包路径,CompanyName,ProductName,编译器选择(mono,IL2Cpp),甚至于安卓SDK的版本,场景等等。

确定数据的方式就是我们打包过程中需要调整的数据,把这些需要调整的数据放到一个页面,并且打包按钮也放过来,在一个页面就可以完成打包,这就是一键打包工具的最终目标。

参考代码:
将要使用的数据声明好
使用GetSet访问器 灵活控制在工具界面是否可读可写

 private string androidPath;
 private string winPath;
 private List<string> m_activeScene = new List<string>();
 public AndroidSdkVersions androidSDK_Version
    {
        get
        {
            return buildConfig.androidSDK_Version;
        }
        set
        {
            if (buildConfig != null)
            {
                buildConfig.androidSDK_Version = value;
            }
        }
    }
     public int versionIncrease
    {
        get
        {
            if (buildConfig == null)
            {
                return 0;
            }
            return buildConfig.versionIncrease;
        }
        set
        {
            if (buildConfig != null)
            {
                buildConfig.versionIncrease = value;
            }
        }
    }

2.明确数据处理流程

数据有了,然后是明确数据处理的流程,这里可以画一个流程图,可以清楚地知道数据是如何一步一步变成我们想要的样子,中间过程可能需要组合新的数据,数据与数据之间进行关联等等

以一键打包为例
在这里插入图片描述
决定数据处理流程的很大程度是 相关API调用决定的,比如打包的核心API:BuildPipeline.BuildPlayer
我们去unity手册可以知道它需要哪些参数,这些参数决定了数据处理流程,当一个工具要调用不同的API时,对数据的要求也不同,条件允许情况下可以寻找更合适的API,从而精简数据处理流程(Nuget,Gethub,很多轮子,工具的意义是省时省力,所以为工具造轮子,本末倒置,反而加大开发难度,增加开发时长)

3.对数据结果负责,对结果正确性检测

对数据结果进行检测,检查是否正确的处理数据,使数据达到我们的处理要求。
以一键打包为例,检测打包后的APP是否正常使用,多次打包是否正常,换项目打包是否正常。
在Odin中要执行功能,一般使用button,在我们的打包函数上,添加特性标签Button,即可显示在工具页面,点击即可执行打包函数
参考代码:

/// <summary>
    /// 打包
    /// </summary>
    [TitleGroup("PlayerSetting 打包设置")]
    [Button(90), GUIColor(0.4f, 0.8f, 1f)]
    public void BuildPackage()
    {
        //this.isBuild = !this.isBuild;
        PlayerSettings.companyName = companyName;
        PlayerSettings.productName = productName;
        PlayerSettings.bundleVersion = version;

        PlayerSettings.Android.targetSdkVersion = androidSDK_Version;

        #region 开始打包

        isBuilding = BuildPipeline.isBuildingPlayer;

        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
        buildPlayerOptions.scenes = activeScenes.ToArray();
        //打包目标路径
        androidPath = buildPath + @"\" + buildTarget.ToString() + @"\" + productName + "_" + version + ".apk";
        winPath = buildPath + @"\" + buildTarget.ToString() + @"\" + productName + "_" + version + ".exe";
        if (buildTarget == BuildTargetGroup.Android)
        {
            buildPlayerOptions.locationPathName = androidPath;
        }
        else
        {
            buildPlayerOptions.locationPathName = winPath;
        }
        //打包目标平台
        if (buildTarget.ToString() == "Standalone")
        {
            buildPlayerOptions.target = BuildTarget.StandaloneWindows;
        }
        else
        {
            buildPlayerOptions.target = (BuildTarget)Enum.Parse(typeof(BuildTarget), buildTarget.ToString());
        }

        buildPlayerOptions.options = BuildOptions.None;
        BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
        BuildSummary summary = report.summary;
        if (summary.result == BuildResult.Succeeded)
        {
            Debug.Log("Build succeeded: " + summary.platform);
            Debug.Log("Build succeeded: " + summary.totalSize + "bytes");
            Debug.Log("Build succeeded: " + summary.totalTime + "s");

            SucessBuild(report);
        }

        if (summary.result == BuildResult.Failed)
        {
            Debug.Log("Build failed");
            FailBuild(report);
        }
        isBuilding = BuildPipeline.isBuildingPlayer;

        #endregion 
    }

4.增加程序鲁棒性

鲁棒是Robust的音译,强壮,健壮,为了使我们的工具更加好用,要进行数据的合理性检测,确保在无效的,错误的数据情况下,工具也不会崩溃(unity自个都会莫名其妙崩溃)
检测最多的是针对String的检测,空字符,空引用等等
还有对数组的检测,空数组,空引用
一些清况下 还需要使用 try catch 包裹

 string.IsNullOrEmpty()//同时检测空字符和空引用

错误用法:

      if(testString==""||testString==null)
        {
            return;
        }

空字符和空引用在同一个if的情况下,如果是空引用,会error
因为这两句都会执行,但是空引用不能检测空字符,会出现null

5.代码重构

重构不必多说,细节之中自有天地
推荐插件CodeMain,打开码锹窗口
在这里插入图片描述
码锹会列出你所有的属性,字段,枚举,方法等,右边的数字如果红色,就意味着可读性差,重构之
如果能通过看码锹就完整的在脑子里能跑通整个流程,说明就可以了。

在这里插入图片描述
重构永无止境,细节之中自有天地

完整代码参考:

using Sirenix.OdinInspector;
using Sirenix.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;

[TypeInfoBox("<size=20>一键打包</size>")]
public class OneKeyBuildlEditor : GlobalConfig<OneKeyBuildlEditor>
{
    private const int addressableBundlesDirOrder = 3;
    private const int AddressableButtonOrder = 2;
    private const int Addressables地址管理Order = 3;
    private const int PlayerSetting打包设置Order = 4;
    private const int PlayerSetting打包结果Order = 5;
    private const string HFS = "HFS";
    private string androidPath;
    private string winPath;
    private bool isBuild = false;

   

    [TitleGroup("PlayerSetting 打包设置", null, TitleAlignments.Left, true, true, false, PlayerSetting打包设置Order)]
    public BuildConfig buildConfig;

    private List<string> m_activeScene = new List<string>();

    [ShowInInspector]
    [TitleGroup("PlayerSetting 打包设置", null, TitleAlignments.Left, true, true, false, PlayerSetting打包设置Order)]
    public List<string> activeScenes
    {
        get
        {
            var tempScenes = new List<string>();
            EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
            foreach (var item in scenes)
            {
                if (item.enabled)
                {
                    tempScenes.Add(item.path);
                }
            }
            return tempScenes;
        }
    }

    private bool isBuilding;

    [TitleGroup("PlayerSetting 打包设置")]
    public AndroidSdkVersions androidSDK_Version
    {
        get
        {
            return buildConfig.androidSDK_Version;
        }
        set
        {
            if (buildConfig != null)
            {
                buildConfig.androidSDK_Version = value;
            }
        }
    }

    [TitleGroup("PlayerSetting 打包设置"), ShowInInspector]
    [FolderPath(AbsolutePath = true)]
    public string buildPath
    {
        get
        {
            if (buildConfig == null)
            {
                return "";
            }
            return buildConfig.buildPath;
        }
        set
        {
            if (buildConfig != null)
            {
                buildConfig.buildPath = value;
            }
            EditorPrefs.SetString("OdinBuild.BuildPath", value);
        }
    }

    [TitleGroup("PlayerSetting 打包设置"), ShowInInspector]
    [ReadOnly]
    public BuildTargetGroup buildTarget
    {
        get
        {
            if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
            {
                return BuildTargetGroup.Android;
            }
            else if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS)
            {
                return BuildTargetGroup.iOS;
            }
            else
            {
                return BuildTargetGroup.Standalone;
            }
        }
    }

    [TitleGroup("PlayerSetting 打包设置"), ShowInInspector]
    public string companyName
    {
        get
        {
            return PlayerSettings.companyName;
        }
        set
        {
            PlayerSettings.companyName = value;
        }
    }

    [TitleGroup("PlayerSetting 打包设置"), ShowInInspector]
    public string productName
    {
        get
        {
            return PlayerSettings.productName;
        }
        set
        {
            PlayerSettings.productName = value;
        }
    }

    [ReadOnly]
    [TitleGroup("PlayerSetting 打包设置"), ShowInInspector]
    [InfoBox("将要打包的版本号(不可修改,默认自增)")]
    public string version
    {
        get
        {
            return PlayerSettings.bundleVersion;
        }
        set
        {
            PlayerSettings.bundleVersion = value;
        }
    }

    [TitleGroup("PlayerSetting 打包设置"), ShowInInspector]
    public int versionIncrease
    {
        get
        {
            if (buildConfig == null)
            {
                return 0;
            }
            return buildConfig.versionIncrease;
        }
        set
        {
            if (buildConfig != null)
            {
                buildConfig.versionIncrease = value;
            }
        }
    }

    [TitleGroup("PlayerSetting 打包设置"), ShowInInspector]
    public ScriptingImplementation scriptingBacked
    {
        get
        {
            ScriptingImplementation ScriptingBackend = PlayerSettings.GetScriptingBackend(buildTarget);
            return ScriptingBackend;
        }
        set
        {
            PlayerSettings.SetScriptingBackend(buildTarget, value);
        }
    }

    /// <summary>
    /// 打包
    /// </summary>
    [TitleGroup("PlayerSetting 打包设置")]
    [Button(90), GUIColor(0.4f, 0.8f, 1f)]
    public void BuildPackage()
    {
        //this.isBuild = !this.isBuild;
        PlayerSettings.companyName = companyName;
        PlayerSettings.productName = productName;
        PlayerSettings.bundleVersion = version;

        PlayerSettings.Android.targetSdkVersion = androidSDK_Version;

        #region 开始打包

        isBuilding = BuildPipeline.isBuildingPlayer;

        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
        buildPlayerOptions.scenes = activeScenes.ToArray();
        //打包目标路径
        androidPath = buildPath + @"\" + buildTarget.ToString() + @"\" + productName + "_" + version + ".apk";
        winPath = buildPath + @"\" + buildTarget.ToString() + @"\" + productName + "_" + version + ".exe";
        if (buildTarget == BuildTargetGroup.Android)
        {
            buildPlayerOptions.locationPathName = androidPath;
        }
        else
        {
            buildPlayerOptions.locationPathName = winPath;
        }
        //打包目标平台
        if (buildTarget.ToString() == "Standalone")
        {
            buildPlayerOptions.target = BuildTarget.StandaloneWindows;
        }
        else
        {
            buildPlayerOptions.target = (BuildTarget)Enum.Parse(typeof(BuildTarget), buildTarget.ToString());
        }

        buildPlayerOptions.options = BuildOptions.None;
        BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
        BuildSummary summary = report.summary;
        if (summary.result == BuildResult.Succeeded)
        {
            Debug.Log("Build succeeded: " + summary.platform);
            Debug.Log("Build succeeded: " + summary.totalSize + "bytes");
            Debug.Log("Build succeeded: " + summary.totalTime + "s");

            SucessBuild(report);
        }

        if (summary.result == BuildResult.Failed)
        {
            Debug.Log("Build failed");
            FailBuild(report);
        }
        isBuilding = BuildPipeline.isBuildingPlayer;

        #endregion 开始打包
    }
    /// <summary>
    /// 打包失败
    /// </summary>
    /// <param name="report"></param>
    private void FailBuild(BuildReport report)
    {
        buildResult = "打包失败,请看Console信息";
    }

    /// <summary>
    /// 打包成功
    /// </summary>
    /// <param name="report"></param>
    private void SucessBuild(BuildReport report)
    {
        BuildSummary summary = report.summary;
        string size = "检测打包大小失败";
        if (File.Exists(androidPath))
        {
            FileInfo apk = new FileInfo(androidPath);
            size = " " + (apk.Length / (1024.00 * 1024.00)).ToString("f2") + "MB";
        }
        else
        {
            long m_size = 0;
            GetDirSizeByPath((buildPath + @"\" + buildTarget.ToString()).Replace(@"/", @"\"), ref m_size);
            size = " " + (m_size / (1024.00 * 1024.00)).ToString("f2") + "MB";
        }

        string time = " " + summary.totalTime + "s";
        buildResult = "打包成功: " + summary.outputPath + "\n" +
            "安装后大小: " + size + "\n" +
            "打包时长: " + time + "\n";

        string[] versionsNum = PlayerSettings.bundleVersion.Split('.');
        int tempInt = int.Parse(versionsNum[2]) + versionIncrease;
        versionsNum[2] = tempInt.ToString();
        var tempVersionsNum = String.Join(".", versionsNum);

        PlayerSettings.bundleVersion = tempVersionsNum;
        EditorUtility.OpenWithDefaultApp(buildPath.Replace(@"/", @"\"));
    }

    [TitleGroup("PlayerSetting 打包结果", null, TitleAlignments.Left, true, true, false, PlayerSetting打包结果Order)]
    [ReadOnly]
    [MultiLineProperty(3), ShowInInspector]
    public string buildResult
    {
        get
        {
            return EditorPrefs.GetString("OdinBuild.buildResult");
        }
        set
        {
            EditorPrefs.SetString("OdinBuild.buildResult", value);
        }
    }

    /// <summary>
    /// 获取文件夹的大小
    /// </summary>
    /// <param name="dir">文件夹目录</param>
    /// <param name="dirSize">返回文件夹大小,传递引用</param>
    private static void GetDirSizeByPath(string dir, ref long dirSize)
    {
        try
        {
            DirectoryInfo dirInfo = new DirectoryInfo(dir);

            DirectoryInfo[] dirs = dirInfo.GetDirectories();
            FileInfo[] files = dirInfo.GetFiles();

            foreach (var item in dirs)
            {
                GetDirSizeByPath(item.FullName, ref dirSize);
            }

            foreach (var item in files)
            {
                dirSize += item.Length;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("获取文件大小失败" + ex.Message);
        }
    }
}
public class BuildConfig : SerializedScriptableObject
{
    public AndroidSdkVersions androidSDK_Version;
    public BuildTarget buildTarget;

    public string buildPath;

   
    public int versionIncrease;

    //public string buildResult;
}
public class MyOdin : OdinMenuEditorWindow
    {
        [MenuItem("Tools/我的工具箱")]
        private static void OpenWindow()
        {
            var window = GetWindow<MyOdin>();
            window.position = GUIHelper.GetEditorWindowRect().AlignCenter(1000, 500);
        }

        protected override OdinMenuTree BuildMenuTree()
        {
            OdinMenuTree tree = new OdinMenuTree();
            tree.Add("一键打包工具", OneKeyBuildlEditor.Instance, EditorIcons.SmartPhone);
            return tree;
        }
    }
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值