332 lines
11 KiB
C#
332 lines
11 KiB
C#
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<string> 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;
|
|
}
|
|
}
|
|
}
|