此篇教程參考了很多內容,本體是siki學院的“暗黑戰神”課程中某一節的內容,我將里面的細節進行了一番探索及拓展,現在記錄下其中的過程:
在Unity中創建新的C#腳本時,會自動去加載某個路徑下的模版,它的后綴為txt,創建的C#腳本初始自帶的內容就是這個模版內的內容。
在Windows下,這個路徑為:Unity安裝目錄\Editor\Data\Resources\ScriptTemplates\81-C# Script-NewBehaviourScript.cs.txt
在Mac下,這個路徑為Unity.app/Contents/Resources/ScriptTemplates/81-C# Script-NewBehaviourScript.cs.txt
下面看下里面有什么:
嘗試着修改一下,加一個myFunc函數:
此時在Unity中創建一個腳本看下:
可以看到,myFunc已經進去了。
那現在有一個問題:模版中有public class #SCRIPTNAME# : MonoBehaviour這一行,顯然是在創建時根據為腳本取的名字,動態地替換掉了模版中的#SCRIPTNAME#。
由於Unity引擎不開源,我們無法了解它底層是怎么實現的,但現在如果我們要進一步對其拓展,比如在模版中寫一段自己的信息,里面也有一段需要根據創建時信息臨時修改的內容,比如#Modify#,我們要在創建腳本時,Unity也根據對應的信息,比如當前時間,動態地替換掉#Modify#中的內容,怎么做呢?
這是通過實現UnityEditor.AssetModificationProcessor這個類下的OnWillCreateAsset方法實現的,看一下它們的文檔:
文檔對AssetModificationProcessor這個類的功能的描述不大容易理解,查閱了一些其它資料后總結一下:AssetModificationProcessor這個類可以用來監聽Project視圖中資源的創建、刪除、移動、保存。類里有幾個方法,既然要在創建腳本時替換掉模版中##內的關鍵詞,那我們只需要關注OnWillCreateAsset方法就行了,按照翻譯,這個方法會在資源被創建之前被調用,而且從例子中看到,這個方法是個static方法。
注意到這個方法有參數string assetName,Unity底層調用這個方法時會把要創建資源的名字作為參數傳入。
那么現在思路就比較明確了:Unity為我們在底層固化了這種創建新腳本時以文件名替換模版中#SCRIPTNAME#的功能,我們在對其拓展時要實現AssetModificationProcessor類下的OnWillCreateAsset靜態方法,在里面根據傳入的資源名,讀取對應的文本文件,將其中指定的一些##字符串,替換成我們動態獲取的信息,隨后把文本重新寫入到這個模版中。
那現在有幾個問題值得注意:
1.首先,OnWillCreateAsset這個方法在AssetModificationProcessor類中,我們難道要去修改AssetModificationProcessor類嗎?
答案是不用,從上面的圖中例子也可以看到,直接用一個類繼承它即可,在繼承的類中實現這個方法。
2.我們讀取對應的文本文件,需要文件名,可是按照之前的說法,OnWillCreateAsset這個方法是在資源被創建之前調用的,這時候腳本都沒創建完成,哪來的文件名?又怎么讀取呢?
這是因為這個方法的解釋不清楚,這個方法的解釋是在資源被創建之前調用的,但其實更詳細地說,它是在Unity內部將腳本模版的內容復制到新創建的腳本中,並將#SCRIPTNAME#替換為文件名之后被調用的。
現在我們要選一個函數打開對應傳入參數的文件,可以選用ReadAllText函數,它可以打開一個文本文件,將文件中所有文本讀取到一個字符串中,然后自動關閉此文件,位於命名空間System.IO中,參考文檔如下:
https://docs.microsoft.com/zh-cn/dotnet/api/system.io.file.readalltext?redirectedfrom=MSDN&view=netframework-4.8#System_IO_File_ReadAllText_System_String_
由於我們看不到.NET Framework的源代碼,只能猜測這個方法應該是封裝了類似open,read和close這樣的系統調用。
3.讀取后的文本中要替換的內容從哪里取得?
比如我們要取得用戶的用戶名,這時可以使用Environment.UserName這個屬性,它位於System命名空間中,它可以獲取到當前已登錄到操作系統的人員的用戶名,參考文檔如下:
https://docs.microsoft.com/zh-cn/dotnet/api/system.environment.username?redirectedfrom=MSDN&view=netframework-4.8
文檔中說了,在Unix平台上,這個屬性封裝了對getpwuid_r的調用,我沒有細查getpwuid_r的信息,但我猜測它的實現可能類似於who指令那樣,是通過讀取utmpx這個文件來獲取utmpx結構體數組,隨后訪問里面的ut_user條目來得到用戶名的,有關who指令的實現,可以參考我的文章:
https://www.cnblogs.com/czw52460183/p/10999434.html
那假如替換的內容中有當前系統的時間呢?這個怎么取得?
答案是可以用DateTime這個數據結構中的信息直接獲取,具體就不描述了,參考文檔如下:
https://docs.microsoft.com/zh-cn/dotnet/api/system.datetime?view=netframework-4.8
好,前戲結束,給出實際操作過程:
1.我們不想對模版做太多改動,只在頭部加入文件名,作者名(登錄用戶名),郵箱(因為實際項目中你寫的文件出了問題,要留聯系方式方便別人找到你),日期和功能。因此,先對81-C# Script-NewBehaviourScript.cs.txt這個文件內容修改如下:
/**************************************************** 文件:#SCRIPTNAME#.cs 作者:#CreateAuthor# 郵箱: czw52460183@163.com 日期:#CreateTime# 功能:Nothing *****************************************************/ using UnityEngine; public class #SCRIPTNAME# : MonoBehaviour { }
2.此時在Unity中創建一個腳本測試一下,打開后結果如圖:
3.現在我們開始寫替換腳本,在寫之前先要明確一點:在Unity中,創建任何資源時,目錄下不僅會生成該資源(包括腳本,文件夾等各種資源),還會生成對應的同名的.meta文件,meta文件本質是一個文本文檔,關於它是什么,可以參考:
https://blog.uwa4d.com/archives/USparkle_inf_UnityEngine.html
舉個例子:我們在Asset目錄下同時創建一個新的腳本文件和一個新的文件夾,結果如下:
現在問題是,傳入到OnWillCreateAsset的參數到底是文件名還是帶.meta的名字?
我們做個實驗,在Asset下創建一個腳本,名為testInfo:
/**************************************************** 文件:testInfo.cs 作者:#CreateAuthor# 郵箱: czw52460183@163.com 日期:#CreateTime# 功能:Nothing *****************************************************/ using System; using System.IO; using UnityEngine; public class testInfo : UnityEditor.AssetModificationProcessor { private static void OnWillCreateAsset(string path) { Debug.Log(path); } }
此時重新創建testFolder文件夾和testScript腳本,Unity日志輸出如下:
說明傳入到OnWillCreateAsset的參數是帶meta的,而且會帶有路徑。
因此我們處理傳入的參數時,要先去掉這個meta后綴,此外,由於只為新建的腳本添加頭部注釋,因此也要限定必須是.cs文件才會進行處理。
刪掉上面的文件,重新寫腳本,命名為AddScriptInfo.cs,代碼如下:
using System; using System.IO; public class AddScriptInfo : UnityEditor.AssetModificationProcessor { private static void OnWillCreateAsset(string path) { //將.meta后綴屏蔽 path = path.Replace(".meta", ""); //只對.cs腳本作操作 if (path.EndsWith(".cs")) { //讀取該路徑下的.cs文件中的所有文本. //注意,此時Unity已經對腳本完成了模版內容的替換,包括#SCRIPTNAME#也已經被替換為文件名了,讀取到的是替換后的文本內容. string str = File.ReadAllText(path); //獲取用戶名和當前系統時間並替換對應位置內容 str = str.Replace("#CreateAuthor#", Environment.UserName).Replace( "#CreateTime#", string.Concat(DateTime.Now.Year, "/", DateTime.Now.Month, "/", DateTime.Now.Day, " ", DateTime.Now.Hour, ":", DateTime.Now.Minute, ":", DateTime.Now.Second)); //重新將文本寫入.cs文件 File.WriteAllText(path, str); } } }
此時在Asset目錄下重新創建文件testAgain.cs來測試,結果如下:
可以看到,功能已經實現了。
有趣的是,在我做這個實驗時,網上查了許多資料,發現這個教程挺多的,但很多都強調,這個修改##信息的腳本(對應圖中的AddScriptInfo),需要放到Editor文件夾下,卻又都沒講為什么。而從實際測試來看,不放也能正常工作,也不知道什么情況。
不過考慮到這其實是個工具插件,和業務代碼無關,可以考慮放到某個文件夾下方便管理,我們就按視頻里的方法做吧:Assets下創建個Plugins文件夾,里面創建個Editor文件夾,放入此腳本,這里專門放置插件,方便管理。
完結。