【Unity】工具類系列教程—— 代碼自動化生成!

【為什麼要做自動化工具】

工具類的創建是為了解決實際問題或者優化既有流程,我們來先看看一些項目裡面經常遇到的問題。

程序代碼有很多時候類是相似的,因此新建一個功能的時候你會直接複製了之前"相似"的代碼塊,然後刪除掉無用的邏輯功能,但是有時候會出現漏網之魚。

開完會,策劃發過來一個功能案子,UI相關的界面非常的多,你費勁拼完了UI,寫腳本類的時候將你用到的一些組件一個一個寫了函數變數加進腳本中去,有時候一運行發現報錯,結果是有一個函數沒有賦值,要麼是有一個按鈕沒有生成變數,創建到調試中間花費大量時間精力。

功能做完了,按理來說你應該放鬆下來喝一杯咖啡,但是你一更新發現UI預製體的紅色衝突警告心直接涼了半截,解決預製體衝突後,又一遍一遍的將丟失的引用賦值到腳本上。

如果計算機能幫我們直接創建一個功能的基礎腳本類,就不用每次去複製上次的代碼了。然後再幫我們把那些亂七八糟又數不勝數的按鈕、文字、圖片組件都自動生成在腳本裡面,然後自己去關聯好引用,一下就能節省好多重複的活。

效果展示

【相關功能實現與解析】

完整的腳本:

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("生成/創建或刷新界面")] 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>(); //循環得到物體路徑 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;
"
; loadedcontant += itemtran.name + " = " + "gameObject.transform.Find("" + nodePathList[itemtran.name] + "").GetComponent<" + typeStr + ">();
"
; } string scriptPath = Application.dataPath + "/Scripts/" + selectobj.name + ".cs"; string classStr = ""; //如果已經存在了腳本,則只替換//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("創建腳本 " + Application.dataPath + "/Scripts/" + selectobj.name + ".cs 成功!"); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } }}

腳本解析:

AutoBuildTemplate類

Unity中的C#腳本代碼本質上為包含字元串內容的文本文件,以.cs後綴保存。因此我們如果要自動生成腳本只用編輯好代碼的文本內容,然後添加文件後綴保存文件就完成了。

我們替換其中的 #類名#、#查找#、#成員#,保存成xxx.cs的文件就可以生成一個腳本類出來。

AutoBuild類

GameObject[] selectobjs = Selection.gameObjects;

Unity中可以在Editor腳本調用Selection類得到當前選中的物體。因為存在多選情況,返回的物體為一個數組。

var dicUIType = new Dictionary<string, string>(); dicUIType.Add("Img", "Image"); dicUIType.Add("Btn", "Button"); dicUIType.Add("Txt", "Text"); dicUIType.Add("Tran", "Transform");

外部按鈕、圖片、文本等組件物體的關鍵字與類型的映射,當子物體中名字存在「Img」、「Btn」就識別為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 語句遍歷尋找出前綴存在字典映射中的物體。

/*查詢是一種從數據源檢索數據的表達式。 查詢通常用專門的查詢語言來表示。 隨著時間的推移,人們已經為各種數據源開發了不同的語言;例如,用於關係資料庫的 SQL 和用於 XML 的 XQuery。 因此,開發人員對於他們必須支持的每種數據源或數據格式,都不得不學習一種新的查詢語言。 LINQ 通過提供一種跨各種數據源和數據格式使用數據的一致模型,簡化了這一情況。 在 LINQ 查詢中,始終會用到對象。 可以使用相同的基本編碼模式來查詢和轉換 XML 文檔、SQL 資料庫、ADO.NET 數據集、.NET 集合中的數據以及對其有 LINQ 提供程序可用的任何其他格式的數據。

資料地址docs.microsoft.com/zh-c*/

string unchangeStr = Regex.Split(classStr, splitStr, RegexOptions.IgnoreCase)[0];

正則表達式去分割字元串,因為當腳本已經存在,我們不能覆蓋掉已經書寫的代碼,所以基礎文本中有一個//auto來分割自動生成代碼區域和手寫區域。

classStr = classStr.Replace("#類名#", selectobj.name);

字元串替換功能,將基礎文本中的關鍵字替換。

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();

創建流文件,當寫入完成後關閉流。在Unity生成了物體必須調用 AssetDatabase.SaveAssets()和AssetDatabase.Refresh()才能即時的看到資源刷新。

【總結】

以上利用UI預製體代碼自動生成為例講解了自動化生成的方法,我這裡是通過Find來查找物體引用的,當然可以利用Unity序列化參數的方法來賦值(就是拖拽操作的賦值方法),用後者可以節約UI第一次打開的性能(畢竟Unity的Find還是很消耗性能的),可以在我們的腳本創建好後加入給預製體掛載腳本賦值的功能流程。

自動化思想是偉大的,可以用到自動化的地方還有很多,比如統計當前的資源載入列表,將策劃的表文件生成類文件,生成版本控制腳本。

如果你對你的項目有改良性建議,你認為可以而且應該實現自動化任務,就可以嘗試去實現,一旦你已經成功了,節省的可不僅僅是開發的時間。

如果沒活不要作死


對遊戲開發感興趣的同學,歡迎圍觀我們:【皮皮關遊戲開發教育】 ,會定期更新各種教程乾貨,更有別具一格的線下小班教育。在你學習進步的路上,有皮皮關陪你!~

我們的官網地址:levelpp.com/

我們的遊戲開發技術交流群:610475807

我們的微信公眾號:皮皮關


推薦閱讀:

幻影坦克架構指南(二)
GPU Gems 基於物理模型的水面模擬 學習筆記 (一)
Unity特效(1) 夢幻旋屏
手游逆向分析<二>: Unity內還原《鎮魔曲》角色渲染效果
Unity命令行模式,也能「日誌實時輸出」

TAG:游戏开发 | 编程 | Unity游戏引擎 |