1 GUI/GUILayout

[[《GUI》#GUI]]

关键字:GameView GUI, MonoBehaviour.OnGUI()

UnityEngine. GUI :可以用于运行时(在 Game 视图中显示)/ 编辑器(诸如在 Inspector)面板下显示相关 UI 组件与内容,需要自行计算 Rect [[1 前言]] ( https://zhuanlan.zhihu.com/p/503154643#ref_1 )

Rect
这个类型在编辑器拓展中十分常见,官方解释为
A 2D Rectangle defined by X and Y position, width and height.
一个由 X,Y 坐标,width,height 宽高定义的 2D 矩形
其以左上角为坐标原点,X 往右递增,Y 往下递增
更加详细介绍可参照:Unity Rect 官方文档

UnityEngine. GUILayout :是基于 GUI 的实现,自动进行排版,计算坐标与宽高。

ff5059c9e25b452a200fdfddf691dcb1_MD5

2 EditorGUI/EditorGUILayout

关键字:编辑器扩展,EditorEditorWindow

UnityEditor. EditorGUI:只可用于编辑器,需要自行计算 Rect。

UnityEditor. EditorGUILayout:只可用于编辑器,自动计算 Rect

386f878452650d21879ce4e22c4c37a6_MD5

汇总

06a49d958bf7cb0e0728a892d41faf46_MD5

0a481a9bf1f3e1e14d93e154d99d1b44_MD5

关于效果图最后它的代码我隐藏掉了如何想看看可以自行打开

f9fbd6a00ccffa20ca887a2e98bc4db8_MD5

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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 Textfield = "";
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(100,100);
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.Width 控制在窗口中物体所在的宽
// GUILayout.Height 控制在窗口中物体所在的高
//它们的返回的类型为GUILayoutOption
#region GUILayout.Label 提示语句

GUILayout.Label("我的编译器(My compiler)", GUILayout.Width(50), GUILayout.Height(10)); //提示语句
#endregion
#region GUILayout.Button( 按钮
GUILayout.Label("按钮");
if( GUILayout.Button("按钮", GUILayout.Width(40), GUILayout.Height(40)))
{

}
#endregion
#region GUILayout.TextField 文本
GUILayout.Label("文本(可以输入)");
Textfield = GUILayout.TextField(Textfield);//单行
//参数2 maxLength 最大有效长度
// Textfield = GUILayout.TextField(Textfield,5);
#endregion
#region GUILayout.Space 空行
//参数为float类型代表空行的距离
GUILayout.Space(10);
#endregion
#region EditorGUILayout.Toggle 开关(跟Toggle一样)
fg = EditorGUILayout.Toggle("Toggle", fg);//开关
#endregion
#region GUILayout.BeginHorizontal 横向
GUILayout.BeginHorizontal();//可以在里面存放多个如果不规定大小系统会平均分配大小
GUILayout.Button("按钮");
Textfield = GUILayout.TextField(Textfield);
GUILayout.EndHorizontal();//结束语一定要有
#endregion
#region GUILayout.BeginVertical 纵向
GUILayout.BeginVertical();//可以在里面存放多个如果不规定大小系统会平均分配大小
GUILayout.Button("按钮");
Textfield = GUILayout.TextField(Textfield);
GUILayout.EndVertical();//结束语一定要有
#endregion
#region GUILayout.HorizontalSlider(横) GUILayout.VerticalSlider(纵) Slider(分横纵 上横下纵)
sum = GUILayout.HorizontalSlider(sum, 0, 10);
// sum = GUILayout.VerticalSlider(sum, 0, 10);
GUILayout.Space(20);
#endregion
#region EditorGUILayout.Popup 下拉
count = EditorGUILayout.Popup("下拉:",count,pathname);
#endregion
#region GUILayout.BeginScrollView 滑动列表
//两个true可以让横纵两条线显示出了
//两个false可以让横纵两条线不显示出来
size = GUILayout.BeginScrollView(size,true,true);

GUILayout.EndScrollView();
#endregion
#region EditorGUILayout.BoundsField (边界输入) EditorGUILayout.ColorField(颜色输入) EditorGUILayout.CurveField(曲线输入) 输入框
//BoundsField 边界输入框
_bounds = EditorGUILayout.BoundsField("BoundsField:", _bounds);
//ColorField 颜色输入框
m_color = EditorGUILayout.ColorField("ColorField:", m_color);
//CurveField 曲线输入框
m_curve = EditorGUILayout.CurveField("CurveField:", m_curve);
#endregion
#region EditorGUILayout.TagField tag(标签)
tag = EditorGUILayout.TagField("TagField:", tag);

#endregion
#region EditorGUILayout.LayerField(可以获取所有的Layer)
//Layerfield 可以获取所有的Layer
Layerfield = EditorGUILayout.LayerField("LayerField:", Layerfield);

#endregion
#region EditorGUILayout.MaskField (下拉可以多选)
flags = EditorGUILayout.MaskField("MaskField:", flags, options);
//Debug.Log(flags);// 除了数组的第一个是1 后面全是2的幂(幂为对应的下标) 如果多选它们会相加 系统默认会添加Nothing (对应的值0) 和Everything(-1)
#endregion
#region EditorGUILayout.ObjectField(选择物体)
game = (GameObject) EditorGUILayout.ObjectField(game,typeof(GameObject),true);//typeof(类型) 确定好类型系统会自动帮我找到所有的关于这个类型的物体
#endregion
#region 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");
}
#endregion
#region EditorGUILayout.BeginToggleGroup(开关)
ToggleGroup = EditorGUILayout.BeginToggleGroup(BeginToggleGroup, ToggleGroup);
Textfield = GUILayout.TextField(Textfield);
EditorGUILayout.EndToggleGroup();
#endregion
#region GUILayout.FlexibleSpace(布局之间左右对齐)
EditorGUILayout.BeginHorizontal();//开始最外层横向布局
GUILayout.FlexibleSpace();//布局之间左右对齐
GUILayout.Label("-----------------分割线-----------------");
GUILayout.FlexibleSpace();//布局之间左右对齐
EditorGUILayout.EndHorizontal();
#endregion
#region EditorGUILayout.HelpBox(提示语句)
EditorGUILayout.HelpBox("HelpBox Error:", MessageType.Error);//红色错误号
EditorGUILayout.HelpBox("HelpBox Info:", MessageType.Info);//白色提示号
EditorGUILayout.HelpBox("HelpBox None:", MessageType.None);//解释号
EditorGUILayout.HelpBox("HelpBox Warning:", MessageType.Warning);//黄色警告号
#endregion
#region EditorGUILayout.Slider(Slider)
sliders = EditorGUILayout.Slider("Slider:",sliders,0,10);
#endregion
#region EditorGUILayout.TextArea(text 自适应高)
m_textArea = EditorGUILayout.TextArea(m_textArea);//可以多行
#endregion
#region GUILayout.PasswordField(可以改变成对应的符号)
EditorGUILayout.BeginHorizontal();
GUILayout.Label("密码text", GUILayout.Width(60));
PasswordField = GUILayout.PasswordField(PasswordField, '*');//可以改变成对应的符号
EditorGUILayout.EndHorizontal();
#endregion
#region EditorGUILayout.Vector2Field EditorGUILayout.Vector3Field EditorGUILayout.Vector4Field
m_vector2 = EditorGUILayout.Vector2Field("Vector2:", m_vector2);
m_vector3 = EditorGUILayout.Vector3Field("Vector3:", m_vector3);
m_vector4 = EditorGUILayout.Vector4Field("Vector4:", m_vector4);
#endregion
#region EditorGUILayout.SelectableLabel (可以复制粘贴)
EditorGUILayout.SelectableLabel("SelectableLabel");
#endregion
#region EditorGUILayout.MinMaxSlider (取值范围)
EditorGUILayout.LabelField("Min Val:", minVal.ToString());
EditorGUILayout.LabelField("Max Val:", maxVal.ToString());
EditorGUILayout.MinMaxSlider("MinMaxSlider", ref minVal, ref maxVal, minLimit, maxLimit);
//现在最小 现在最大 最小长度 最大长度
#endregion
#region EditorGUILayout.IntSlider(只能是整数)
slidera = EditorGUILayout.IntSlider("IntSlider:", slidera, 1, 10);
#endregion
#region EditorGUILayout.InspectorTitlebar(将物体返回回来)
//Transform selectedTransform = Selection.activeGameObject.transform;
//GameObject selectedGameObject = Selection.activeGameObject;//选择物体(GameObject)
//fold = EditorGUILayout.InspectorTitlebar(fold, selectedTransform);
//fold2 = EditorGUILayout.InspectorTitlebar(fold2, selectedGameObject);
#endregion
}
}

一、Editor 的基本面板

1. 浮动窗口(EditorWindow)

关键字:UnityEditor.EditorWindow,MenuItem

样例:

33d08d603393c529ef62bc81ccfda064_MD5

