前一篇主要将如何建立自己的工具箱,此篇主要讲如何往工具箱里添加工具,并以一键打包为例,探寻一种通用的工具制作流程
要做工具首先要明确我们工具要处理的对象是谁,即数据是谁,数据的形式是怎样的,数据的量,数据的来源,甚至于数据的结构,就像是厨子要做一道菜,第一件事是选择食材,处理食材。
对数据的处理不是一下子就完成的,可能在工具制作过程中,我们会对数据有新的要求,比如命名,数据结构,数据路径要求等等,这些要求是对数据的规范,只有统一的,有规律的,有秩序的数据才能被工具批处理。
以一键打包为例,我们首先确定我们都有哪些数据,一键打包不需要处理数据,但是需要获得数据,我们需要获取打包平台,打包路径,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;
}
}
}
数据有了,然后是明确数据处理的流程,这里可以画一个流程图,可以清楚地知道数据是如何一步一步变成我们想要的样子,中间过程可能需要组合新的数据,数据与数据之间进行关联等等
以一键打包为例
决定数据处理流程的很大程度是 相关API调用决定的,比如打包的核心API:BuildPipeline.BuildPlayer
我们去unity手册可以知道它需要哪些参数,这些参数决定了数据处理流程,当一个工具要调用不同的API时,对数据的要求也不同,条件允许情况下可以寻找更合适的API,从而精简数据处理流程(Nuget,Gethub,很多轮子,工具的意义是省时省力,所以为工具造轮子,本末倒置,反而加大开发难度,增加开发时长)
对数据结果进行检测,检查是否正确的处理数据,使数据达到我们的处理要求。
以一键打包为例,检测打包后的APP是否正常使用,多次打包是否正常,换项目打包是否正常。
在Odin中要执行功能,一般使用button,在我们的打包函数上,添加特性标签Button,即可显示在工具页面,点击即可执行打包函数
参考代码:
///
/// 打包
///
[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
}
鲁棒是Robust的音译,强壮,健壮,为了使我们的工具更加好用,要进行数据的合理性检测,确保在无效的,错误的数据情况下,工具也不会崩溃(unity自个都会莫名其妙崩溃)
检测最多的是针对String的检测,空字符,空引用等等
还有对数组的检测,空数组,空引用
一些清况下 还需要使用 try catch 包裹
string.IsNullOrEmpty()//同时检测空字符和空引用
错误用法:
if(testString==""||testString==null)
{
return;
}
空字符和空引用在同一个if的情况下,如果是空引用,会error
因为这两句都会执行,但是空引用不能检测空字符,会出现null
重构不必多说,细节之中自有天地
推荐插件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("一键打包 ")]
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);
}
}
///
/// 打包
///
[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 开始打包
}
///
/// 打包失败
///
///
private void FailBuild(BuildReport report)
{
buildResult = "打包失败,请看Console信息";
}
///
/// 打包成功
///
///
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);
}
}
///
/// 获取文件夹的大小
///
/// 文件夹目录
/// 返回文件夹大小,传递引用
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;
}
}