using UnityEditor; using UnityEngine; using System.Collections.Generic; // UIMask 的材质面板:分组折叠 + 中文标签。 // shader 里用 CustomEditor "UIMaskShaderGUI" 挂上这个类。 // 折叠状态存在 EditorPrefs(按组名),重编译/重启 Unity 后仍然保持。 // 未在 Groups 里列出的属性(_SrcBlend/_DstBlend/_Cull、硬边溶解、Stencil 等)不绘制。 public class UIMaskShaderGUI : ShaderGUI { private const string FoldKeyPrefix = "UIMask.GUI."; // 用来读取 shader 里声明的默认值(新建一个隐藏材质读它的初始值) private static Material defaultsMaterial; 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", "_SoftParticleFade" } }, 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", "_DissolveCustomChannel", "_DissolveEdgeColor", "_DissolveEdgeSharpness", "_DissolveHighlightColor", "_DissolveHighlightWidth" } }, 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); GUILayout.FlexibleSpace(); if (toggle != null) { editor.ShaderProperty(toggle, GUIContent.none, 0); } if (GUILayout.Button(new GUIContent("重置", "把本组参数恢复为 shader 默认值"), EditorStyles.miniButton, GUILayout.Width(42f))) { ResetGroup(editor, props, group); } EditorGUILayout.EndHorizontal(); if (newExpanded != expanded) { EditorPrefs.SetBool(foldKey, newExpanded); } if (newExpanded) { EditorGUI.indentLevel++; DrawProps(editor, props, group.Props); EditorGUI.indentLevel--; } EditorGUILayout.EndVertical(); } // 把某一组的属性恢复成 shader 默认值(启用开关同时同步它的关键字) private static void ResetGroup(MaterialEditor editor, MaterialProperty[] props, Group group) { Material sample = null; foreach (Object target in editor.targets) { sample = target as Material; if (sample != null) { break; } } if (sample == null) { return; } if (defaultsMaterial == null || defaultsMaterial.shader != sample.shader) { if (defaultsMaterial != null) { Object.DestroyImmediate(defaultsMaterial); } defaultsMaterial = new Material(sample.shader) { hideFlags = HideFlags.HideAndDontSave }; } Undo.RecordObjects(editor.targets, "Reset " + group.Label); foreach (string name in EnumerateNames(group)) { MaterialProperty prop = FindProperty(name, props, false); if (prop == null || !defaultsMaterial.HasProperty(name)) { continue; } switch (prop.propertyType) { case UnityEngine.Rendering.ShaderPropertyType.Color: prop.colorValue = defaultsMaterial.GetColor(name); break; case UnityEngine.Rendering.ShaderPropertyType.Vector: prop.vectorValue = defaultsMaterial.GetVector(name); break; case UnityEngine.Rendering.ShaderPropertyType.Texture: prop.textureValue = defaultsMaterial.GetTexture(name); break; default: float value = defaultsMaterial.GetFloat(name); prop.floatValue = value; // 开关类属性(_UseXxx)要把关键字一起恢复,否则面板勾选和实际编译状态会对不上 if (name.StartsWith("_Use")) { SetKeyword(editor, KeywordOf(name), value > 0.5f); } break; } } foreach (Object target in editor.targets) { EditorUtility.SetDirty(target); } } private static IEnumerable EnumerateNames(Group group) { if (!string.IsNullOrEmpty(group.Toggle)) { yield return group.Toggle; } foreach (string row in group.Props) { int separator = row.IndexOf('|'); if (separator < 0) { yield return row; } else { yield return row.Substring(0, separator); yield return row.Substring(separator + 1); } } } private static string KeywordOf(string propertyName) { string trimmed = propertyName.StartsWith("_") ? propertyName.Substring(1) : propertyName; return "_" + trimmed.ToUpperInvariant() + "_ON"; } private static void SetKeyword(MaterialEditor editor, string keyword, bool enabled) { foreach (Object target in editor.targets) { var material = target as Material; if (material == null) { continue; } if (enabled) { material.EnableKeyword(keyword); } else { material.DisableKeyword(keyword); } } } // 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.propertyType == UnityEngine.Rendering.ShaderPropertyType.Texture) { // Unity 原生贴图选择器(缩略图 + 平铺/偏移都由它画) bool scaleOffset = (prop.propertyFlags & UnityEngine.Rendering.ShaderPropertyFlags.NoScaleOffset) == 0; editor.TextureProperty(prop, prop.displayName, scaleOffset); return; } if (prop.propertyType == UnityEngine.Rendering.ShaderPropertyType.Vector) { DrawVectorTwoLines(prop); return; } editor.ShaderProperty(prop, prop.displayName); } private static bool IsSingleValue(MaterialProperty prop) { return prop.propertyType == UnityEngine.Rendering.ShaderPropertyType.Float || prop.propertyType == UnityEngine.Rendering.ShaderPropertyType.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; } } }