示例代码:

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

public class TutorialWindow : EditorWindow
{
private static TutorialWindow window; //窗口实例对象,必须是一个static

[MenuItem("MyWindows/TutorialWindow")] //定义菜单栏位置
public static void OpenWindow() //打开窗口函数,必须是static
{
window = GetWindow<TutorialWindow>(false, "TutorialWindow", true); //实例化窗口
window.Show(); //显示窗口
}
/// <summary>
/// 窗口内显示的GUI面板
/// </summary>
private void OnGUI()
{

}
}

在 Unity3d 上方菜单栏,我们可以看见:

959b8dd4bd1d105b5fbed49915bf5201_MD5

点击就可以生成自定义窗口啦。

之后所有 EditorWindow的 GUI 绘制,都是在 OnGUI() 函数中,为了预览方便,后续只会给出核心代码,实际上都是放在 OnGUI() 中。

2. 检视面板(Inspector)

关键字:UnityEditor.Editor, CustomEditor

样例:

d82b8eb22bc534dd2efa66310c01fa0d_MD5

Mono 代码:

1
2
3
public class TutorialMono : MonoBehaviour //基本就是个空的Mono
{
}

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

[CustomEditor(typeof(TutorialMono))] //指定自定义Editor所要绑定的Mono类型,这里就是typeof(TutorialMono)
public class TutorialMonoInspector : Editor //继承Editor
{
private TutorialMono m_target; //在Inspector上显示的实例目标
/// <summary>
/// 当对象活跃时(在Inspector中显示时),unity自动调用此函数
/// </summary>
private void OnEnable()
{
m_target = target as TutorialMono; //绑定target,target官方解释: The object being inspected
}
/// <summary>
/// 重写OnInspectorGUI,之后所有的GUI绘制都在此方法中。
/// </summary>
public override void OnInspectorGUI()
{
base.OnInspectorGUI(); //调用父类方法绘制一次GUI,TutorialMono中原本的可序列化数据等会在这里绘制一次。
//如果不调用父类方法,则这个Mono的Inspector全权由下面代码绘制。

if (GUILayout.Button("这是一个按钮")) //自定义按钮
{
Debug.Log("Hello world");
}
}
}

之后 Inspector 面板的所有的 GUI 绘制,都是在 OnInspectorGUI() 函数中,为了预览方便,后续只会给出核心代码,实际上都是放在 OnInspectorGUI() 中。

二、按钮控件

1. 普通按钮(Button)

关键字: GUILayout.Button

样例:

8c03dd9088bd4b93a4f8d381067977bd_MD5

示例代码:

1
2
3
4
if (GUILayout.Button("这是一个按钮"))
{
Debug.Log("按了下按钮");
}

2. 按下触发按钮(DropdownButton)

关键字: EditorGUILayout.DropdownButton

样例:

7dbd6502611710672ce2163b5c8fa375_MD5

示例代码:

1
2
3
4
5
6
private GUIContent content = new GUIContent("DropdownButton"); //定义一下显示内容
...
if (EditorGUILayout.DropdownButton(content, FocusType.Passive))
{
Debug.Log("按下就触发,而不是抬起");
}

看了看官方描述也是云里雾里,反正和 Button 的区别就是:

Button 是鼠标抬起触发。

Dropdown 是鼠标按下就触发。

咱也不是很清楚,几乎没用过这个组件,粗略了解就好(逃)。

三、显示域(Field)

1. 对象域(ObjectField)

关键字:EditorGUILayout.ObjectField

样例:

d96bef83d59f2b9f348ce51caae4af22_MD5

示例代码:

1
2
3
4
5
6
7
private GameObject m_objectValue; //定义Object
...
//第一个参数: 目标Object
//第二个参数: Object的类型
//第三个参数: 是否允许赋值场景对象(一般是赋值Project中的Asset资源对象)
//最后需要一个【as】进行类型转换
m_objectValue = EditorGUILayout.ObjectField(m_objectValue, typeof(GameObject), true) as GameObject;

这个组件几乎是必用控件,Editor 的根本目的就是在编辑模式下,对某些资源对象(Object)进行 “增删改查”。

同时,这个 “Object” 不只是 GameObject,只要是继承 UnityEngine.Object 的类型,都可以用这个方法来获取,比如:Texture,Material 等等。

bbafc994c940ef611bc64478d5260908_MD5

值得注意的是,所有的 “Field” 类,如果不把数据放在左边赋值,那么在输入框内的修改将不会保存。

(仔细想想这个代码逻辑是这样的),这个特性可以拿来显示某些 “只读” 数据。

2. 整数域(IntField)

关键字:EditorGUILayout.IntField

样例:

6c605ceb53eecbfde7b0f5fcf8f2cbd3_MD5

示例代码:

1
2
3
private int m_intValue; //定义修改内容;
...
m_intValue = EditorGUILayout.IntField("整型输入框", m_intValue); //Title + Value

3. 浮点、字符串、向量等各种域

关键字:FloatField, TextField,Vector3Field…

样例:

2a6b5765f3c5442d58dc6d757193cb42_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private string m_textValue;
private float m_floatValue;
private Vector2 m_vec2;
private Vector3 m_vec3;
private Vector4 m_vec4;
private Bounds m_bounds;
private BoundsInt m_boundsInt;
...
m_floatValue = EditorGUILayout.FloatField("Float 输入:", m_floatValue);
m_textValue = EditorGUILayout.TextField("Text输入:", m_textValue);
m_vec2 = EditorGUILayout.Vector2Field("Vec2输入: ", m_vec2);
m_vec3 = EditorGUILayout.Vector3Field("Vec3输入: ", m_vec3);
m_vec4 = EditorGUILayout.Vector4Field("Vec4输入: ", m_vec4);
m_bounds = EditorGUILayout.BoundsField("Bounds输入: ", m_bounds);
m_boundsInt = EditorGUILayout.BoundsIntField("Bounds输入: ", m_boundsInt);

4. 标签文本域(LabelField)

关键字:EditorGUILayout.LabelField

样例:

56f3c6fe45f13c44376f6da9fa168934_MD5

示例代码:

1
EditorGUILayout.LabelField("文本标题", "文本内容");

