- 最后登录
- 2021-7-6
- 注册时间
- 2012-12-27
- 阅读权限
- 90
- 积分
- 76145
- 纳金币
- 53488
- 精华
- 316
|
关于如何对unity3d编辑器进行扩展的教程,推荐阅读!包括译文及原文:
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 SyncFolderS***cture() { … }
}
但是我要何时以及如何调用呢?我采用的做法是将其连同到Assets菜单中的菜单项目中,使用MenuItem属性:
[MenuItem("Assets/Sync folder s***cture")]
public static void SyncFolderS***cture() { … }
点击菜单项目就可以调用功能。应当注意的是,功能需要是静态的,但是其中的类可以是多种类型的。
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”);
}
译文转自(游戏邦):http://gamerboom.com/
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 builtThe different projects generated by Unity 3.4. The assets folder in each project is the same physical folder on disk; each project includes different files within it. Note that this only shows the C# projects - if you're using UnityScript, you get another four.
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***ntime and editor code in separate assemblies. The compilation order is such that***ntime code is compiled before editor code, so that your editor classes can safely refer to***ntime components (otherwise it’d be difficult to edit them) – but it does mean that your***ntime 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,***ntime 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 classBy 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<T> : 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<MyCustomComponent>, 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 GUIOnce 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 includesEditorGUILayout. EditorGUILayout provides a bunch of really simple controls for use in the editor, over and above what Unity’s regular***ntime 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***ntime 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 menuIt’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 theUnityEngine.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 menusEverything 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 s***cture, 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 s***cture, 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 SyncFolderS***cture() { ... }
}
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 s***cture")]
public static void SyncFolderS***cture() { ... }
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).
WizardsEditor 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.
Unity's built-in Create Ragdoll wizard (GameObject->Create Other->Ragdoll). You drag and drop the relevant bones and bits into the slots in the window, and then hit the 'create' button to have it rig your ragdoll with rigidbodies and joints.
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<ExplosionWizard>("Create explosion");
}
ConclusionThere’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…
|
|