}

유니티 MaterialPropertyBlock를 이용해 "색상만" 손쉽게 수정하자! #MPBColorSetter

using System.Collections.Generic;
using UnityEngine;

[AddComponentMenu("Rendering/MPB Color Setter")]
[ExecuteAlways, DisallowMultipleComponent]
public class MPBColorSetter : MonoBehaviour
{
    [Header("Target Renderers (Reorderable)")]
    [SerializeField] private Renderer[] targetRenderers;

    [Header("Override Color")]
    [ColorUsage(true, true)] [SerializeField]
    private Color overrideColor = Color.white;

    [Header("Shader Color Property (Auto Detect)")]
    [SerializeField, Tooltip("비워두면 자동 탐색")]
    private string colorPropertyName = string.Empty;

    private static readonly Dictionary<string, int> s_IdCache = new();
    private static readonly int s_FallbackId = Shader.PropertyToID("_BaseColor");
    private static MaterialPropertyBlock s_Mpb;

    [SerializeField, HideInInspector] private int colorPropertyID = -1;

    private readonly Dictionary<Renderer, Color> originalColors = new();

    public Color OverrideColor => overrideColor;

    private void Awake()
    {
        RefreshCache();
        ApplyCachedColor();
    }

    private void OnEnable() => ApplyCachedColor();

#if UNITY_EDITOR
    private void OnValidate()
    {
        if (!Application.isPlaying)
        {
            if (colorPropertyID <= 0) RefreshCache();
            ApplyCachedColor();
        }
    }
#endif

    private void OnDestroy()
    {
        if (originalColors.Count == 0) return;

        s_Mpb ??= new MaterialPropertyBlock();
        foreach (var r in targetRenderers)
        {
            if (!r || !originalColors.TryGetValue(r, out var originalColor)) continue;

            r.GetPropertyBlock(s_Mpb);
            s_Mpb.SetColor(colorPropertyID, originalColor);
            r.SetPropertyBlock(s_Mpb);
        }
    }

    public void RefreshAndApply()
    {
        RefreshCache();
        ApplyCachedColor();
    }

    public void ApplyCachedColor()
    {
        if (targetRenderers == null || targetRenderers.Length == 0) return;
        if (colorPropertyID < 0) colorPropertyID = s_FallbackId;

        s_Mpb ??= new MaterialPropertyBlock();

        foreach (var r in targetRenderers)
        {
            if (!r) continue;

            r.GetPropertyBlock(s_Mpb);

            if (!originalColors.ContainsKey(r) && r.sharedMaterial.HasProperty(colorPropertyID))
            {
                originalColors[r] = r.sharedMaterial.GetColor(colorPropertyID);
            }

            s_Mpb.SetColor(colorPropertyID, overrideColor);
            r.SetPropertyBlock(s_Mpb);
        }
    }
    
    /// <summary>
    /// 런타임에서 OverrideColor를 설정하고 즉시 적용하는 함수.
    /// </summary>
    /// <param name="newColor">새로 적용할 색상</param>
    public void SetOverrideColor(Color newColor)
    {
        overrideColor = newColor;
        ApplyCachedColor();
    }


    private void RefreshCache()
    {
        if (targetRenderers == null || targetRenderers.Length == 0)
            targetRenderers = GetComponentsInChildren<Renderer>(true);

        if (string.IsNullOrWhiteSpace(colorPropertyName))
            colorPropertyName = DetectColorProperty(targetRenderers);

        colorPropertyID = GetPropertyID(colorPropertyName);
    }

    private static int GetPropertyID(string name)
    {
        if (string.IsNullOrEmpty(name)) return s_FallbackId;
        if (!s_IdCache.TryGetValue(name, out var id))
        {
            id = Shader.PropertyToID(name);
            s_IdCache[name] = id;
        }
        return id;
    }