5. 标签 / 层级选择域(TagField/LayerField

关键字:EditorGUILayout.TagField, EditorGUILayout.LayerField

样例:

df9c908478e81e8c9212d8c9570ca472_MD5

示例代码:

1
2
3
4
5
private int m_layer;
private string m_tag;
...
m_layer = EditorGUILayout.LayerField("层级选择", m_layer);
m_tag = EditorGUILayout.TagField("标签选择", m_tag);

6. 颜色域(ColorField)

关键字:EditorGUILayout.ColorField

样例:

fd7d5ed4ada0b7b51acf42dafdc837fd_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
private Color m_color;
private GUIContent colorTitle = new GUIContent("颜色选择");
...
//着重强调一下这个重载方法
//第一个参数: GUIContent,通常拿来作为Title
//第二个参数: Color,目标修改数据
//第三个参数: bool ,是否显示拾色器
//第四个参数: bool ,是否显示透明度通道
//第五个参数: bool ,是否支持HDR。
m_color = EditorGUILayout.ColorField(colorTitle, m_color, true, true, true);

现在 URP 基本标配,该上 HDR 啦!

7. 动画曲线域(CurveField)

关键字:EditorGUILayout.CurveField, AnimationCurve

样例:

df2a381cc3524b07c952c3b9d877d1bc_MD5

示例代码:

1
2
3
private AnimationCurve m_curve = AnimationCurve.Linear(0, 0, 1, 1);
...
m_curve = EditorGUILayout.CurveField("动画曲线:", m_curve);

四、枚举选择

1. 单选枚举(EnumPopup)

关键字:EditorGUILayout.EnumPopup

样例:

dba22300bc28d854871abc5ba466f1fe_MD5

示例代码:

1
2
3
4
5
6
7
8
9
private enum TutorialEnum
{
One,
Two,
Three
}
private TutorialEnum m_enum;
...
m_enum = (TutorialEnum)EditorGUILayout.EnumPopup("枚举选择", m_enum);

2. 多选枚举(EnumFlagsField)

关键字:EditorGUILayout.EnumFlagsField

样例:

f607db8bb768dc9c8421da1bec005766_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
11
private enum TutorialEnum
{
None = 0,
OneAndTwo = One | Two,
One = 1 << 0,
Two = 1 << 1,
Three = 1 << 2
}
private TutorialEnum m_enum;
...
m_enum = (TutorialEnum)EditorGUILayout.EnumFlagsField("枚举多选", m_enum);

3. 单选 / 多选整型(IntPopup/MaskField)

关键字: EditorGUILayout.IntPopup, EditorGUILayout.MaskField

样例:

8d74732cc45fde05581fee53f001549a_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
private int m_singleInt;
private int m_multiInt;
private string[] intSelections = new string[] { "整数10", "整数20", "整数30" };
private string[] intMultiSelections = new string[] { "1号", "2号", "3号" };
private int[] intValues = new int[] { 10, 20, 30 };
...
m_singleInt = EditorGUILayout.IntPopup("整数单选框", m_singleInt, intSelections, intValues);
EditorGUILayout.LabelField($"m_singleInt is {m_singleInt}");
m_multiInt = EditorGUILayout.MaskField("整数多选框", m_multiInt, intMultiSelections);
EditorGUILayout.LabelField($"m_multiInt is {m_multiInt}");

笔者对这种用得少,也不是很熟悉。

五. 折叠栏

1. 折叠(Foldout

关键字: EditorGUILayout.Foldout

样例:

ea0ddd7b78c2d1857d565efa0dcecdfe_MD5

示例代码:

1
2
3
4
5
6
7
private bool foldOut;
...
foldOut = EditorGUILayout.Foldout(foldOut, "一般路过折叠栏");
if (foldOut) //只有foldout为true时,才会显示下方内容,相当于“折叠”了。
{
EditorGUILayout.LabelField("这是折叠标签内容");
}

2. 折叠组(FoldoutGroup

关键字:EditorGUILayout.BeginFoldoutHeaderGroup, EditorGUILayout.EndFoldoutHeaderGroup

样例:

cb9ce4e7a4bc2821eb272dfb065353dc_MD5

示例代码:

1
2
3
4
5
6
7
8
private bool foldOut;
...
foldOut = EditorGUILayout.BeginFoldoutHeaderGroup(foldOut, "折叠栏组");
if (foldOut) //只有foldout为true时,才会显示下方内容,相当于“折叠”了。
{
EditorGUILayout.LabelField("这是折叠标签内容");
}
EditorGUILayout.EndFoldoutHeaderGroup(); //只不过这种折叠需要成对使用,不然会有BUG

六. 开关控件

1. 单个开关(Toggle)

关键字: EditorGUILayout.Toggle,EditorGUILayout.ToggleLeft

样例:

506376c3bbb56966d28c885742f93a01_MD5

示例代码:

1
2
3
4
private bool toggle;
...
toggle = EditorGUILayout.Toggle("Normal Toggle", toggle);
toggle = EditorGUILayout.ToggleLeft("Left Toggle", toggle);

2. 开关组(ToggleGroup)

关键字:EditorGUILayout.BeginToggleGroup,EditorGUILayout.EndToggleGroup

样例:

4b6665fa4ccfa3782901fc6aa2417618_MD5

示例代码:

1
2
3
4
5
6
7
private bool toggle;
private string m_inputText;
...
toggle = EditorGUILayout.BeginToggleGroup("开关组", toggle);
EditorGUILayout.LabelField("------Input Field------");
m_inputText = EditorGUILayout.TextField("输入内容:", m_inputText);
EditorGUILayout.EndToggleGroup();

七. 滑动控件

1. 滑动条(Slider)

关键字:EditorGUILayout.Slider,EditorGUILayout.IntSlider

样例:

48ab2acbcb12e705c877737e52354b3b_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
11
private float m_sliderValue;
private int m_sliderIntValue;
...
//参数详解:
//第一个参数: string label 控件名称标签
//第二个参数: float value 滑动当前值
//第三个参数: float leftValue 滑动最小值
//第四个参数: float rightValue 滑动最大值
m_sliderValue = EditorGUILayout.Slider("滑动条Sample:", m_sliderValue, 0.123f, 7.77f);
//参数同上
m_sliderIntValue = EditorGUILayout.IntSlider("整数值滑动条", m_sliderIntValue, 2, 16);

2. 双滑块滑动条(MinMaxSlider

关键字:EditorGUILayout.MinMaxSlider

样例:

8f227d23433b017697e77783c87b9fa1_MD5

示例代码:

1
2
3
4
5
6
private float m_leftValue;
private float m_rightValue;
...
EditorGUILayout.MinMaxSlider("双块滑动条", ref m_leftValue, ref m_rightValue, 0.25f, 10.25f);
EditorGUILayout.FloatField("滑动左值:", m_leftValue);
EditorGUILayout.FloatField("滑动右值:", m_rightValue);

八. 其他控件

1. 帮助框(HelpBox)

关键字: EditorGUILayout.HelpBox

样例:

b688f2f791afe2367075b5f23a0e64df_MD5

示例代码:

1
2
3
EditorGUILayout.HelpBox("一般提示,你应该这样做...", MessageType.Info);
EditorGUILayout.HelpBox("警告提示,你可能需要这样做...", MessageType.Warning);
EditorGUILayout.HelpBox("错误提示,你不能这样做...", MessageType.Error);

2. 间隔(Space)

关键字:EditorGUILayout.Space,GUILayout.FlexibleSpace

样例:

eb3f93134586b92b8566938c04f29146_MD5

示例代码:

1
2
3
4
5
6
7
8
9
EditorGUILayout.LabelField("----上面的Label----");
EditorGUILayout.Space(10); //进行10个单位的间隔
EditorGUILayout.LabelField("----下面的Label----");

EditorGUILayout.BeginHorizontal(); //开始水平布局
GUILayout.Button("1号button", GUILayout.Width(200)); //固定button长度: 200
GUILayout.FlexibleSpace(); // 自动填充间隔,如果窗口宽600px,那这种写法就是:【左button:200px】【自动间隔:200px】【右button :200px】
GUILayout.Button("2号button", GUILayout.Width(200));//固定button长度: 200
EditorGUILayout.EndHorizontal(); //结束水平布局

3. GUILayout 派生控件

【EditorGUILayout 】本就是基于【GUILayout】进行的派生,用于 Editor GUI 的绘制,很多时候【http://EditorGUILayout.xxx()】甚至完全可以用【http://GUILayout.xxx()】代替。

当然,这两个类都有独自的一些控件,比如只有【GUILayout.Button】,只有【EditorGUILayout.Foldout】,有时候想要的控件在【EditorGUILayout】里找不到就去【GUILayout】里面找,反之亦然。

下面简单举例一些布局操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
private Vector2 scrollRoot;
...
EditorGUILayout.BeginHorizontal(); //开始水平布局
//一大堆控件...
EditorGUILayout.EndHorizontal();//结束水平布局

EditorGUILayout.BeginVertical();//开始垂直布局
//一大堆控件...
EditorGUILayout.EndVertical();//结束垂直布局

scrollRoot = EditorGUILayout.BeginScrollView(scrollRoot); //开启滚动视图
//一大堆控件...
EditorGUILayout.EndScrollView(); //结束滚动视图

4 Gizmos

关键字Gizmos,Monobehavior. OnDrawGizmos (), DrawLine,DrawRay,DrawSphere

这两者是在 Scene 视图下,进行图形绘制。

比如一个经典应用:画出 AI 敌人的视锥范围,就可以用 Gizmos. DraeRay 进行绘制,便于 Debug。

4b6f52a7617ac64245693f22a61b22ba_MD5

一、 Gizmos 基础介绍

官方文档: Important Classes - Gizmos & Handles

Important Classes - Gizmos & Handles
The Gizmos and Handles classes allows you to draw lines and shapes in the Scene view and Game view, as well as interactive handles and controls. These two classes together provide a way for you to extend what is shown in these views and build interactive tools to edit your project in any way you like. For example, rather than entering numbers in the inspector, you could create a draggable circle radius gizmo around a non-player character in a game, which represents the area within which they can hear or see the player.
This page provides a simple overview of the Gizmos and Handles classes. For full documentation and an exhaustive reference of every member of the Gizmos and Handles classes, see the script reference pages for Gizmos and Handles.
重要的类: Gizmos 与 Handles
Gizmos 与 Handles 类允许你在 “SceneView”(场景视图)与 “GameView”(游戏视图)中绘制线段与图形,或是可交互控制柄(比如 Transform 移动的三轴箭头)
这两者一同作用,能让用户在这些视图中自定义扩展显示信息,或是构建自己喜欢的编辑、操作工具。
比如,相比起在 Inspector(检视器)中输入数字,你可以在游戏里创建一个可拖拽的圆环附着在 NPC 上,这代表了 NPC 发现玩家的视听检测范围。
本页面仅简单介绍了 Gizmos 与 Handles 的概览,对于详尽的文档与参考,请见:Gizmos Handles

简单地说,Gizmos 与 Handles 主要用于开发编辑中,可视化一些数据信息,便于 Debug。

二、 Gizmos 使用方式

关键字: MonoBehaviour.OnDrawGizmos, MonoBehaviour.OnDrawGizmosSelected

Gizmos 能且只能在 MonoBehaviour 相关子类中,使用特定的函数调用,其中:

OnDrawGizmos() 在每帧调佣。所有在 OnDrawGizmos 中的渲染都是可见的。

OnDrawGizmosSelected() 仅在脚本附加的物体被选择时调用。

示例代码:

1
2
3
4
5
6
7
public class GizmosTutotial : MonoBehaviour //新建脚本,继承自Mono
{
private Vector3 size = new Vector3(10, 10, 10); //定义Cube大小
private void OnDrawGizmos() //定义OnDrawGizmos方法,类似于Start,Update的定义方式 {
Gizmos.DrawCube(Vector3.zero, size); //使用Gizmos绘制一个Cube
}
}

将此脚本挂载在场景中的 Gameobject 上,我们可以看见:

230b9cf4b478676cf3d767ebaccc3000_MD5

值得注意的是,场景上方工具栏的【Gizmos】开关定义了全局 “是否显示 Gizmos”,如果我们把此按钮取消激活,场景将不会绘制任何 Gizmos 图形。当然,点开小三角,能看见更详细的设置信息。

869ed04cd52a29e64e953a32ed7c70b5_MD5

三、 Gizmos 基本绘制 API

注:本文不会对所有的 API 的全部定义进行讲解,一个方法实际存在多种重载,只介绍最基本最常用的方法;若想查看方法详细情况与重载,请自行在 Visual Studio 中 “转到定义”。

1. 立方体(Cube)

关键字: Gizmos.DrawCube

样例:

c326a9a6763c84c4bfd12db2dfc8df8d_MD5

示例代码:

1
2
3
4
5
6
7
public class GizmosTutotial : MonoBehaviour
{
private void OnDrawGizmos() {
Gizmos.DrawCube(Vector3.zero, Vector3.one);
//参数释义: 1.Cube中心点 2.Cube大小
}
}

注:为了方便起见,下文将省略 Mono 类定义与 OnDrawGizmos 定义,请记得所有 Gizmos 方法都是在 OnDrawGizmos/OnDrawGizmosSelected 中使用。

2. 视锥(Frustum)

关键字:Gizmos.DrawFrustum

样例:

353da638ae7dc1e42065f6972480eabf_MD5

示例代码:

1
2
Gizmos.DrawFrustum(Vector3.zero, 60, 300, 0.3f, 1.7f); 
//参数释义:1. 绘制中心 2. FOV角度 3. 远裁切平面 4. 近裁切平面 5. aspect 屏幕长宽比

3. 贴图(Texture)

关键字:Gizmos.DrawGUITexture

样例:

9391233742dad45977fa2f6d68285e26_MD5

示例代码:

1
2
3
4
5
6
public Texture texture;
...
if (texture != null)
{
Gizmos.DrawGUITexture(new Rect(0, 0, 10, 10), texture); //1.指定Rect 2.指定贴图
}

4. 图标(Icon)

关键字: Gizmos.DrawIcon

样例:

1800ab02ededdc321459bee495b37d9b_MD5

示例代码:

1
Gizmos.DrawIcon(Vector3.up, "MyIcon"); //1.绘制中心 2.图标名

注: 此处图标名 “MyIcon” 是指在路径:“Assets/Gizmos/…”中的图片名称。

DrawIcon 的前提是在资源文件夹 “Gizmos” 中存放想绘制的图片,定义好名称后,在代码中调用。

此处图片全路径为:“Assets/Gizmos/MyIcon.png”,所以写 “MyIcon”。

值得注意的是,所有在 Gizmos/Handles 中的绘制都不会发布到实际游戏中,只是在使用 Editor 开发时作为可视化辅助工具。

5. 线段(Line)

关键字: Gizmos.DrawLine

样例:

460b98a3ba29a85b6ab7a8fa8e6c10d5_MD5

示例代码:

1
Gizmos.DrawLine(Vector3.zero, Vector3.one);  //1.from(线段起点) 2.to(线段终点)

6. 网格(Mesh)

关键字:Gizmos.DrawMesh

样例:

561945fbcf6d8d26a26b6b1a7cc988d5_MD5

示例代码:

1
2
3
4
5
6
public Mesh mesh;
...
if (mesh != null)
{
Gizmos.DrawMesh(mesh, 0); //1. mesh 2.submeshIndex
}

7. 射线(Ray)

关键字:Gizmos.DrawRay

样例:

235beaaba702c9a58866666a7f1e066e_MD5

示例代码:

1
Gizmos.DrawRay(Vector3.zero, Vector3.one); //1.from 2.direction

8. 球体(Sphere)

关键字:Gizmos.DrawSphere

样例:

247b28c04497b05b489e9cf139243735_MD5

示例代码:

1
Gizmos.DrawSphere(Vector3.up, 1); //1.center 2.radius

9. 网格线(Wire)

关键字:Gizmos.DrawWireCube

样例:

6b5a54c7edbceda19a193e2bc8704d0f_MD5

示例代码:

1
2
3
4
5
6
7
8
public Mesh mesh;
...
Gizmos.DrawWireCube(Vector3.left, Vector3.one);
Gizmos.DrawWireSphere(Vector3.right, 1);
if (mesh != null)
{
Gizmos.DrawWireMesh(mesh, 0, Vector3.forward);
}

10. 颜色设置(Color)

关键字:Gizmos.Color

样例:

cb750620477aa4572ab72c03dace9c16_MD5

示例代码:

1
2
3
4
5
6
Gizmos.color = Color.red;
Gizmos.DrawSphere(Vector3.left, 0.5f);
Gizmos.color = Color.green;
Gizmos.DrawCube(Vector3.zero, Vector3.one);
Gizmos.color = Color.blue;
Gizmos.DrawLine(Vector3.right, Vector3.right * 2);

11. 矩阵设置(Matrix)

关键字:Gizmos.matrix

样例:

02f55548fd2da5c59c1c0cd89c8c9edf_MD5

示例代码:

1
2
Gizmos.matrix = this.transform.localToWorldMatrix; //这里使用挂载transform进行举例
Gizmos.DrawWireSphere(Vector3.zero, 1);

如果不对 Gizmos 的矩阵进行设置,默认为单位矩阵。

四、 Gizmos 经验使用

1. 绘制圆环(Circle)

由于 Gizmos 没有自带的圆环绘制 API,这里我们使用 DrawLine 自行计算圆环线段来实现圆环绘制。

样例:

022c8ae91e340dd2f539356a8be0a7ff_MD5

示例代码:

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
public Transform player; //绑定player
public Transform enemy; //绑定enemy
...
if (player != null && enemy != null)
{
Gizmos.color = Color.green; //设置绿色
Gizmos.DrawSphere(player.position, 0.5f); //在player处绘制球体
Gizmos.color = Color.red; //设置红色
Gizmos.DrawSphere(enemy.position, 0.5f); //在enemy处绘制球体
Gizmos.color = Color.yellow; //设置黄色
Gizmos.DrawLine(player.position, enemy.position); //绘制线段连接player enemy
DrawCircle(enemy, 3, 0.1f, Color.red); //绘制半径3的红色圆环
DrawCircle(enemy, 5, 0.1f, Color.green); //绘制半径5的绿色圆环
}
// theta越小,线段绘制数越多,圆环越平滑。
private void DrawCircle(Transform transform, float radius, float theta, Color color)
{
var matrix = Gizmos.matrix; //保存原本的矩阵信息
Gizmos.matrix = transform.localToWorldMatrix; //应用目标trans矩阵信息
Gizmos.color = color; //设置颜色
Vector3 beginPoint = new Vector3(radius, 0, 0); //定义起始点
Vector3 firstPoint = new Vector3(radius, 0, 0); //定义起始点
for (float t = 0; t < 2 * Mathf.PI; t += theta) //循环线段绘制
{
float x = radius * Mathf.Cos(t); //计算cos
float z = radius * Mathf.Sin(t); //计算sin
Vector3 endPoint = new Vector3(x, 0, z); //确定圆环采样点
Gizmos.DrawLine(beginPoint, endPoint); //绘制线段
beginPoint = endPoint; //迭代赋值
}
Gizmos.DrawLine(firstPoint, beginPoint); //绘制最后一段
Gizmos.matrix = matrix; //还原Gizmos矩阵信息
}

2. 绘制弧线(Arc)

样例:

37cedb4fd956ac6e14e8bb9e95d0aacf_MD5

示例代码:

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
public Transform enemy;
...
Gizmos.color = Color.red;
Gizmos.DrawSphere(enemy.position, 0.5f);
DrawArc(enemy, 1, 90, 0.1f, Color.green);
...
private void DrawArc(Transform transform, float radius, float angle, float theta, Color color)
{
var matrix = Gizmos.matrix;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.color = color;
Vector3 beginPoint = Vector3.zero;
Vector3 firstPoint = Vector3.zero;
var rad = Mathf.Deg2Rad * angle;
for (float t = 0; t < rad; t += theta)
{
float x = radius * Mathf.Cos(t);
float z = radius * Mathf.Sin(t);
Vector3 endPoint = new Vector3(x, 0, z);
Gizmos.DrawLine(beginPoint, endPoint);
beginPoint = endPoint;
}
Gizmos.DrawLine(firstPoint, beginPoint);
Gizmos.matrix = matrix;
}

值得注意的是,这种弧线的绘制算法,以单位圆为例,起点是从局部坐标(1,0,0)开始,也就是相对于 forward 的 right 方向,90 度弧线实际扫过的面积是从 right(1,0,0) 到 forward(0,0,1) 的面积。

所以这种算法不适用于 AI 视锥感知范围的绘制(90 度视觉范围应该是相对于 forward 方向的左右各 45 度)。

如何修改,那就是简单的数学题了,就待读者自行计算了。

五、小结

Gizmos 提供了基本的图形绘制,而场景文本(Text)绘制则是由 UnityEditor.Handles.Label 提供,我们将在后续介绍中对 Handles 进行说明。

5 Handles

关键字:SceneView, 3D GUIEditor.OnSceneGUI(), 3D Label

主要是在 Scene View 下,用于显示 / 控制物体的某些属性,便于 Debug 查看。

比如 Collider (碰撞盒) 的绿色线框就属于 Handles 的处理。

a13d231c147c8f089ac7c43edf98083a_MD5

一、 Handles 基础介绍

Handles
Custom 3D GUI controls and drawing in the Scene view.
Handles are the 3D controls that Unity uses to manipulate items in the Scene view. There are a number of built-in Handle GUIs, such as the familiar tools to position, scale and rotate an object via the Transform component. However, it is also possible to define your own Handle GUIs to use with custom component editors. Such GUIs can be a very useful way to edit procedurally-generated Scene content, “invisible” items and groups of related objects, such as waypoints and location markers.
在 Sceneview(场景视图中)自定义 3D GUI 控制器与绘制的类
Handles 是 Unity 在场景视图中,用于操控物体的 3D 控制器,已内置许多操作 GUI,比如我们熟悉的基于 Transform 对位置、缩放、旋转坐标的操作工具。当然,我们使用自定义的 Editor,定义自己的 Handle GUI 操作显示也是可能的。这种 GUIs 将会非常有用于程序化生成的场景内容、“不可见” 的子对象与组。比如路径点与坐标标记点。

一言以蔽之,许多需要在场景中程序化生成的数据,使用 Handles 类能轻松地显示控制柄来进行编辑,典型使用就是 “地编”。

二、 Handles 使用方式

1. 基于 Editor 类的 OnSceneGUI

在继承自 Editor 的类中,可以定义【OnSceneGUI()】

这样当此 Edtior 在活跃状态时(比如一个 Inspector 面板展开),在【OnSceneGUI()】方法内的内容将根据 SceneView 的刷新而调用,触发对应逻辑。

比如进行基本定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
------HandlesMono .cs------
using UnityEngine;
public class HandlesMono : MonoBehaviour
{

}

------HandlesMonoEditor.cs------
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(HandlesMono))]
public class HandlesMonoEditor : Editor
{
private void OnSceneGUI()
{
Debug.Log("在Editor OnSceneGUI中 调用....");
}
}

在【Hierarchy】中新建空对象,挂载脚本,点击对象在【Inspector】中显示【HandlesMono】信息,就可以看见信息打印:

b5dd698c4179afbc91c46102d969fb36_MD5

feba4702e358e90d9f14246482a816e2_MD5

当然,如果在【Hierarchy】中不选择对应物体,则【HandlesMono】不在检视器中显示,意味着【HandlesMono】 Disable 掉了,将不会调用【OnSceneGUI】,读者可自行尝试。

(Tips:如果想要它一直调用,但是又需要选择其他对象怎么办呢?可以点击【Inspector】右侧的【锁】按钮,加锁之后只会显示当前的【Inspector】信息,不会随着选择而更改。)

2. 基于 EditorWindow 的手动注册

有些时候,我们更想在一个 Window 中进行数据编辑与操作,但是 EditorWindow 可没有 OnSceneGUI,怎么办呢?这时候,需要手动对 SceneView 的刷新事件进行注册了。

比如:

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
using UnityEngine;
using UnityEditor;
public class HandlesWindow : EditorWindow
{
public static HandlesWindow m_mainWindow;

[MenuItem("MyWindows/HandlesWindow")]
public static void OpenWindow() //打开窗口
{
m_mainWindow = EditorWindow.GetWindow<HandlesWindow>();
m_mainWindow.Show();
}
private void OnEnable()
{
SceneView.duringSceneGui += OnSceneGUI; //对SceneView的刷新事件进行注册
}
private void OnDisable()
{
SceneView.duringSceneGui -= OnSceneGUI; //对SceneView的刷新事件取消注册
}
private void OnSceneGUI(SceneView sceneView) //自定义刷新事件的委托方法
{
Debug.Log("在 Window OnSceneGUI中 调用...."); //具体逻辑
}
}

6eef0d8aab13398992d5c8e8b5387213_MD5

75d8ed240c4130f4c71c7dcfd9674857_MD5

注:下文实例代码都是在 EditorWindow 中编写调用。

三、 Handles 基本绘制方法

1. Label(文本)

关键字: Handles.Label

样例:

dfa8e9f3785f7d0da3fd4b43c3989310_MD5

示例代码:

1
2
3
4
5
6
private Vector3 labelPos = Vector3.zero;
private string labelText = "这是在Handles中绘制的文本信息!!!";
...
//Window GUI 绘制(使用EditorGUILayout)
...
Handles.Label(labelPos, labelText); // 绘制Label

注:EditorWindow 的绘制只是为了显示信息方便,Editor 绘制方式见:Unity Editor 编辑器扩展功能详解(3)

2. Line/Dotted Line(线段 / 虚线)

关键字: Handles.DrawLine,Handles.DrawDottedLine

样例:

79d7db52823fd45f874ee7924ea77271_MD5

示例代码:

1
2
3
4
//1.from 2.to 3.thickness(厚度)决定线段厚度
Handles.DrawLine(Vector3.zero, new Vector3(-1, 1, 1), 2f);
//1.from 2.to 3.ScreenSpaceSize(屏幕空间大小)决定虚线长度
Handles.DrawDottedLine(Vector3.zero, Vector3.one, 2f);

3. Arc/Disc(弧线 / 圆环)

关键字:Handles.DrawWireArc,Handles.DrawSolidDisc

样例:

e7912229603646508f07c34b76b1e48e_MD5

示例代码:

1
2
3
4
5
6
7
Handles.color = new Color(1, 0, 0, 0.3f); //提前使用Color进行颜色设置,便于观察
Handles.DrawWireArc(Vector3.zero, Vector3.up, Vector3.right, 90, 2); //绘制线框弧线
Handles.DrawSolidArc(Vector3.zero, Vector3.up, Vector3.back, 90, 2); // 绘制填充弧线

Handles.color = new Color(0, 1, 0, 0.3f);
Handles.DrawSolidDisc(new Vector3(0, 0, 5), Vector3.up, 5); //绘制填充圆环
Handles.DrawWireDisc(new Vector3(0, 0, -5), Vector3.up, 5); //绘制线框圆环

4. Rectangle(矩形)

关键字:Handles.DrawSolidRectangleWithOutline

样例:

b150ddea6ad1df033b57c2f860a6cfb1_MD5

示例代码:

1
Handles.DrawSolidRectangleWithOutline(new Rect(0, 0, 1, 1), Color.green, Color.red);

5. GUI

关键字:Handles.BeginGUI(),Handles.EndGUI()

样例:

f0b5c2458b3aa9b8d6926be1a06c05f2_MD5

示例代码:

1
2
3
4
5
6
7
Handles.BeginGUI();
GUILayout.Label("我是SceneView中的Label");
if (GUILayout.Button("我是SceneView中的Button"))
{
Debug.Log("芜湖");
}
Handles.EndGUI();

四、 控制柄

实际上,Handles 核心功能是他的 Handle(控制)类,比如:Handles.PositionHandle 就是一个类似于 Position 移动的方法。经典使用例就是在一个 List<Vector3> 列表中,选择某个列表元素显示他的 Position 控制柄,然后进行移动。

又或者类似这种:

0cddc5ddb45f31146759d889fa0cd39e_MD5

1
2
3
4
private Vector3 root = Vector3.zero;
...
Handles.DrawSolidDisc(root, Vector3.up, 1); //画个圆,假设这是目标对象
root = Handles.PositionHandle(root, Quaternion.identity); //控制柄

但是笔者现阶段使用的较少,不是很熟悉,就不作解释了。

详情可见:Unity 的 Handles 类_林新发的博客 - CSDN 博客_unity 里 handle

或者看看官方 API 文档

或者是 Unity 编辑器扩展五 Handles 扩展 Scene 视图

笔者摸了。_(:з」∠)_

五、 小结

实际上,OnGUIScene + Handles 还有一种应用就是 “地形笔刷”,就像 Terrain 笔刷那样在 SceneView 里面刷刷刷调整高度、生成植被,这种功能我们也是能自己写一套的,核心是编辑模式下的 Event 事件。

具体的可见:Unity - Scripting API: Event

有空再写,大概(

6 Editor 常用辅助类

本节主要介绍在编辑器扩展中,经常打交道的几个编辑器类的常用方法,他们几乎只会在 Editor 模式下使用,不会在 Runtime 使用。

常见的有:ScriptableObject, EditorUtility,AssetDatabase,Selection,Event 等。
EditorUtility, AssetDatabase, Selection

关键字:EditorUtility, AssetDatabase, Selection, 序列化对象修改 / 保存,资源读取 / 保存

EditorUtility : unity 内置的关于 editor 使用的一些工具类,用的最多的应该是:

1
2
EditorUtility.SetDirty(Object obj); //标记目标资源为“脏”
AssetDatabase.SaveAssets(); //保存项目内的“脏数据”

AssetDatabase : 在编辑器模式下,对项目资源的管理。类似:

1
2
public static T LoadAssetAtPath<T>(string assetPath)whereT:UnityEngine.Object;//读取路径下的资源文件
public static string GetAssetPath(UnityEngine.Object assetObject);//获取目标资源文件的在项目中的路径

Selection : 含有当前选择的资源文件 / 文件夹的各种信息。

一个应用就是通过代码检索资源,然后选择目标资源,Inspector 就自动显示了目标资源文件信息。使用类似于

1
Selection.activeObject = obj; //obj就是目标资源文件

一、ScriptableObject

1. 简介与定义

这里初略介绍一下【ScriptableObject】,我们可以把他理解为一种存储于【硬盘】而不是【内存】上的 Config 资产文件。

就像一张 Excel 表一样,在一个【ScriptableObject】中配置的数据是持久化保存的。

简单案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//定义创建资产菜单,menuName : 路径名称, fileName:资产文件名
[CreateAssetMenu(menuName = "MyConfig/SimpleObj", fileName = "SimpleScriptableObejct")]
public class SimpleScriptableObject : ScriptableObject //继承自【ScriptableObject】
{
[SerializeField] //定义序列化
public List<InfoPair> pairs; //具体的数据字段
}

[System.Serializable] //定义序列化
public class InfoPair //一种数据类定义
{
public int someID;
public string someName;
public Texture someTex;
}

在【Project】视图中,右键打开菜单,即可创建我们自定义的文件:

68dcf1dd61580f24c12ea5f1e6477bb7_MD5

48ce37116534ede5dba3249c6792b9d4_MD5

410dc809b14aad166b3e934eabf73300_MD5

2. 基本使用

在使用时,可以在一个 Mono 文件里面定义字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
public SimpleScriptableObject scriptableObject; //定义字段

private void Start() //测试用,在Start时写逻辑
{
if (scriptableObject != null) //非空(比如提前在Inspector中赋值)
{
if (scriptableObject.pairs != null && scriptableObject.pairs.Count > 0) //list非空且有数据
{
var name = scriptableObject.pairs[0].someName; //获取下标0的数据
Debug.Log($"SO element name : {name}"); //打印信息
}
}
}

bca7d862ac96147daec37b74e69a6bcc_MD5

7698ecdfd7b0a91f5e784889390dee0f_MD5

基本上,所有的【配置数据】都可以用【ScriptableObject】保存(当然你也可以选择直接序列化为 bytes 文件,或者 json 文件等等,但是没有 unity 原生支持的 ScriptableObject 直观,而且可以直接在 Inspector 中修改数据)。

3. Tip

值得注意的是,我们选中一个【ScriptableObject】时,在【Inspector】是有显示 GUI 的,这意味着我们也可以自定义【ScriptableObject】的【Editor】,方法就跟自定义一个【MonoBehaviour】的【Editor】一样。

同时,手动在【ScriptableObject】面板中写入数据也不是我们想要的,更多是使用代码对其进行数据填充(比如读取一张 Excel 的数据转换成【ScriptableObject】保存形式)

4. 保存方式(重要)

但是!重点来了,我们在【Inspector】中的数据填充操作,编辑器是自动标识 “脏数据” 且刷新保存的,如果使用【代码填充】数据,切记使用以下方法进行保存!否则数据将不会保存!

1
2
3
//一般这段代码写在EditorWindow的OnGUI()的某个button下,实现“点击保存”功能
EditorUtility.SetDirty(simpleScriptableObject); //标记脏数据
AssetDatabase.SaveAssets(); //对所有未写入硬盘保存的脏数据信息保存

5. 使用代码创建 ScriptableObject

1
2
3
4
5
6
var simpleScriptableObject = ScriptableObject.CreateInstance<SimpleScriptableObject>();
//值得注意的是,这种创建方式没有创建资产实例,仅仅是在内存中创建,不会保存在硬盘上。

//如果想保存在硬盘上,需要使用如下方式
AssetDatabase.CreateAsset(simpleScriptableObject, "Assets/SomePath/simpleSO.asset");
//记得路径必须合法,也要加上文件后缀

二、EditorUtility

Utility:有用的,多功能的;

1. 脏标记

1
EditorUtility.SetDirty(someObject); //使用例见上文

2. 提示框

关键字: EditorUtility.DisplayDialog

fb5553cb9937a0b0535b6c1b20fa8aa8_MD5

1
2
var flag = EditorUtility.DisplayDialog("标题", "显示消息", "确认键");
//当然,这方法有许多重载,请自行查看

3. 进度条

关键字:EditorUtility.DisplayProgressBarEditorUtility.ClearProgressBar

426cc503ce17f12ec699f1da030a7d9d_MD5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private float value; //定义进度条填充值
private void OnGUI() //EditorWindow.OnGUI
{
if (GUILayout.Button("增加进度")) //手动增加进度
{
value += 0.1f;
value = Mathf.Clamp01(value); //约束value值到0~1
}
EditorUtility.DisplayProgressBar("进度条", "显示信息", value); //显示进度条
if (value == 1)
{
EditorUtility.ClearProgressBar(); //关闭进度条
}
}

当然还有其他许多 API,有兴趣请自行查看。

三、EditorGUIUtility

1. 搜索框(ObjectPicker)

关键字:EditorGUIUtility.ShowObjectPicker

5277cb8972d511418d6ad5eef823e871_MD5

1
2
3
4
5
6
7
8
9
10
11
private Texture tex;
...
if (GUILayout.Button("查找法线贴图"))
{
//参数释义
//1. 查找对象的引用
//2. 是否允许查找场景对象
//3. 查找对象名称过滤(比如这里的normal是指文件名称中有normal的会被搜索到)
//4. controlID, 默认写0
EditorGUIUtility.ShowObjectPicker<Texture>(tex, false, "normal", 0);
}

2. 选中提示

关键字: EditorGUIUtility.PingObject

320ac44a2c417315841697148ad8975b_MD5

8eb96a2237f68ef176e19e4983b260b5_MD5

1
EditorGUIUtility.PingObject(someObj);

四、AssetDatabase

1. 获取资产路径

关键字:AssetDatabase.GetAssetPath

faf4c132f496496724877bdc08c1d51d_MD5

1
2
3
4
5
6
7
8
9
private Texture tex;
private string path;
...
tex = EditorGUILayout.ObjectField(tex, typeof(Texture), false) as Texture;
if (GUILayout.Button("获取文件路径"))
{
path = AssetDatabase.GetAssetPath(tex);
}
EditorGUILayout.LabelField("资产路径是", path);

2. 加载资产

关键字: AssetDatabase.LoadAssetAtPath

71a31a009effd35c61530b86b5983a8a_MD5

1
2
3
4
5
6
7
8
9
private string texPath = "Assets/MyShaders/Brick_Normal.JPG"; //定义好路径
private Texture myTex; //定义好对象
...
EditorGUILayout.LabelField("自定义文件路径", texPath);
if (GUILayout.Button("加载资产"))
{
myTex = AssetDatabase.LoadAssetAtPath<Texture>(texPath); //加载文件
}
EditorGUILayout.ObjectField(myTex, typeof(Texture), false);

3. 创建资产

关键字:AssetDatabase.CreateAsset

详见上文: 5. 使用代码创建 ScriptableObject

实际上,【AssetDatabase】几乎涵盖了所有关于资产文件的 CRUD(增删改查)方法,其 API 的使用方式也是顾名思义,更多的请自行查阅。

五、Selection

Selection 主要是用于返回在编辑器中的选择对象(Object)信息,这个对象(Object)可以是一份资产文件(Asset、Prefab),也可是场景中的实例对象(Gameobject)。

一个经典应用就是,比如在 EditorWindow 中实例化了一个对象,出于操作友好性,应该把当前选择对象直接变为生成实例,这样在【Inspector】面板中就能显示对应信息了,这种操作就是通过 Selection 类实现的。

(注:Selection 在命名空间 UnityEditor 中定义)

1. Object Selection (对象选择)

1
2
3
4
5
6
7
8
9
Selection.activeGameObejct //返回当前点击的场景游戏物体或Project Prefab;选择多个则返回第一个选择;未选择则返回null
Selection.activeTransform //返回当前点击的场景游戏物体transform。
Selection.activeObject // 返回当前点击的场景游戏物体或Project资产文件。

Selection.gameObjects //返回一个数组,内容为当前点击场景物体或Project Prefab,非GameObject的选择不会加入数组。未选择返回长度为0的数组
Selection.objects //返回一个数组,内容为当前点击的场景物体或Project资源。
Selection.transforms //返回一个数组,内容为当前点击的场景物体。

Selection.selectionChanged //委托,选择变化时调用。

2. Static Methods (静态方法)

1
2
3
4
5
6
7
8
9
10
11
// Contains 选择项中是否包含物体
public static bool Selection.Contains(int instanceID)
public static bool Selection.Contains(Object obj)

// GetFiltered :返回按类型与模式过滤当前选择
// T : 过滤的泛型,只有某一种类型的对象才会被选择
// SelectionMode : 选择的模式,下文细讲
public static T[] GetFiltered<T>(SelectionMode mode);

// GetTransforms : 允许使用SelectionMode对选择类型进行颗粒度控制
public static Transform[] GetTransforms(SelectionMode mode);

3. SelectionMode 详解

1
2
3
4
5
6
7
Unfiltered 返回整个选择(无模式过滤)
TopLevel 只返回最上层transform, 子物体的transform将被过滤掉
Deep 返回选择对象与他所有的子对象
ExcludePrefab 排除选择中的prefab对象
Editable 排除任何无法被修改的对象(只选择可以编辑的对象)
Assets 返回Asset资源
DeepAssets 返回整个文件夹内容(根目录与子文件夹)

4. 示例案例

目的:获取某个文件夹下所有的 Texture 资产

74d1ba52c9630af739858dc5b595df83_MD5

f6a9b4d7679dcc1f99a2bdc7bd311f05_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void OnGUI() //EditorWindow的OnGUI方法
{
if (GUILayout.Button("选择一个文件夹,获取下面所有的Texture资源文件"))
{
var texs = Selection.GetFiltered<Texture>(SelectionMode.DeepAssets); //进行深度遍历所有文件夹
Debug.Log($"Tex总计数:{texs.Length}");
foreach (var tex in texs)
{
//使用AssetDatabase获取一次资产路径
Debug.Log($"Tex 文件名:{tex.name},文件路径 :{AssetDatabase.GetAssetPath(tex)}");
}
}
}

目的:新建一个 GameObject,同时选中

aadc434a6958fffe3a6a95625641b6c4_MD5

示例代码:

1
2
3
4
5
6
if (GUILayout.Button("创建一个 Cube,同时选中"))
{
var obj = GameObject.CreatePrimitive(PrimitiveType.Cube); //创建Cube
obj.name = "新建的Cube";
Selection.activeGameObject = obj; //选中Cube
}

六、 Event

Event 主要用于编辑器模式下,对 IMGUI 操作输入的响应。

在 Editor 模式(而不是 Playing 模式)下,写在代码中的类似【Input. GetKeyDown】是无效的,是无法获取键盘 / 鼠标操作信息的,取而代之的是使用类似【Event. current. type == EventType. MouseDown】检测输入。

它的使用可以在 Editor 的面板下,也可以在场景视图中(SceneView)。

1. 简单输入

0d673f3a46e635271a0c22c0827d1c16_MD5

示例代码:

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
public class EventTutorialWindow : EditorWindow
{
public static EventTutorialWindow m_mainWindow;

[MenuItem ("MyWindows/EventWindow")]
public static void OpenWindow()
{
m_mainWindow = EditorWindow.GetWindow<EventTutorialWindow>();
m_mainWindow.Show();
}
/// <summary>
/// 窗口激活时调用
/// </summary>
private void OnEnable()
{
SceneView.duringSceneGui += OnSceneView; //注册场景视图刷新回调
}
/// <summary>
/// 窗口关闭时调用
/// </summary>
private void OnDisable()
{
SceneView.duringSceneGui -= OnSceneView; //移除场景视图刷新回调
}
/// <summary>
/// SceneView刷新回调
/// </summary>
/// <param name="sceneView"></param>
private void OnSceneView(SceneView sceneView)
{
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
Debug.Log("鼠标左键在场景中Clicked....");
}
}
/// <summary>
/// 面板绘制
/// </summary>
private void OnGUI()
{
if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.A)
{
Debug.Log("键盘A键在Window面板中按下....");
}
}
}

