Unity3d插件编写

First Post:

Last Update:

Word Count:
2.9k

Read Time:
11 min

Unity3d插件编写

如何让编辑器运行你的代码如何让编辑器运行你的代码

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目录下。

如何创建自定义编辑器窗口

创建窗口,如果想自定义一个可编辑的面板,那么你需要编写一个继承自Editor的类。通常情况下,你还需要写一个[MenuItem]来告知编译器何时打开这个面板。这个事件的回调应该是一个静态方法,并且返回一个窗口的实例。现在,当点击对应的菜单项时,会弹出一个空白的窗口。并且可以像Unity3D编辑器预制的窗口一样随意拖动和停靠。下面来看看如何来在窗口内实现想要的功能吧。

扩展你的窗口

和运行时的GUI一样,如果你需要在窗口中添加交互控件,那么必须重写OnGUI方法。具体的重写方式和运行时的GUI一样,你甚至可以使用任何扩展自原生GUI系统的插件(例如iGUI和GUIX)来简化你的插件开发流程(仅经过初步测试,更深层次的可用性尚待验证)。同时UnityEditor名称空间下的EditorGUILayout在原生GUI之上提供了一些更方便的接口和控件,让你可以轻松的使用一些编辑器特有的UI控件。 除了OnGUI外,你可能还会需要如下一些回调来触发某些具体的逻辑(完整的列表请参考官方文档):  OnSelectionChange,但你点选物品时触发  OnFocus /OnLostFocus,获得和失去焦点时触发

进一步扩展你的窗口

自定义控件 和运行时GUI的使用方式一样,如果你打算自定义自己的控件,那么最简单的方式就是实现一个静态方法(也可以不是静态的),并提供一些可选参数,在方法内部根据这些参数来完成对控件的布局(就像你在OnGUI中做的一样)。 如果你打算把自定义控件实现在窗口类内部,你可以使用Partial类来更好的管理你的代码。 绘制2D内容 绘制图片 可以使用GUI.DrawTexture来完成对图片资源的绘制。 绘制基础图元 GUI本身并没有提供绘制基础图元的方法,但是可以通过一些方式来封装出这些方法。  绘制线段:通过一个像素的贴图资源配合GUI.DrawTexture和矩阵旋转来完成线段的绘制。  绘制矩形框:通过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控制器)。 具体的使用方式可以参考官方的参考文档。

一些常用的功能说明

 AssetDatabase.CreateAsset可以帮住你从资源目录中创建一个资源实例。  Selection.activeObject返回当前选中的对象。  EditorGUIUtility.PingObject用来实现在Project窗口中点击某一项的操作。  Editor.Repaint用来重绘界面所有的控件。  XXXImporter用来设置某种资源的具体导入设置(例如在某些情况下你需要设置导入的贴图为可读的)。 EditorUtility.UnloadUnusedAssets用于释放没有使用的资源,避免你的插件产生内存泄漏。  Event.Use用来标记事件已经被处理结束了。  EditorUtility.SetDirty用来通知编辑器数据已被修改,这样在下次保存时新的数据将被存储。

例子

保存为CopyComponent.cs,放置在Plugin/Editor目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;

public class CopyComponent : Editor
{
#if UNITY_EDITOR
static Component[] compoentArr;

[MenuItem("Tools/Test")]
static void DoCopyComponent()
{
Debug.Log("Test")
}
#endif
}

自定义快捷键

一、前言
在使用Unity中可能需要使用快捷键执行一些操作,或者修改Unity自带的快捷键,接下来就看一下,如何设置自定义快捷键吧。

组合快捷键Ctrl+Shift+Q

