编辑器扩展

编辑器扩展

相关

编辑器相关文件夹

Editor

  1. 该文件夹可以放在项目的任何文件夹下,可以有多个”Editor”文件夹。
  2. 编辑器扩展相关的脚本都要放在该文件夹内,该文件夹中的脚本只会对Unity编辑器起作用。
  3. 项目打包的时候,不会被打包到项目中。如果编辑器相关脚本不放在该文件夹中,打包项目可能会出错。
  4. 如果非要有些编辑器相关脚本不放在该文件夹中,需要在该类的前后加上UNITY_EDITOR的宏定义

Editor Default Resources

  1. 该文件夹需要放在Assets根目录下,用来存储编辑器所需要的图片等资源,书写的时候需要注意中间有空格隔开。
  2. 此文件夹也不会被打包,访问方法为:EditorGUIUtility.Load()
  3. 当然,也可以在Editor文件夹内创建一个Resources文件夹,将相关资源放在该文件夹内,通过Resources.Load()获取资源,也是可以的

Gizmos

该文件夹也需要放在Assets根目录下,可以用来存放Gizmos.DrawIcon()的图片资源

Selection

Selection:用于获取选择的游戏物体

  • Selection.activeGameObject 返回第一个选择的场景中的对象
  • Selection.gameObjects 返回场景中选择的多个对象,包含预制体等
  • Selection.objects 返回选择的多个对象
1
2
3
4
5
//遍历选择的对象,并立刻销毁
foreach(object obj in Selection.objects)
{
DestroyImmediate(obj);
}

编辑器特性

常见特性

简单类特性

1
2
3
4
5
6
7
8
9
10
11
12
13
[Serializable] //序列化一个类,作为一个子属性显示在监视面板

[RequireComponent(typeof(xxx))] //挂载该类的对象,必须要有xxx组件

[DisallowMultipleComponent] //不允许挂载多个该类或其子类

[ExecuteInEditMode] //允许脚本在编辑器未运行的情况下运行

[CanEditMultipleObjects] //允许当选择挂有该脚本的多个对象时,统一修改值

[AddComponentMenu] //可以在菜单栏Component内添加组件按钮

[SelectionBase] //选择在场景视图中使用此属性的组件对象,即不会误选中子物体

复杂类特性