2. 组合键

e27a5a3075475e8db669e3a06d04abdc_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
...//在上文OnSceneView()中加入以下代码即可
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
if (Event.current.alt) //检测alt键是否被按下
{
Debug.Log("在场景视图中按下了Alt + 鼠标左键");
}
else if (Event.current.shift) //检测shift键是否被按下
{
Debug.Log("在场景视图中按下了Shift + 鼠标左键");
}
}

3. 坐标转换

一个很常见的需求就是,我们在 SceneView 中点击了,希望在点击处发射一条射线检测场景中的物体,求一个交点然后放置什么东西(就像 Terrain 的笔刷一样),这是通过以下方式实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private LayerMask layerMask = -1; //设置为-1,包含所有Layer
...
private void OnSceneView(SceneView sceneView)
{
if (Event.current.type == EventType.MouseDown && Event.current.button == 0) //检测鼠标点击
{
var mousePos = Event.current.mousePosition; //获取鼠标在SceneView中的屏幕位置
var ray = HandleUtility.GUIPointToWorldRay(mousePos); //生成屏幕坐标-->世界坐标的射线
RaycastHit hit; //定义射线检测结果结构体
if (Physics.Raycast(ray, out hit, float.MaxValue, layerMask)) //进行射线检测,layerMask是检测层级
{
var obj = GameObject.CreatePrimitive (PrimitiveType. Sphere); //如果检测成功,创建一个球体。
obj.transform.position = hit.point; //把球体坐标设置为hit交点处。
}
}
}

