MenuItem菜单

MenuItem特性可以拓展Unity原有的菜单和脚本的Context菜单,当然也可以自定义自己的菜单

所有MenuItem执行的方法都必须是静态的

覆盖系统菜单

1
2
3
4
5
6
[MenuItem("GameObject/UI/Text")]

static void MyTextMethod()//直接写有可能会弹出警告
{
Debug.Log("Text");
}

自定义菜单

在Editor文件夹中新建CustomTitleMenu脚本

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
using UnityEngine;
using UnityEditor;

public class CustomTitleMenu
{
[MenuItem("Root/Test1", false, 0)]

static void MyTest1() { }

[MenuItem("Root/Test2", false, 1)]

static void MyTest2() { }

[MenuItem("Root/Test/My Test")]//自动添加下划线,原因书中没有表述清楚,待查

static void MyTest() { }
[MenuItem("Root/Test/My Test", true, 20)]//第二个参数,表示此方法会先于同名特性方法执行,并设定此特性激活条件

static bool MyTestValidation()
{
return false;//返回FALSE后,菜单中的My Test就不能点击了,也就是这个方法先执行,用来验证My Test能不能被激活,返回bool
}

[MenuItem("Root/Test3", false, 3)]

static void MyTest3()//设置菜单可选中的方法
{
var menuPath = "Root/Test3";
bool mchecked = Menu.GetChecked(menuPath);
Menu.SetChecked("Root/Test3", !mchecked);
}
}

结果如下图所示

image-20211229192326824

原生自定义菜单

使用下列脚本,在Scene窗口中单击右键就可以弹出一个菜单

在Editor文件夹中新建CreatingOriginMenu脚本

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
using UnityEngine;
using UnityEditor;

public class CreatingOriginMenu
{
[InitializeOnLoadMethod]

static void InitializeOnLoadMethod()
{
SceneView.duringSceneGui += delegate (SceneView sceneView)
{
Event e = Event.current;
if (e != null && e.button == 1 && e.type == EventType.MouseUp)//当鼠标右键抬起时运行
{
Vector2 mousePosition = e.mousePosition;

//设置菜单项
var options = new GUIContent[]
{
new GUIContent("Text1"),
new GUIContent("Text2"),
new GUIContent(""),
new GUIContent("Text3"),
new GUIContent("Text4"),
};

//设置菜单显示区域和EditorUtility.DisplayCustomMenu的必要参数
var selected = -1;
var userData = Selection.activeGameObject;
var width = 100;
var height = 100;
var position = new Rect(mousePosition.x, mousePosition.y - height, width, height);

//执行EditorUtility.DisplayCustomMenu来显示菜单
EditorUtility.DisplayCustomMenu(position, options, selected, delegate (object data, string[] opt, int select)
{
Debug.Log(opt[select]);
}, userData);
e.Use();
}


};

}
}

拓展全局自定义按键

在Editor文件夹中新建CustomHotKey脚本

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

public class CustomHotKey
{
[MenuItem("Assets/Hotkey %#D",false,-1)]

static void MyHotKey()
{
Debug.Log("Ctrl Shift D");
}
}

符号表示的是快捷键,

  • %: Windows的Ctrl键、macOS下的Commad键
  • #: Shift键
  • &: Alt键
  • LEFT/RIGHT/UP/DOWN: 表示上下左右键
  • F1……F2:表示F1~F12快捷键
  • HOME、END、PGUP、PGUN

面板拓展

使用EditorGUI来自定义脚本Inspector的序列化方式以及拓展自己的窗口等

Inspector面板

拓展方式类似于之前拓展Inspector窗口时使用的方式,使用CustomEditor特性

在Script文件夹中新建InspectorWithEditorGUI脚本