1
2
3
[CustomEditor(typeof(xxx)] //自定义继承自Mono的组件,要修改其inspector或者在场景中的显示就要加这个特性

[CustomPropertyDrawer] //用于绘制自定义PropertyDrawer的特性

方法特性

1
2
3
[MenuItem()]

[DrawGizmo]

属性特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[SerializeField] //序列化字段,主要用于序列化私有字段

[NonSerialized] //反序列化一个变量,并且在Inspector上隐藏

[HideInInspector] //public变量在Inspector面板隐藏

[Range(0,100)] //限制int范围

[Multiline(3)] //用于string,多行显示

[TextArea(2,4)] //用于string,文本输入框

[ColorUsage(true)] //显示Color

[FormerlySerializedAs(“Value1”)] //当Value1变量名发生改变时,可以保存inspector面板中原来Value1配置的值

[Header(“Header Name”)] //加粗效果的标题

[Tooltip(“Tips”)] //显示字段的提示信息

[Space(10)] //表示间隔空间,数字越大,间隔越大

其他特性

1
2
3
4

[ContextMenuItem]

[ContextMenu]

自定义菜单栏

MenuItem用于在编辑器中添加自定义的菜单栏目,或者添加到既有栏目中

用法一:需要注意的就是 unity 的顶部菜单的父一级,是不支持中文的,就是Menu​那一级,它的子级就没关系了。

1
2
3
4
5
[MenuItem("Menu/普通的顶部菜单")]
private static void MenuItemNormal()
{
Debug.Log("普通的顶部菜单");
}

用法二:

  1. 第二个参数用来选择当前方法是否用作有效判断,如果为true,则需要跟一个返回值为bool的方法,该方法返回了true,才会执行下一个方法
  2. 第三个参数priority是优先级,用来表示菜单按钮的先后顺序,默认值为1000。一般菜单中的分栏,数值相差大于10。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[MenuItem("MyTool/DeleteAllObj", true)]
private static bool DeleteValidate()
{
if (Selection.objects.Length > 0)
return true;
else
return false;
}

[MenuItem("MyTool/DeleteAllObj",false)]
private static void MyToolDelete()
{
//Selection.objects 返回场景或者Project中选择的多个对象
foreach (Object item in Selection.objects)
{
//记录删除操作,允许撤销
Undo.DestroyObjectImmediate(item);
}
}

//DeleteValidate方法是MyToolDelete方法的有效函数,所以第二个参数为true。该有效函数用来判断当前是否选择了对象,如果选择了,返回true,才可以执行MyToolDelete方法。

快捷键

符号 字符
% Ctr/Command
# Shift
& Alt
LEFT/Right/UP/DOWN 方向键
F1-F2 F功能键
_g 字母g
1
2
[MenuItem(“MyTools/test1 %_q”)] 
//快捷键 Ctrl+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
//Project面板右键
[MenuItem("Assets/测试1")]
private static void Test1()
{
//TODO
}

//Hierarchy面板右键
[MenuItem("GameObject/测试2")]
private static void Test2()
{
//TODO
}

//自带组件右键
[MenuItem("CONTEXT/Rigidbody/测试3")]
private static void Test3()
{
//TODO
}

//自定义组件
[MenuItem("CONTEXT/PlayerHealth/Init")]
private static void Init(MenuCommand cmd)
{
PlayerHealth health = cmd.context as PlayerHealth;
//TODO
}

扩展Scene视图

场景视图是编辑游戏对象的窗口,扩展场景视图可以实现网格编辑,地形绘制或高级Gizmos等操作。视图的扩展主要通过OnSceneGUI()方法实现,因为场景扩展是基于场景对象的,所以可以选择不同的对象实现不同的场景视图操作。

CustomEditor

先创建一个脚本,挂载在场景对象身上。

再编写编辑器脚本,方式和创建Inspector扩展方式差不多,也需要放在Editor文件夹内

OnSceneGUI方法是通过Handles来绘制内容的

Handls

Unity Editor 基础篇(四):Handles (qq.com)

Gizmo

方法特性——DrawGizmo

在Scene下为带有”XX”的组件绘制gizmo

绘制模式

1
2
3
4
[DrawGizmo(GizmoType.SelectedOrChild)] //当自身或子物体被选中
[DrawGizmo(GizmoType.Active)] //当被激活
[DrawGizmo(GizmoType.Selected)] //当自身被选中
[DrawGizmo(GizmoType.Selected | GizmoType.Active)] //当自身被选中或者被激活

举例

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

public class GimoTest
{
[DrawGizmo(GizmoType.SelectedOrChild)] //当自身或者子物体被选中时,自动调用如下方法
private static void MyGizmo(Light light, GizmoType gizmoType) //参数1为“XX”组件,可以随意选,参数2 必须写,不用赋值
{
Gizmos.color = Color.red; //绘制时颜色
Gizmos.DrawSphere(light.transform.position, light.range); //参数1绘制坐标,参数2绘制半径
}
}

OnDrawGizmos

除了特性之外,MonoBehaviour​中可以实现OnDrawGizmos()​和OnDrawGizmosSelected()​方法

常用Gizmos的方法

  • Gizmos.DrawCube() 绘制实体立方体
  • Gizmos.DrawWireCube() 绘制立方体边框
  • Gizmos.DrawRay() 绘制射线
  • Gizmos.DrawLine() 绘制直线
  • Gizmos.DrawIcon() 绘制Icon,Icon素材需要放在Gizmos文件夹中
  • Gizmos.DrawFrustum() 绘制摄像机视椎体的视野范围

自定义Inspector面板

ContextMenu和ContextMenuItem

ContextMenu​和ContextMenuItem​都是给脚本组件用的,前者是给这个脚本组件中的方法提供一个右键调用,后者是给这个脚本组件中的属性提供一个右键调用,都是在Inspector面板中进行

从使用来看,ContextMenu是方法特性,ContextMenuItem是属性特性

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

public class PlayerHealth : MonoBehaviour
{
[ContextMenuItem("AddHp","AddHp")]//属性右键新增调用方法
public int startingHealth;
public Color flashColor;
void AddHp()
{
startingHealth += 30;
}
//组件右键新增方法
[ContextMenu("SetColor")]
void SetColor()
{
flashColor = Color.green;
}
}

CustomEditor

CustomEditor​是类特性,为一个组件或者脚本上的字段自定义绘制方法,定义的类要继承editor​类

CustomEditor​只能直接修改一个类的绘制,但对嵌套不起作用,例如它修改了NPCDetails​类的绘制,但是在另一个类面板中的List<NPCDetails>​字段的绘制不受影响,这时候需要使用CustomPropertyDrawer

1
2
3
4
5
6
7
8
//挂载的脚本
public class EditorAttribute : MonoBehaviour
{
//属性必须是public的,并且和你自定义编辑器中的类型是一致的,比如这里是int类型,你在编辑器中就用intslider
public int damage;
public int armor;
public GameObject gun;
}
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
[CustomEditor(typeof(EditorAttribute))]	//建立和Mono脚本的对应关系,修改EditorAttribute的绘制
public class EditorAttributes : Editor
{
SerializedProperty damageProp;
SerializedProperty armorProp;
SerializedProperty gunProp;

void OnEnable()
{
// Setup the SerializedProperties.
damageProp = serializedObject.FindProperty("damage");
armorProp = serializedObject.FindProperty("armor");
gunProp = serializedObject.FindProperty("gun");

//target即当前编辑器中选中的对象;如果同时选中多个,target则为Hierarchy视图中选中的最后一个
m_card = target as Card;

//通过targets获取编辑器中选中的多个对象
var cards = targets;
}

public override void OnInspectorGUI()
{
// Update the serializedProperty - always do this in the beginning of OnInspectorGUI.
serializedObject.Update();

// Show the custom GUI controls.
EditorGUILayout.IntSlider(damageProp, 0, 100, new GUIContent("Damage"));

// Only show the damage progress bar if all the objects have the same damage value:
if (!damageProp.hasMultipleDifferentValues)
ProgressBar(damageProp.intValue / 100.0f, "Damage");

EditorGUILayout.IntSlider(armorProp, 0, 100, new GUIContent("Armor"));

// Only show the armor progress bar if all the objects have the same armor value:
if (!armorProp.hasMultipleDifferentValues)
ProgressBar(armorProp.intValue / 100.0f, "Armor");

EditorGUILayout.PropertyField(gunProp, new GUIContent("Gun Object"));

// Apply changes to the serializedProperty - always do this in the end of OnInspectorGUI.
serializedObject.ApplyModifiedProperties();
}

// Custom GUILayout progress bar.
void ProgressBar(float value, string label)
{
// Get a rect for the progress bar using the same margins as a textfield:
Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
EditorGUI.ProgressBar(rect, value, label);
EditorGUILayout.Space();
}
}

CustomPropertyDrawer

PropertyDrawer用于自定义属性绘制器的基类。使用PropertyDrawer来控制它在Inspector中的样式。可以使用CustomPropertyDrawer 特性将 PropertyDrawer附加到 Serializable类,然后传入绘制器进行渲染。使用此基类有两种用途:绑定到类或者绑定到特性

  1. 绑定到使用了[Serializable]​的自定义类,可以自定义绘制类在Inspector上显示的GUI

之前

之后

代码:

  • 绑定到一个类xxx,[CustomPropertyDrawer(typeof(xxx))]​,这个类不能继承自MonoBehaviour,否则会报错
  • 继承自PropertyDrawer
  • 只需重载OnGUI
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
[Serializable]
public class MonoTest
{
public enum EnumValue
{
EnumValue1,
EnumValue2,
EnumValue3,
}

public int intValue;
public bool boolValue;
public EnumValue enumValue;
}

[CustomPropertyDrawer(typeof(MonoTest))]
public class MonoTestEditor : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var nameRect = new Rect(position.x, position.y, 222, position.height);
var amountRect = new Rect(position.x + 222, position.y, 222, position.height);
var unitRect = new Rect(position.x + 222 + 222, position.y, 222, position.height);

EditorGUIUtility.labelWidth = 100;
EditorGUI.PropertyField(nameRect, property.FindPropertyRelative("intValue"));
EditorGUI.PropertyField(amountRect, property.FindPropertyRelative("boolValue"));
EditorGUI.PropertyField(unitRect, property.FindPropertyRelative("enumValue"));
}
}
  1. 绘制使用了某种特性的字段的显示方式