样例:

d7875e2ed71496bda965bd80bcb56231_MD5

4. Tip

查阅资料的时候发现有这样一种使用方法:

1
Event.current.Use();

文档解释:Unity - Scripting API: Event.Use

文档上说,在某些 Event 检测后,使用 Event.current.Use (); 就能让其他 GUI elements 忽视这次 Event。

比如说使用 Alt + 鼠标左键创建物体,但是这个组合键也是 unity 场景自带的组合键,用于旋转 SceneView 的,使用

Event.current.Use (); 就会让自带的旋转功能失效。

主要是为了解决组合键冲突吧。

七、小结

这些玩意儿纯粹是【经验性质】的使用方法,基本没啥技术含量,多读读 API 文档就好了,比如:

AssetDatabase 的方法总结_Real_JumpChen 的博客 - CSDN 博客

7 Editor 美化

本节介绍一些常用的编辑器面板美化方法,牵涉【GUIStyle、GUISkin】,你可以理解为这两者是 Unity 内置的 GUI 绘制约束与模板。

一、字体格式 / 颜色

关键字:GUIStyle

样例:

a1bc15f68a5f425058d4015d3199b54d_MD5

示例代码:

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

public class PolishWindow : EditorWindow
{
public static PolishWindow m_mainWindow; //主要窗口对象

private GUIStyle defaultStyle = new GUIStyle(); //定义 GUIStyle

[MenuItem("MyWindows/PolishWindow")] //定义窗口菜单栏
public static void OpenWindow()
{
m_mainWindow = EditorWindow.GetWindow<PolishWindow>(); //创建窗口
m_mainWindow.Show(); //打开窗口
}
private void OnGUI()
{
defaultStyle.fontSize = 10; //字体大小: 10
defaultStyle.fontStyle = FontStyle.Normal; //字体样式:normal

defaultStyle.alignment = TextAnchor.MiddleCenter; //字体对齐方式: 居中对齐
defaultStyle.normal.textColor = Color.red; //字体颜色: 红色
EditorGUILayout.LabelField("这是红色字体", defaultStyle); //绘制GUI组件,使用 defaultStyle

defaultStyle.alignment = TextAnchor.MiddleLeft; //字体对齐方式: 水平靠左,垂直居中
defaultStyle.normal.textColor = Color.yellow; //字体颜色:黄色
defaultStyle.fontSize = 20; //字体大小: 20
EditorGUILayout.LabelField("这是黄色字体", defaultStyle); //绘制GUI组件

defaultStyle.normal.textColor = Color.green; //字体颜色: 绿色
defaultStyle.fontSize = 12; //字体大小 : 12
defaultStyle.fontStyle = FontStyle.Bold; //字体样式: Bold(加粗)
EditorGUILayout.SelectableLabel("这是绿色字体", defaultStyle); //绘制GUI
}
}

