Unity3D引擎擴充套件中的編輯器定製方法

xy849288321發表於2013-01-29
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… (Source: Gamasutra)



原味連結:http://gamerboom.com/archives/36432

相關文章