代码:

  1. 绑定到某个自定义特性
  2. 继承自PropertyDrawer
  3. 重载OnGUI
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
//定义自定义属性类
public sealed class RangeTestAttribute : PropertyAttribute
{
public readonly float min;

public readonly float max;

public RangeTestAttribute(float min, float max)
{
this.min = min;
this.max = max;
}
}

//对属性类自定义显示
[CustomPropertyDrawer(typeof(RangeTestAttribute))]
public class RangeTestAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
RangeTestAttribute range = (RangeTestAttribute)attribute;
//类型是float
if (property.propertyType == SerializedPropertyType.Float)
{
EditorGUI.Slider(new Rect(position.x, position.y, position.width * 0.8f, position.height), property, range.min, range.max);
EditorGUI.LabelField(new Rect(position.x + position.width * 0.8f, position.y, position.width - (position.x + position.width * 0.8f),
position.height), "滑到了" + property.floatValue);
}
else
{
EditorGUI.HelpBox(new Rect(position.x, position.y, position.width, position.height), "只支持float类型属性", MessageType.Error);
}
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return base.GetPropertyHeight(property, label);
}
}

List添加下拉框

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TargetExample : MonoBehaviour
{
[SerializeField]
public List<PlayerItem> playerItemArray = new List<PlayerItem>();
}