在实际应用中,建议将此脚本后面编辑器的部分拆分出来放在Editor文件夹里

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
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class InspectorWithEditorGUI : MonoBehaviour
{
public Vector3 scollPos;
public int myId;
public string myName;
public GameObject prefab;
public MyEnum myEnum = MyEnum.One;
public bool toggle1;
public bool toggle2;
public enum MyEnum
{
One = 1,
Two,
}
}

#if UNITY_EDITOR
[CustomEditor(typeof(InspectorWithEditorGUI))]
public class InspectorWithEditor : Editor
{
private bool m_EnableToggle;

public override void OnInspectorGUI()
{
//在这一步获取对象,之前我们绘制Camera辅助UI时也有这一步
InspectorWithEditorGUI script = target as InspectorWithEditorGUI;

//绘制滚动条
script.scollPos = EditorGUILayout.BeginScrollView(script.scollPos, false, true);

script.myName = EditorGUILayout.TextField("text", script.myName);

script.myId = EditorGUILayout.IntField("int", script.myId);

script.prefab = EditorGUILayout.ObjectField("GameObject", script.prefab, typeof(GameObject), true) as GameObject;

//绘制按钮
EditorGUILayout.BeginHorizontal();
GUILayout.Button("1");
GUILayout.Button("2");
EditorGUILayout.EndHorizontal();

EditorGUILayout.BeginHorizontal();
script.myEnum = (InspectorWithEditorGUI.MyEnum)EditorGUILayout.EnumPopup("My Enum:", script.myEnum);
EditorGUILayout.EndHorizontal();

m_EnableToggle = EditorGUILayout.BeginToggleGroup("EnableToggle", m_EnableToggle);
script.toggle1 = EditorGUILayout.Toggle("Toggle1", script.toggle1);
script.toggle2 = EditorGUILayout.Toggle("Toggle2", script.toggle2);
EditorGUILayout.EndToggleGroup();

//结束绘制滚动条
EditorGUILayout.EndScrollView();

}
}
#endif

EditorWindows窗口

使用EditorWindows可以制作自己的窗口,继承后使用EditorWindow.GetWindow方法即可打开自己的窗口

在Editor文件夹中新建CustomEditorWindow脚本

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
using UnityEngine;
using UnityEditor;

public class CustomEditorWindow : EditorWindow
{
[MenuItem("Window/My Window")]
static void Init()
{
CustomEditorWindow window = (CustomEditorWindow)EditorWindow.GetWindow(typeof(CustomEditorWindow),false,"MY CUSTOM");
window.Show();
}
private Texture m_Mytexture = null;
private float m_Myfloat = 0.5f;

private void Awake()
{
Debug.LogFormat("窗口初始化时调用");
m_Mytexture = AssetDatabase.LoadAssetAtPath<Texture>("Assets/unity.png");
}

private void OnGUI()
{
GUILayout.Label("Hello World!", EditorStyles.boldLabel);
m_Myfloat = EditorGUILayout.Slider("滑块", m_Myfloat, -5, 5);
GUI.DrawTexture(new Rect(0, 30, 100, 100), m_Mytexture);
}
private void OnDestroy()
{
Debug.LogFormat("窗口销毁时调用");
}
private void OnFocus()
{
Debug.LogFormat("窗口有焦点时调用");
}
private void OnHierarchyChange()
{
Debug.LogFormat("hierarchy窗口发生变化调用");
}
private void OnInspectorUpdate()
{
// Debug.LogFormat("Inspector窗口每帧更新");
}
private void OnLostFocus()
{
Debug.LogFormat("失去焦点时调用");
}
private void OnProjectChange()
{
Debug.LogFormat("Project视图发生变化时调用");
}
private void OnSelectionChange()
{
Debug.LogFormat("在Project或Hierarchy窗口选择一个对象时调用");
}
private void Update()
{
//Debug.LogFormat("每帧调用");
}
}

EditorWindow下拉菜单

为了使自定义的Editor窗口也拥有自定义的下拉菜单,需要先实现IHasCustomMenu接口