    private static string DetectColorProperty(Renderer[] rends)
    {
        string fallback = null;

        foreach (var r in rends)
        {
            if (!r) continue;
            foreach (var mat in r.sharedMaterials)
            {
                if (!mat || !mat.shader) continue;

#if UNITY_EDITOR
                int cnt = UnityEditor.ShaderUtil.GetPropertyCount(mat.shader);
                for (int i = 0; i < cnt; ++i)
                {
                    if (UnityEditor.ShaderUtil.GetPropertyType(mat.shader, i) != UnityEditor.ShaderUtil.ShaderPropertyType.Color)
                        continue;

                    string name = UnityEditor.ShaderUtil.GetPropertyName(mat.shader, i);
                    if (name == "_BaseColor" || name == "_Color") return name;
                    fallback ??= name;
                }
#else
                foreach (var name in k_CommonColorProps)
                    if (mat.HasProperty(name)) return name;
#endif
            }
        }
        return fallback ?? "_BaseColor";
    }

#if !UNITY_EDITOR
    private static readonly string[] k_CommonColorProps =
        { "_BaseColor", "_Color", "_Tint", "_Albedo" };
#endif
}
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(MPBColorSetter))]
public class MPBColorSetterEditor : Editor
{
    private static readonly Color[] presetColors =
    {
        new Color(1f, 1f, 1f),       // White
        new Color(0f, 0f, 0f),       // Black
        new Color(1f, 0f, 0f),       // Red
        new Color(0f, 1f, 0f),       // Green
        new Color(0f, 0f, 1f),       // Blue
        new Color(1f, 1f, 0f),       // Yellow
        new Color(1f, 0f, 1f),       // Magenta
        new Color(0f, 1f, 1f),       // Cyan
        new Color(1f, 0.5f, 0f),     // Orange
        new Color(0.6f, 0.3f, 1f),   // Purple
        new Color(0.3f, 0.6f, 1f),   // Sky Blue
        new Color(0.2f, 0.8f, 0.4f), // Mint
        new Color(0.5f, 0.2f, 0.2f), // Brown
        new Color(0.2f, 0.2f, 0.2f), // Dark Gray
        new Color(0.6f, 0.6f, 0.6f), // Light Gray
    };

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        DrawPropertiesExcluding(serializedObject, "m_Script");

        EditorGUILayout.Space(10);
        EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
        EditorGUILayout.LabelField("🎨 Quick Color Presets", EditorStyles.boldLabel);
        EditorGUILayout.Space(4);

        EditorGUILayout.BeginVertical("box");
        var setter = (MPBColorSetter)target;
        var current = setter.OverrideColor;

        int total = presetColors.Length;
        int columnWidth = Mathf.Max(1, Mathf.FloorToInt(EditorGUIUtility.currentViewWidth / 50f));
        int rowCount = Mathf.CeilToInt((float)total / columnWidth);

        for (int row = 0; row < rowCount; row++)
        {
            EditorGUILayout.BeginHorizontal();
            for (int col = 0; col < columnWidth; col++)
            {
                int i = row * columnWidth + col;
                if (i >= total) break;

                var color = presetColors[i];
                var rect = GUILayoutUtility.GetRect(24, 24, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));

                // 테두리 처리
                EditorGUI.DrawRect(rect, current == color ? Color.white : Color.gray);
                Rect inner = new Rect(rect.x + 2, rect.y + 2, rect.width - 4, rect.height - 4);
                EditorGUI.DrawRect(inner, color);

                // 클릭 이벤트
                if (Event.current.type == EventType.MouseDown && inner.Contains(Event.current.mousePosition))
                {
                    Undo.RecordObject(setter, "Change Override Color");
                    var prop = serializedObject.FindProperty("overrideColor");
                    prop.colorValue = color;
                    serializedObject.ApplyModifiedProperties();
                    setter.RefreshAndApply();
                    GUI.changed = true;
                }

                // 툴팁 (마우스 오버 시 RGB 표시)
                if (inner.Contains(Event.current.mousePosition))
                {
                    GUI.Label(rect, new GUIContent("", $"R:{color.r:F2} G:{color.g:F2} B:{color.b:F2}"));
                    Repaint();
                }
            }
            EditorGUILayout.EndHorizontal();
        }

        EditorGUILayout.EndVertical();
        serializedObject.ApplyModifiedProperties();
    }
}

MPB Color Setter / 기존 사용방법

위 컴포넌트를 붙이면 자동으로 Color Property 이름을 찾은 후, 사용할 수 있게 도와줍니다.

그 후 Color색상을 바꾸면 렌더러의 색상을 쉽게 바꿀 수 있습니다!

 

우측 이미지처럼 이제 색상을 바꿔주려고 귀찮게 메테리얼을 생성하고 할당해주고 바꿔줄 필요가 없다는 거죠!! WoW 진짜 최고!