注:下文代码将只给出关键代码,实际使用位置如上所示在 OnGUI() 方法中。

二、GUI 颜色

关键字:GUI.color

样例:

0b6e8871bc6a4b56b8143308bb713080_MD5

示例代码:

1
2
3
4
5
6
GUI.color = Color.red;
GUILayout.Button("红色Button");
GUI.color = Color.green;
EditorGUILayout.LabelField("绿色Label");
GUI.color = Color.yellow;
EditorGUILayout.IntField("黄色Int Field", 20);

三、面板分区 / 搜索框

关键字:

样例:

2c1251c891a2d8414b9a7e6e66779ac4_MD5

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("这是【box】样式,用于显示面板的分区");
EditorGUILayout.Vector3Field("比如这是一个Vec3 Field", Vector3.one);
EditorGUILayout.BeginToggleGroup("比如这是一个Toggle Group", false);
EditorGUILayout.HelpBox("比如这是一个HelpBox", MessageType.Warning);
EditorGUILayout.TextArea("比如这是一个TextArea");
EditorGUILayout.EndToggleGroup();
EditorGUILayout.EndVertical();

EditorGUILayout.Space(10);

EditorGUILayout.BeginVertical("frameBox");
EditorGUILayout.LabelField("这是【frameBox】样式,用于显示面板的分区");
EditorGUILayout.BoundsField("比如这是一个BoundField", new Bounds());
EditorGUILayout.Slider("比如这是一个Slider", 5, 0, 10);
EditorGUILayout.TextArea("这是一个【SearchTextField】样式的文本框", "SearchTextField");
EditorGUILayout.EndVertical();