[System.Serializable]
public class PlayerItem
{
[SerializeField]
public Texture icon;
[SerializeField]
public GameObject prefab;
[SerializeField]
public string name;
[SerializeField]
public int attack;
}
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;

public enum PrefabType
{
Player,
Enemy,
}

public struct Creation
{
public PrefabType prefabType;
public string path;
}


[CustomEditor(typeof(TargetExample))]
public class TargetExampleEditor : Editor
{
private ReorderableList _playerItemArray;

private void OnEnable()
{
_playerItemArray = new ReorderableList(serializedObject, serializedObject.FindProperty("playerItemArray")
, true, true, true, true);

//自定义列表名称
_playerItemArray.drawHeaderCallback = (Rect rect) =>
{
GUI.Label(rect, "Player Array");
};

//定义元素的高度
_playerItemArray.elementHeight = 68;

//自定义绘制列表元素
_playerItemArray.drawElementCallback = (Rect rect,int index,bool selected,bool focused) =>
{
//根据index获取对应元素
SerializedProperty item = _playerItemArray.serializedProperty.GetArrayElementAtIndex(index);
rect.height -=4;
rect.y += 2;
EditorGUI.PropertyField(rect, item,new GUIContent("Index "+index));
};

//当删除元素时候的回调函数,实现删除元素时,有提示框跳出
_playerItemArray.onRemoveCallback = (ReorderableList list) =>
{
if (EditorUtility.DisplayDialog("Warnning","Do you want to remove this element?","Remove","Cancel"))
{
ReorderableList.defaultBehaviours.DoRemoveButton(list);
}
};

_playerItemArray.onAddDropdownCallback = (Rect rect, ReorderableList list) =>
{
GenericMenu menu = new GenericMenu();
var guids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Prefabs/Player" });
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
menu.AddItem(new GUIContent("Player/" + System.IO.Path.GetFileNameWithoutExtension(path))
, false, ClickHandler, new Creation() { prefabType = PrefabType.Player, path = path });
}
//添加分割线
menu.AddSeparator("");
guids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Prefabs/Enemy" });
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
menu.AddItem(new GUIContent("Enemy/" + System.IO.Path.GetFileNameWithoutExtension(path))
, false, ClickHandler, new Creation() { prefabType = PrefabType.Enemy, path = path });
}
//显示鼠标下方的菜单
menu.ShowAsContext();
};

}

public override void OnInspectorGUI()
{
serializedObject.Update();
//自动布局绘制列表
_playerItemArray.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}

private void ClickHandler(object target)
{
Creation creation = (Creation)target;
int index = _playerItemArray.serializedProperty.arraySize;
_playerItemArray.serializedProperty.arraySize++;
_playerItemArray.index = index;
SerializedProperty element = _playerItemArray.serializedProperty.GetArrayElementAtIndex(index);

switch (creation.prefabType)
{
case PrefabType.Player:
SpawnCharacter(creation,element,90);
break;
case PrefabType.Enemy:
SpawnCharacter(creation, element, 80);
break;
}

serializedObject.ApplyModifiedProperties();
}

