1. 基本介绍
1. MenuItem
- 使用 MenuItem 特性,让静态函数作为一个菜单栏功能
[MenuItem(“Tools/mytest”)]
- 第三个参数层级,可控制显示的顺序。不同级别的参数最小值和最大值之间最小为11
- 添加快捷键
- %-CTRL
- # -Shift
- & -Alt
[MenuItem( “Tools/New Option %#a” )] 即Alt+Shift+A
[MenuItem( “Tools/Item2 _g” )] 即G
- 特殊路径
- Assets/[] -添加到“Assets”菜单下,同时也显示在右键单击项目视图时弹出的菜单中。
- Asset/Create/[] - 添加在“Assets ->Create”子菜单中。
- CONTEXT/[任意组件名]/[] - 菜单项将出现在给定组件的右键单击菜单中。
[MenuItem("CONTEXT/Rigidbody/Do Something")]
public static void DoSomething (MenuCommand command) {
Rigidbody body= command.context as Rigidbody ;
body.mass = 5 ;
}
- GameObject/[] 菜单项会出现在层级的右键菜单中。但你需要给定一个层级 < 50
[MenuItem(“GameObject/mytest2”, false, 12)]
- 限定
通过第二个参数来限定该按钮是否启用
// 效果
[MenuItem("Assets/SetMass", false, 12)]
static void SetMass()
{
Rigidbody rigidbody = Selection.gameObjects[0].GetComponent<Rigidbody>();
rigidbody.mass = 5;
}
// 约束
[MenuItem("Assets/SetMass", true)]
static bool SetMassOption()
{
return Selection.gameObjects[0].GetComponent<Rigidbody>() != null;
}
2. AddComponentMenu
添加组件到“添加组件”菜单下。
3. ContextMenu、ContextMenuItem
在继承MonoBehaviour时:
特性 | 描述 |
---|---|
ContextMenu | 添加到自身的右键菜单中 |
ContextMenuItem | 添加到菜单列表 |
public class MyObject : MonoBehaviour
{
[ContextMenuItem("+1", "AddValue")]
public int value;
public string data;
[ContextMenu("重置")]
void ResetData()
{
value = 0;
data = "";
}
void AddValue()
{
value++;
}
}
4. ScriptableWizard
继承该类可生成一个点击后关闭并重置的面板,可进行方便的对象创建修改操作
using UnityEngine;
using UnityEditor;
public class MyObjectEditor : ScriptableWizard
{
// 参数
public string changeName = "未命名";
// 创建按钮到编辑器,并设置按钮名称以及标题
[MenuItem("Tools/CreateWizard")]
public static void CreateWizard()
{
// 一个点击后关闭并重置的面板
DisplayWizard<MyObjectEditor>("统一修改", "修改", "第二个按钮");
}
// 点击后关闭界面并执行
private void OnWizardCreate()
{
// 修改选取对象的名字
foreach (var obj in Selection.gameObjects)
{
// 操作记录到unity记录中,以便ctrl+z撤回
Undo.RecordObject(obj, "修改名字");
obj.name = changeName;
}
// 在界面显示提示信息,仅在未关闭时显示
ShowNotification(new GUIContent(Selection.gameObjects.Length + "个对象被修改了"));
}
// 点击另一个按钮,这不会关闭界面
private void OnWizardOtherButton() => OnWizardCreate();
// 仅在初始化和值改变时变化
private void OnWizardUpdate() => HelpInfor();
// 选中物体时调用
private void OnSelectionChange() => HelpInfor();
// 用于提示信息
void HelpInfor()
{
if (Selection.gameObjects.Length == 0)
{
// 错误信息
errorString = "请选择一个对象!";
helpString = "";
}
else
{
// 帮助信息
errorString = "";
helpString = "您选择了" + Selection.gameObjects.Length + "个对象";
}
}
}
5. [UnityEditor.InitializeOnLoadMethod]
每次Unity编译都会运行一次
2. 编译器绘图
1. 绘图方法
- GUILayout
方法 | 描述 |
---|---|
Label | 写入一个文本 |
BeginVertical | 开始垂直绘图 |
BeginHorizontal | 开始水平绘图 |
BeginScrollView | 开始滚动视图 |
Button | 按钮控件,放在IF语句中判断点击 |
- EditorGUILayout
方法 | 描述 |
---|---|
TextField | 文本框 |
EnumPopup | 枚举选项 |
IntField | 整数选项 |
ObjectField(object, typeof, false) | 一个Unity对象 |
PropertyField | 一个SerializedObject类中的对象 |
2. 示例:窗口形式
主面板
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class AudioWindowEditor : EditorWindow
{
static SaveData data;
string path = "Assets/Resources/MainData.asset";
string text;
string search = "";
static GUIStyle textStyle;
static AudioWindowEditor window;
[MenuItem("Tools/test")]
public static void CreateWindow()
{
// 固定大小窗口
// Rect rect = new Rect(0, 0, 800, 600);
// AudioWindowEditor window = GetWindowWithRect(typeof(AudioWindowEditor), rect) as AudioWindowEditor;
if (window == null)
{
window = GetWindow<AudioWindowEditor>("数据管理");
textStyle = new GUIStyle("HeaderLabel");
textStyle.fontSize = 24;
}
window.Show();
}
/// <summary>
/// 每秒调用十次
/// </summary>
private void OnInspectorUpdate()
{
}
private void OnGUI()
{
// 设置标题和其显示的值,将其值返回
// text = EditorGUILayout.TextField("输入文字", text);
if (data == null) data = AssetDatabase.LoadAssetAtPath<SaveData>(path);
if (data == null)
{
// new SaveData()可替换为 CreateInstance<SaveData>()
AssetDatabase.CreateAsset(new SaveData(), path);
AssetDatabase.SaveAssets();
data = AssetDatabase.LoadAssetAtPath<SaveData>(path);
}
var settings = new SerializedObject(data);
GUI.skin.label.fontSize = 16;
GUILayout.Label("基本内容");
EditorGUILayout.PropertyField(settings.FindProperty("key"), new GUIContent("名称"));
// 第一栏
// GUILayout.BeginHorizontal("Wizard Box");
search = EditorGUILayout.TextField("", search, "SearchTextField");
// GUILayout.EndHorizontal();
// 第二栏
GUILayout.BeginVertical("Wizard Box", GUILayout.MaxHeight(1));
GUILayout.BeginHorizontal(new GUIStyle("HelpBox")
{
padding = new RectOffset(10, 0, 4, 4)
});
GUILayout.Label("名称", new GUIStyle("SettingsHeader")
{
fontSize = 16,
alignment = TextAnchor.MiddleLeft,
});
GUILayout.Label("值", new GUIStyle("SettingsHeader")
{
fontSize = 16,
alignment = TextAnchor.MiddleLeft,
});
GUILayout.EndHorizontal();
KeyValue removeOne = null;
KeyValue moveUp = null;
KeyValue moveDown = null;
// 这其实是错误的写法,不应该直接操纵数据源,而是通过settings.FindProperty("datas")arraySize和settings.FindProperty("datas").GetArrayElementAtIndex(i)
for (int i = 0; i < data.keyvalues.Count; i++)
{
var dataSlot = data.keyvalues[i];
if (dataSlot.key == null) dataSlot.key = "";
if (dataSlot.key.ToLower().Contains(search.ToLower()))
{
GUILayout.Space(2);
GUILayout.BeginHorizontal("HelpBox");
dataSlot.key = EditorGUILayout.TextField(dataSlot.key);
dataSlot.value = (GameObject)EditorGUILayout.ObjectField(dataSlot.value, typeof(GameObject), false);
if (i != 0)
{
if (GUILayout.Button("↑", GUILayout.MaxWidth(24), GUILayout.MaxHeight(18))) moveUp = dataSlot;
}
else GUILayout.Button("", GUILayout.MaxWidth(24), GUILayout.MaxHeight(18));
if (i != data.keyvalues.Count - 1)
{
if (GUILayout.Button("↓", GUILayout.MaxWidth(24), GUILayout.MaxHeight(18))) moveDown = dataSlot;
}
else GUILayout.Button("", GUILayout.MaxWidth(24), GUILayout.MaxHeight(18));
if (GUILayout.Button("-", GUILayout.MaxWidth(24), GUILayout.MaxHeight(18))) removeOne = dataSlot;
GUILayout.EndHorizontal();
}
}
if (removeOne != null) data.keyvalues.Remove(removeOne);
if (moveUp != null)
{
int index = -1;
for (int i = 0; i < data.keyvalues.Count; i++) if (data.keyvalues[i] == moveUp) index = i;
if (index != -1)
{
var move = moveUp;
data.keyvalues.Remove(moveUp);
data.keyvalues.Insert(index - 1, move);
}
}
if (moveDown != null)
{
int index = -1;
for (int i = 0; i < data.keyvalues.Count; i++) if (data.keyvalues[i] == moveDown) index = i;
if (index != -1)
{
var move = moveDown;
data.keyvalues.Remove(moveDown);
data.keyvalues.Insert(index + 1, move);
}
}
if (GUILayout.Button("+"))
{
data.keyvalues.Add(new KeyValue());
}
GUILayout.EndVertical();
// 应用修改
settings.ApplyModifiedPropertiesWithoutUndo();
}
// 修改asset文件
[MenuItem("Tools/CardDataArraysLoad")]
public static void CardDataArraysLoad()
{
string path = "Assets/Configs/CardDataArrayConfig.asset";
CardDataArrayConfig config = AssetDatabase.LoadAssetAtPath<CardDataArrayConfig>(path);
config.baseCardDatas = new CardData[baseCardDatas.Count];
EditorUtility.SetDirty(config);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
数据类
using UnityEngine;
using System.Collections.Generic;
public class SaveData : ScriptableObject
{
public string key;
public List<KeyValue> keyvalues;
}
[System.Serializable]
public class KeyValue
{
public string key;
public GameObject value;
}
3. 示例:项目设置中添加项形式
using UnityEditor;
using UnityEngine;
public class MainDataEditor
{
static MainData data;
[SettingsProvider]
public static SettingsProvider MyCustom()
{
string path = "Assets/Resources/Data/MainData.asset";
var provider = new SettingsProvider("MainData", SettingsScope.Project)
{
// 默认情况下,如果未提供标签,则路径的最后一个标记将用作显示名称。
label = "主线资源库",
// 填充搜索关键字
// 创建SettingsProvider并将其绘图(IMGUI)功能初始化
guiHandler = (searchContext) =>
{
if (data == null) data = AssetDatabase.LoadAssetAtPath<MainData>(path);
if (data == null)
{
// new MainData()可替换为 CreateInstance<MainData>()
AssetDatabase.CreateAsset(new MainData(), path);
AssetDatabase.SaveAssets();
data = AssetDatabase.LoadAssetAtPath<MainData>(path);
}
var settings = new SerializedObject(data);
GUI.skin.label.fontSize = 16;
GUILayout.Label("基本扩展");
EditorGUILayout.PropertyField(settings.FindProperty("blood"), new GUIContent("溅血特效"));
EditorGUILayout.PropertyField(settings.FindProperty("spark"), new GUIContent("火星特效"));
EditorGUILayout.PropertyField(settings.FindProperty("sharp"), new GUIContent("锋利特效"));
// 应用修改
settings.ApplyModifiedPropertiesWithoutUndo();
}
};
return provider;
}
}
数据类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
[CreateAssetMenu(fileName = "MainData", menuName = "Data/MainData", order = 1)]
public class MainData : ScriptableObject
{
/// <summary>
/// 溅血特效
/// </summary>
public GameObject blood;
/// <summary>
/// 火星特效
/// </summary>
public GameObject spark;
/// <summary>
/// 锋利特效
/// </summary>
public GameObject sharp;
/// <summary>
/// 结束的线
/// </summary>
public GameObject endLine;
/// <summary>
/// 结束光环
/// </summary>
public GameObject endNova;
/// <summary>
/// 孔明灯
/// </summary>
public GameObject KongMingLight;
/// <summary>
/// 枪线
/// </summary>
public GameObject weaponLine;
public TileBase[] tileBases;
public AudioClip[] audioClips;
/// <summary>
/// 背包
/// </summary>
public Bag bag;
/// <summary>
/// 提示信息
/// </summary>
public Infor infor;
public Dialog dialog;
/// <summary>
/// 失败面板
/// </summary>
public Fail failPanel;
public Fail winPanel;
public LoadScene loadScenePanel;
public Sprite[] move_Sprs;
public WeaponData[] weapons;
/// <summary>
/// 保存值
/// </summary>
public SaveData saveData;
public Player player;
/// <summary>
/// 物品列表
/// </summary>
public Good[] goods;
}
3. 实例化自定义-PropertyDrawer
数据类
using System;
using UnityEngine;
[Serializable]
public class MyCustomVariable
{
public string name;
public int key;
public float value;
public bool flag;
public KeyCode input;
public Color color;
public GameObject obj;
public Vector3 dir;
}
使用PropertyDrawer实例化到一行中
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
//给MyCustomVariable指定编译器实例化渲染方式
[CustomPropertyDrawer(typeof(MyCustomVariable))]
public class MyCustomVariableDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//重载BeginProperty
EditorGUI.BeginProperty(position,label,property);
//绘制单元格头的标签名,不需要可以去掉
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
//Unity默认的每个属性字段都会占用一行,我们这里希望一条自定义Property占一行
//要是实现这个要求我们分三步: 1. 取消缩进 2. 设置PropertyField 3.还原缩进
//不要缩进子字段,由我们自定义间距
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
//计算要用到的属性显示rect Rect(x,y,width,height)x,y是左顶点
var nameRect = new Rect(position.x, position.y, 50, position.height);
var keyRect = new Rect(position.x + 60, position.y, 40, position.height);
var flagRect = new Rect(position.x + 110, position.y, 20, position.height);
var inputRect = new Rect(position.x + 140, position.y, 60, position.height);
var colorRect = new Rect(position.x + 210, position.y, 50, position.height);
var dirRect = new Rect(position.x + 270, position.y, 100, position.height);
var objRect = new Rect(position.x + 380, position.y, position.width - 390, position.height);
//绘制字段 - 将GUIContent.none传递给每个字段,以便绘制它们而不是用标签
//属性绘制器不支持布局来创建GUI;
//因此,您必须使用的类是EditorGUI而不是EditorGUILayout。这就是为什么要给每个属性指定Rect
EditorGUI.PropertyField(nameRect, property.FindPropertyRelative("name"), GUIContent.none);
EditorGUI.PropertyField(keyRect, property.FindPropertyRelative("key"), GUIContent.none);
EditorGUI.PropertyField(flagRect, property.FindPropertyRelative("flag"), GUIContent.none);
EditorGUI.PropertyField(inputRect, property.FindPropertyRelative("input"), GUIContent.none);
EditorGUI.PropertyField(colorRect, property.FindPropertyRelative("color"), GUIContent.none);
EditorGUI.PropertyField(dirRect, property.FindPropertyRelative("dir"), GUIContent.none);
EditorGUI.PropertyField(objRect, property.FindPropertyRelative("obj"), GUIContent.none);
//将缩进还原
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
//设定单元格高度,默认返回为默认高度
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return base.GetPropertyHeight(property, label);
}
}
列表元素的编译器扩展-PropertyDrawer
using System;
using Cinemachine.Editor;
using Framework.GameObjectBinder.Views.Variables;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEditorInternal;
using UnityEngine;
[CustomPropertyDrawer(typeof(MyCustomVaribleArray))]
public class MyCustomVariableArrayDrawer : PropertyDrawer
{
private ReorderableList list;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//获取要渲染的列表
property = property.FindPropertyRelative("variables");
//设定动作函数
if (list == null)
{
list = new ReorderableList(property.serializedObject, property, true, true, true, true)
{
multiSelect = true,
elementHeight = 22,
drawElementCallback = DrawElement,
drawHeaderCallback = DrawHeader,
// onAddCallback =
onAddDropdownCallback = OnAddElement,
onRemoveCallback = OnRemoveElement,
drawElementBackgroundCallback = DrawElementBackground
};
}
else
{
list.serializedProperty = property;
}
list.DoList(position);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
// 基础高度
var height = base.GetPropertyHeight(property, label) + 60;
// 计算列表
var variables = property.FindPropertyRelative("variables");
for (var i = 0; i < variables.arraySize; i++)
height += EditorGUI.GetPropertyHeight(variables.GetArrayElementAtIndex(i)) + 5;
return height;
}
private void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
{
var variables = list.serializedProperty;
if (index < 0 || index >= variables.arraySize)
return;
var variable = variables.GetArrayElementAtIndex(index);
var x = rect.x;
var y = rect.y + 2;
var width = rect.width - 20;
var height = rect.height - 4;
var variableRect = new Rect(x, y, width, height);
EditorGUI.PropertyField(variableRect, variable, GUIContent.none);
var buttonRect = new Rect(variableRect.xMax + 5, y + 1, 18, 18);
if (GUI.Button(buttonRect, new GUIContent("+")))
{
Debug.Log("点击了按钮");
}
}
private void DrawHeader(Rect rect)
{
GUI.Label(rect, "数据列表");
}
private void OnAddElement(Rect rect, ReorderableList list)
{
var variables = list.serializedProperty;
//当前列表数量
var index = variables.arraySize > 0 ? variables.arraySize : 0;
//生成多选菜单
var menu = new GenericMenu();
//根据枚举项,决定生成菜单选项名称
foreach (KeyCode variableType in Enum.GetValues(typeof(KeyCode)))
{
var type = variableType;
menu.AddItem(new GUIContent(variableType.ToString()), false,
context =>
{
AddVariable(variables, index, type);
}, null);
}
menu.ShowAsContext();
}
//根据选项,生成元素
protected virtual void AddVariable(SerializedProperty variables, int index, KeyCode type)
{
if (index < 0 || index > variables.arraySize)
return;
variables.serializedObject.Update();
//在在列表底插入元素,相当于追加元素
variables.InsertArrayElementAtIndex(index);
//获取到最新添加的元素,并根据选项修改枚举值
var variableProperty = variables.GetArrayElementAtIndex(index);
variableProperty.FindPropertyRelative("input").SetEnumValue(type);
//写入数据到实例化对象
variables.serializedObject.ApplyModifiedProperties();
//清除焦点
GUI.FocusControl(null);
}
private void OnRemoveElement(ReorderableList list)
{
var variables = list.serializedProperty;
var selectedIndices = list.selectedIndices;
//如果没选择任何表单元素,那么删除最后一个
if (selectedIndices.Count == 0)
{
AskRemoveVariable(variables, variables.arraySize - 1);
}
//删除所有选中的对象
for (var i = selectedIndices.Count - 1; i >= 0; i--)
{
var index = selectedIndices[i];
list.Deselect(index);
AskRemoveVariable(variables, index);
}
}
protected virtual void AskRemoveVariable(SerializedProperty variables, int index)
{
if (variables == null || index < 0 || index >= variables.arraySize)
return;
var variable = variables.GetArrayElementAtIndex(index);
var name = variable.FindPropertyRelative("name").stringValue;
// 某项被填入值时,弹出提示避免直接清除
if (string.IsNullOrEmpty(name))
{
RemoveVariable(variables, index);
return;
}
if (EditorUtility.DisplayDialog("Confirm delete",
string.Format("Are you sure you want to delete the item named \"{0}\"?", name), "Yes", "Cancel"))
RemoveVariable(variables, index);
}
protected virtual void RemoveVariable(SerializedProperty variables, int index)
{
if (index < 0 || index >= variables.arraySize)
return;
variables.serializedObject.Update();
//清除表单元素
variables.DeleteArrayElementAtIndex(index);
//写入数据到实例化对象
variables.serializedObject.ApplyModifiedProperties();
GUI.FocusControl(null);
}
private void DrawElementBackground(Rect rect, int index, bool isActive, bool isFocused)
{
ReorderableList.defaultBehaviours.DrawElementBackground(rect, index, isActive, false, true);
}
}