天天看點

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

http://gamerboom.com/archives/36432

作者:Richard Fine

Unity3D的友善之處在于,它很容易地擴充編輯器套件。每款遊戲都對加工有着不同的需求,可以快速地以完全內建的方法來建構這些内容并極大地提升開發速度。

目前有大量複雜的軟體包提供以基本Unity功能套件為基礎的複雜工具,從視覺腳本編輯器到編輯器内導航網格生成。但是,有關如何自行建構此類事物的程式說明卻很少。我将在下文列舉某些在自己的工作中總結的編輯器定制相關資訊。

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

Unity-Window(from gamasutra)

如何建構編輯器腳本

因為你不想在遊戲中包含所有的編輯器定制,而且你也不想遊戲對某些Unity編輯器内的東西有所依賴,是以Unity将運作時間和編輯器代碼放置在單獨的編譯中。

在編輯指令中,運作時間代碼在編輯器代碼之前執行,這樣編輯器類型就可以可靠地聯系至運作時間元件(遊戲邦注:否則就會變得難以編輯),但是你的運作時間元件并不涉及任何編輯器代碼。

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

在某個點上的程式說明有些不太清楚。當我首次開始使用時,我認為需要在我的項目上建立單個“Editor”檔案夾,然後把所有的編輯器類型放入其中。事實上,系統的靈活性要更高些,你可以在項目中建立任意數量的“Editor”檔案夾,将其埋藏在“資産”檔案夾的任何地方,所有這些都可以存放編輯器代碼。

是以,現在通常情況下我會以功能(遊戲邦注:比如命名為“AI”)為機關來建立檔案夾,然後納入所有功能相關元件,然後在旁邊放上Editor檔案夾(遊戲邦注:比如命名為“AI/Editor”),裝上所有運作這些元件的編輯器擴充。

隻要Unity的内在類型能夠發揮作用,運作時間類型都會存在于UnityEngine命名空間中,而所有的編輯器類型都會存在于UnityEditor命名空間裡。

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

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)。

在檢查器中産生的效果如下圖所示:

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

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屬性:

[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,這很像一個對話盒。你在呈現後設定某些值,然後點選按鍵讓其施展“魔法”。

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

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:

[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… (Source: Gamasutra)

http://www.j2megame.com/html/xwzx/ty/3842.html

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

Unity3D可以通過事件觸發來執行你的編輯器代碼,但是我們需要一些編譯器參數來告知編譯器何時需要觸發該段代碼。

[MenuItem(XXX)]聲明在一個函數上方,告知編譯器給Unity3D編輯器添加一個菜單項,并且當點選該菜單項的時候調用該函數。觸發函數裡可以編寫任何合法的代碼,可以是一個資源批處理程式,也可以彈出一個編輯器視窗。代碼裡可以通路到目前選中的内容(通過Selection類),并據此來确定顯示視圖。與此類似,[ContextMenu("XXX")]可以向你的上下文菜單中添加一個菜單項。

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

開始編寫編輯器

為了避免不必要的包含,Unity3D的運作時和編輯器類分辨存儲在不同的Assemblies裡(UnityEngine和UnityEditor)。當你準備開始編寫編輯器之前,你需要using UnityEditor來導入編輯器的名稱空間。

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

在你開始真正編寫代碼之前,我認為你還需要知道所有放在命名為Editor目錄下的腳本會在其它腳本之後進行編譯,這友善了你去使用那些運作時的内容。而那些目錄下的腳本是不能通路到Editor目錄下的内容的。是以,你最好把你的編輯器腳本寫在Editor目錄下。

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

建立你的視窗

如果你想自定義一個可編輯的面闆,那麼你需要編寫一個繼承自EditorWIndow的類。通常情況下,你還需要寫一個[MenuItem]來告知編譯器何時打開這個面闆。這個事件的回調應該是一個靜态方法,并且傳回一個視窗的執行個體。

現在,當你點選對應的菜單項時,會彈出一個空白的視窗。并且你可以像Unity3D編輯器預制的視窗一樣随意拖動和停靠。下面來看看我們如何來在視窗内實作我們想要的功能吧。

擴充你的視窗

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

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

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功能套件為基礎的複雜工具,從視覺腳本編輯器到編輯器内導航網格生成。但是,有關如何自行建構此類事物的程式說明卻很少。我将在下文列舉某些在自己的工作中總結的編輯器定制相關資訊。

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

Unity-Window(from gamasutra)

如何建構編輯器腳本

因為你不想在遊戲中包含所有的編輯器定制,而且你也不想遊戲對某些Unity編輯器内的東西有所依賴,是以Unity将運作時間和編輯器代碼放置在單獨的編譯中。

在編輯指令中,運作時間代碼在編輯器代碼之前執行,這樣編輯器類型就可以可靠地聯系至運作時間元件(遊戲邦注:否則就會變得難以編輯),但是你的運作時間元件并不涉及任何編輯器代碼。

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

在某個點上的程式說明有些不太清楚。當我首次開始使用時,我認為需要在我的項目上建立單個“Editor”檔案夾,然後把所有的編輯器類型放入其中。事實上,系統的靈活性要更高些,你可以在項目中建立任意數量的“Editor”檔案夾,将其埋藏在“資産”檔案夾的任何地方,所有這些都可以存放編輯器代碼。

是以,現在通常情況下我會以功能(遊戲邦注:比如命名為“AI”)為機關來建立檔案夾,然後納入所有功能相關元件,然後在旁邊放上Editor檔案夾(遊戲邦注:比如命名為“AI/Editor”),裝上所有運作這些元件的編輯器擴充。

隻要Unity的内在類型能夠發揮作用,運作時間類型都會存在于UnityEngine命名空間中,而所有的編輯器類型都會存在于UnityEditor命名空間裡。

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

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)。

在檢查器中産生的效果如下圖所示:

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

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屬性:

[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,這很像一個對話盒。你在呈現後設定某些值,然後點選按鍵讓其施展“魔法”。

Unity3D引擎擴充中的編輯器定制方法如何讓編輯器運作你的代碼開始編寫編輯器如何建立自定義編輯器視窗如何擴充Inspector面闆一些常用的功能說明

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:

[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…

繼續閱讀