private void SpawnCharacter(Creation creation, SerializedProperty element,int atk)
{
GameObject character = AssetDatabase.LoadAssetAtPath<GameObject>(creation.path);

GameObject obj = GameObject.Instantiate(character);
obj.name = character.name;

SerializedProperty prefabPreperty = element.FindPropertyRelative("prefab");
SerializedProperty iconPreperty = element.FindPropertyRelative("icon");
SerializedProperty namePreperty = element.FindPropertyRelative("name");
SerializedProperty attackPreperty = element.FindPropertyRelative("attack");

prefabPreperty.objectReferenceValue = character;
iconPreperty.objectReferenceValue = GetPreviewTex(character);
namePreperty.stringValue = character.name;
attackPreperty.intValue = atk;
}

//获取预制体的预览图
private Texture GetPreviewTex(GameObject obj)
{
return AssetPreview.GetAssetPreview(obj) as Texture;
}

}

重点其实就是通过委托onAddDropdownCallback和GenericMenu实现下拉列表功能

参考:Unity 编辑器扩展总结 七:数组或list集合的显示方式_editorguilayout展示list-CSDN博客

编辑器窗体

ScriptableWizard

ScriptableWizard​类是一个用于创建自定义向导式编辑器界面的类

详细API见ScriptableWizard - Unity 脚本 API

举例

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
public class EditorWindow : ScriptableWizard
{
public string msg = "";

//显示窗体
[MenuItem("MyWindow/First Window")]
private static void ShowWindow()
{
ScriptableWizard.DisplayWizard<EditorWindow>("WindowExample1", "确定", "应用");
}

//显示时调用
private void OnEnable()
{
Debug.Log("OnEnable");
}

//隐藏时调用
private void OnDisable()
{
Debug.Log("OnDisable");
}

//销毁时调用
private void OnDestroy()
{
Debug.Log("OnDestroy");
}

//更新时调用
private void OnWizardUpdate()
{
Debug.Log("OnWizardUpdate");

if (string.IsNullOrEmpty(msg))
{
errorString = "请输入信息内容"; //错误提示
helpString = ""; //帮助提示
}
else
{
errorString = "";
helpString = "请点击确认按钮";
}
}

//当用户按下"应用"时被调用,保存设置但不关闭窗口
void OnWizardOtherButton()
{
Debug.Log("OnWizardOtherButton");
}

//点击"确定"时调用,关闭窗口并保存设置
void OnWizardCreate()
{
Debug.Log("OnWizardCreate");
}

//当ScriptableWizard需要更新其GUI时,将调用此函数以绘制内容
//为GUI绘制提供自定义行为,默认行为是按垂直方向排列绘制所有公共属性字段
//一般不重写该方法,按照默认绘制方法即可
protected override bool DrawWizardGUI()
{
return base.DrawWizardGUI();
}
}

EditorWindow

创建自定义的编辑器窗体都需要继承自EditorWindow类

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
public class WindowExample2 : EditorWindow
{
private static WindowExample2 window;//窗体实例

//显示窗体
[MenuItem("MyWindow/Second Window")]
private static void ShowWindow()
{
window = EditorWindow.GetWindow<WindowExample2>("Window Example");
window.Show();
}

//显示时调用
private void OnEnable()
{
Debug.Log("OnEnable");
}

//绘制窗体内容
private void OnGUI()
{
EditorGUILayout.LabelField("Your Second Window", EditorStyles.boldLabel);
}

//固定帧数调用
private void Update()
{
Debug.Log("Update");
}

//隐藏时调用
private void OnDisable()
{
Debug.Log("OnDisable");
}

//销毁时调用
private void OnDestroy()
{
Debug.Log("OnDestroy");
}
}

PopupWindowContent

用于实现在编辑器中弹出窗口,弹窗类继承自PopupWindowContent类,

