赞
踩
模式一:通过SerializedField方式自动生成UI变量代码,并在Inspector面板自动填充UI变量
模式二:通过GetComponent初始化变量
移除变量:
一键快速添加Button按钮事件(支持批量处理):
2022.9.1更新:
新增了变量绑定模式设置:
模式一:先点Generate Script自动生成[SerializeField]变量,然后点击Bind Properties将组件赋值给[SerializeField]变量; 优点是可以在Inspector面板上显示出每一个变量值,缺点是由于某种bug,直接生成完[SerialzedField]变量代码后,即使做了等待脚本编译和资源导入全部完成后再对[SerializeField]变量赋值,依然会出现"Type mismatch"的情况。所以只能把生成代码和绑定变量拆成两个按钮步骤才能正常。
模式二:不用SerializeField,直接生成通过GetComponent从已绑定GameObject上获取组件的代码。优点是步骤简单,面板显示简洁,不需要保存多个序列化对象。缺点是在界面逻辑实例化时会有调用GetComponent的微微微微微小的开销。
1. 在Prefab UI界面中右键点击节点,指定变量为private/protected/public,然后弹出可选择的变量类型菜单。
2. 添加绑定后,数据在Inspector面板显示,方便修改变量相关类型等;支持添加变量和数组变量;默认变量名为节点名,变量名重复时自动修改变量名,可自行编辑变量名;支持数组添加/移除;拖动数组元素可调整索引;
3. 选择变量类型。根据绑定节点筛选出交集组件,展示在下拉列表里以便选择变量类型
4. 在Hierarchy面板直观显示已绑定的组件信息:
5. 绑定完UI变量后点击生成代码按钮一键生成对应UIForm的partial类绑定脚本;如果当前UIForm逻辑脚本类没有添加partial修饰符,程序会自动提示添加。UI绑定代码独立生成partial脚本是为了避免与UIForm界面逻辑脚本冲突,比如VS中存在为保存的代码,这时直接操作UIForm类会为保存部分丢失的可能。
1. 定义SerializeFieldData类用于记录变量数据,在UI逻辑的基类添加_fields用于存放变量数据列表
- [Serializable]
- public class SerializeFieldData
- {
- public string VarName; //变量名
- public GameObject[] Targets;//关联的GameObject
- public string VarType; //变量类型FullName,带有名字空间
- public bool Foldout; //列表展开
- public string VarPrefix;//变量private/protect/public
- public string VarSampleType;//变量类型不带名字空间
- public SerializeFieldData(string varName, GameObject[] targets = null)
- {
- VarName = varName;
- Targets = targets ?? new GameObject[1];
- Foldout = true;
- }
- public T GetComponent<T>(int idx) where T : Component
- {
- return Targets[idx].GetComponent<T>();
- }
- public T[] GetComponents<T>() where T : Component
- {
- T[] result = new T[Targets.Length];
- for (int i = 0; i < Targets.Length; i++)
- {
- result[i] = Targets[i].GetComponent<T>();
- }
- return result;
- }
- }
-
- public class UIFormBase : UIFormLogic
- {
- [HideInInspector][SerializeField] SerializeFieldData[] _fields = new SerializeFieldData[0];
- ...
- }
2. 自定义UIFormEditor类,扩展编辑器:
[CustomEditor(typeof(UIFormBase), true)] 传入true使编辑器扩展对所有继承自UIFormBase的类都能拥有编辑器扩展部分出的功能。
- [CustomEditor(typeof(UIFormBase), true)]
- public class UIFormBaseEditor : Editor
- {
- ...
- }
通过注册 EditorApplication.hierarchyWindowItemOnGUI = delegate (int id, Rect rect){...},可以自定义Hierarchy面板的UI显示。如,在已经添加到变量的节点后面用绿色文字显示出变量信息
- private static GUIStyle normalStyle;
- private static GUIStyle selectedStyle;
- [InitializeOnLoadMethod]
- static void InitEditor()
- {
-
- normalStyle = new GUIStyle();
- normalStyle.normal.textColor = Color.white;
-
- selectedStyle = new GUIStyle();
- selectedStyle.normal.textColor = Color.green;
- Selection.selectionChanged = () =>
- {
- addToFieldToggle = false;
- removeToFieldToggle = false;
- };
-
- EditorApplication.hierarchyWindowItemOnGUI = delegate (int id, Rect rect)
- {
- OpenSelectComponentMenuListener(rect);
- var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
- if (prefabStage == null)
- {
- return;
- }
- var uiForm = prefabStage.prefabContentsRoot.GetComponent<UIFormBase>();
- if (uiForm == null)
- {
- return;
- }
- var curDrawNode = EditorUtility.InstanceIDToObject(id) as GameObject;
- if (curDrawNode == null)
- {
- return;
- }
- var fields = uiForm.GetFieldsProperties();
- SerializeFieldData drawItem = null;
- foreach (var item in fields)
- {
- if (item == null) continue;
- if (ArrayUtility.Contains(item.Targets, curDrawNode))
- {
- drawItem = item;
- break;
- }
- }
- if (drawItem != null)
- {
- rect.x = rect.xMax - Mathf.Min(300, rect.size.x * 0.35f);
- GUI.Label(rect, string.Format("{0} {1} {2}", drawItem.VarPrefix, drawItem.VarSampleType, drawItem.VarName), selectedStyle);
- }
-
- };
- }
3. 自定义右键菜单:
通过[MenuItem("GameObject/Add UIForm Variable/private", false, priority = 0)],设置路径为GameObject/就出现在Hierarchy的右键菜单里。
- static int varPrefixIndex = -1;
- static bool mShowSelectTypeMenu;//是否显示变量类型选择菜单
- [MenuItem("GameObject/Add UIForm Variable/private", false, priority = 0)]
- private static void AddPrivateVariable2UIForm()
- {
- varPrefixIndex = 0;
- mShowSelectTypeMenu = true;
- }
4. 变量类型选择菜单框:
在EditorApplication.hierarchyWindowItemOnGUI回调中调用了OpenSelectComponentMenuListener(rect)方法,然后获取选中节点的所有组件的交集,显示在菜单中:
- private static void OpenSelectComponentMenuListener(Rect rect)
- {
- if (mShowSelectTypeMenu)
- {
- int idx = -1;
- var strArr = GetPopupContents(GetTargetsFromSelectedNodes(Selection.gameObjects));
- var contents = new GUIContent[strArr.Length];
- for (int i = 0; i < strArr.Length; i++)
- {
- contents[i] = new GUIContent(strArr[i]);
- }
- rect.width = 200;
- rect.height = MathF.Max(100, contents.Length * rect.height);
- EditorUtility.DisplayCustomMenu(rect, contents, idx, (userData, contents, selected) =>
- {
- AddToFields(varPrefixArr[varPrefixIndex], contents[selected]);
- }, null);
- mShowSelectTypeMenu = false;
- }
- }
5. 根据选择的变量信息创建SerializeFieldData并存入UI基类的_fields变量列表;
通过PrefabStageUtility.GetCurrentPrefabStage().prefabContentsRoot可以拿到已打开Prefab的根节点。
- private static void AddToFields(string varPrefix, string varType)
- {
- if (addToFieldToggle)
- {
- return;
- }
- if (Selection.count <= 0) return;
- var uiForm = GetPrefabRootComponent<UIFormBase>();
- if (uiForm == null)
- {
- Debug.LogWarning("UIForm Script is not exist.");
- return;
- }
- var targets = GetTargetsFromSelectedNodes(Selection.gameObjects);
-
- var fieldsProperties = uiForm.GetFieldsProperties();
- if (fieldsProperties == null) fieldsProperties = new SerializeFieldData[0];
- Undo.RecordObject(uiForm, uiForm.name);
- SerializeFieldData field = new SerializeFieldData(GenerateFieldName(fieldsProperties, targets), targets);
- field.VarType = varType;
- field.VarSampleType = GetSampleType(field.VarType).Name;
- field.VarPrefix = varPrefix;
-
- ArrayUtility.Add(ref fieldsProperties, field);
- uiForm.ModifyFieldsProperties(fieldsProperties);
- EditorUtility.SetDirty(uiForm);
- addToFieldToggle = true;
- removeToFieldToggle = false;
- }
6. Inspector面板扩展:
先了解一些比较常用的编辑器扩展API:
1.垂直布局: EditorGUILayout.BeginVertical();EditorGUILayout.EndVertical(); 需成对存在;
2.水平布局:EditorGUILayout.BeginHorizontal(); EditorGUILayout.EndHorizontal();需成对存在;
3.代码编译中:EditorApplication.isCompiling
4.资源导入中:EditorApplication.isUpdating
5.警示框:EditorGUILayout.HelpBox("Wiatting for compiling or updating...", MessageType.Warning);
6.UI禁止交互;
EditorGUI.BeginDisabledGroup(EditorApplication.isCompiling || EditorApplication.isUpdating);
EditorGUI.EndDisabledGroup();
成对存在,可控制被包裹的UI是否可交互。如上,当编译和导入资源过程让UI不可交互;
7.可折叠按钮:
- boolValue = EditorGUILayout.BeginFoldoutHeaderGroup(boolValue, “Title”);
-
- if(boolValue){
- 可折叠的UI放到这里
- }
8. 下拉菜单
①EditorGUILayout.Popup:intValue为当前选择的菜单项索引
intValue = EditorGUILayout.Popup(intValue, varPrefixArr, GUILayout.MaxWidth(fieldPrefixWidth));
②EditorGUILayout.DropdownButton: 下拉按钮,优点是可以在下拉按钮点击后再加载下拉数据并显示,减少不必要性能消耗。点击下拉按钮后再创建GenericMenu(下拉列表):
- if (EditorGUILayout.DropdownButton(new GUIContent(varPrefixProperty.stringValue), FocusType.Passive, GUILayout.MaxWidth(fieldPrefixWidth)))
- {
- GenericMenu popMenu = new GenericMenu();
- foreach (var varPrefix in varPrefixArr)
- {
- popMenu.AddItem(new GUIContent(varPrefix), varPrefix.CompareTo(varPrefixProperty.stringValue) == 0, selectObj =>
- {
- varPrefixProperty.stringValue = selectObj.ToString();
- serializedObject.ApplyModifiedProperties();
- }, varPrefix);
- }
- popMenu.ShowAsContext();
- }
9. 可编辑文本框:
stringValue = GUILayout.TextField(stringValue, GUILayout.MinWidth(fieldItemWidth));
10. 按钮:
- if (GUILayout.Button("+", GUILayout.Width(smallButtonWidth)))
- {
- //按钮被点击
- }
11. 对齐:GUILayout.FlexibleSpace();GUILayout.Space(10);
GUILayout.FlexibleSpace(); 动态间距大小;GUILayout.Space(10)固定间距,空出10个像素
让Label右对齐:
- EditorGUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- EditorGUILayout.LabelField("Label");
- EditorGUILayout.EndHorizontal();
让Label左对齐:
- EditorGUILayout.BeginHorizontal();
- EditorGUILayout.LabelField("Label");
- GUILayout.FlexibleSpace();
- EditorGUILayout.EndHorizontal();
让Label居中对齐:
- EditorGUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- EditorGUILayout.LabelField("Label");
- GUILayout.FlexibleSpace();
- EditorGUILayout.EndHorizontal();
- public override void OnInspectorGUI()
- {
- CheckAndInitFields();
- serializedObject.Update();
- EditorGUILayout.BeginVertical();
-
- bool disableAct = EditorApplication.isCompiling || EditorApplication.isUpdating || EditorApplication.isPlaying;
- if (disableAct)
- {
- EditorGUILayout.HelpBox("Wiatting for compiling or updating...", MessageType.Warning);
- }
- EditorGUILayout.BeginHorizontal();
- EditorGUI.BeginDisabledGroup(disableAct);
-
- if (GUILayout.Button("1. Generate Script")) //生成脚本
- {
- GenerateUIFormVariables(uiForm, serializedObject);
- }
-
- if (useSerializeMode && GUILayout.Button("2. Bind Properties")) //绑定变量
- {
- SerializeFieldProperties(serializedObject, uiForm.GetFieldsProperties());
- }
- if (GUILayout.Button("Open Script"))
- {
- var monoScript = MonoScript.FromMonoBehaviour(uiForm);
- string scriptFile = AssetDatabase.GetAssetPath(monoScript);
- InternalEditorUtility.OpenFileAtLineExternal(scriptFile, 0);
- }
- if (GUILayout.Button("Open Bind Script"))
- {
- var uiFormClassName = uiForm.GetType().Name;
- string scriptFile = UtilityBuiltin.ResPath.GetCombinePath(ConstEditor.UISerializeFieldDir, Utility.Text.Format("{0}.Variables.cs", uiFormClassName));
- InternalEditorUtility.OpenFileAtLineExternal(scriptFile, 0);
- }
- if (GUILayout.Button("Clear", GUILayout.MaxWidth(55)))
- {
- mFields.ClearArray();
- }
- EditorGUILayout.EndHorizontal();
- EditorGUILayout.BeginHorizontal();
- EditorGUI.BeginChangeCheck();
-
- useSerializeMode = EditorGUILayout.ToggleLeft("SerializeMode", useSerializeMode, GUILayout.MaxWidth(115));
- if (EditorGUI.EndChangeCheck())
- {
- UnityEditor.EditorPrefs.SetBool("SerializeMode", useSerializeMode);
- }
- GUILayout.FlexibleSpace();
- if (GUILayout.Button(EditorGUIUtility.TrIconContent("_Help", "使用说明"), GUIStyle.none))
- {
- EditorUtility.DisplayDialog("使用说明", "1.打开UI界面预制体.\n2.右键节点'[Add/Remove] UI Variable'添加/移除变量.\n3.在Inspector面板点击Generate Script生成代码.", "OK");
- GUIUtility.ExitGUI();
- }
- EditorGUILayout.EndHorizontal();
- for (int i = 0; i < mFields.arraySize; i++)
- {
- EditorGUILayout.BeginHorizontal();
- GUILayout.Label(i.ToString(), GUILayout.Width(30f));
- var item = mFields.GetArrayElementAtIndex(i);
- var varNameProperty = item.FindPropertyRelative("VarName");
- var varTypeProperty = item.FindPropertyRelative("VarType");
- var targetsProperty = item.FindPropertyRelative("Targets");
- var unfoldProperty = item.FindPropertyRelative("Foldout");
- var varPrefixProperty = item.FindPropertyRelative("VarPrefix");
- var varSampleType = item.FindPropertyRelative("VarSampleType");
-
- int targetsCount = targetsProperty != null ? targetsProperty.arraySize : 0;
- string targetsTitle = $"Size {targetsCount}";
-
- unfoldProperty.boolValue = EditorGUILayout.BeginFoldoutHeaderGroup(unfoldProperty.boolValue, targetsTitle);
- if (EditorGUILayout.DropdownButton(new GUIContent(varPrefixProperty.stringValue), FocusType.Passive, GUILayout.MaxWidth(fieldPrefixWidth)))
- {
- GenericMenu popMenu = new GenericMenu();
- foreach (var varPrefix in varPrefixArr)
- {
- popMenu.AddItem(new GUIContent(varPrefix), varPrefix.CompareTo(varPrefixProperty.stringValue) == 0, selectObj =>
- {
- varPrefixProperty.stringValue = selectObj.ToString();
- serializedObject.ApplyModifiedProperties();
- }, varPrefix);
- }
- popMenu.ShowAsContext();
- }
- if (EditorGUILayout.DropdownButton(new GUIContent(varTypeProperty.stringValue), FocusType.Passive, GUILayout.MaxWidth(fieldTypeWidth)))
- {
- GenericMenu popMenu = new GenericMenu();
- var popContens = GetPopupContents(targetsProperty);
- foreach (var tpName in popContens)
- {
- popMenu.AddItem(new GUIContent(tpName), tpName.CompareTo(varTypeProperty.stringValue) == 0, selectObj =>
- {
- varTypeProperty.stringValue = selectObj.ToString();
- varSampleType.stringValue = GetSampleType(varTypeProperty.stringValue).Name;
- serializedObject.ApplyModifiedProperties();
- }, tpName);
- }
- popMenu.ShowAsContext();
- }
-
- varNameProperty.stringValue = GUILayout.TextField(varNameProperty.stringValue, GUILayout.MinWidth(fieldItemWidth));
-
- if (GUILayout.Button("+", GUILayout.Width(smallButtonWidth)))
- {
- InsertField(i + 1);
- }
- if (GUILayout.Button("-", GUILayout.Width(smallButtonWidth)))
- {
- RemoveField(i);
- }
- EditorGUILayout.EndHorizontal();
- if (i < mFields.arraySize && mFields.GetArrayElementAtIndex(i).FindPropertyRelative("Foldout").boolValue)
- {
- item = mFields.GetArrayElementAtIndex(i);
- targetsProperty = item.FindPropertyRelative("Targets");
-
- mCurFieldIdx = i;
- if (mReorderableList[i] == null)
- {
- mReorderableList[i] = new ReorderableList(serializedObject, targetsProperty, true, false, true, true);
- mReorderableList[i].drawElementCallback = DrawTargets;
- }
- else
- {
- mReorderableList[i].serializedProperty = targetsProperty;
- }
- mReorderableList[i].DoLayoutList();
- }
- EditorGUILayout.EndFoldoutHeaderGroup();
- }
-
- if (serializedObject.hasModifiedProperties)
- {
- serializedObject.ApplyModifiedProperties();
- serializedObject.Update();
- }
-
- EditorGUI.EndDisabledGroup();
- EditorGUILayout.EndVertical();
- base.OnInspectorGUI();
- }
12. 获取GameObject上挂的脚本文件所在目录:
- var monoScript = MonoScript.FromMonoBehaviour(monoBehaviorInstance);
- string scriptPath = AssetDatabase.GetAssetPath(monoScript);//.cs脚本文件路径
13. 显示对话框:
EditorUtility.DisplayDialog("标题", "内容", "确定按钮",“取消按钮”);
14. 打开文件夹:EditorUtility.RevealInFinder(path);
选择文件:EditorUtility.OpenFilePanel()
选择文件夹:EditorUtility.OpenFolderPanel()
15. 可调数组顺序/可增删的列表ReorderableList用法:
- mReorderableList[i] = new ReorderableList(serializedObject, arrayProperty, true, false, true, true);
- mReorderableList[i].drawElementCallback = DrawTargets;
-
- private void DrawTargets(Rect rect, int index, bool isActive, bool isFocused)
- {
- EditorGUI.BeginDisabledGroup(EditorApplication.isCompiling || EditorApplication.isUpdating || EditorApplication.isPlaying);
- var field = mFields.GetArrayElementAtIndex(mCurFieldIdx);
- var targetsProperty = field.FindPropertyRelative("Targets");
- var targetProperty = targetsProperty.GetArrayElementAtIndex(index);
- var curRect = new Rect(rect.x, rect.y, 20, EditorGUIUtility.singleLineHeight);
- EditorGUI.LabelField(curRect, index.ToString());
- curRect.x += 20;
- curRect.width = rect.width - 20;
- EditorGUI.ObjectField(curRect, targetProperty, GUIContent.none);
- EditorGUI.EndDisabledGroup();
- }
16. EditorPrefs,作用同PlayerPrefs,可用于保存编辑器设置等。如:EditorPrefs.SetBool("SerializeMode", useSerializeMode);
17. 通过调用编辑器API在代码里打开脚本文件:
InternalEditorUtility.OpenFileAtLineExternal(scriptFile, 0); 甚至可以指定打开并定位到代码的几行几列。
18. 支持撤销操作Undo.RecordObject(uiForm, uiForm.name); 例如移除UI变量时提前调用Undo.RecordObject记录对象状态,然后用户撤销操作时会自动回退到这一状态:
- private void RemoveField(int idx)
- {
- Undo.RecordObject(uiForm, uiForm.name);//记录移除数组元素前的对象状态
- mFields.DeleteArrayElementAtIndex(idx);
- ArrayUtility.RemoveAt(ref mReorderableList, idx);
- }
19. 使用Unity编辑器内置图标。Unity编辑器有很多内置图标,并且是可以用获取到共用的。获取编辑器内置图标API: EditorGUIUtility.TrIconContent(), EditorGUIUtility.IconContent(); 只需传入图标名即可。
如显示一个帮助按钮, IconContent和TrIconContent的区别就是,TrIconContent("_Help", "使用说明")当鼠标悬停在图标上会有显示提示;
- if (GUILayout.Button(EditorGUIUtility.TrIconContent("_Help", "使用说明"), GUIStyle.none))
- {
- EditorUtility.DisplayDialog("使用说明", "1.打开UI界面预制体.\n2.右键节点'[Add/Remove] UI Variable'添加/移除变量.\n3.在Inspector面板点击Generate Script生成代码.", "OK");
- GUIUtility.ExitGUI();
- }
如何知道Unity所有内置图标名称呢?可以写个编辑器通过Resources.FindObjectsOfTypeAll<Texture2D>()获取全部图标,并显示出每个图标和对应图标名,一目了然。
当然也有人已经整理列出了图标及名称,但是由于Unity版本不同图标样式也会有区别,白嫖地址:GitHub - halak/unity-editor-icons
20. 一键清理prefab丢失脚本Missing Scrips, GameObjectUtility.RemoveMonoBehavioursWithMissingScript(pfb);
- [MenuItem("Game Framework/GameTools/Clear Missing Scripts【清除Prefab丢失脚本】")]
- public static void ClearMissingScripts()
- {
- var pfbArr = AssetDatabase.FindAssets("t:Prefab");
- foreach (var item in pfbArr)
- {
- var pfbFileName = AssetDatabase.GUIDToAssetPath(item);
- var pfb = AssetDatabase.LoadAssetAtPath<GameObject>(pfbFileName);
- GameObjectUtility.RemoveMonoBehavioursWithMissingScript(pfb);
- }
- }
自动生成UI变量核心代码:
- #if UNITY_EDITOR
- using UnityEditor.SceneManagement;
- using System.Linq;
- using UnityEditor;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEditorInternal;
- using System;
- using GameFramework;
- using System.Text.RegularExpressions;
- using System.Text;
- using System.IO;
-
-
- [CustomEditor(typeof(UIFormBase), true)]
- public class UIFormBaseEditor : Editor
- {
- readonly static string[] varPrefixArr = { "private", "protected", "public" };
- const string arrFlag = "Arr";
- const float fieldItemWidth = 100;
- const float fieldPrefixWidth = 100;
- const float fieldTypeWidth = 240;
- const float smallButtonWidth = 25;
- SerializedProperty mFields;
- static bool addToFieldToggle;
- static bool removeToFieldToggle;
- ReorderableList[] mReorderableList;
- UIFormBase uiForm;
- int mCurFieldIdx;
-
- bool useSerializeMode = false;
- static int varPrefixIndex = -1;
- static bool mShowSelectTypeMenu;
-
- #region #右键菜单
- private static GUIStyle normalStyle;
- private static GUIStyle selectedStyle;
- [InitializeOnLoadMethod]
- static void InitEditor()
- {
-
- normalStyle = new GUIStyle();
- normalStyle.normal.textColor = Color.white;
-
- selectedStyle = new GUIStyle();
- selectedStyle.normal.textColor = Color.green;
- Selection.selectionChanged = () =>
- {
- addToFieldToggle = false;
- removeToFieldToggle = false;
- };
-
- EditorApplication.hierarchyWindowItemOnGUI = delegate (int id, Rect rect)
- {
- OpenSelectComponentMenuListener(rect);
- var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
- if (prefabStage == null)
- {
- return;
- }
- var uiForm = prefabStage.prefabContentsRoot.GetComponent<UIFormBase>();
- if (uiForm == null)
- {
- return;
- }
- var curDrawNode = EditorUtility.InstanceIDToObject(id) as GameObject;
- if (curDrawNode == null)
- {
- return;
- }
- var fields = uiForm.GetFieldsProperties();
- SerializeFieldData drawItem = null;
- foreach (var item in fields)
- {
- if (item == null) continue;
- if (ArrayUtility.Contains(item.Targets, curDrawNode))
- {
- drawItem = item;
- break;
- }
- }
- if (drawItem != null)
- {
- rect.x = rect.xMax - Mathf.Min(300, rect.size.x * 0.35f);
- GUI.Label(rect, string.Format("{0} {1} {2}", drawItem.VarPrefix, drawItem.VarSampleType, drawItem.VarName), selectedStyle);
- }
-
- };
- }
- [MenuItem("GameObject/UIForm Fields Tool/Add private", false, priority = 2)]
- private static void AddPrivateVariable2UIForm()
- {
- varPrefixIndex = 0;
- mShowSelectTypeMenu = true;
- }
- [MenuItem("GameObject/UIForm Fields Tool/Add protected", false, priority = 3)]
- private static void AddProtectedVariable2UIForm()
- {
- varPrefixIndex = 1;
- mShowSelectTypeMenu = true;
- }
- [MenuItem("GameObject/UIForm Fields Tool/Add public", false, priority = 4)]
- private static void AddPublicVariable2UIForm()
- {
- varPrefixIndex = 2;
- mShowSelectTypeMenu = true;
- }
-
- [MenuItem("GameObject/UIForm Fields Tool/Remove", false, priority = 5)]
- private static void RemoveUIFormVariable()
- {
- if (removeToFieldToggle)
- {
- return;
- }
- if (Selection.count <= 0) return;
-
- var uiForm = GetPrefabRootComponent<UIFormBase>();
- if (uiForm == null)
- {
- Debug.LogWarning("UIForm Script is not exist.");
- return;
- }
- var fieldsProperties = uiForm.GetFieldsProperties();
- if (fieldsProperties == null) return;
- Undo.RecordObject(uiForm, uiForm.name);
- for (int i = 0; i < Selection.gameObjects.Length; i++)
- {
- var itm = Selection.gameObjects[i];
- if (itm == null) continue;
-
- for (int j = fieldsProperties.Length - 1; j >= 0; j--)
- {
- var fields = fieldsProperties[j];
- if (fields == null || fields.Targets == null || fields.Targets.Length <= 0) continue;
- for (int k = fields.Targets.Length - 1; k >= 0; k--)
- {
- if (fields.Targets[k] == itm)
- {
- if (fields.Targets.Length <= 1)
- {
- ArrayUtility.RemoveAt(ref fieldsProperties, j);
- }
- else
- {
- ArrayUtility.RemoveAt(ref fields.Targets, k);
- }
- }
- }
- }
- }
- uiForm.ModifyFieldsProperties(fieldsProperties);
- EditorUtility.SetDirty(uiForm);
- removeToFieldToggle = true;
- addToFieldToggle = false;
- }
- /// <summary>
- /// 不带名字空间的类型名
- /// </summary>
- /// <returns></returns>
- private static Type GetSampleType(string fullName)
- {
- Type result = null;
- var assemblyArr = AppDomain.CurrentDomain.GetAssemblies();
- foreach (var item in assemblyArr)
- {
- foreach (var tp in item.GetTypes())
- {
- if (tp.FullName.CompareTo(fullName) == 0)
- {
- result = tp;
- break;
- }
- }
- if (result != null) break;
- }
-
- return result;
- }
- private static T GetPrefabRootComponent<T>() where T : Component
- {
- var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
- if (prefabStage == null)
- {
- Debug.LogWarning("GetCurrentPrefabStage is null.");
- return null;
- }
- return prefabStage.prefabContentsRoot.GetComponent<T>();
- }
- private static void AddToFields(string varPrefix, string varType)
- {
- if (addToFieldToggle)
- {
- return;
- }
- if (Selection.count <= 0) return;
- var uiForm = GetPrefabRootComponent<UIFormBase>();
- if (uiForm == null)
- {
- Debug.LogWarning("UIForm Script is not exist.");
- return;
- }
- var targets = GetTargetsFromSelectedNodes(Selection.gameObjects);
-
- var fieldsProperties = uiForm.GetFieldsProperties();
- if (fieldsProperties == null) fieldsProperties = new SerializeFieldData[0];
- Undo.RecordObject(uiForm, uiForm.name);
- SerializeFieldData field = new SerializeFieldData(GenerateFieldName(fieldsProperties, targets), targets);
- field.VarType = varType;
- field.VarSampleType = GetSampleType(field.VarType).Name;
- field.VarPrefix = varPrefix;
-
- ArrayUtility.Add(ref fieldsProperties, field);
- uiForm.ModifyFieldsProperties(fieldsProperties);
- EditorUtility.SetDirty(uiForm);
- addToFieldToggle = true;
- removeToFieldToggle = false;
- }
- private static GameObject[] GetTargetsFromSelectedNodes(GameObject[] selectedList)
- {
- GameObject[] targets = new GameObject[selectedList.Length];
- for (int i = 0; i < selectedList.Length; i++)
- {
- targets[i] = selectedList[i];
- }
- targets = targets.OrderBy(go => go.transform.GetSiblingIndex()).ToArray();
- return targets;
- }
- #endregion
-
- private void OnEnable()
- {
- useSerializeMode = EditorPrefs.GetBool("SerializeMode", true);
- varPrefixIndex = 0;
- mShowSelectTypeMenu = false;
- uiForm = (target as UIFormBase);
- if (uiForm.GetFieldsProperties() == null)
- {
- uiForm.ModifyFieldsProperties(new SerializeFieldData[0]);
- }
- mFields = serializedObject.FindProperty("_fields");
- mReorderableList = new ReorderableList[mFields.arraySize];
- }
-
-
- private static void OpenSelectComponentMenuListener(Rect rect)
- {
- if (mShowSelectTypeMenu)
- {
- int idx = -1;
- var strArr = GetPopupContents(GetTargetsFromSelectedNodes(Selection.gameObjects));
- var contents = new GUIContent[strArr.Length];
- for (int i = 0; i < strArr.Length; i++)
- {
- contents[i] = new GUIContent(strArr[i]);
- }
- rect.width = 200;
- rect.height = MathF.Max(100, contents.Length * rect.height);
- EditorUtility.DisplayCustomMenu(rect, contents, idx, (userData, contents, selected) =>
- {
- AddToFields(varPrefixArr[varPrefixIndex], contents[selected]);
- }, null);
- mShowSelectTypeMenu = false;
- }
- }
-
- public override void OnInspectorGUI()
- {
- CheckAndInitFields();
- serializedObject.Update();
- EditorGUILayout.BeginVertical();
-
- bool disableAct = EditorApplication.isCompiling || EditorApplication.isUpdating || EditorApplication.isPlaying;
- if (disableAct)
- {
- EditorGUILayout.HelpBox("Wiatting for compiling or updating...", MessageType.Warning);
- }
- EditorGUILayout.BeginHorizontal();
- EditorGUI.BeginDisabledGroup(disableAct);
-
- if (GUILayout.Button("1. Generate Script")) //生成脚本
- {
- GenerateUIFormVariables(uiForm, serializedObject);
- }
-
- if (useSerializeMode && GUILayout.Button("2. Bind Properties")) //绑定变量
- {
- SerializeFieldProperties(serializedObject, uiForm.GetFieldsProperties());
- }
- if (GUILayout.Button("Open Script"))
- {
- var monoScript = MonoScript.FromMonoBehaviour(uiForm);
- string scriptFile = AssetDatabase.GetAssetPath(monoScript);
- InternalEditorUtility.OpenFileAtLineExternal(scriptFile, 0);
- }
- if (GUILayout.Button("Open Bind Script"))
- {
- var uiFormClassName = uiForm.GetType().Name;
- string scriptFile = UtilityBuiltin.ResPath.GetCombinePath(ConstEditor.UISerializeFieldDir, Utility.Text.Format("{0}.Variables.cs", uiFormClassName));
- InternalEditorUtility.OpenFileAtLineExternal(scriptFile, 0);
- }
- if (GUILayout.Button("Clear", GUILayout.MaxWidth(55)))
- {
- mFields.ClearArray();
- }
- EditorGUILayout.EndHorizontal();
- EditorGUILayout.BeginHorizontal();
- EditorGUI.BeginChangeCheck();
-
- useSerializeMode = EditorGUILayout.ToggleLeft("SerializeMode", useSerializeMode, GUILayout.MaxWidth(115));
- if (EditorGUI.EndChangeCheck())
- {
- UnityEditor.EditorPrefs.SetBool("SerializeMode", useSerializeMode);
- }
- GUILayout.FlexibleSpace();
- if (GUILayout.Button(EditorGUIUtility.TrIconContent("_Help", "使用说明"), GUIStyle.none))
- {
- EditorUtility.DisplayDialog("使用说明", "1.打开UI界面预制体.\n2.右键节点'[Add/Remove] UI Variable'添加/移除变量.\n3.在Inspector面板点击Generate Script生成代码.", "OK");
- GUIUtility.ExitGUI();
- }
- EditorGUILayout.EndHorizontal();
- for (int i = 0; i < mFields.arraySize; i++)
- {
- EditorGUILayout.BeginHorizontal();
- GUILayout.Label(i.ToString(), GUILayout.Width(30f));
- var item = mFields.GetArrayElementAtIndex(i);
- var varNameProperty = item.FindPropertyRelative("VarName");
- var varTypeProperty = item.FindPropertyRelative("VarType");
- var targetsProperty = item.FindPropertyRelative("Targets");
- var unfoldProperty = item.FindPropertyRelative("Foldout");
- var varPrefixProperty = item.FindPropertyRelative("VarPrefix");
- var varSampleType = item.FindPropertyRelative("VarSampleType");
-
- int targetsCount = targetsProperty != null ? targetsProperty.arraySize : 0;
- string targetsTitle = $"Size {targetsCount}";
-
- unfoldProperty.boolValue = EditorGUILayout.BeginFoldoutHeaderGroup(unfoldProperty.boolValue, targetsTitle);
- if (EditorGUILayout.DropdownButton(new GUIContent(varPrefixProperty.stringValue), FocusType.Passive, GUILayout.MaxWidth(fieldPrefixWidth)))
- {
- GenericMenu popMenu = new GenericMenu();
- foreach (var varPrefix in varPrefixArr)
- {
- popMenu.AddItem(new GUIContent(varPrefix), varPrefix.CompareTo(varPrefixProperty.stringValue) == 0, selectObj =>
- {
- varPrefixProperty.stringValue = selectObj.ToString();
- serializedObject.ApplyModifiedProperties();
- }, varPrefix);
- }
- popMenu.ShowAsContext();
- }
- if (EditorGUILayout.DropdownButton(new GUIContent(varTypeProperty.stringValue), FocusType.Passive, GUILayout.MaxWidth(fieldTypeWidth)))
- {
- GenericMenu popMenu = new GenericMenu();
- var popContens = GetPopupContents(targetsProperty);
- foreach (var tpName in popContens)
- {
- popMenu.AddItem(new GUIContent(tpName), tpName.CompareTo(varTypeProperty.stringValue) == 0, selectObj =>
- {
- varTypeProperty.stringValue = selectObj.ToString();
- varSampleType.stringValue = GetSampleType(varTypeProperty.stringValue).Name;
- serializedObject.ApplyModifiedProperties();
- }, tpName);
- }
- popMenu.ShowAsContext();
- }
-
- varNameProperty.stringValue = GUILayout.TextField(varNameProperty.stringValue, GUILayout.MinWidth(fieldItemWidth));
-
- if (GUILayout.Button("+", GUILayout.Width(smallButtonWidth)))
- {
- InsertField(i + 1);
- }
- if (GUILayout.Button("-", GUILayout.Width(smallButtonWidth)))
- {
- RemoveField(i);
- }
- EditorGUILayout.EndHorizontal();
- if (i < mFields.arraySize && mFields.GetArrayElementAtIndex(i).FindPropertyRelative("Foldout").boolValue)
- {
- item = mFields.GetArrayElementAtIndex(i);
- targetsProperty = item.FindPropertyRelative("Targets");
-
- mCurFieldIdx = i;
- if (mReorderableList[i] == null)
- {
- mReorderableList[i] = new ReorderableList(serializedObject, targetsProperty, true, false, true, true);
- mReorderableList[i].drawElementCallback = DrawTargets;
- }
- else
- {
- mReorderableList[i].serializedProperty = targetsProperty;
- }
- mReorderableList[i].DoLayoutList();
- }
- EditorGUILayout.EndFoldoutHeaderGroup();
- }
-
- if (serializedObject.hasModifiedProperties)
- {
- serializedObject.ApplyModifiedProperties();
- serializedObject.Update();
- }
-
- EditorGUI.EndDisabledGroup();
- EditorGUILayout.EndVertical();
- base.OnInspectorGUI();
- }
- /// <summary>
- /// 生成UI脚本.cs
- /// </summary>
- /// <param name="uiForm"></param>
- /// <param name="uiFormSerializer"></param>
- private void GenerateUIFormVariables(UIFormBase uiForm, SerializedObject uiFormSerializer)
- {
- if (uiForm == null) return;
-
- var monoScript = MonoScript.FromMonoBehaviour(uiForm);
- var uiFormClassName = monoScript.GetClass().Name;
- string scriptFile = UtilityBuiltin.ResPath.GetCombinePath(ConstEditor.UISerializeFieldDir, Utility.Text.Format("{0}.Variables.cs", uiFormClassName));
- var fields = uiForm.GetFieldsProperties();
- if (fields == null || fields.Length <= 0)
- {
- if (File.Exists(scriptFile))
- {
- File.Delete(scriptFile);
- }
- var metaFile = scriptFile + ".meta";
- if (File.Exists(metaFile))
- {
- File.Delete(metaFile);
- }
- AssetDatabase.Refresh();
- return;
- }
- var matchResult = Regex.Match(monoScript.text, Utility.Text.Format("partial[\\s]+class[\\s]+{0}", uiFormClassName));
- string scriptPath = AssetDatabase.GetAssetPath(monoScript);
- if (!matchResult.Success)
- {
- EditorUtility.DisplayDialog("生成UI变量失败!", Utility.Text.Format("请先手动为{0}类添加'partial'修饰符!\n{1}", uiFormClassName, scriptPath), "OK");
- return;
- }
- List<string> nameSpaceList = new List<string> { "UnityEngine" };//默认自带的名字空间
- List<string> fieldList = new List<string>();
- foreach (var field in fields)
- {
- if (string.IsNullOrWhiteSpace(field.VarType) || string.IsNullOrWhiteSpace(field.VarName))
- {
- continue;
- }
- var varType = GetSampleType(field.VarType);
- if (varType == null)
- {
- continue;
- }
- if (!string.IsNullOrEmpty(varType.Namespace) && !nameSpaceList.Contains(varType.Namespace))
- {
- nameSpaceList.Add(varType.Namespace);
- }
- bool isArray = field.Targets.Length > 1;
-
- var varPrefix = field.VarPrefix;// varPrefixArr[field.VarPrefixSelectedIdx];
- string serializeFieldPrefix = useSerializeMode ? "[SerializeField] " : "";
- string fieldLine;
- if (isArray)
- {
- fieldLine = Utility.Text.Format("{0}{1} {2}[] {3} = null;", serializeFieldPrefix, varPrefix, varType.Name, field.VarName);
- }
- else
- {
- fieldLine = Utility.Text.Format("{0}{1} {2} {3} = null;", serializeFieldPrefix, varPrefix, varType.Name, field.VarName);
- }
- fieldList.Add(fieldLine);
- }
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.AppendLine("//---------------------------------");
- stringBuilder.AppendLine("//此文件由工具自动生成,请勿手动修改");
- stringBuilder.AppendLine($"//更新自:{CloudProjectSettings.userName}");
- stringBuilder.AppendLine($"//更新时间:{DateTime.Now}");
- stringBuilder.AppendLine("//---------------------------------");
- foreach (var item in nameSpaceList)
- {
- stringBuilder.AppendLine(Utility.Text.Format("using {0};", item));
- }
- string uiFormClassNameSpace = monoScript.GetClass().Namespace;
- bool hasNameSpace = !string.IsNullOrWhiteSpace(uiFormClassNameSpace);
- if (hasNameSpace)
- {
- stringBuilder.AppendLine(Utility.Text.Format("namespace {0}", uiFormClassNameSpace));
- stringBuilder.AppendLine("{");
- }
- stringBuilder.AppendLine(Utility.Text.Format("public partial class {0}", uiFormClassName));
- stringBuilder.AppendLine("{");
- if (useSerializeMode)
- {
- stringBuilder.AppendLine("\t[Space(10)]");
- stringBuilder.AppendLine("\t[Header(\"Auto Genertate Properties:\")]");
- }
- foreach (var item in fieldList)
- {
- stringBuilder.AppendLine("\t" + item);
- }
- //不使用Serialize模式时直接生成获取组件的代码
- if (!useSerializeMode)
- {
- GeneratePropertiesUseGetComponent(stringBuilder, fields);
- }
- stringBuilder.AppendLine("}");
- if (hasNameSpace) stringBuilder.AppendLine("}");
-
- File.WriteAllText(scriptFile, stringBuilder.ToString());
- AssetDatabase.Refresh();
- //SerializeFieldProperties(uiFormSerializer, fields);
- }
- private void GeneratePropertiesUseGetComponent(StringBuilder stringBuilder, SerializeFieldData[] fields)
- {
- stringBuilder.AppendLine("\tprotected override void InitUIProperties()");
- stringBuilder.AppendLine("\t{");
- stringBuilder.AppendLine("\t\tvar fields = this.GetFieldsProperties();");
- for (int i = 0; i < fields.Length; i++)
- {
- var field = fields[i];
- bool isArray = field.Targets.Length > 1;
- bool isGameObject = field.VarType.CompareTo(typeof(GameObject).FullName) == 0;
- if (isArray)
- {
- if (isGameObject)
- stringBuilder.AppendLine(Utility.Text.Format("\t\t{0} = fields[{1}].Targets;", field.VarName, i));
- else
- stringBuilder.AppendLine(Utility.Text.Format("\t\t{0} = fields[{1}].GetComponents<{2}>();", field.VarName, i, field.VarSampleType));
- }
- else
- {
- if (isGameObject)
- stringBuilder.AppendLine(Utility.Text.Format("\t\t{0} = fields[{1}].Targets[0];", field.VarName, i));
- else
- stringBuilder.AppendLine(Utility.Text.Format("\t\t{0} = fields[{1}].GetComponent<{2}>(0);", field.VarName, i, field.VarSampleType));
- }
- }
- stringBuilder.AppendLine("\t}");
- }
- private void SerializeFieldProperties(SerializedObject serializedObject, SerializeFieldData[] fields)
- {
- if (serializedObject == null)
- {
- Debug.LogError("生成UI SerializedField失败, serializedObject为null");
- return;
- }
-
- foreach (var item in fields)
- {
- string varName = item.VarName;
- string varType = item.VarType;
- bool isGameObject = varType.CompareTo(typeof(GameObject).FullName) == 0;
- var property = serializedObject.FindProperty(varName);
- if (property == null) continue;
- if (item.Targets.Length <= 1)
- {
- property.objectReferenceValue = isGameObject ? item.Targets[0] : item.Targets[0]?.GetComponent(GetSampleType(varType));
- }
- else if (property.isArray)
- {
- property.ClearArray();
- for (int i = 0; i < item.Targets.Length; i++)
- {
- if (i >= property.arraySize)
- {
- property.InsertArrayElementAtIndex(i);
- }
- property.GetArrayElementAtIndex(i).objectReferenceValue = isGameObject ? item.Targets[i] : item.Targets[i]?.GetComponent(GetSampleType(varType));
- }
- //for (int i = property.arraySize - 1; i >= item.Targets.Length; i--)
- //{
- // property.DeleteArrayElementAtIndex(i);
- //}
- }
- }
- serializedObject.ApplyModifiedProperties();
- }
-
- private void CheckAndInitFields()
- {
- if (uiForm.GetFieldsProperties() == null)
- {
- uiForm.ModifyFieldsProperties(new SerializeFieldData[0]);
- }
- if (mFields == null) mFields = serializedObject.FindProperty("_fields");
-
- if (mFields.arraySize != mReorderableList.Length)
- {
- if (mFields.arraySize > mReorderableList.Length)
- {
- for (int i = mReorderableList.Length; i < mFields.arraySize; i++)
- {
- ArrayUtility.Insert(ref mReorderableList, mReorderableList.Length, null);
- }
- }
- else
- {
- for (int i = mFields.arraySize; i < mReorderableList.Length; i++)
- {
- ArrayUtility.RemoveAt(ref mReorderableList, mReorderableList.Length - 1);
- }
- }
- }
- }
-
- private void InsertField(int idx)
- {
- Undo.RecordObject(uiForm, uiForm.name);
- mFields.InsertArrayElementAtIndex(idx);
- ArrayUtility.Insert(ref mReorderableList, idx, null);
- var lastField = mFields.GetArrayElementAtIndex(idx);
- if (lastField != null)
- {
- var lastVarName = lastField.FindPropertyRelative("VarName");
- if (!string.IsNullOrEmpty(lastVarName.stringValue))
- {
- lastVarName.stringValue += idx.ToString();
- }
- }
- }
- private void RemoveField(int idx)
- {
- Undo.RecordObject(uiForm, uiForm.name);
- mFields.DeleteArrayElementAtIndex(idx);
- ArrayUtility.RemoveAt(ref mReorderableList, idx);
- }
- private void DrawTargets(Rect rect, int index, bool isActive, bool isFocused)
- {
- EditorGUI.BeginDisabledGroup(EditorApplication.isCompiling || EditorApplication.isUpdating || EditorApplication.isPlaying);
- var field = mFields.GetArrayElementAtIndex(mCurFieldIdx);
- var targetsProperty = field.FindPropertyRelative("Targets");
- var targetProperty = targetsProperty.GetArrayElementAtIndex(index);
- var curRect = new Rect(rect.x, rect.y, 20, EditorGUIUtility.singleLineHeight);
- EditorGUI.LabelField(curRect, index.ToString());
- curRect.x += 20;
- curRect.width = rect.width - 20;
- EditorGUI.ObjectField(curRect, targetProperty, GUIContent.none);
- EditorGUI.EndDisabledGroup();
- }
- private static string[] GetPopupContents(GameObject[] targets)
- {
- if (targets == null || targets.Length <= 0)
- {
- return new string[0];
- }
- var typeNames = GetIntersectionComponents(targets);
- if (typeNames == null || typeNames.Length <= 0)
- {
- return new string[0];
- }
- ArrayUtility.Insert(ref typeNames, 0, typeof(GameObject).FullName);
- return typeNames;
- }
- private static string[] GetPopupContents(SerializedProperty targets)
- {
- var goArr = new GameObject[targets.arraySize];
- for (int i = 0; i < targets.arraySize; i++)
- {
- var pp = targets.GetArrayElementAtIndex(i);
- goArr[i] = (pp != null && pp.objectReferenceValue != null) ? (pp.objectReferenceValue as GameObject) : null;
- }
- return GetPopupContents(goArr);
- }
- private static string[] GetIntersectionComponents(GameObject[] targets)
- {
- var firstItm = targets[0];
- if (firstItm == null)
- {
- return new string[0];
- }
- var coms = firstItm.GetComponents(typeof(Component));
- coms = coms.Distinct().ToArray();//去重
-
- for (int i = coms.Length - 1; i >= 1; i--)
- {
- var comType = coms[i].GetType().FullName;
- bool allContains = true;
- for (int j = 1; j < targets.Length; j++)
- {
- var target = targets[j];
- if (target == null) return new string[0];
- var tComs = target.GetComponents(typeof(Component));
- bool containsType = false;
- for (int k = 0; k < tComs.Length; k++)
- {
- if (tComs[k].GetType().FullName.CompareTo(comType) == 0)
- {
- containsType = true;
- break;
- }
- }
- allContains &= containsType;
- if (!allContains) break;
- }
- if (!allContains)
- {
- ArrayUtility.RemoveAt(ref coms, i);
- }
- }
- string[] typesArr = new string[coms.Length];
- for (int i = 0; i < coms.Length; i++)
- {
- typesArr[i] = coms[i].GetType().FullName;
- }
- return typesArr;
- }
-
- /// <summary>
- /// 生成一个与变量列表里不重名的变量名
- /// </summary>
- /// <param name="fields"></param>
- /// <param name="name"></param>
- /// <returns></returns>
- private static string GenerateFieldName(SerializeFieldData[] fields, GameObject[] targets)
- {
- var go = targets[0];
- string varName = Regex.Replace(go.name, "[^\\w]", string.Empty);
- if (fields == null || fields.Length <= 0)
- {
- return FirstCharToLower(targets.Length > 1 ? varName + arrFlag : varName);
- }
- bool contains = false;
-
- foreach (SerializeFieldData item in fields)
- {
- if (item != null && item.VarName.CompareTo(varName) == 0)
- {
- contains = true;
- }
- }
- if (targets.Length > 1)
- {
- varName += arrFlag;
- }
- if (contains)
- {
- varName += go.GetInstanceID();
- }
-
- return FirstCharToLower(varName);
- }
- private static string FirstCharToLower(string str)
- {
- if (string.IsNullOrEmpty(str))
- {
- return string.Empty;
- }
- return str.Substring(0, 1).ToLower() + str.Substring(1);
- }
- }
- #endif
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。