在Editor文件夹中新建EditorWindowWithDropdown脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;
using UnityEditor;

public class EditorWindowWithDropdown : EditorWindow ,IHasCustomMenu
{
void IHasCustomMenu.AddItemsToMenu(GenericMenu menu)
{
menu.AddDisabledItem(new GUIContent("Disable"));
menu.AddItem(new GUIContent("Test1"), true, () => { Debug.Log("Test1"); });
menu.AddItem(new GUIContent("Test2"), true, () => { Debug.Log("Test2"); });
menu.AddSeparator("Test/");
menu.AddItem(new GUIContent("Test/Test3"), true, () => { Debug.Log("Test3"); });
}

[MenuItem("Window/Open My Window")]
static void Init()
{
EditorWindowWithDropdown windowWithDropdown = (EditorWindowWithDropdown)EditorWindow.GetWindow(typeof(EditorWindowWithDropdown),false,"WithDropDown");
windowWithDropdown.Show();
}
}

通过AddItem来添加列表元素,并且监听选择后的事件

image-20211230155826687

预览窗口

有些资源选择后,Inspector面板下方会出现预览窗口,但是有些资源是没有预览信息的,我们可以重写覆盖此资源的预览信息

在Editor文件夹中新建CustomPreviewWindow脚本

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

[CustomPreview(typeof(GameObject))]
public class CustomPreviewWindow : ObjectPreview
{
public override bool HasPreviewGUI()
{
return true;
}

public override void OnPreviewGUI(Rect r, GUIStyle background)
{
GUI.DrawTexture(r, AssetDatabase.LoadAssetAtPath<Texture>("Assets/unity.png"));
GUILayout.Label("Hello World");
}
}

image-20211230160748416

继承ObjectPreview并且重写OnPreviewGUI方法,在CustomPreview(typeof(GameObject))中的GameObject按自己的需要修改为系统对象或自定义脚本对象

获取预览信息

有一些资源是有预览信息的,如果需要在自定义窗口中也能显示资源的预览信息,可以使用下面的脚本

在Editor文件夹中新建GetPreviewMessage脚本

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
using UnityEngine;
using UnityEditor;

public class GetPreviewMessage : EditorWindow
{
private GameObject m_MyGo;
private Editor m_MyEditor;

[MenuItem("Window/Open My Preview")]
static void Init()
{
GetPreviewMessage window = (GetPreviewMessage)EditorWindow.GetWindow(typeof(GetPreviewMessage), false, "Preview");
window.Show();
}

private void OnGUI()
{
m_MyGo = (GameObject)EditorGUILayout.ObjectField(m_MyGo, typeof(GameObject), true);

if(m_MyGo != null)
{
if(m_MyEditor == null)
{
//创建Editor实例
m_MyEditor = Editor.CreateEditor(m_MyGo);
}

//预览它
m_MyEditor.OnPreviewGUI(GUILayoutUtility.GetRect(500, 500), EditorStyles.whiteLabel);
}
}
}

打开自定义的窗口,然后拖拽进一个有预览信息的物体

image-20211230174349026

Unity编辑器的源码

通过Unity Hub安装的Unity,默认安装目录下的编辑器源码地址:“C:\Program Files\Unity\Hub\Editor\2020.3.6f1c1\Editor\Data\Managed\UnityEditor.dll”可参考

查看DLL

常用的查看工具:.NET Reflectior以及ILSpy,可以使用Visual Studio查看类名和方法,但是无法查看源码

清空控制台日志

在Editor文件夹中新建ClearConsoleWindow脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine;
using UnityEditor;
using System.Reflection;

public class ClearConsoleWindow
{
[MenuItem("Root/CreatConsole")]
static void CreatConsole()
{
Debug.Log("CreatConsole");
}
[MenuItem("Root/CleanConsole")]
static void CleanConsole()
{
//获取Assembly
Assembly assembly = Assembly.GetAssembly(typeof(Editor));
//反射获取LogEntries对象
MethodInfo methodInfo = assembly.GetType("UnityEditor.LogEntries").GetMethod("Clear");
//反射调用它的Clear方法
methodInfo.Invoke(new object(), null);
}
}