特点

  1. 弹窗位置一般会指定在上层窗口的GetLastRect
  2. 弹窗会覆盖上层窗口显示
  3. 当弹窗失去焦点时,就会自动关闭。
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
public class WindowExample3 : EditorWindow
{
private static WindowExample3 window;
private PopWindowExample popWindow = new PopWindowExample();
private Rect buttonRect;

//显示窗体
[MenuItem("MyWindow/Third Window")]
private static void ShowWindow()
{
window = EditorWindow.GetWindow<WindowExample3>("Window Example 3");
window.Show();
}

//绘制窗体内容
private void OnGUI()
{
GUILayout.Label("Popup example", EditorStyles.boldLabel);
if (GUILayout.Button("Popup Options", GUILayout.Width(200)))
{
PopupWindow.Show(buttonRect, popWindow);
}
//获取GUILayout最后用于控件的矩形
if (Event.current.type == EventType.Repaint)
buttonRect = GUILayoutUtility.GetLastRect();

GUILayout.Label("Popup e1232p e12321xamp e12321xamp e12321xam1xample", EditorStyles.boldLabel);
}
}

public class PopWindowExample : PopupWindowContent
{
bool toggle = true;

//开启弹窗时调用
public override void OnOpen()
{
Debug.Log("OnOpen");
}

//绘制弹窗内容
public override void OnGUI(Rect rect)
{
EditorGUILayout.LabelField("PopWindow");
toggle = EditorGUILayout.Toggle("Toggle", toggle);
}

//关闭弹窗时调用
public override void OnClose()
{
Debug.Log("OnClose");
}

public override Vector2 GetWindowSize()
{
//设置弹窗的尺寸
return new Vector2(200, 100);
}
}

Layout

概述

解释一下Unity中的编辑器扩展类,分为两组:

  • 在编辑器或者Runtime都能使用,命名空间UnityEngine

    • GUI: ui系统,包括button,lable,toggle等控件
    • GUILayout: 在GUI基础上,控件新增自动布局功能
    • GUILayoutUtility: 对布局类的一些补充,工具类。
  • 只能在编辑器使用,命名空间UnityEditor

    • EditorGUI: 编辑器ui系统,和GUI非常相似,包括button,lable,toggle等控件
    • EditorGUILayout: 在EditorGUI基础上,控件新增自动布局功能
    • EditorGUIUtility: 对EditorGUILayout的一些补充,工具类。

GUI和GUILayout的比较

1.使用GUI和EditorGUI需要手动设置控件的Rect,位置宽高固定,不能自适应宽高,例如:

1
2
GUI.Button(new Rect(0, 0, 100, 100),"btn");
EditorGUI.LabelField(new Rect(0, 0, 100, 100),"label");

2.使用GUILayout和EditorGUILayout生成控件是自动布局,不用设置Rect。例如:

1
2
GUILayout.Button("btn");
GUILayout.Label("label");

API

