赞
踩
前一篇主要将如何建立自己的工具箱,此篇主要讲如何往工具箱里添加工具,并以一键打包为例,探寻一种通用的工具制作流程
要做工具首先要明确我们工具要处理的对象是谁,即数据是谁,数据的形式是怎样的,数据的量,数据的来源,甚至于数据的结构,就像是厨子要做一道菜,第一件事是选择食材,处理食材。
对数据的处理不是一下子就完成的,可能在工具制作过程中,我们会对数据有新的要求,比如命名,数据结构,数据路径要求等等,这些要求是对数据的规范,只有统一的,有规律的,有秩序的数据才能被工具批处理。
以一键打包为例,我们首先确定我们都有哪些数据,一键打包不需要处理数据,但是需要获得数据,我们需要获取打包平台,打包路径,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,即可显示在工具页面,点击即可执行打包函数
参考代码:
/// <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 }
鲁棒是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("<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; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。