获取EditorStyles样式

样式是开发C#应用交互时使用的各种风格,Unity并没有明确说明编辑器使用的样式,我们反射出EditorStyles的属性(Properties),找到GUIStyle并最终在OnGUI中预览出来

在Editor文件夹中新建GetEditorStyles脚本

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
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Reflection;

public class GetEditorStyles : EditorWindow
{
static List<GUIStyle> styles = null;
[MenuItem("Window/Open Edtior Styles")]
public static void Test()
{
EditorWindow.GetWindow<GetEditorStyles>("styles");

styles = new List<GUIStyle>();
//BindingFlags表示反射属性的类型
foreach (PropertyInfo fi in typeof(EditorStyles).GetProperties(BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic))
{
object o = fi.GetValue(null, null);
if(o.GetType() == typeof(GUIStyle))
{
styles.Add(o as GUIStyle);
}
}
}

public Vector2 scrollPosition = Vector2.zero;
private void OnGUI()
{
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
for (int i = 0; i < styles.Count; i++)
{
GUILayout.Label("EditorStyles." + styles[i].name, styles[i]);
}
GUILayout.EndScrollView();
}
}

image-20211231165223439

根据反射出来的样式名称,制作Unity编辑器时需要的样式,直接设置style名字即可。(比如之前获取预览信息使用的EditorStyles.WhiteLabel

获取内置图标样式

Unity内置了很多图标样式,接下来使用一个小脚本来列出所有的图标

在Editor文件夹中新建GetAllIcons脚本

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
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class GetAllIcons : EditorWindow
{
[MenuItem("Window/Open Icons")]
public static void OnenIconWindow()
{
EditorWindow.GetWindow<GetAllIcons>("Icons");
}

private Vector2 m_Scroll;
private List<string> m_Icons = null;
private void Awake()
{
m_Icons = new List<string>();
Texture2D[] t = Resources.FindObjectsOfTypeAll<Texture2D>();//查出所有贴图
foreach (Texture2D x in t)
{
Debug.unityLogger.logEnabled = false;
GUIContent gc = EditorGUIUtility.IconContent(x.name);//从具有给定名称的 Unity 内置资源中获取 GUIContent。
Debug.unityLogger.logEnabled = true;
if(gc != null && gc.image != null)
{
m_Icons.Add(x.name);
}
}
Debug.Log(m_Icons.Count);
}
private void OnGUI()
{
m_Scroll = GUILayout.BeginScrollView(m_Scroll);
float width = 50f;
int count = (int)(position.width / width);
for (int i = 0; i < m_Icons.Count; i += count)
{
GUILayout.BeginHorizontal();
for (int j = 0; j < count; j++)
{
int index = i + j;
if (index < m_Icons.Count)
GUILayout.Button(EditorGUIUtility.IconContent(m_Icons[index]), GUILayout.Width(width), GUILayout.Height(30));
}
GUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
}
}

image-20220102123112657

拓展默认面板(文件夹面板)

在Unity中点击文件夹,inspector中没有提供多余的显示信息,我们来拓展一下这个默认面板

image-20220102131459366

在Editor文件夹中新建ExtendingFolderPanel脚本

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

[CustomEditor(typeof(UnityEditor.DefaultAsset))]
public class ExtendingFolderPanel : Editor
{
public override void OnInspectorGUI()
{
string path = AssetDatabase.GetAssetPath(target);
GUI.enabled = true;
if (path.EndsWith(string.Empty))//判断文件后缀,这里表示引擎无法识别的资源类型,比如文件夹
{
GUILayout.Label("拓展文件夹");
GUILayout.Button("我是文件夹");
}
}
}