【Unity】工具類系列教程—— 代碼自動(dòng)化生成!

【為什么要做自動(dòng)化工具】
工具類的創(chuàng)建是為了解決實(shí)際問題或者優(yōu)化既有流程,我們來先看看一些項(xiàng)目里面經(jīng)常遇到的問題。
程序代碼有很多時(shí)候類是相似的,因此新建一個(gè)功能的時(shí)候你會(huì)直接復(fù)制了之前"相似"的代碼塊,然后刪除掉無用的邏輯功能,但是有時(shí)候會(huì)出現(xiàn)漏網(wǎng)之魚。
開完會(huì),策劃發(fā)過來一個(gè)功能案子,UI相關(guān)的界面非常的多,你費(fèi)勁拼完了UI,寫腳本類的時(shí)候?qū)⒛阌玫降囊恍┙M件一個(gè)一個(gè)寫了函數(shù)變量加進(jìn)腳本中去,有時(shí)候一運(yùn)行發(fā)現(xiàn)報(bào)錯(cuò),結(jié)果是有一個(gè)函數(shù)沒有賦值,要么是有一個(gè)按鈕沒有生成變量,創(chuàng)建到調(diào)試中間花費(fèi)大量時(shí)間精力。
功能做完了,按理來說你應(yīng)該放松下來喝一杯咖啡,但是你一更新發(fā)現(xiàn)UI預(yù)制體的紅色沖突警告心直接涼了半截,解決預(yù)制體沖突后,又一遍一遍的將丟失的引用賦值到腳本上。
如果計(jì)算機(jī)能幫我們直接創(chuàng)建一個(gè)功能的基礎(chǔ)腳本類,就不用每次去復(fù)制上次的代碼了。然后再幫我們把那些亂七八糟又?jǐn)?shù)不勝數(shù)的按鈕、文字、圖片組件都自動(dòng)生成在腳本里面,然后自己去關(guān)聯(lián)好引用,一下就能節(jié)省好多重復(fù)的活。