[MenuItem(“Custom快捷键/Ctrl+Shift+Q %#Q”)]

Custom快捷键: 自定义,随便写
Ctrl+Shift+Q: 自定义,随便写
%#Q: 快捷键设置,Ctrl=% Shift=# Q=Q。。前面记得加空格.

四、功能实例
暂停编辑器:
EditorApplication.isPaused = !EditorApplication.isPaused;

播放:
EditorApplication.isPlaying = true;

单步执行:
EditorApplication.Step();

打开场景,并运行
EditorSceneManager.OpenScene(“Assets/Scenes/LandInit.unity”);
EditorApplication.isPlaying = true;

效果:

二、快捷键大全
快捷键 指令

1
2
3
4
5
6
%	CTRL
# Shift
& Alt
LEFT/RIGHT/UP/DOWN 箭头上下左右
F1-F12 键盘快捷键F1-F12
HOME/END/PGUP/PDDN 对应键盘的Home/End/PageUp/PageDown

三、实例
代码:

1
2
3
4
5
6
7
8
9
10
11
using UnityEditor;
using UnityEngine;

public class CustomKeys : Editor
{
[MenuItem("Custom快捷键/F1按键 _F1")]
static void EditorCustorkKeys1()
{
Debug.Log("F1点击执行的指令");
}
}

这个就是自定义了一个F1的快捷键指令

1
2
3
4
5
6
7
8
9
10
11
using UnityEditor;
using UnityEngine;

public class CustomKeys : Editor
{
[MenuItem("Custom快捷键/Ctrl+Q %Q")]
static void EditorCustorkKeys2()
{
Debug.Log("Ctrl+Q点击执行的指令");
}
}

组合快捷键Ctrl+Q

1
2
3
4
5
6
7
8
9
10
11
using UnityEditor;
using UnityEngine;

public class CustomKeys : Editor
{
[MenuItem("Custom快捷键/Ctrl+Shift+Q %#Q")]
static void EditorCustorkKeys3()
{
Debug.Log("Ctrl+Shift+Q点击执行的指令");
}
}

例子

写一个复制组件的插件,更方便的进行组件复制和粘贴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;

public class CopyComponent : Editor
{

#if UNITY_EDITOR
static Component[] compoentArr;

[MenuItem("Tools/Component/Copy All %&A")]
static void DoCopyComponent()
{
Component[] allCompoents = Selection.activeGameObject.GetComponents<Component>();
if (allCompoents == null) return;
}

[MenuItem("Tools/Component/Copy All Execpt Transform %&E")]
static void DoCopyComponent_Except_1()
{
Component[] allCompoents = Selection.activeGameObject.GetComponents<Component>();
if (allCompoents == null) return;

int length = 0;
for (int i = 0; i < allCompoents.Length; i++)
{
if (allCompoents[i].GetType().Name == "RectTransform" || allCompoents[i].GetType().Name == "Transform") continue;
length++;
}

if (length == 0) return;

compoentArr = new Component[length];
for (int i = 0,j = 0; i < allCompoents.Length; i++)
{
if (allCompoents[i].GetType().Name == "RectTransform" || allCompoents[i].GetType().Name == "Transform") continue;
compoentArr[j] = allCompoents[i];
j++;
}

}

[MenuItem("Tools/Component/Paste %&V")]
static void DoPasteComponent()
{
if (compoentArr == null)
{
return;
}

GameObject targetObject = Selection.activeGameObject;
if (targetObject == null)
{
return;
}

for (int i = 0; i < compoentArr.Length; i++)
{
Component newComponent = compoentArr[i];
if (newComponent == null)
{
continue;
}
UnityEditorInternal.ComponentUtility.CopyComponent(newComponent);
Component oldComponent = targetObject.GetComponent(newComponent.GetType());
if (oldComponent != null) // 存在旧的组件
{
if (UnityEditorInternal.ComponentUtility.PasteComponentValues(oldComponent))
{
Debug.Log("Paste Component" + newComponent.GetType().ToString() + " Success");
}
else
{
Debug.Log("Paste Component " + newComponent.GetType().ToString() + " Failed");
}
}
else // 存在旧的组件
{
if (UnityEditorInternal.ComponentUtility.PasteComponentAsNew(targetObject))
{
Debug.Log("Paste Component Overwrited" + newComponent.GetType().ToString() + " Success");
}
else
{
Debug.Log("Paste Component Overwrited" + newComponent.GetType().ToString() + " Failed");
}
}
}
}
#endif
}

ref: https://blog.csdn.net/q764424567/article/details/108639136

打赏点小钱
支付宝 | Alipay
微信 | WeChat