Skip to content

Instantly share code, notes, and snippets.

@fanhexin
Last active April 17, 2023 08:22
Show Gist options
  • Save fanhexin/6785098ab1a0dcfe85938da266fff98b to your computer and use it in GitHub Desktop.
Save fanhexin/6785098ab1a0dcfe85938da266fff98b to your computer and use it in GitHub Desktop.
Custom unity component to modify material property through MaterialPropertyBlockModifier
using System;
using GameFoundation.Extension;
using UnityEngine;
namespace GameFoundation.Util
{
[ExecuteInEditMode]
[RequireComponent(typeof(Renderer))]
public class MaterialPropertyBlockModifier : MonoBehaviour
{
#region NestedClasses
public class ShaderProperty<T>
{
public string name;
public T value;
}
[Serializable]
public class ColorProperty : ShaderProperty<Color> { }
[Serializable]
public class FloatProperty : ShaderProperty<float> { }
#endregion
[SerializeField]
ColorProperty[] _colorProperties;
[SerializeField]
FloatProperty[] _floatProperties;
void Start()
{
var render = GetComponent<MeshRenderer>();
render.ApplyPropertyBlock(mpb =>
{
SetMpbProperties(_colorProperties, mpb.SetColor);
SetMpbProperties(_floatProperties, mpb.SetFloat);
});
}
void SetMpbProperties<T>(ShaderProperty<T>[] properties, Action<string, T> fn)
{
if (null == properties)
{
return;
}
foreach (var item in properties)
{
fn(item.name, item.value);
}
}
}
}
using GameFoundation.Extension;
using GameFoundation.Util;
using UnityEditor;
using UnityEngine;
namespace GameFoundation.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(MaterialPropertyBlockModifier))]
public class MaterialPropertyBlockModifierInspector : UnityEditor.Editor
{
#region NestedClasses
abstract class AbstractPropertyGuiRender<T>
{
readonly MaterialPropertyBlockModifierInspector _editor;
readonly MaterialPropertyBlockModifier _mpbm;
protected readonly MeshRenderer _mr;
protected AbstractPropertyGuiRender(MaterialPropertyBlockModifierInspector editor)
{
_editor = editor;
_mpbm = _editor.target as MaterialPropertyBlockModifier;
_mr = _mpbm.GetComponent<MeshRenderer>();
}
protected abstract T GetOriginValue(string propertyName);
protected abstract void SetValue(MaterialPropertyBlockModifier mpbm, string name, T value);
protected abstract T GetValue(SerializedProperty sp);
protected abstract void SetValue(SerializedProperty sp, T value);
protected abstract T RenderView(string labelName, T value);
protected abstract bool PropertyEquals(SerializedProperty property, T value);
public void Render(string propertyName, string propertiesName)
{
var originValue = GetOriginValue(propertyName);
SerializedProperty sp = _editor.serializedObject.FindProperty(propertiesName);
int index = sp.IndexOfArrayItem(x => x.FindPropertyRelative("name").stringValue == propertyName);
if (index < 0)
{
EditorGUI.BeginChangeCheck();
var newValue = RenderView(propertyName, originValue);
if (EditorGUI.EndChangeCheck())
{
sp.AddArrayItem(x =>
{
x.FindPropertyRelative("name").stringValue = propertyName;
SetValue(x.FindPropertyRelative("value"), newValue);
});
SetValue(_mpbm, propertyName, newValue);
_editor.serializedObject.ApplyModifiedProperties();
}
return;
}
SerializedProperty property = sp.GetArrayElementAtIndex(index);
SerializedProperty valueProperty = property.FindPropertyRelative("value");
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(valueProperty, new GUIContent(propertyName));
if (EditorGUI.EndChangeCheck())
{
SetValue(_mpbm, propertyName, GetValue(valueProperty));
_editor.serializedObject.ApplyModifiedProperties();
}
if (!PropertyEquals(valueProperty, originValue) && GUILayout.Button("revert"))
{
sp.DeleteArrayElementAtIndex(index);
SetValue(_mpbm, propertyName, originValue);
_editor.serializedObject.ApplyModifiedProperties();
}
}
}
class ColorPropertyGuiRender: AbstractPropertyGuiRender<Color>
{
public ColorPropertyGuiRender(MaterialPropertyBlockModifierInspector editor)
: base(editor)
{
}
protected override Color GetOriginValue(string propertyName)
{
return _mr.sharedMaterial.GetColor(propertyName);
}
protected override void SetValue(MaterialPropertyBlockModifier mpbm, string name, Color value)
{
_mr.ApplyPropertyBlock(x => x.SetColor(name, value));
}
protected override Color GetValue(SerializedProperty sp)
{
return sp.colorValue;
}
protected override void SetValue(SerializedProperty sp, Color value)
{
sp.colorValue = value;
}
protected override Color RenderView(string labelName, Color value)
{
return EditorGUILayout.ColorField(labelName, value);
}
protected override bool PropertyEquals(SerializedProperty property, Color value)
{
return property.colorValue == value;
}
}
class FloatPropertyGuiRender: AbstractPropertyGuiRender<float>
{
public FloatPropertyGuiRender(MaterialPropertyBlockModifierInspector editor)
: base(editor)
{
}
protected override float GetOriginValue(string propertyName)
{
return _mr.sharedMaterial.GetFloat(propertyName);
}
protected override void SetValue(MaterialPropertyBlockModifier mpbm, string name, float value)
{
_mr.ApplyPropertyBlock(x => x.SetFloat(name, value));
}
protected override float GetValue(SerializedProperty sp)
{
return sp.floatValue;
}
protected override void SetValue(SerializedProperty sp, float value)
{
sp.floatValue = value;
}
protected override float RenderView(string labelName, float value)
{
return EditorGUILayout.FloatField(labelName, value);
}
protected override bool PropertyEquals(SerializedProperty property, float value)
{
return property.floatValue == value;
}
}
#endregion
ColorPropertyGuiRender _colorPropertyGuiRender;
FloatPropertyGuiRender _floatPropertyGuiRender;
Renderer _render;
//private TexturePropertyGuiRender _texturePropertyGuiRender;
void OnEnable()
{
_render = (target as MaterialPropertyBlockModifier).GetComponent<Renderer>();
if (_render.sharedMaterial != null)
{
_render.sharedMaterial.hideFlags = HideFlags.NotEditable;
}
// TODO 不用每次都 new,多实例可复用
_colorPropertyGuiRender = new ColorPropertyGuiRender(this);
_floatPropertyGuiRender = new FloatPropertyGuiRender(this);
//_texturePropertyGuiRender = new TexturePropertyGuiRender(this);
}
void OnDestroy()
{
if (_render != null && _render.sharedMaterial != null)
{
_render.sharedMaterial.hideFlags = HideFlags.None;
}
}
public override void OnInspectorGUI()
{
var mpbm = target as MaterialPropertyBlockModifier;
var mr = mpbm.GetComponent<MeshRenderer>();
if (mr.sharedMaterial == null)
{
return;
}
var shader = mr.sharedMaterial.shader;
for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)
{
string name = ShaderUtil.GetPropertyName(shader, i);
ShaderUtil.ShaderPropertyType type = ShaderUtil.GetPropertyType(shader, i);
EditorGUILayout.BeginHorizontal();
switch (type)
{
case ShaderUtil.ShaderPropertyType.Color:
_colorPropertyGuiRender.Render(name, "_colorProperties");
break;
case ShaderUtil.ShaderPropertyType.Float:
case ShaderUtil.ShaderPropertyType.Range:
_floatPropertyGuiRender.Render(name, "_floatProperties");
break;
default:
break;
}
EditorGUILayout.EndHorizontal();
}
}
}
}
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Graphs;
using UnityEngine;
namespace GameFoundation.Extension
{
public static class SupportExtension
{
#if UNITY_EDITOR
public static void AddArrayItem(this SerializedProperty sp, Action<SerializedProperty> initFn)
{
if (!sp.isArray)
{
throw new Exception("SerializedProperty must be array type");
}
int index = sp.arraySize;
sp.InsertArrayElementAtIndex(index);
SerializedProperty newItemProperty = sp.GetArrayElementAtIndex(index);
if (initFn != null)
{
initFn(newItemProperty);
}
}
public static SerializedProperty FindArrayItem(this SerializedProperty sp, Predicate<SerializedProperty> predicate)
{
int index = sp.IndexOfArrayItem(predicate);
if (index < 0)
{
return null;
}
return sp.GetArrayElementAtIndex(index);
}
public static int IndexOfArrayItem(this SerializedProperty sp, Predicate<SerializedProperty> predicate)
{
for (int i = 0; i < sp.arraySize; i++)
{
SerializedProperty item = sp.GetArrayElementAtIndex(i);
if (predicate(item))
{
return i;
}
}
return -1;
}
#endif
static MaterialPropertyBlock _mpb;
public static void ApplyPropertyBlock(this Renderer renderer, Action<MaterialPropertyBlock> changePropertiesFn)
{
if (changePropertiesFn == null)
{
return;
}
if (_mpb == null)
{
_mpb = new MaterialPropertyBlock();
}
renderer.GetPropertyBlock(_mpb);
changePropertiesFn(_mpb);
renderer.SetPropertyBlock(_mpb);
}
}
}
@eduardhasanaj
Copy link

Thank you.
This put me in a right direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment