近期在學習Shader時感受Shader語言參數衆多、語法詭異,假設每次都從頭開始寫Shader必定是一件痛苦的事情。假設可以在本地定義好一組標準的Shader模板,這樣當咱們需要實現某些效果類似的Shader時,就可以在這個Shader模板的基礎上進行改動。編程
因爲Shader文件是一個文本文件,因此咱們可以很easy地建立這樣一個模板,在這個模板中咱們可以進一步無缺相關的參數凝視,這樣就不用每次寫Shader的時候都需要查文檔了,從這個角度出發,就進入了這篇文章的正題:擴展Unity3D編輯器的腳本模板。
markdown
Unity3D默認的腳本模版位於/Editor/Data/Resources/ScriptTemplates/文件夾下,注意該文件夾相對Unity3D的安裝文件夾而言,在這個文件夾中咱們可以找到Unity3D中腳本模板的某些蛛絲馬跡,首先,腳本模板是一個簡單的文本文件,這個文本文件裏預先填充了內容,咱們在編輯器中建立模腳本或者Shader的時候其實是讀取這些文件而後在寫入項目中的指定路徑的。編輯器
其次。這些模板文件裏#SCRIPTNAME#或者#NAME#這種標記。當咱們在編輯器中建立文件的時候,這個標記會被替換成指定的文件名稱。比方Unity3D中繼承自MonoBehaviour的腳本。有一個很重要的特性是文件名稱必須和類名保持一致。這當然是Unity3D引擎的一個設定,可是在這裏亦可以找到一個可以稱得上理由的理由。ide
咱們注意到這些模板的文件名稱中都有一個獨一無二的數字,比方C#腳本的模板中的數字是8一、Shader模板中的數字是83,這些數字是什麼呢,博主這裏將其稱爲來自星星的黑科技。post
做爲一個常常搗鼓Unity3D編輯器的人。假設說你不知道MenuItem、EditorWindow、ScriptableWizard這些黑科技。那麼說明你不是一個喜歡折騰和探索的人。學習
從Unity3D的API文檔中,咱們知道MenuItem的原型爲:lua
MenuItem(string itemName,bool isValidateFunction,int priority)
我知道咱們一般使用MenuItem經常使用的是它的第一個參數,即定義一個菜單項的名稱,咱們可以使用」/」這種分隔符來表示菜單的層級。MenuItem需要配合一個靜態方法來使用,可以理解爲當咱們點擊當前定義的菜單後就會去運行靜態方法中的代碼。所以MenuItem常常可以幫助咱們作些編輯器擴展開發的工做。好了,第二個參數做爲一個驗證的標誌。假設該標誌爲true,意味着咱們定義的靜態方法是一個驗證方法在運行靜態方法前會首先對方法進行驗證,這個咱們暫且不管,因爲今天咱們這個來自星星的黑科技主要和第三個參數有關,第三個參數表示一個優先級,它表示菜單項在菜單條中的展現順序,優先級大的菜單項會展現在優先級小的菜單項如下,由此咱們就明確了了模板文件名稱中的類似8一、83這種數字的真實含義,注意到模板文件的排列順序和編輯器中的菜單項順序是同樣的,咱們作一個嘗試。編寫如下的代碼:spa
[MenuItem("Assets/Create/Lua Scripts", false, 85)]
static void CreateLuaScripts()
{
}
[MenuItem("Assets/Create/固定功能着色器", false, 86)]
static void CreateFixedFunctionShader()
{
}
[MenuItem("Assets/Create/表面着色器", false, 87)]
static void CreateSurfaceShader()
{
}
[MenuItem("Assets/Create/可編程着色器", false, 88)]
static void CreateVertexAndFragmentShader()
{
}
注意到咱們依照已知的優先級繼續寫了四個方法,現在咱們在編輯器中可以發現默認的菜單條發生了變化:code
咱們可以看到咱們編寫的這四個菜單都生效了,儘管它們臨時什麼都作不了。但順着這個方向去探索,咱們是可以實現最初的夢想的。orm
現在咱們來思考怎樣依據模板來建立文件,這個對咱們來講簡直太簡單了,經過StreamReader來讀取模板。而後再用StreamWriter來生成文件就可以了。
可是這樣建立的文件的文件名稱是固定的。在建立文件的時候咱們無法改動。而且即便改動了文件內定義的名字並不會改變啊。因此咱們需要一個更好的解決方式。Unity3D提供了一個UnityEditor.ProjectWindowCallback的命名空間,在這個空間中提供了一個稱爲EndNameEditAction的類,咱們僅僅需要繼承這個類就可以完畢這個任務。這個類需要重寫Action的方法,咱們知道建立一個文件的完整步驟是建立文件而後使其高亮顯示,所以這部分代碼實現例如如下:
/// <summary>
/// 定義一個建立資源的Action類並實現其Action方法
/// </summary>
class CreateAssetAction : EndNameEditAction
{
public override void Action(int instanceId, string pathName, string resourceFile)
{
//建立資源
Object obj = CreateAssetFormTemplate(pathName, resourceFile);
//高亮顯示該資源
ProjectWindowUtil.ShowCreatedAsset(obj);
}
internal static Object CreateAssetFormTemplate(string pathName, string resourceFile)
{
//獲取要建立資源的絕對路徑
string fullName = Path.GetFullPath(pathName);
//讀取本地模版文件
StreamReader reader = new StreamReader(resourceFile);
string content = reader.ReadToEnd();
reader.Close();
//獲取資源的文件名稱
string fileName = Path.GetFileNameWithoutExtension(pathName);
//替換默認的文件名稱
content = content.Replace("#NAME", fileName);
//寫入新文件
StreamWriter writer = new StreamWriter(fullName, false, System.Text.Encoding.UTF8);
writer.Write(content);
writer.Close();
//刷新本地資源
AssetDatabase.ImportAsset(pathName);
AssetDatabase.Refresh();
return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object));
}
}
這部分代碼相對來講比較簡單,就是讀取本地模板文件而後生成新文件,在生成新文件的時候會將#NAME替換成實際的文件名稱,這樣咱們就完畢了文件資源的建立。
現在的問題是怎樣在建立文件的時候獲取實際的路徑,這部分代碼實現例如如下:
private static string GetSelectedPath()
{
//默認路徑爲Assets
string selectedPath = "Assets";
//獲取選中的資源
Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.Assets);
//遍歷選中的資源以返回路徑
foreach (Object obj in selection)
{
selectedPath = AssetDatabase.GetAssetPath(obj);
if (!string.IsNullOrEmpty(selectedPath) && File.Exists(selectedPath))
{
selectedPath = Path.GetDirectoryName(selectedPath);
break;
}
}
return selectedPath;
}
現在攻克了建立資源的問題。咱們接下來僅僅要調用ProjectWindowUtil的StartNameEditingIfProjectWindowExists方法就能夠。該方法需要傳入一個繼承自EndNameEditAction的類的實例、目標文件路徑和模板文件的路徑。
好比要建立一個Lua腳本可以這樣實現:
[MenuItem("Assets/Create/Lua Scripts", false, 85)]
static void CreateLuaScripts()
{
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,
ScriptableObject.CreateInstance<CreateAssetAction>(),
GetSelectedPath() + "/NewLuaScript.lua", null,
"Assets/Editor/Template/85-Lua-NewLuaScript.lua.txt");
}
現在有了這個黑科技之後,咱們可以建立不少其它的模板來擴展編輯器的功能,比方對Shader而言,咱們可以建立些基礎性的Shader模板,而後每次需要寫Shader的時候直接從模板庫中選擇一個功能類似的Shader而後在此基礎上進行改動,這樣比從頭開始寫一個新的Shader應該會輕鬆很多,這段時間學習Shader,感受進程緩慢離圖形學高手遙遙無期,行了,這篇博客就是這樣了。