using UnityEditor; using UnityEngine; // UIMask 的材质面板:分组折叠 + 中文标签。 // shader 里用 CustomEditor "UIMaskShaderGUI" 挂上这个类。 // 折叠状态存在 EditorPrefs(按组名),重编译/重启 Unity 后仍然保持。 // 未在 Groups 里列出的属性(_SrcBlend/_DstBlend/_Cull、硬边溶解、Stencil 等)不绘制。 public class UIMaskShaderGUI : ShaderGUI { private const string FoldKeyPrefix = "UIMask.GUI."; private class Group { public string Label; public string Toggle; // 组的启用开关(没有则为 null) public string[] Props; // 组内属性,按显示顺序 } private static readonly Group[] Groups = { new Group { Label = "基础", Props = new[] { "_BlendMode", "_RenderFace", "_TintColor", "_GlowScale|_AlphaScale", "_MainTex" } }, new Group { Label = "UV 滚动", Toggle = "_UseUvAni", Props = new[] { "_SpeedU|_SpeedV" } }, new Group { Label = "UV 扰乱", Toggle = "_UseUvDistortion", Props = new[] { "_UVNoiseTex", "_Distortion", "_NoiseScroll" } }, new Group { Label = "遮罩 Mask", Toggle = "_UseMask", Props = new[] { "_UseMaskUvAni", "_Mask", "_MaskChannel", "_MaskSpeedU|_MaskSpeedV" } }, new Group { Label = "溶解", Toggle = "_UseDissolve", Props = new[] { "_DissolveTex", "_DissolveChannel", "_Dissolve", "_DissolveWidth" } }, new Group { Label = "UI 裁切", Toggle = "_UseUIMaskClip", Props = new[] { "_ClipRect" } }, new Group { Label = "顶点遮罩", Toggle = "_UseVertexMask", Props = new[] { "_VertexOffset" } }, }; public override void OnGUI(MaterialEditor editor, MaterialProperty[] props) { foreach (Group group in Groups) { DrawGroup(editor, props, group); } } private static void DrawGroup(MaterialEditor editor, MaterialProperty[] props, Group group) { MaterialProperty toggle = string.IsNullOrEmpty(group.Toggle) ? null : FindProperty(group.Toggle, props, false); string foldKey = FoldKeyPrefix + group.Label; // 默认只展开"基础",其余收起(收起时右边的勾选状态仍能看出该功能有没有开) bool expanded = EditorPrefs.GetBool(foldKey, group.Label == "基础"); EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(); bool newExpanded = EditorGUILayout.Foldout(expanded, group.Label, true, EditorStyles.foldoutHeader); if (toggle != null) { GUILayout.FlexibleSpace(); editor.ShaderProperty(toggle, GUIContent.none, 0); } EditorGUILayout.EndHorizontal(); if (newExpanded != expanded) { EditorPrefs.SetBool(foldKey, newExpanded); } if (newExpanded) { EditorGUI.indentLevel++; DrawProps(editor, props, group.Props); EditorGUI.indentLevel--; } EditorGUILayout.EndVertical(); } // group.Props 里一项 = 一行;写 "A|B" 表示这两个单值属性并排在同一行 private static void DrawProps(MaterialEditor editor, MaterialProperty[] props, string[] rows) { foreach (string row in rows) { int separator = row.IndexOf('|'); if (separator < 0) { DrawOne(editor, props, row); continue; } MaterialProperty left = FindProperty(row.Substring(0, separator), props, false); MaterialProperty right = FindProperty(row.Substring(separator + 1), props, false); if (left != null && right != null && IsSingleValue(left) && IsSingleValue(right)) { DrawTwoPerRow(editor, left, right); } else { // 其中一边不存在或不是单值属性,退回各自一行,避免出错 if (left != null) DrawOne(editor, props, left.name); if (right != null) DrawOne(editor, props, right.name); } } } private static void DrawOne(MaterialEditor editor, MaterialProperty[] props, string name) { MaterialProperty prop = FindProperty(name, props, false); if (prop == null) { return; } if (prop.type == MaterialProperty.PropType.Texture) { // Unity 原生贴图选择器(缩略图 + 平铺/偏移都由它画) bool scaleOffset = (prop.flags & MaterialProperty.PropFlags.NoScaleOffset) == 0; editor.TextureProperty(prop, prop.displayName, scaleOffset); return; } if (prop.type == MaterialProperty.PropType.Vector) { DrawVectorTwoLines(prop); return; } editor.ShaderProperty(prop, prop.displayName); } private static bool IsSingleValue(MaterialProperty prop) { return prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Int; } // 两个单值属性并排一行;半行放不下默认标签宽度,所以临时收窄标签 private static void DrawTwoPerRow(MaterialEditor editor, MaterialProperty left, MaterialProperty right) { Rect row = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight); const float gap = 6f; float half = (row.width - gap) * 0.5f; float oldLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = Mathf.Min(oldLabelWidth, half * 0.5f); editor.ShaderProperty(new Rect(row.x, row.y, half, row.height), left, new GUIContent(left.displayName)); editor.ShaderProperty(new Rect(row.x + half + gap, row.y, half, row.height), right, new GUIContent(right.displayName)); EditorGUIUtility.labelWidth = oldLabelWidth; } // 四分量向量:属性名独占一行,X/Y/Z/W 单独占下一行(默认是挤在同一行,名字会被切掉) private static void DrawVectorTwoLines(MaterialProperty prop) { EditorGUILayout.LabelField(prop.displayName, EditorStyles.label); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = prop.hasMixedValue; Vector4 value = EditorGUILayout.Vector4Field(GUIContent.none, prop.vectorValue); EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { prop.vectorValue = value; } } }