1. 基本介绍
1. MenuItem
使用 MenuItem 特性,让静态函数作为一个菜单栏功能第三个参数层级,可控制显示的顺序。不同级别的参数最小值和最大值之间最小为11添加快捷键 %-CTRL# -Shift& -Alt[MenuItem(“Tools/mytest”)]
特殊路径 Assets/[] -添加到“Assets”菜单下,同时也显示在右键单击项目视图时弹出的菜单中。Asset/Create/[] - 添加在“Assets ->Create”子菜单中。CONTEXT/[任意组件名]/[] - 菜单项将出现在给定组件的右键单击菜单中。[MenuItem( “Tools/New Option %#a” )] 即Alt+Shift+A
[MenuItem( “Tools/Item2 _g” )] 即G
[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时:
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. 绘图方法
GUILayoutEditorGUILayout2. 示例:窗口形式
主面板
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){//重载BeginPropertyEditorGUI.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。这就是为什么要给每个属性指定RectEditorGUI.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);}}