如何讓編輯器運行你的代碼

如何讓編輯器運行你的代碼

Unity3D能夠經過事件觸發來執行你的編輯器代碼,可是咱們須要一些編譯器參數來告知編譯器什麼時候須要觸發該段代碼。html

[MenuItem(XXX)]聲明在一個函數上方,告知編譯器給Unity3D編輯器添加一個菜單項,而且當點擊該菜單項的時候調用該函數。觸發函數裏能夠編寫任何合法的代碼,能夠是一個資源批處理程序,也能夠彈出一個編輯器窗口。代碼裏能夠訪問到當前選中的內容(經過Selection類),並據此來肯定顯示視圖。與此相似,[ContextMenu("XXX")]能夠向你的上下文菜單中添加一個菜單項。windows

當你編寫了一些Component腳本,當它被附屬到某個GameObject時,想在編輯視圖便可在Scene視圖觀察到效果,那麼你能夠把[ExecuteInEditMode]寫在類上方來通知編譯器,該類的OnGUI和Update等函數在編輯模式也也會被調用。咱們還可使用[AddComponentMenu("XXX/XXX")]來把該腳本關聯到Component菜單中,點擊相應菜單項便可爲GameObject添加該Component腳本。架構

開始編寫編輯器

爲了不沒必要要的包含,Unity3D的運行時和編輯器類分辨存儲在不一樣的Assemblies裏(UnityEngine和UnityEditor)。當你準備開始編寫編輯器以前,你須要using UnityEditor來導入編輯器的名稱空間。app

有些代碼多是運行時和編輯器都須要執行的,若是你想在其中加以區分,那麼可使用#if UNITY_EDITOR ... #endif宏來對編輯器代碼作特殊處理。編輯器

在你開始真正編寫代碼以前,我認爲你還須要知道全部放在命名爲Editor目錄下的腳本會在其它腳本以後進行編譯,這方便了你去使用那些運行時的內容。而那些目錄下的腳本是不能訪問到Editor目錄下的內容的。因此,你最好把你的編輯器腳本寫在Editor目錄下。ide

如何建立自定義編輯器窗口

建立你的窗口

若是你想自定義一個可編輯的面板,那麼你須要編寫一個繼承自EditorWIndow的類。一般狀況下,你還須要寫一個[MenuItem]來告知編譯器什麼時候打開這個面板。這個事件的回調應該是一個靜態方法,而且返回一個窗口的實例。函數

如今,當你點擊對應的菜單項時,會彈出一個空白的窗口。而且你能夠像Unity3D編輯器預製的窗口同樣隨意拖動和停靠。下面來看看咱們如何來在窗口內實現咱們想要的功能吧。工具

擴展你的窗口

和運行時的GUI同樣,若是你須要在窗口中添加交互控件,那麼必須重寫OnGUI方法。具體的重寫方式和運行時的GUI同樣,你甚至可使用任何擴展自原生GUI系統的插件(例如iGUI和GUIX)來簡化你的插件開發流程(僅通過初步測試,更深層次的可用性尚待驗證)。同時UnityEditor名稱空間下的EditorGUILayout在原生GUI之上提供了一些更方便的接口和控件,讓你能夠輕鬆的使用一些編輯器特有的UI控件。佈局

除了OnGUI外,你可能還會須要以下一些回調來觸發某些具體的邏輯(完整的列表請參考官方文檔):post

l OnSelectionChange,但你點選物品時觸發

l OnFocus /OnLostFocus,得到和失去焦點時觸發

進一步擴展你的窗口

自定義控件

和運行時GUI的使用方式同樣,若是你打算自定義本身的控件,那麼最簡單的方式就是實現一個靜態方法(也能夠不是靜態的),並提供一些可選參數,在方法內部根據這些參數來完成對控件的佈局(就像你在OnGUI中作的同樣)。

若是你打算把自定義控件實如今窗口類內部,你可使用Partial類來更好的管理你的代碼。

繪製2D內容

繪製圖片

可使用GUI.DrawTexture來完成對圖片資源的繪製。

繪製基礎圖元

