前言:
在编写UnityShader时,我们常常会使用特性来更换材质球面板的属性外观,除此之外,还可以使用自定义的扩展脚本来实现自定义的材质球界面,参考我之前的文章UnityShaderUI编辑器扩展
但是自定义扩展每次都要单独写一个Editor脚本来扩展对应的Shader,而不能像特性那样,只要加一个[HDR],[Range]就能更改材质面板的界面。
那么我们如何方便又快速的创建自定义的特性,方便我们只要写一个全功能脚本,就能快速自定义扩展不同的Shader呢?
原理:
如图,只需要编写脚本,类继承MaterialPropertyDrawer就可以了,类名的命名方式是特性名+Drawer,如:SingleLineTextureDrawer,或者不加Drawer也可以,Unity会自动添加并识别。
在Shader中使用:
写法:[特性类名(不加Drawer)]
材质面板显示:
自定义Group自动分组功能:
构想:一个特性Group,在Shader属性前添加时,作为折叠栏功能,如[Group]_group1表示一个折叠栏,如[Group(_MainTex,_IntTest)]_group2表示group2折叠栏下包含属性_MainTex与_IntTest属性的绘制或者不绘制。
如图:
效果:
实现:
在我们的构想中,Group可以包含子属性,也可以不包含,包含的子属性的数量不定,所以构建构造函数:
启用keys数组用来存放传递的命名,这些命名作为我们用来分组和查询属性的必要条件。
重写OnGUI函数:
这其中有一个GUIData调用,这是用来存放Shader的属性是否应该被绘制的数据类,如果不这样做,你就会发现就算使用了自定义的特性,这个属性还会被绘制一次,因为我们的实现逻辑需要在Group中处理其子属性的可见性。
GUIData类与自定义方法:
很简单,立面有一个字典,这个字典存放了属性的可见性,同样有两个方法,一个方法设置属性的可见性,一个方法获取属性的可见性。
然后,需要写一个自定义脚本,继承ShaderGUI并且在Shader中使用CustomEditor调用,我们自主实现Shader的OnGUI逻辑,如图:
为什么还要这样自定义ShaderGUI呢?因为上述说过,为了实现Group分组效果,需要避免其子属性不管有没有特性,都应该被Group折叠栏决定显影,若是不自主实现,你就会发现你自定义绘制了一遍,这些子属性不管有没有特性,也会再被绘制一遍,因为没有处理Unity默认的绘制逻辑,所以需要我们自定义一个ShaderGUI实现。
全部代码:
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace CustomShaderPropertyDrawer
{
#region Data
public class GUIData
{
private static Dictionary<string, bool> propertyShow = new Dictionary<string, bool>();
public static void SetPropertyVisible(string property, bool visiable)
{
if (propertyShow.ContainsKey(property))
propertyShow[property] = visiable;
else
propertyShow.Add(property, visiable);
}
public static bool GetPropertyVisible(string property)
{
if (propertyShow.ContainsKey(property))
return propertyShow[property];
else
return false;
}
}
#endregion
public class GUIShow : ShaderGUI
{
internal MaterialProperty[] Pops { get; private set; }
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
Pops = properties;
var material = materialEditor.target as Material;
var shader = material.shader;
for (int i = 0; i < properties.Length; i++)
{
if (System.String.Concat(shader.GetPropertyAttributes(i)).Contains("Group"))
{
materialEditor.ShaderProperty(properties[i], properties[i].displayName);
}
else
{
if (GUIData.GetPropertyVisible(properties[i].name))
materialEditor.ShaderProperty(properties[i], properties[i].displayName);
}
}
}
}
#region MaterialPropertyDrawer
/// <summary>
/// Group分组,采样Flodout形式,可使用自定义GUIStyle
/// </summary>
public class GroupDrawer : MaterialPropertyDrawer
{
readonly string[] keys;
public GroupDrawer() { }
public GroupDrawer(params string[] keys)
{
this.keys = keys;
}
public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
if (editor.customShaderGUI == null || !(editor.customShaderGUI is GUIShow))
editor.DefaultShaderProperty(prop, prop.displayName);
else
{
if (prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Int)
{
bool group_foldout = false;
switch (prop.type)
{
case MaterialProperty.PropType.Float:
group_foldout = prop.floatValue == 1.0f ? true : false;
EditorGUI.BeginChangeCheck();
group_foldout = EditorGUI.Foldout(position, group_foldout, prop.displayName);
if (EditorGUI.EndChangeCheck())
{
prop.floatValue = group_foldout ? 1f : 0f;
if (keys != null && keys.Length > 0)
{
foreach (var key in keys)
{
GUIData.SetPropertyVisible(key, group_foldout);
}
}
}
break;
case MaterialProperty.PropType.Int:
group_foldout = prop.intValue == 1 ? true : false;
EditorGUI.BeginChangeCheck();
group_foldout = EditorGUI.Foldout(position, group_foldout, prop.displayName);
if (EditorGUI.EndChangeCheck())
{
prop.intValue = group_foldout ? 1 : 0;
if (keys != null && keys.Length > 0)
{
foreach (var key in keys)
{
GUIData.SetPropertyVisible(key, group_foldout);
}
}
}
break;
}
}
}
}
}
/// <summary>
/// 单线贴图样式
/// </summary>
public class SingleLineTexture : MaterialPropertyDrawer
{
public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor)
{
return 0;
}
public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
if (prop.type == MaterialProperty.PropType.Texture)
{
editor.TexturePropertySingleLine(label, prop);
}
}
}
#endregion
}
注意:官方说明,为了性能,EditorGUILayout不能和MaterialPropertyDrawers一起使用,所以最好还是使用EditorGUI
更多内容可以参考官方MaterialEditor,MaterialPropertyDrawer,ShaderOnGUI,EditorGUI等编辑器内容,同时可以阅读我的编辑器扩展基础知识:Unity拓展编辑器基础知识