(转)Unity3d UnityEditor编辑器定制和开发插件
在閱讀本教程之前,你需要對(duì)Unity的操作流程有一些基礎(chǔ)的認(rèn)識(shí),并且最好了解內(nèi)置的GUI系統(tǒng)如何使用。
如何讓編輯器運(yùn)行你的代碼
Unity3D可以通過(guò)事件觸發(fā)來(lái)執(zhí)行你的編輯器代碼,但是我們需要一些編譯器參數(shù)來(lái)告知編譯器何時(shí)需要觸發(fā)該段代碼。
[MenuItem(XXX)]聲明在一個(gè)函數(shù)上方,告知編譯器給Unity3D編輯器添加一個(gè)菜單項(xiàng),并且當(dāng)點(diǎn)擊該菜單項(xiàng)的時(shí)候調(diào)用該函數(shù)。觸發(fā)函數(shù)里可以編寫任何合法的代碼,可以是一個(gè)資源批處理程序,也可以彈出一個(gè)編輯器窗口。代碼里可以訪問到當(dāng)前選中的內(nèi)容(通過(guò)Selection類),并據(jù)此來(lái)確定顯示視圖。與此類似,[ContextMenu("XXX")]可以向你的上下文菜單中添加一個(gè)菜單項(xiàng)。
當(dāng)你編寫了一些Component腳本,當(dāng)它被附屬到某個(gè)GameObject時(shí),想在編輯視圖即可在Scene視圖觀察到效果,那么你可以把[ExecuteInEditMode]寫在類上方來(lái)通知編譯器,該類的OnGUI和Update等函數(shù)在編輯模式也也會(huì)被調(diào)用。我們還可以使用[AddComponentMenu("XXX/XXX")]來(lái)把該腳本關(guān)聯(lián)到Component菜單中,點(diǎn)擊相應(yīng)菜單項(xiàng)即可為GameObject添加該Component腳本。
開始編寫編輯器
為了避免不必要的包含,Unity3D的運(yùn)行時(shí)和編輯器類分辨存儲(chǔ)在不同的Assemblies里(UnityEngine和UnityEditor)。當(dāng)你準(zhǔn)備開始編寫編輯器之前,你需要using UnityEditor來(lái)導(dǎo)入編輯器的名稱空間。
有些代碼可能是運(yùn)行時(shí)和編輯器都需要執(zhí)行的,如果你想在其中加以區(qū)分,那么可以使用#if UNITY_EDITOR ... #endif宏來(lái)對(duì)編輯器代碼做特殊處理。
在你開始真正編寫代碼之前,我認(rèn)為你還需要知道所有放在命名為Editor目錄下的腳本會(huì)在其它腳本之后進(jìn)行編譯,這方便了你去使用那些運(yùn)行時(shí)的內(nèi)容。而那些目錄下的腳本是不能訪問到Editor目錄下的內(nèi)容的。所以,你最好把你的編輯器腳本寫在Editor目錄下。
如何創(chuàng)建自定義編輯器窗口
創(chuàng)建你的窗口
如果你想自定義一個(gè)可編輯的面板,那么你需要編寫一個(gè)繼承自EditorWIndow的類。通常情況下,你還需要寫一個(gè)[MenuItem]來(lái)告知編譯器何時(shí)打開這個(gè)面板。這個(gè)事件的回調(diào)應(yīng)該是一個(gè)靜態(tài)方法,并且返回一個(gè)窗口的實(shí)例。
現(xiàn)在,當(dāng)你點(diǎn)擊對(duì)應(yīng)的菜單項(xiàng)時(shí),會(huì)彈出一個(gè)空白的窗口。并且你可以像Unity3D編輯器預(yù)制的窗口一樣隨意拖動(dòng)和??俊O旅鎭?lái)看看我們?nèi)绾蝸?lái)在窗口內(nèi)實(shí)現(xiàn)我們想要的功能吧。
擴(kuò)展你的窗口
和運(yùn)行時(shí)的GUI一樣,如果你需要在窗口中添加交互控件,那么必須重寫OnGUI方法。具體的重寫方式和運(yùn)行時(shí)的GUI一樣,你甚至可以使用任何擴(kuò)展自原生GUI系統(tǒng)的插件(例如iGUI和GUIX)來(lái)簡(jiǎn)化你的插件開發(fā)流程(僅經(jīng)過(guò)初步測(cè)試,更深層次的可用性尚待驗(yàn)證)。同時(shí)UnityEditor名稱空間下的EditorGUILayout在原生GUI之上提供了一些更方便的接口和控件,讓你可以輕松的使用一些編輯器特有的UI控件。
除了OnGUI外,你可能還會(huì)需要如下一些回調(diào)來(lái)觸發(fā)某些具體的邏輯(完整的列表請(qǐng)參考官方文檔):
l OnSelectionChange,但你點(diǎn)選物品時(shí)觸發(fā)
l OnFocus /OnLostFocus,獲得和失去焦點(diǎn)時(shí)觸發(fā)
進(jìn)一步擴(kuò)展你的窗口
自定義控件
和運(yùn)行時(shí)GUI的使用方式一樣,如果你打算自定義自己的控件,那么最簡(jiǎn)單的方式就是實(shí)現(xiàn)一個(gè)靜態(tài)方法(也可以不是靜態(tài)的),并提供一些可選參數(shù),在方法內(nèi)部根據(jù)這些參數(shù)來(lái)完成對(duì)控件的布局(就像你在OnGUI中做的一樣)。
如果你打算把自定義控件實(shí)現(xiàn)在窗口類內(nèi)部,你可以使用Partial類來(lái)更好的管理你的代碼。
繪制2D內(nèi)容
繪制圖片
可以使用GUI.DrawTexture來(lái)完成對(duì)圖片資源的繪制。
繪制基礎(chǔ)圖元
GUI本身并沒有提供繪制基礎(chǔ)圖元的方法,但是可以通過(guò)一些方式來(lái)封裝出這些方法。
l 繪制線段:通過(guò)一個(gè)像素的貼圖資源配合GUI.DrawTexture和矩陣旋轉(zhuǎn)來(lái)完成線段的繪制。
l 繪制矩形框:通過(guò)GUI.Box和樣式設(shè)置來(lái)封裝出對(duì)矩形框和矩形填
充框。
資源選擇器
EditorLayout.ObjectField控件提供一個(gè)資源選擇邏輯,生成時(shí)需要指定某種資源類型。然后你可以拖動(dòng)該種資源到該控件或點(diǎn)擊控件旁邊的小圓圈進(jìn)行列表進(jìn)行選擇。
如何存儲(chǔ)編輯內(nèi)容
你可能需要?jiǎng)?chuàng)建一個(gè)繼承自SerializedObject的類來(lái)保存編輯的數(shù)據(jù)。繼承自SerializedObject的對(duì)象能用于存儲(chǔ)數(shù)據(jù)而不參與渲染,并可以最終打包到AssetBundle。
針對(duì)當(dāng)前的編輯選項(xiàng)等內(nèi)容的存儲(chǔ),可能需要另外一個(gè)SerializedObject類(和具體的系統(tǒng)設(shè)計(jì)相關(guān))。
向?qū)降木庉嫶翱?br />在很多情況下可能你都會(huì)需要一個(gè)有很多參數(shù)的編輯面板,然后在編輯結(jié)束后有一個(gè)按鈕加以確認(rèn)。這你不用自己來(lái)實(shí)現(xiàn),UnityEditor提供了ScriptableWizard來(lái)幫助你快捷的進(jìn)行開發(fā)。
他是繼承自EditorWindow的,所以他們的使用是很類似的。不過(guò)注意,當(dāng)你點(diǎn)擊確認(rèn)按鈕時(shí),OnWizardCreate()會(huì)被調(diào)用。另外,ScriptableWizard.DisplayWizard可以幫助你生成并顯示出該窗口。
如何擴(kuò)展Inspector面板
當(dāng)你在Unity3D中點(diǎn)選一個(gè)對(duì)象時(shí),Inspector面板會(huì)隨即顯示出此對(duì)象的屬性。我們可以針對(duì)某個(gè)類型的對(duì)象擴(kuò)展該面板,這在為Unity3D開發(fā)插件時(shí)是非常有用的。
定義INSPECTOR何時(shí)被觸發(fā)
自定義的Inspector面板需要繼承Editor類。由于功能相對(duì)具體,所以你無(wú)需定義代碼何時(shí)被觸發(fā),對(duì)應(yīng)代碼會(huì)在你點(diǎn)擊它所對(duì)應(yīng)的物體時(shí)自動(dòng)執(zhí)行。
那么如何定義它所對(duì)應(yīng)的類型呢?只需要在你的類定義之前通過(guò)編譯器的命令[CustomEditor(typeof(XXX))]就可以完成這項(xiàng)工作了。
訪問被編輯的對(duì)象
在Inspector視圖中,我們經(jīng)常需要訪問正在被編輯的對(duì)象。Editor類的成員變量target正是提供了這一關(guān)聯(lián)。
盡管如此,需要注意target是一個(gè)Object類型的對(duì)象,具體使用時(shí)可能需要類型轉(zhuǎn)換(可以使用C#的泛型來(lái)避免重復(fù)的類型轉(zhuǎn)換)。
實(shí)現(xiàn)你自己的Inspector界面
擴(kuò)展Editor與擴(kuò)展EditorWindow唯一的不同在于你需要重寫的是OnInspectorGUI而不是OnGUI。另外,如果你想繪制默認(rèn)的可編輯項(xiàng),只需調(diào)用DrawDefaultInspector即可。
在Scene界面定義編輯句柄
當(dāng)選中一個(gè)物體的時(shí)候,可能我們希望在Scene視圖里也能定義一些編輯或展現(xiàn)。這一工作可以通過(guò)OnSceneGUI和Handle類來(lái)完成。OnSceneGUI用來(lái)處理來(lái)自Scene視圖的事件,而Handle類用來(lái)在Scene視圖實(shí)現(xiàn)一些3D的GUI控件(例如控制對(duì)象位置的Position控制器)。
具體的使用方式可以參考官方的參考文檔。
一些常用的功能說(shuō)明
l AssetDatabase.CreateAsset可以幫住你從資源目錄中創(chuàng)建一個(gè)資源實(shí)例。
l Selection.activeObject返回當(dāng)前選中的對(duì)象。
l EditorGUIUtility.PingObject用來(lái)實(shí)現(xiàn)在Project窗口中點(diǎn)擊某一項(xiàng)的操作。
l Editor.Repaint用來(lái)重繪界面所有的控件。
l XXXImporter用來(lái)設(shè)置某種資源的具體導(dǎo)入設(shè)置(例如在某些情況下你需要設(shè)置導(dǎo)入的貼圖為可讀的)。
l EditorUtility.UnloadUnusedAssets用于釋放沒有使用的資源,避免你的插件產(chǎn)生內(nèi)存泄漏。
l Event.Use用來(lái)標(biāo)記事件已經(jīng)被處理結(jié)束了。
l EditorUtility.SetDirty用來(lái)通知編輯器數(shù)據(jù)已被修改,這樣在下次保存時(shí)新的數(shù)據(jù)將被存儲(chǔ)。
Unity3D的方便之處在于,它很容易地?cái)U(kuò)展編輯器套件。每款游戲都對(duì)加工有著不同的需求,可以快速地以完全集成的方法來(lái)構(gòu)建這些內(nèi)容并極大地提升開發(fā)速度。
目前有大量復(fù)雜的軟件包提供以基本Unity功能套件為基礎(chǔ)的復(fù)雜工具,從視覺腳本編輯器到編輯器內(nèi)導(dǎo)航網(wǎng)格生成。但是,有關(guān)如何自行構(gòu)建此類事物的程序說(shuō)明卻很少。我將在下文列舉某些在自己的工作中總結(jié)的編輯器定制相關(guān)信息。
<ignore_js_op>?
Unity-Window(from gamasutra)
如何構(gòu)建編輯器腳本
因?yàn)槟悴幌朐谟螒蛑邪械木庉嬈鞫ㄖ?#xff0c;而且你也不想游戲?qū)δ承︰nity編輯器內(nèi)的東西有所依賴,所以Unity將運(yùn)行時(shí)間和編輯器代碼放置在單獨(dú)的編譯中。
在編輯命令中,運(yùn)行時(shí)間代碼在編輯器代碼之前執(zhí)行,這樣編輯器類型就可以可靠地聯(lián)系至運(yùn)行時(shí)間組件(游戲邦注:否則就會(huì)變得難以編輯),但是你的運(yùn)行時(shí)間組件并不涉及任何編輯器代碼。
你必須維持嚴(yán)格的層次。Unity 3.4版本中這個(gè)方面做得更加具體,現(xiàn)在其產(chǎn)生的項(xiàng)目文件與其提供的4個(gè)編輯階段相對(duì)應(yīng),這樣就不會(huì)混淆文件的構(gòu)建時(shí)間。
在某個(gè)點(diǎn)上的程序說(shuō)明有些不太清楚。當(dāng)我首次開始使用時(shí),我認(rèn)為需要在我的項(xiàng)目上創(chuàng)建單個(gè)“Editor”文件夾,然后把所有的編輯器類型放入其中。事實(shí)上,系統(tǒng)的靈活性要更高些,你可以在項(xiàng)目中創(chuàng)建任意數(shù)量的“Editor”文件夾,將其埋藏在“資產(chǎn)”文件夾的任何地方,所有這些都可以存放編輯器代碼。
所以,現(xiàn)在通常情況下我會(huì)以功能(游戲邦注:比如命名為“AI”)為單位來(lái)創(chuàng)建文件夾,然后納入所有功能相關(guān)組件,然后在旁邊放上Editor文件夾(游戲邦注:比如命名為“AI/Editor”),裝上所有運(yùn)行這些組件的編輯器擴(kuò)展。
只要Unity的內(nèi)在類型能夠發(fā)揮作用,運(yùn)行時(shí)間類型都會(huì)存在于UnityEngine命名空間中,而所有的編輯器類型都會(huì)存在于UnityEditor命名空間里。
<ignore_js_op>?
unity-projectlist(from gamasutra)
UnityEditor.Editor類
到目前為止,我設(shè)立的最普遍的定制是一個(gè)自定義檢查器。Unity的Inspector面板提供看到組件狀態(tài)的窗口,但是這種基本設(shè)置只能理解有限的類型,而且只能展示公共區(qū)域。
自定義檢查器讓你可以完全控制用戶查看和編輯你的組件的方式。比如,它們可以讓你呈現(xiàn)只讀資產(chǎn)、強(qiáng)迫性價(jià)值限制或只改變選項(xiàng)呈現(xiàn)的方式。
Unity中的Inspector都是Editor類的子類別,所以你應(yīng)該從這里開始。但是,我對(duì)編輯器類處理樣式的方法不是很喜歡。里面有個(gè)“Target”用來(lái)提及檢查器正在編輯的物體,但是只是基本的“Object”樣式,所以你要不斷將其轉(zhuǎn)變成更有用的樣式。為避開這個(gè)問題,我使用了一個(gè)非常簡(jiǎn)單的類別,具體如下:
現(xiàn)在,如果我想要為MyCustomComponent創(chuàng)造檢查器,我就可以從InspectorBase得到檢查器,然后使用“Target”,這樣我就不用時(shí)常更改了。
應(yīng)當(dāng)注意的是,你還需要將CustomEditor屬性附到檢查器類中,Unity才能夠真正使用它們。
編輯器GUI
一旦你創(chuàng)造自定義檢查器后,你通常想要執(zhí)行的方法就是OnInspectorGUI()。OnInspectorGUI()可用來(lái)指定在檢查器中展示的所有東西,使用的是Unity的GUI系統(tǒng)。
因?yàn)檫@是編輯器代碼,我們可以使用UnityEditor命名空間中的類型,這包括EditorGUILayout。EditorGUILayout使得了大量的簡(jiǎn)單控制,可以在編輯器中使用,比Unity普通運(yùn)行時(shí)間GUI系統(tǒng)提供的更好。比如,假如我想向用戶展示進(jìn)入3D位置的領(lǐng)域,我可以使用
在檢查器中產(chǎn)生的效果如下圖所示:
<ignore_js_op>?
unity-vec3field(from gamasutra)
正因?yàn)镚UI系統(tǒng)能夠發(fā)揮作用,所以如果我改變UI中的值,Vector3Field就會(huì)傳回新的值,Target.somePosition就會(huì)得到更新。在將其指派給目標(biāo)之前,你可以自由改變值(游戲邦注:比如將值定義在某個(gè)范圍內(nèi)),你也可以完全忽略傳回的值。
值并不一定來(lái)自于域,你可以曝光檢查器中的資產(chǎn),可以采用調(diào)用一個(gè)功能來(lái)獲得當(dāng)前值并使用另一個(gè)功能來(lái)保存。
當(dāng)然,Unity會(huì)默認(rèn)處理這個(gè)事情。如果你只是想要在Unity已經(jīng)展示的為基礎(chǔ)來(lái)構(gòu)建,你就不必要重新執(zhí)行所有那些域。Editor有個(gè)DrawDefaultInspector()方法,告訴Unity調(diào)用所有通常調(diào)用的控制,但是在這個(gè)過(guò)程完成之后,你仍然有機(jī)會(huì)添加額外域和按鍵。
說(shuō)到按鍵,EditorGUILayout的用途確實(shí)很廣泛,但是你或許已經(jīng)注意到存在漏洞。比如,如果我想要在導(dǎo)航網(wǎng)格組件上添加“重新計(jì)算”按鍵,這又會(huì)怎么樣呢?技巧在于EditorGUILayout仍然構(gòu)建于常規(guī)運(yùn)行時(shí)間GUILayout之上,所以你還是可以使用GUILayout中的所有東西。
你對(duì)檢查器中的域做出改變并且為目標(biāo)物體的域指派新值時(shí),Unity會(huì)察覺到你正在改變物體,所以下次保存屏幕或項(xiàng)目時(shí)就會(huì)將其寫入磁盤。這種察覺是有限的,它只能識(shí)別公共資產(chǎn)的直接指派。如果你通過(guò)資產(chǎn)或調(diào)用方法來(lái)修改目標(biāo)物體,你可能就需要自行調(diào)用EditorUtility.SetDirty了。
擴(kuò)展組件背景菜單
在測(cè)試時(shí),有時(shí)手動(dòng)引發(fā)某些行為還是很有用的。你可以通過(guò)在自定義檢查器上安放按鍵來(lái)觸發(fā)行為:if(GUILayout.Button(“Explode now!”)) Target.ExplodeNow()。
但是還有個(gè)更加簡(jiǎn)單的方法,這個(gè)方法完全不需要自定義檢查器。你可以使用的是UnityEngine.ContextMenu屬性:
/* In the target class… */
[ContextMenu("Explode now!")]
public void ExplodeNow() { … }
右鍵點(diǎn)擊組件的檢查器(游戲邦注:無(wú)論是否自定義化),你會(huì)看到背景菜單,其中有額外的功能。可以快速地進(jìn)行測(cè)試。
擴(kuò)展主菜單
到這里為止,我所說(shuō)的所有東西都是圍繞某個(gè)特別組件為中心的定制。其他種類的擴(kuò)展又會(huì)如何呢?
在我的游戲中,動(dòng)畫系統(tǒng)將其資產(chǎn)存放在文件夾架構(gòu)中,這樣每個(gè)文件夾都對(duì)應(yīng)enum的一個(gè)入口。當(dāng)我改變enum時(shí),如果可以同步文件夾結(jié)構(gòu)會(huì)起到很大作用,添加任何丟失的文件夾并刪除
任何多余的文件夾。所以我采用了以下較為簡(jiǎn)單的方法:
但是我要何時(shí)以及如何調(diào)用呢?我采用的做法是將其連同到Assets菜單中的菜單項(xiàng)目中,使用MenuItem屬性:
點(diǎn)擊菜單項(xiàng)目就可以調(diào)用功能。應(yīng)當(dāng)注意的是,功能需要是靜態(tài)的,但是其中的類可以是多種類型的。
Wizards
Editor GUI元素并不一定要在Inspector中。它還可以創(chuàng)造主觀編輯器窗口,可以像任何Unity內(nèi)置窗口那樣一動(dòng),而且可以像在檢查器中那樣使用GUI命令。最簡(jiǎn)單的方法就是使用ScriptableWizard,這很像一個(gè)對(duì)話盒。你在呈現(xiàn)后設(shè)定某些值,然后點(diǎn)擊按鍵讓其施展“魔法”。
<ignore_js_op>?
unity-ragdollwizard(from gamasutra)
在默認(rèn)情況下,ScriptableWizard的作用很像檢查器:類中的任何公共域都會(huì)自動(dòng)呈現(xiàn)在wizard窗口中。你的wizard會(huì)像一大串公共域那樣簡(jiǎn)單,而且還有個(gè)OnWizardCreate()方法,當(dāng)用戶點(diǎn)擊“Create”按鍵時(shí)Unity就會(huì)調(diào)用這個(gè)方法。而且,你可以改變按鍵上的文字,“Apply”或“OK”之類的會(huì)顯得更加直觀。
wizard的另一個(gè)層面是決定用戶如何開啟,常用方法是使用有靜態(tài)功能的菜單選項(xiàng),如上圖所示:
(本文為游戲邦/gamerboom.com編譯,如需轉(zhuǎn)載請(qǐng)聯(lián)系:游戲邦)
原文鏈接?http://www.j2megame.com/html/xwzx/ty/3842.html
總結(jié)
以上是生活随笔為你收集整理的(转)Unity3d UnityEditor编辑器定制和开发插件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【HDOJ】2510 符号三角形
- 下一篇: Open Source: 开源软件许可的