GUIStyle、GUISkin 的许多内置样式咱也不是很清楚全貌,只介绍了自己常用的几个,有额外需求的可以自行搜索看看。

四、案例实战

好了,你已经学会 1+1=2 了,下面我们来算一个微积分吧。

样例:

0b60da3d68b3484b52b702cac44d79d7_MD5

示例代码:

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

public class PolishWindow : EditorWindow
{
public static PolishWindow m_mainWindow; //主要窗口对象

private const int arrayLength = 35; //定义元素数组长度
private const int countPerPage = 10; //定义每一页的数量

private GUILayoutOption maxWidth = GUILayout.MaxWidth(120); //定义一个GUI组件的最大宽度
private Vector2 scrollRoot; //定义组件ScrollView的滚动Root
private int page = 1; //当前page
private int currentSelectionIndex = -1; //当前选择index
private int totalPage; //页面总数
private int[] someValues; //显示的元素
private bool foldOut; //折叠flag

[MenuItem("MyWindows/PolishWindow")] //定义窗口菜单栏
public static void OpenWindow()
{
m_mainWindow = EditorWindow.GetWindow<PolishWindow>(); //创建窗口
m_mainWindow.Show(); //打开窗口
}
/// <summary>
/// 当窗口打开时调用
/// </summary>
private void OnEnable()
{
someValues = new int[arrayLength]; //实例化数组
totalPage = (int)Mathf.Ceil((float)arrayLength / (float)countPerPage); //计算总页数
for (int i = 0; i < someValues.Length; i++)
{
someValues[i] = Random.Range(0, 100); //进行随机数据填充
}
}
/// <summary>
/// Editor面板绘制方法
/// </summary>
private void OnGUI()
{
scrollRoot = EditorGUILayout.BeginScrollView(scrollRoot); //开启 滚动视图
foldOut = EditorGUILayout.BeginFoldoutHeaderGroup(foldOut, "假装这是一个检索页面"); //开启 折叠组

if (foldOut) //如果折叠打开,则进行绘制
{
EditorGUILayout.BeginVertical("frameBox"); //开始垂直布局,使用GUIStyle : frameBox
for (int i = (page - 1) * countPerPage; i < someValues.Length; i++) //循环绘制,page 默认是1开始,这里【page-1】对齐数组下标。
{
if (i >= page * countPerPage) //一页只能绘制【countPerPage】个元素,但是arrayLength可能不止,在这里截断。
break;

if (currentSelectionIndex == i) //判断当前index与选择index
GUI.color = Color.green; //为真,突出显示选择项
else
GUI.color = Color.white; //为假,还原默认颜色

EditorGUILayout.BeginHorizontal("box"); //开始水平布局
if (GUILayout.Button("Select This One", maxWidth)) //绘制选择按钮,使用maxWidth限制组件宽度
{
currentSelectionIndex = i; //记录选择Index
}
EditorGUILayout.LabelField($"这是【{i}】号,他的随机值是:{someValues[i]}"); //显示数据信息
EditorGUILayout.EndHorizontal(); //结束水平布局
}

EditorGUILayout.BeginHorizontal(); //开始水平布局
EditorGUILayout.LabelField($"当前页数:{page.ToString()} / 总页数:{totalPage}"); //绘制页数信息
if (GUILayout.Button("上一页")) //绘制上一页Button
{
page -= 1; //当前页面-1
page = Mathf.Clamp(page, 1, totalPage); //由于页面数取值是1~totalPage,这里进行一次取值范围约束
}
if (GUILayout.Button("下一页")) //绘制下一页Button
{
page += 1; //当前页面+1
page = Mathf.Clamp(page, 1, totalPage); //由于页面数取值是1~totalPage,这里进行一次取值范围约束
}
EditorGUILayout.EndHorizontal(); //结束水平布局

EditorGUILayout.EndVertical(); //结束垂直布局
}

EditorGUILayout.EndFoldoutHeaderGroup(); //关闭 折叠组
EditorGUILayout.EndScrollView(); //关闭 滚动视图
}
}