【相關(guān)功能實(shí)現(xiàn)與解析】
完整的腳本:
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;
public class AutoBuildTemplate
{
?? public static string UIClass =
@"using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
public class #類名# : MonoBehaviour
{
//auto
? public void Start()
{
#查找#
}
#成員#
}
";
}
public class AutoBuild
{
? [MenuItem("生成/創(chuàng)建或刷新界面")]
?? public static void BuildUIScript()
?? {
?????? var dicUIType = new Dictionary<string, string>();
?????? dicUIType.Add("Img", "Image");
?????? dicUIType.Add("Btn", "Button");
?????? dicUIType.Add("Txt", "Text");
?????? dicUIType.Add("Tran", "Transform");
?????? GameObject[] selectobjs = Selection.gameObjects;
?????? foreach (GameObject go in selectobjs)
?????? {
?????????? //選擇的物體
?????????? GameObject selectobj = go.transform.root.gameObject;
?????????? //物體的子物體
?????????? Transform[] _transforms = selectobj.GetComponentsInChildren<Transform>(true);
?????????? List<Transform> childList = new List<Transform>(_transforms);
?????????? //UI需要查詢的物體
?????????? var mainNode = from trans in childList where trans.name.Contains('_') && dicUIType.Keys.Contains(trans.name.Split('_')[0]) select trans;
?????????? var nodePathList = new Dictionary<string, string>();
?????????? //循環(huán)得到物體路徑
?????????? foreach (Transform node in mainNode)
?????????? {
?????????????? Transform tempNode = node;
?????????????? string nodePath = "/" + tempNode.name;
?????????????? while (tempNode != tempNode.root)
?????????????? {
?????????????????? tempNode = tempNode.parent;
?????????????????? int index = nodePath.IndexOf('/');
?????????????????? nodePath = nodePath.Insert(index, "/" + tempNode.name);
?????????????? }
?????????????? nodePathList.Add(node.name, nodePath);
?????????? }
?????????? //成員變量字符串
?????????? string memberstring = "";
?????????? //查詢代碼字符串
?????????? string loadedcontant = "";
?????????? foreach (Transform itemtran in mainNode)
?????????? {
?????????????? string typeStr = dicUIType[itemtran.name.Split('_')[0]];
?????????????? memberstring += "public " + typeStr + " " + itemtran.name + " = null;\r\n\t";
?????????????? loadedcontant += itemtran.name + " = " + "gameObject.transform.Find(\"" + nodePathList[itemtran.name] + "\").GetComponent<" + typeStr + ">();\r\n\t\t";
?????????? }
?????????? string scriptPath = Application.dataPath + "/Scripts/" + selectobj.name + ".cs";
?????????? string classStr = "";
?????????? //如果已經(jīng)存在了腳本,則只替換//auto下方的字符串
?????????? if (File.Exists(scriptPath))
?????????? {
?????????????? FileStream classfile = new FileStream(scriptPath, FileMode.Open);
?????????????? StreamReader read = new StreamReader(classfile);
?????????????? classStr = read.ReadToEnd();
?????????????? read.Close();
?????????????? classfile.Close();
?????????????? File.Delete(scriptPath);
?????????????? string splitStr = "//auto";
?????????????? string unchangeStr = Regex.Split(classStr, splitStr, RegexOptions.IgnoreCase)[0];
?????????????? string changeStr = Regex.Split(AutoBuildTemplate.UIClass, splitStr, RegexOptions.IgnoreCase)[1];
?????????????? StringBuilder build = new StringBuilder();
?????????????? build.Append(unchangeStr);
?????????????? build.Append(splitStr);
?????????????? build.Append(changeStr);
?????????????? classStr = build.ToString();
?????????? }
?????????? else
?????????? {
?????????????? classStr = AutoBuildTemplate.UIClass;
?????????? }
?????????? classStr = classStr.Replace("#類名#", selectobj.name);
?????????? classStr = classStr.Replace("#查找#", loadedcontant);
?????????? classStr = classStr.Replace("#成員#", memberstring);
?????????? FileStream file = new FileStream(scriptPath, FileMode.CreateNew);
?????????? StreamWriter fileW = new StreamWriter(file, System.Text.Encoding.UTF8);
?????????? fileW.Write(classStr);
?????????? fileW.Flush();
?????????? fileW.Close();
?????????? file.Close();
?????????? Debug.Log("創(chuàng)建腳本 " + Application.dataPath + "/Scripts/" + selectobj.name + ".cs 成功!");
?????????? AssetDatabase.SaveAssets();
?????????? AssetDatabase.Refresh();
?????? }
?? }
}
腳本解析:
AutoBuildTemplate類
Unity中的C#腳本代碼本質(zhì)上為包含字符串內(nèi)容的文本文件,以.cs后綴保存。因此我們?nèi)绻詣?dòng)生成腳本只用編輯好代碼的文本內(nèi)容,然后添加文件后綴保存文件就完成了。
我們替換其中的 #類名#、#查找#、#成員#,保存成xxx.cs的文件就可以生成一個(gè)腳本類出來。
AutoBuild類
GameObject[] selectobjs = Selection.gameObjects;
Unity中可以在Editor腳本調(diào)用Selection類得到當(dāng)前選中的物體。因?yàn)榇嬖诙噙x情況,返回的物體為一個(gè)數(shù)組。
??? var dicUIType = new Dictionary<string, string>();
?????? dicUIType.Add("Img", "Image");
?????? dicUIType.Add("Btn", "Button");
?????? dicUIType.Add("Txt", "Text");
?????? dicUIType.Add("Tran", "Transform");
外部按鈕、圖片、文本等組件物體的關(guān)鍵字與類型的映射,當(dāng)子物體中名字存在“Img”、“Btn”就識(shí)別為Image和Button。
var mainNode = from trans in childList where trans.name.Contains('_') && dicUIType.Keys.Contains(trans.name.Split('_')[0]) select trans;
Linq的from 、where ?、select 語句遍歷尋找出前綴存在字典映射中的物體。
/*查詢是一種從數(shù)據(jù)源檢索數(shù)據(jù)的表達(dá)式。 查詢通常用專門的查詢語言來表示。 隨著時(shí)間的推移,人們已經(jīng)為各種數(shù)據(jù)源開發(fā)了不同的語言;例如,用于關(guān)系數(shù)據(jù)庫的 SQL 和用于 XML 的 XQuery。 因此,開發(fā)人員對(duì)于他們必須支持的每種數(shù)據(jù)源或數(shù)據(jù)格式,都不得不學(xué)習(xí)一種新的查詢語言。 LINQ 通過提供一種跨各種數(shù)據(jù)源和數(shù)據(jù)格式使用數(shù)據(jù)的一致模型,簡(jiǎn)化了這一情況。 在 LINQ 查詢中,始終會(huì)用到對(duì)象。 可以使用相同的基本編碼模式來查詢和轉(zhuǎn)換 XML 文檔、SQL 數(shù)據(jù)庫、http://ADO.NET 數(shù)據(jù)集、.NET 集合中的數(shù)據(jù)以及對(duì)其有 LINQ 提供程序可用的任何其他格式的數(shù)據(jù)。
資料地址https://docs.microsoft.com/zh-cn/dotnet/csharp/linq/linq-in-csharp*/
string unchangeStr = Regex.Split(classStr, splitStr, RegexOptions.IgnoreCase)[0];
正則表達(dá)式去分割字符串,因?yàn)楫?dāng)腳本已經(jīng)存在,我們不能覆蓋掉已經(jīng)書寫的代碼,所以基礎(chǔ)文本中有一個(gè)//auto來分割自動(dòng)生成代碼區(qū)域和手寫區(qū)域。
classStr = classStr.Replace("#類名#", selectobj.name);
字符串替換功能,將基礎(chǔ)文本中的關(guān)鍵字替換。
? FileStream file = new FileStream(scriptPath, FileMode.CreateNew);
?????????? StreamWriter fileW = new StreamWriter(file, System.Text.Encoding.UTF8);
?????????? fileW.Write(classStr);
?????????? fileW.Flush();
?????????? fileW.Close();
?????????? file.Close();
AssetDatabase.SaveAssets();
?????????? AssetDatabase.Refresh();
創(chuàng)建流文件,當(dāng)寫入完成后關(guān)閉流。在Unity生成了物體必須調(diào)用 AssetDatabase.SaveAssets()和AssetDatabase.Refresh()才能即時(shí)的看到資源刷新。
【總結(jié)】
以上利用UI預(yù)制體代碼自動(dòng)生成為例講解了自動(dòng)化生成的方法,我這里是通過Find來查找物體引用的,當(dāng)然可以利用Unity序列化參數(shù)的方法來賦值(就是拖拽操作的賦值方法),用后者可以節(jié)約UI第一次打開的性能(畢竟Unity的Find還是很消耗性能的),可以在我們的腳本創(chuàng)建好后加入給預(yù)制體掛載腳本賦值的功能流程。
自動(dòng)化思想是偉大的,可以用到自動(dòng)化的地方還有很多,比如統(tǒng)計(jì)當(dāng)前的資源加載列表,將策劃的表文件生成類文件,生成版本控制腳本。
如果你對(duì)你的項(xiàng)目有改良性建議,你認(rèn)為可以而且應(yīng)該實(shí)現(xiàn)自動(dòng)化任務(wù),就可以嘗試去實(shí)現(xiàn),一旦你已經(jīng)成功了,節(jié)省的可不僅僅是開發(fā)的時(shí)間。

對(duì)游戲開發(fā)感興趣的同學(xué),歡迎圍觀我們:【皮皮關(guān)游戲開發(fā)教育】 ,會(huì)定期更新各種教程干貨,更有別具一格的線下小班教育。
我們的官網(wǎng)地址:http://www.levelpp.com/
我們的游戲開發(fā)技術(shù)交流群:610475807
我們的微信公眾號(hào):皮皮關(guān)