2024-02-12 Unity 编辑器开发之编辑器拓展3 —— EditorGUI_unity编辑器拓展开发-CSDN博客

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class Mybianyi : EditorWindow
{
string PasswordField = "";
string m_textArea = "";
float sliders = 0;
int slidera = 0;
string BeginToggleGroup = "BeginToggleGroup";
bool ToggleGroup = false;
string Textfield1 = "";
string Textfield2 = "";
bool fg = false;
float sum = 0;
int count = 0;
string tag = "aaa";
int Layerfield=0;
string[] pathname = new string[] { "All", "Asset", "..." };
float minVal = 1;
float maxVal = 2;
float minLimit = -5;
float maxLimit = 5;
static Vector3 center = new Vector3(1, 2, 3);
static Vector3 size1 = new Vector3(1, 2, 3);
Bounds _bounds = new Bounds(center, size1);
Color m_color = Color.white;
AnimationCurve m_curve = AnimationCurve.Linear(0, 0, 10, 10);
Vector2 size = new Vector2(200,200);
int flags = 0;
string[] options = new string[] { "CanJump", "CanShoot", "CanSwim" ,"Canabc","Canacc"};
GameObject game ;
bool showFoldout;
Vector2 m_vector2 = new Vector2();
Vector3 m_vector3 = new Vector3();
Vector4 m_vector4 = new Vector4();
Transform selectedTransform;
GameObject selectedGameObject;
bool fold;
bool fold2;



[MenuItem("MyWindow/Window")]
static void window()
{
Mybianyi mybianyi = GetWindow<Mybianyi>();
mybianyi.Show();
}


private void OnGUI()
{
// 标签
GUILayout.Label("Label", GUILayout.Width(60), GUILayout.Height(20));
// 可复制标签
EditorGUILayout.SelectableLabel("SelectableLabel");

if( GUILayout.Button("按钮", GUILayout.Width(40), GUILayout.Height(20)))
{

}
fg = EditorGUILayout.Toggle("Toggle", fg, GUILayout.Width(40), GUILayout.Height(20));

EditorGUILayout.BeginHorizontal();
ToggleGroup = EditorGUILayout.BeginToggleGroup(BeginToggleGroup, ToggleGroup);
Textfield1 = GUILayout.TextField(Textfield1);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.EndHorizontal();

GUILayout.Space(20);
sum = GUILayout.HorizontalSlider(sum, 0, 10);
// sum = GUILayout.VerticalSlider(sum, 0, 10);
GUILayout.Space(20);

slidera = EditorGUILayout.IntSlider("IntSlider:", slidera, 1, 10);

sliders = EditorGUILayout.Slider("Slider:",sliders,0,10);

EditorGUILayout.LabelField("Min Val:", minVal.ToString());
EditorGUILayout.LabelField("Max Val:", maxVal.ToString());
// 现在最小 现在最大 最小长度 最大长度
EditorGUILayout.MinMaxSlider("MinMaxSlider", ref minVal, ref maxVal, minLimit, maxLimit);

count = EditorGUILayout.Popup("下拉:",count,pathname);

// tag(标签)
tag = EditorGUILayout.TagField("TagField:", tag);
// 可以获取所有的Layer
Layerfield = EditorGUILayout.LayerField("LayerField:", Layerfield);
// 下拉多选 除了数组的第一个是1 后面全是2的幂(幂为对应的下标) 如果多选它们会相加 系统默认会添加Nothing (对应的值0) 和Everything(-1)
flags = EditorGUILayout.MaskField("MaskField:", flags, options);

//参数2 maxLength 最大有效字符长度
Textfield1 = GUILayout.TextField(Textfield1);//单行
// Textfield1 = GUILayout.TextField(Textfield,5);
// 自适应高
m_textArea = EditorGUILayout.TextArea(m_textArea);//可以多行
// 密码保护
PasswordField = GUILayout.PasswordField(PasswordField, '*');//可以改变成对应的符号
// 边界输入框
_bounds = EditorGUILayout.BoundsField("BoundsField:", _bounds);
// 颜色输入框
m_color = EditorGUILayout.ColorField("ColorField:", m_color);
// 曲线输入框
m_curve = EditorGUILayout.CurveField("CurveField:", m_curve);

// EditorGUILayout.ObjectField(选择物体)
game = (GameObject) EditorGUILayout.ObjectField(game,typeof(GameObject),true);//typeof(类型) 确定好类型系统会自动帮我找到所有的关于这个类型的物体

GUILayout.Space(10);
GUILayout.BeginHorizontal();//可以在里面存放多个如果不规定大小系统会平均分配大小
GUILayout.EndHorizontal();//结束语一定要有
GUILayout.BeginVertical();//可以在里面存放多个如果不规定大小系统会平均分配大小
GUILayout.EndVertical();//结束语一定要有


//两个true可以让横纵两条线显示出了
//两个false可以让横纵两条线不显示出来
size = GUILayout.BeginScrollView(size,true,true);
GUILayout.Space(40);
GUILayout.EndScrollView();
// EditorGUILayout.Foldout 折叠
showFoldout = EditorGUILayout.Foldout(showFoldout, "折叠子物体:");
if (showFoldout)
{
EditorGUI.indentLevel++; //缩进级别
EditorGUILayout.LabelField("折叠块内容1");
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("折叠块内容2");
EditorGUI.indentLevel--;
EditorGUI.indentLevel--;
EditorGUILayout.LabelField("折叠块内容3");
}
// 提示语句
EditorGUILayout.HelpBox("HelpBox Error:", MessageType.Error);//红色错误号
EditorGUILayout.HelpBox("HelpBox Info:", MessageType.Info);//白色提示号
EditorGUILayout.HelpBox("HelpBox None:", MessageType.None);//解释号
EditorGUILayout.HelpBox("HelpBox Warning:", MessageType.Warning);//黄色警告号
m_vector2 = EditorGUILayout.Vector2Field("Vector2:", m_vector2);
m_vector3 = EditorGUILayout.Vector3Field("Vector3:", m_vector3);
m_vector4 = EditorGUILayout.Vector4Field("Vector4:", m_vector4);
}
}