小结:

实际上,但我们足够熟悉 Editor 编辑器这一套时,我们完全可以遵循 OOP 思想,进行编辑器组件的封装继承。

比如定一个总的 EditorWindow 类,定义一个抽象基类,然后在总窗口使用反射进行类型检索,对所有继承于抽象基类的进行汇总,在面板上显示对应的按钮,点哪个就实例化哪个窗口进行显示。

这样在写一个新窗口时,只需要继承于抽象基类,定义基本的逻辑与绘制就可以了,总窗口会自动地把新窗口归纳整理到自己的菜单栏中。

当然,编辑器的奇技淫巧还有许多,全靠经验积累。

终于写完了,完结撒花呜呜呜。

8 OnValidate

需要验证一些数据

我们都知道,搭建创建的编辑器扩展脚本,基本上都是给关卡设计或者策划人员用的。在编译游戏的时候对他们输入的一些数值进行校验,是一个好的习惯。他们不需要关心一些数值的限制,但是作为开发人员的我们是需要的。

如何使用 OnValidate?

Validate 字面意思是生效

在官方文档上仅有一个简短的说明,并且没有示例代码。

**编辑器模式下 OnValidate 仅在下面两种情况下被调用:

  • 脚本被加载时

  • Inspector 中的任何值被修改时

    下面是脚本中如何使用它:

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

public class OnValidateExample : MonoBehaviour {
public int size;

void OnValidate() {
Debug.Log("OnValidate");
}
}

上面的脚本挂在 gameobject 上,效果如下

8cb4d3512f6ea3b04ad50d64723251ff_MD5

Examples

下面是一些体现 OnValidate 函数强大功能的使用场景

角度简化

使用场景 - 我们需要将设计人员输入的角度限定在 - 359 到 359 之间,因为 360 相当于 0 度。

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

public class OnValidateExample : MonoBehaviour {
public float objectRotation;

void OnValidate() {
// objectRotation
objectRotation = objectRotation % 360;
}
}

效果如下:

e52f2ff8eec3500a102c38445a7abad5_MD5

二次方

使用场景 - 当需要设计人员输入 16 到 4096 之间 2 的整数次幂时
Unity 提供了 ClosestPowerOfTwo 函数,方便我们取得最接近的值。同时我们使用 RangeAttribute 属性来限定一下输入数值的区间,同时能更好的看出来处理后的值跟原始输入值的区别。

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

public class OnValidateExample : MonoBehaviour {
[RangeAttribute(16, 4096)]
public int textureSize;

void OnValidate() {
// textureSize
textureSize = Mathf.ClosestPowerOfTwo(textureSize);
}
}

效果如下

d6f4b4dc29d8e38e489497ed93c39b1d_MD5

关联值

使用场景 - 需要 “Nitro” 车的速度比其他车的速度大至少 20mph.

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

public class OnValidateExample : MonoBehaviour {
[RangeAttribute(10, 300)] [Tooltip("mph")]
public int maxCarSpeed;
[RangeAttribute(10, 300)] [Tooltip("mph")]
public int maxNitroSpeed;

const int minNitroSpeedExtra = 20;

void OnValidate() {
// speed check
if (maxNitroSpeed < maxCarSpeed + minNitroSpeedExtra)
maxNitroSpeed = maxCarSpeed + minNitroSpeedExtra;
}
}

效果如下

edb3a46436e8b41813a7c7d85c16eab7_MD5