GUI自己並無提供繪製基礎圖元的方法,可是能夠經過一些方式來封裝出這些方法。

l 繪製線段:經過一個像素的貼圖資源配合GUI.DrawTexture和矩陣旋轉來完成線段的繪製。

l 繪製矩形框:經過GUI.Box和樣式設置來封裝出對矩形框和矩形填充框。

資源選擇器

EditorLayout.ObjectField控件提供一個資源選擇邏輯,生成時須要指定某種資源類型。而後你能夠拖動該種資源到該控件或點擊控件旁邊的小圓圈進行列表進行選擇。

如何存儲編輯內容

你可能須要建立一個繼承自SerializedObject的類來保存編輯的數據。繼承自SerializedObject的對象能用於存儲數據而不參與渲染,並能夠最終打包到AssetBundle。

針對當前的編輯選項等內容的存儲,可能須要另一個SerializedObject類(和具體的系統設計相關)。

嚮導式的編輯窗口

在不少狀況下可能你都會須要一個有不少參數的編輯面板,而後在編輯結束後有一個按鈕加以確認。這你不用本身來實現,UnityEditor提供了ScriptableWizard來幫助你快捷的進行開發。

他是繼承自EditorWindow的,因此他們的使用是很相似的。不過注意,當你點擊確認按鈕時,OnWizardCreate()會被調用。另外,ScriptableWizard.DisplayWizard能夠幫助你生成並顯示出該窗口。

如何擴展Inspector面板

當你在Unity3D中點選一個對象時,Inspector面板會隨即顯示出此對象的屬性。咱們能夠針對某個類型的對象擴展該面板,這在爲Unity3D開發插件時是很是有用的。

定義INSPECTOR什麼時候被觸發

自定義的Inspector面板須要繼承Editor類。因爲功能相對具體,因此你無需定義代碼什麼時候被觸發,對應代碼會在你點擊它所對應的物體時自動執行。

那麼如何定義它所對應的類型呢?只須要在你的類定義以前經過編譯器的命令[CustomEditor(typeof(XXX))]就能夠完成這項工做了。

訪問被編輯的對象

在Inspector視圖中,咱們常常須要訪問正在被編輯的對象。Editor類的成員變量target正是提供了這一關聯。