EditorGUI - Unity 脚本 API (unity3d.com)

EditorGUILayout - Unity 脚本 API —- EditorGUILayout - Unity 脚本 API (unity3d.com)

EditorGUIUtility - Unity 脚本 API —- EditorGUIUtility - Unity 脚本 API (unity3d.com)

GUIStyle、GUISkin

GUIStyle

GUIStyle用于修改GUI的风格样式,除了适用于编辑器开发,也适用于Unity旧版的UI系统(IMGUI)。GUIStyleu拥有多种属性,可以方便开发者自定义编辑器UI样式。

当我们未自定义GUIStyle时,使用的就是unity默认的GUIStyle样式。GUIStyle有点像网页前端开发的层叠样式表CSS,拥有很多状态属性可以调整。

GUISkin

GUISkin是基本所有样式的集合,可以作为一种配置资源。如果开发者需要自定义大量的GUIStyle,可以通过GUISkin配置资源来定义,并且开发者可以在Inspector面板中直接修改样式。

在Project面板,鼠标右键Create-GUISkin既可以创建。

可以将新建的GUISkin资源放在Editor里的Resources文件内,方便动态加载。

接下来,就可以通过GUISkin资源来修改样式效果了。如下修改会得到和之前通过直接修改GUIStyle一样的效果。

数据保存

EditorPrefs

Unity编辑器为开发者提供了类似PlayerPrefs的数据保存方式EditorPrefs。EditorPrefs是适用于编辑器模式,而PlayerPrefs适用于游戏运行时。

EditorPrefs提供了四种数据的保存:int,float,string,bool

通过Set方法保存下数据,下次则通过Get方法来获取数据,HasKey方法可以判断是否存在该数据的保存,删除数据调用DeleteKey方法即可。

注意:需要谨慎调用EditorPrefs.DeleteAll()方法,因为该方法还可能会删除Unity编辑器自身存储的一些数据,给开发者带来不必要的麻烦。

ScriptableObject

只需要继承自ScriptableObject,然后在类中定义需要的数据即可。

在类前可以添加CreateAssetMenu属性,方便开发者在Project面板右键Create创建该资源。

也可以通过方法调用来创建资源,最终得到的资源是一样的,该脚本需要放在Editor文件夹中。

可以将创建的资源放在Resources文件夹中,通过动态的方式加载。

如果遇到ScriptableObject数据不能正常保存的情况,可以尝试使用EditorUtility.SetDirty方法,标记该ScriptableObject为“脏”,然后就能正常保存了。

Undo

Undo用于编辑器模式下的撤销操作,这里介绍几种常用的API。

  1. Undo.RegisterCreatedObjectUndo : 记录新建的对象状态,可以撤销新建的对象
  2. Undo.RecordObject:记录对象的状态,需要在修改之前调用
  3. Undo.AddComponent:可以撤销新挂载的组件
  4. Undo.DestroyObjectImmediate:可以撤销删除对象的操作
  5. Undo.SetTransformParent:可以撤销修改父对象的操作

参考

Unity 编辑器扩展总结 一:编辑器开发入门_unity 编辑器扩展总结一:编辑器开发入门-CSDN博客

Unity中GUI、GUILayout、EditorGUI、EditorGUILayout、GUILayoutUtility、EditorGUIUtility区别_editorguilayout和guilayout-CSDN博客

Unity编辑器扩展之自定义Inspector面板_unity自定义inspector class-CSDN博客

Unity编辑器扩展之CustomPropertyDrawer理解-CSDN博客

革命性Unity 编辑器扩展工具 —- Odin Inspector 系列教程_odininspector-CSDN博客


编辑器扩展
https://enlight3n.github.io/2024/05/21/UnitySummary/编辑器扩展/
作者
Enlight3n
发布于
2024年5月21日
许可协议