关于最近Unity编辑器代码使用总结(二)
Unity编辑器扩展的代码总结(二)
前言
本来这些应该在Unity编辑器扩展的代码总结中一并写了。但是我突然觉得我最近写的文章篇幅都太长了。以至于当我写到后面的时候就感觉:我都写了这么多了,还要写,最终导致我敷衍了事。所以这次我索性就分文章了。
环境
Window10系统
Unity2022.3.8f1c1
选择文件
我们可以调用EditorUtility.OpenFilePanel来打开选择文件的面板。这个最好使用一个Button来判断是否打开。如果你直接调用,那么就会一直打开这个面板直到你关闭Unity编辑器。代码如下:
1 | private void OnGUI() |
我们还可以通过EditorUtility.OpenFolderPanel来打开文件夹。(更多信息请查看官方的EditorUtility文档)
获取我们在编辑器中的选择
在游戏开发中,我们经常会选中编辑器中的一些资源然后将其拖到一些变量上。然后变量就自动填充上我们选定的资源。而在编辑器扩展中,我们可以使用DragAndDrop类来帮助我们实现这一点。如果你使用过EditorGUILayout.PropertyField,你会发现Unity已经帮我们实现了这样的功能。但是这些只针对简单的情况,比如List
这里我们要解决两个问题:一个是如何获取到我们在编辑器中的选择,另一个则是我如何知道用户将其选择拖入到变量对应的UI中。第一个问题我们可以使用DragAndDrop类来解决。第二个问题我们要拆分一下,首先我们要先知道我们变量对应的UI位置在哪里和大小是多大,然后我们再解决获取鼠标坐标的问题。最后我们只要知道一下何时算用户结束操作。这样我们就可以解决第二个问题了。
我们先来解决第一个问题。在GUILayoutUtility类中有一个函数GetLastRect,它会返回Rect。我们可以通过这个来得到UI的大小和位置。但是我们要注意一点就是这个函数只有在触发EventType.Repaint事件后才有用,否则我们得到的就是错误的信息。
要获取用户输入事件,我们可以使用Event。所以我们就能够知道何时用户输入开始,何时用户输入结束。并且通过它,我们还可以知道鼠标的位置。这样我们所有的问题都解决了。我这边也给出一个示例,具体代码如下:
1 | using System; |
这里我使用了EventType.DragUpdated和EventType.DragExited来判断用户的输入。显然当触发了EventType.DragExited时,用户输入结束。我也就在这个时候对用户选中的物体进行判断。通过DragAndDrop.objectReferences,我们可以拿到用户选中的物体。然后我判断其类型是否为我想要的,最后进行加入操作。这里你可能有一些疑问,为什么我要将事件判断放到EditorGUILayout.PropertyField前面?这是因为如果你不放在它前面的话,EditorGUILayout.PropertyField会将EventType.DragExited事件使用掉。这样事件到其执行后就变成了EventType.Used。这样我们判断不了用户何时结束操作,所以我将这个判断提到EditorGUILayout.PropertyField前面。这里我还做了改变鼠标样式的操作即这段代码:
1 | DragAndDrop.visualMode = DragAndDropVisualMode.Generic; |
其文档为:DragAndDrop.visualMode,DragAndDropVisualMode。
Selection
如果你有在网上找过关于这些Unity编辑器扩展获取拖拽的信息的话,你就会看到关于Selection去获取拖拽的信息。但我个人没搞懂这个Selection和DragAndDrop间的区别。我使用下来感觉差不多。如果你在使用的过程中发现了DragAndDrop有问题,那么你可以去试试看Selection。
Unity编辑器扩展使用协程
协程其实是一个很常见的功能。而协程也可以在编辑器扩展中进行使用(在我使用的Unity版本中Editor Coroutines插件是直接安装好了,但是我不确定其他的版本也会安装好这个插件)。其官方文档链接:Editor Coroutines。下面我就简单做一个例子:
1 | private void OnGUI() |
这里需要注意一下,如果你使用的是WaitForSeconds,那么这个会不起作用的。所以我们只能使用EditorWaitForSeconds。
Unity编辑器进度条
有了协程了,我们就可以来制作编辑器进度条了。虽然不用协程也可以做,但是我认为用协程会方便一些。以下是示例代码:
1 | private void OnGUI() |
有关进度条的更多信息可以查阅:EditorUtility,EditorUtility.DisplayProgressBar,EditorUtility.ClearProgressBar。
提示
关于做提示,你可以直接使用Debug来给出提示。但是我们既然都做编辑器扩展了,那还是看看编辑器相关的提示API。这其实可以说的不多,所以我就展示一下相关的API和其样子。
帮助盒子
1 | EditorGUILayout.HelpBox("这是一条提示信息", MessageType.Info); |
\img1.png)
文档链接:EditorGUILayout.HelpBox,MessageType。
提示窗口
1 | private void OnGUI() |
\img2.png)
文档链接:EditorUtility.DisplayDialog。除了这种提示窗口外,还有其他的提示窗口。但我认为这种应该最常用,所以我只展示了这个。更多信息可以查阅EditorUtility的官方说明。
编辑器对GameObject进行操作
我们有时候也需要对场景中的对象进行操作。那么如何获取到场景中的对象就是我们要解决的问题了。一般而言我们选取对象给窗口都是使用拖拽的方法。那么之前的提到的Selection和DragAndDrop都是可以的。但是我这里更加推荐使用Selection。因为它可以不用拖拽,你可以直接选中这些物体然后点击编辑器扩展窗口中的按钮。
这里我们仍然会遇到一个问题。我们其实可以在选中Scene下的物体同时选中Asset下的物体。那么我们就要分别一下哪些是Scene下的物体。你在Selection可以找到这样的API:Selection.GetFiltered,SelectionMode.Assets。你或许觉得用他们就可以实现我们想要的功能了。但是如果你选中的是Assets文件夹下的预制体,你用这样的组合仍然会将这个预制体计算进去。经过一些尝试,我找到了下面这样的方法来判断我们选中的物体是否是在场景下的。
1 | private void OnGUI() |
创建空物体
创建空物体的方法很简单就是下面这段代码:
1 | new GameObject(); |
新创建空对象会直接在场景中。这段代码虽然简单,但是网上一直没有说明的文章。我也是找了很久没有文章后试了一下才发现了,所以我就做一个记录。
关于预制体
Unity有提供一个处理预制体的类:PrefabUtility。我个人觉得这个没什么可以说明的,至少我现在的需求也没有遇到什么坑点。如果大家遇到需要处理预制体的时候可以去查看一下这个类的API。
关于资源加载和保存
一般而言,我们对一个资源的修改只要调用AssetDatabase.Refresh函数就可以了。但是最近我在帮同事做一些工具的时候,我发现我使用代码创建出来的资源在修改后无法被保存。即使我使用了上面的函数也不可以。
虽然最终我没有找到原因,但是我找到了另外的方法让我的修改后的资源被强制保存。我们先对我们想要保存的资源对象使用EditorUtility.SetDirty方法,然后我们在使用AssetDatabase.SaveAssetIfDirty保存。