儘管如此,須要注意target是一個Object類型的對象,具體使用時可能須要類型轉換(可使用C#的泛型來避免重複的類型轉換)。

實現你本身的Inspector界面

擴展Editor與擴展EditorWindow惟一的不一樣在於你須要重寫的是OnInspectorGUI而不是OnGUI。另外,若是你想繪製默認的可編輯項,只需調用DrawDefaultInspector便可。

在Scene界面定義編輯句柄

當選中一個物體的時候,可能咱們但願在Scene視圖裏也能定義一些編輯或展示。這一工做能夠經過OnSceneGUI和Handle類來完成。OnSceneGUI用來處理來自Scene視圖的事件,而Handle類用來在Scene視圖實現一些3D的GUI控件(例如控制對象位置的Position控制器)。

具體的使用方式能夠參考官方的參考文檔。

一些經常使用的功能說明

l AssetDatabase.CreateAsset能夠幫住你從資源目錄中建立一個資源實例。

l Selection.activeObject返回當前選中的對象。

l EditorGUIUtility.PingObject用來實如今Project窗口中點擊某一項的操做。

l Editor.Repaint用來重繪界面全部的控件。

l XXXImporter用來設置某種資源的具體導入設置(例如在某些狀況下你須要設置導入的貼圖爲可讀的)。

l EditorUtility.UnloadUnusedAssets用於釋放沒有使用的資源,避免你的插件產生內存泄漏。

l Event.Use用來標記事件已經被處理結束了。

l EditorUtility.SetDirty用來通知編輯器數據已被修改,這樣在下次保存時新的數據將被存儲。

 

 

 

Unity3D的方便之處在於,它很容易地擴展編輯器套件。每款遊戲都對加工有着不一樣的需求,能夠快速地以徹底集成的方法來構建這些內容並極大地提高開發速度。

目前有大量複雜的軟件包提供以基本Unity功能套件爲基礎的複雜工具,從視覺腳本編輯器到編輯器內導航網格生成。可是,有關如何自行構建此類事物的程序說明卻不多。我將在下文列舉某些在本身的工做中總結的編輯器定製相關信息。

點擊瀏覽下一頁

Unity-Window(from gamasutra)

如何構建編輯器腳本

由於你不想在遊戲中包含全部的編輯器定製,並且你也不想遊戲對某些Unity編輯器內的東西有所依賴,因此Unity將運行時間和編輯器代碼放置在單獨的編譯中。

在編輯命令中,運行時間代碼在編輯器代碼以前執行,這樣編輯器類型就能夠可靠地聯繫至運行時間組件(遊戲邦注:不然就會變得難以編輯),可是你的運行時間組件並不涉及任何編輯器代碼。

你必須維持嚴格的層次。Unity 3.4版本中這個方面作得更加具體,如今其產生的項目文件與其提供的4個編輯階段相對應,這樣就不會混淆文件的構建時間。

在某個點上的程序說明有些不太清楚。當我首次開始使用時,我認爲須要在個人項目上建立單個「Editor」文件夾,而後把全部的編輯器類型放入其中。事實上,系統的靈活性要更高些,你能夠在項目中建立任意數量的「Editor」文件夾,將其埋藏在「資產」文件夾的任何地方,全部這些均可以存放編輯器代碼。

因此,如今一般狀況下我會以功能(遊戲邦注:好比命名爲「AI」)爲單位來建立文件夾,而後歸入全部功能相關組件,而後在旁邊放上Editor文件夾(遊戲邦注:好比命名爲「AI/Editor」),裝上全部運行這些組件的編輯器擴展。

只要Unity的內在類型可以發揮做用,運行時間類型都會存在於UnityEngine命名空間中,而全部的編輯器類型都會存在於UnityEditor命名空間裏。

點擊瀏覽下一頁

unity-projectlist(from gamasutra)

UnityEditor.Editor類

到目前爲止,我設立的最廣泛的定製是一個自定義檢查器。Unity的Inspector面板提供看到組件狀態的窗口,可是這種基本設置只能理解有限的類型,並且只能展現公共區域。

自定義檢查器讓你能夠徹底控制用戶查看和編輯你的組件的方式。好比,它們可讓你呈現只讀資產、強迫性價值限制或只改變選項呈現的方式。

Unity中的Inspector都是Editor類的子類別,因此你應該從這裏開始。可是,我對編輯器類處理樣式的方法不是很喜歡。裏面有個「Target」用來說起檢查器正在編輯的物體,可是隻是基本的「Object」樣式,因此你要不斷將其轉變成更有用的樣式。爲避開這個問題,我使用了一個很是簡單的類別,具體以下:

public class InspectorBase : Editor where T : UnityEngine.Object
{
protected T Target { get { return (T) target; } }
}

如今,若是我想要爲MyCustomComponent創造檢查器,我就能夠從InspectorBase獲得檢查器,而後使用「Target」,這樣我就不用時常更改了。

應當注意的是,你還須要將CustomEditor屬性附到檢查器類中,Unity纔可以真正使用它們。

編輯器GUI

一旦你創造自定義檢查器後,你一般想要執行的方法就是OnInspectorGUI()。OnInspectorGUI()可用來指定在檢查器中展現的全部東西,使用的是Unity的GUI系統。

由於這是編輯器代碼,咱們可使用UnityEditor命名空間中的類型,這包括EditorGUILayout。EditorGUILayout使得了大量的簡單控制,能夠在編輯器中使用,比Unity普通運行時間GUI系統提供的更好。好比,假如我想向用戶展現進入3D位置的領域,我可使用EditorGUILayout.Vector3Field():Target.somePosition = EditorGUILayout.Vector3Field(「Some position」, Target.somePosition)。

在檢查器中產生的效果以下圖所示:

點擊瀏覽下一頁

unity-vec3field(from gamasutra)

正由於GUI系統可以發揮做用,因此若是我改變UI中的值,Vector3Field就會傳回新的值,Target.somePosition就會獲得更新。在將其指派給目標以前,你能夠自由改變值(遊戲邦注:好比將值定義在某個範圍內),你也能夠徹底忽略傳回的值。

值並不必定來自於域,你能夠曝光檢查器中的資產,能夠採用調用一個功能來得到當前值並使用另外一個功能來保存。

固然,Unity會默認處理這個事情。若是你只是想要在Unity已經展現的爲基礎來構建,你就沒必要要從新執行全部那些域。Editor有個DrawDefaultInspector()方法,告訴Unity調用全部一般調用的控制,可是在這個過程完成以後,你仍然有機會添加額外域和按鍵。

說到按鍵,EditorGUILayout的用途確實很普遍,可是你或許已經注意到存在漏洞。好比,若是我想要在導航網格組件上添加「從新計算」按鍵,這又會怎麼樣呢?技巧在於EditorGUILayout仍然構建於常規運行時間GUILayout之上,因此你仍是可使用GUILayout中的全部東西。

你對檢查器中的域作出改變而且爲目標物體的域指派新值時,Unity會察覺到你正在改變物體,因此下次保存屏幕或項目時就會將其寫入磁盤。這種察覺是有限的,它只能識別公共資產的直接指派。若是你經過資產或調用方法來修改目標物體,你可能就須要自行調用EditorUtility.SetDirty了。

擴展組件背景菜單

在測試時,有時手動引起某些行爲仍是頗有用的。你能夠經過在自定義檢查器上安放按鍵來觸發行爲:if(GUILayout.Button(「Explode now!」)) Target.ExplodeNow()。

可是還有個更加簡單的方法,這個方法徹底不須要自定義檢查器。你可使用的是UnityEngine.ContextMenu屬性:

/* In the target class… */
[ContextMenu("Explode now!")]
public void ExplodeNow() { … }

右鍵點擊組件的檢查器(遊戲邦注:不管是否自定義化),你會看到背景菜單,其中有額外的功能。能夠快速地進行測試。

擴展主菜單

到這裏爲止,我所說的全部東西都是圍繞某個特別組件爲中心的定製。其餘種類的擴展又會如何呢?

在個人遊戲中,動畫系統將其資產存放在文件夾架構中,這樣每一個文件夾都對應enum的一個入口。當我改變enum時,若是能夠同步文件夾結構會起到很大做用,添加任何丟失的文件夾並刪除

任何多餘的文件夾。因此我採用瞭如下較爲簡單的方法:

public class AnimationSystem{
public static void SyncFolderStructure() { … }
}

可是我要什麼時候以及如何調用呢?我採用的作法是將其連同到Assets菜單中的菜單項目中,使用MenuItem屬性:

[MenuItem("Assets/Sync folder structure")]
public static void SyncFolderStructure() { … }

點擊菜單項目就能夠調用功能。應當注意的是,功能須要是靜態的,可是其中的類能夠是多種類型的。

Wizards

Editor GUI元素並不必定要在Inspector中。它還能夠創造主觀編輯器窗口,能夠像任何Unity內置窗口那樣一動,並且能夠像在檢查器中那樣使用GUI命令。最簡單的方法就是使用ScriptableWizard,這很像一個對話盒。你在呈現後設定某些值,而後點擊按鍵讓其施展「魔法」。

點擊瀏覽下一頁

unity-ragdollwizard(from gamasutra)

在默認狀況下,ScriptableWizard的做用很像檢查器:類中的任何公共域都會自動呈如今wizard窗口中。你的wizard會像一大串公共域那樣簡單,並且還有個OnWizardCreate()方法,當用戶點擊「Create」按鍵時Unity就會調用這個方法。並且,你能夠改變按鍵上的文字,「Apply」或「OK」之類的會顯得更加直觀。

wizard的另外一個層面是決定用戶如何開啓,經常使用方法是使用有靜態功能的菜單選項,如上圖所示:

[MenuItem("GameObject/Create Other/Explosion")]
public static void CreateExplosion()
{
ScriptableWizard.DisplayWizard(「Create explosion」);
}
(本文爲遊戲邦/gamerboom.com編譯,如需轉載請聯繫:遊戲邦)

Opinion: Extending The Unity3D Editor

Richard Fine

One of the handy things about Unity3D is that it’s very easy to extend the editor suite. Every game has unique requirements for tooling, and being able to build those up quickly and in a fully integrated manner can make a world of difference to your development speed.

A number of pretty sophisticated packages exist that offer complex tools on top of the base Unity featureset, from visual script editors to in-editor navigation mesh generation. The documentation for how you might build such a thing yourself, however, is a bit thin. Here’s a whirlwind tour of some of the most useful bits of info about editor customization that I’ve found in the course of my work.

How Editor Scripts Are Built

Because you don’t want all your editor customization to be included in the game that you ship, and because you don’t want your shipping game to have any dependencies on things in the Unity editor, Unity keeps runtime and editor code in separate assemblies.

The compilation order is such that runtime code is compiled before editor code, so that your editor classes can safely refer to runtime components (otherwise it’d be difficult to edit them) – but it does mean that your runtime components can’t reference any of your editor code.

You have to maintain a strict layering. Unity’s gotten a bit more explicit about this in 3.4 – now the project files it generates (for VS/MonoDevelop) clearly correspond to the four compilation stages it provides, so there’s no confusion about which files will get build at which times.

The documentation is a little unclear on one particular point. When I first started, I thought that I had to have a single ‘Editor’ folder at the top of my project, and that all editor classes had to go inside it. The system’s actually a bit more flexible than that; you can have as many ‘Editor’ folders as you want in your project, buried wherever you like inside your Assets folder, and all of them can contain editor code.

So now it’s very common that I’ll have a folder for one particular feature (e.g. ‘AI’) that contains all the components for that feature, with an Editor folder alongside (e.g. ‘AI/Editor’) that contains the editor extensions for working with those components.

As far as Unity’s built-in types go, runtime types all live in the UnityEngine namespace (in the UnityEngine assembly), while editor types all live in the UnityEditor namespace (in the UnityEditor assembly).

The UnityEditor.Editor Class

By far the most common kind of customization I set up is a custom inspector. Unity’s Inspector panel provides your window into a component’s state, but in its base form it only understands a limited set of types, and will only expose public fields (no properties).

Custom inspectors give you the opportunity to completely control how users view and edit your components; for example, they let you display read-only properties, enforce value constraints, or just change the way an option is presented – for example, replacing a [0..1] float field with a percentage slider.

Inspectors in Unity are all subclasses of the Editor class, so that’s where you start. One thing I don’t like about the editor class, though, is the way it handles types: it has a ‘target’ member that refers to the object the inspector is editing, but it’s of the base ‘Object’ type, so you keep on having to cast it to a more useful type. To get around this I use a very simple generic class:

public class InspectorBase : Editor where T : UnityEngine.Object
{
protected T Target { get { return (T) target; } }
}

Now, if I want to create an inspector for MyCustomComponent, I can derive the inspector from InspectorBase, and use the ‘Target’ member instead of the untyped ‘target’ member, and I don’t have to keep casting everywhere.

Note that you also need to attach the [CustomEditor] attribute to your inspector classes for Unity to actually pick them up and use them.

Editor GUI

Once you’ve created your custom inspector, the method you usually want to implement is OnInspectorGUI(). OnInspectorGUI() is responsible for specifying everything shown in the inspector, using Unity’s immediate-mode GUI system.

Because this is editor code, we can use the types in the UnityEditor namespace, which includes EditorGUILayout. EditorGUILayout provides a bunch of really simple controls for use in the editor, over and above what Unity’s regular runtime GUI system offers. For example, say I want to show the user a field for entering a 3D position. I could use EditorGUILayout.Vector3Field():

Target.somePosition = EditorGUILayout.Vector3Field(「Some position」, Target.somePosition);

This results in a line in your inspector that looks like this:

The immediate-mode GUI system works such that if I change the values in the UI, Vector3Field will return the new values, and Target.somePosition gets updated. You’re free to manipulate the value – for example, clamping it to a range – before assigning it to the target; and you’re free to ignore the return value completely (which would effectively make the field read-only).

The values don’t have to be coming to/from fields, either – you can expose properties in the inspector by adding lines for them in this way, or call a function to get the current value and another function to save it, whatever you like.

Of course, Unity does this stuff by default for any public members. If you just want to build on top of what Unity’s already showing, you don’t have to reimplement all those fields – Editor has a DrawDefaultInspector() method that tells Unity to draw all the controls it would usually draw, but when it’s finished, you’ve still got the opportunity to add a few extra fields and buttons yourself.

Speaking of buttons… EditorGUILayout is pretty comprehensive, but you might notice that there are some things missing – what if I want to put a 「recalculate」 button on my navigation mesh component, for example? The trick is that EditorGUILayout is still built on top of the regular runtime GUILayout, so everything in GUILayout is available to you as well.

As you make changes to the fields in the inspector, and assign new values to the fields of your target object, Unity detects that you’re changing the object and flags it as ‘dirty’ so that it will be written out to disk the next time you save the scene or project. This detection is limited: it only picks up direct assignment to public properties. If you’re changing the target object through properties or through calling methods on it, you may need to call EditorUtility.SetDirty yourself.

Extending The Component Context Menu

It’s often useful to be able to manually trigger certain behavior when testing things out. You could do this by putting a button on the custom inspector that triggers the behavior:

if(GUILayout.Button(「Explode now!」)) Target.ExplodeNow();

but there’s an even easier way – one that doesn’t require a custom inspector at all. What you can do instead is use the UnityEngine.ContextMenu attribute:

/* In the target class… */
[ContextMenu("Explode now!")]
public void ExplodeNow() { … }

Right-clicking in the component’s inspector – whether you’ve customized it or not – will then show you a context menu, with the extra item in there. Very handy for quickly rigging things up for testing.

Extending the main menus

Everything I’ve talked about so far has been about customization centered around a particular component. What about other kinds of extension, like general utilities?

The animation system in my game stores its assets in a folder structure, such that each folder corresponds to one entry in an enum. When I change the enum, it’s useful to be able to synchronize that folder structure, adding any missing folders and deleting any obsolete ones. So I’ve got this class, with a simple method:

public class AnimationSystem{
public static void SyncFolderStructure() { … }
}

But how and when do I call it? What I’ve done is to wire it up to a menu item in the Assets menu, using the MenuItem attribute:

[MenuItem("Assets/Sync folder structure")]
public static void SyncFolderStructure() { … }

Clicking the menu item calls the function. Note that the function needs to be static – but the class it’s in can be anything, and can derive from anything (including nothing).

Wizards

Editor GUI elements don’t only have to live inside the Inspector. It’s also possible to create arbitrary editor windows, that can be moved and docked like any of

Unity’s built-in windows, and that are populated using GUI commands just like in an inspector. One of the simplest ways of doing this is to use a ScriptableWizard, which is like a dialogue box – you display it, set some values in it, then hit a button to make it work its magic.

ScriptableWizard, by default, works almost like an inspector: any public fields in your derived class will be automatically shown in the wizard window. Your wizard might be as simple as a bunch of public fields, plus an OnWizardCreate() method, which Unity will call when the user hits the ‘Create’ button. Worth noting that you can change the text on that button, too, if ‘Apply’ or ‘OK’ or similar would be more intuitive.

The only other aspect of the wizard is deciding how the user will launch it; the usual approach is to use a menu item bound to a static function, as shown above:

[MenuItem("GameObject/Create Other/Explosion")]
public static void CreateExplosion()
{
ScriptableWizard.DisplayWizard(「Create explosion」);
}

Conclusion

There’s loads more – I’ve not even touched on custom asset types yet, or asset import postprocessors, or scene view customization. They’ll wait for another day…

原文連接 http://www.j2megame.com/html/xwzx/ty/3842.html

相關文章
相關標籤/搜索