Search Unity

Material.SetColor on custom UI Shader not working

Discussion in 'Scripting' started by Yandalf, Mar 18, 2022.

  1. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Hey everyone!

    I've written a small multi-channel mask shader to be used in my game's UI.
    This shader is being used for selectable objects, and need to change one or more colors on selection state change.
    The shader seems to work as intended in inspector, changing all colors properly, but at runtime some weirdness begins to happen.
    I start by spawning a few objects using a material with this shader, and use a method in these objects' Awake to set their initial colors. Then I change the colors of my initially selected object.
    This all works fine, but when I later try to change the selection through a click on a UI element, the state change goes through fine but the material colors do not update. I have verified that all the necessary code is being called for this to happen, and it worked with a previous shader I used for these materials (that I had to replace due to Masking issues. Shadergraph shaders seem to not support masking yet).
    Relevant Shader Properties:
    Code (CSharp):
    1. _Color_Outline("Color_Outline", Color) = (1,1,1,1)
    2. _Color_Border("Color_Border", Color) = (1,1,1,1)
    3. _Color_Fill("Color_Fill", Color) = (1,1,1,1)
    UI Element Code:
    Code (CSharp):
    1.         public bool Selected
    2.         {
    3.             get
    4.             {
    5.                 return _selected;
    6.             }
    7.             set
    8.             {
    9.                 if(_selected != value)
    10.                 {
    11.                     _selected = value;
    12.                     SetOutlineColors(_selected);
    13.                 }
    14.             }
    15.         }
    16.  
    17.         private void Awake()
    18.         {
    19.             var coIndex = _baseImage.material.shader.FindPropertyIndex("_Color_Outline");
    20.             var cbIndex = _baseImage.material.shader.FindPropertyIndex("_Color_Border");
    21.             _outlineColorId = _baseImage.material.shader.GetPropertyNameId(coIndex);
    22.             _borderColorId    = _baseImage.material.shader.GetPropertyNameId(cbIndex);
    23.             _button.onClick.AddListener(() => { Selected = true; });
    24.             SetOutlineColors(Selected);
    25.         }
    26.         private void SetOutlineColors(bool selected)
    27.         {
    28.             _baseImage.material.SetColor(_outlineColorId, selected ? _outlineColorSelected : _outlineColorDefault);
    29.             _baseImage.material.SetColor(_borderColorId, selected  ? _borderColorSelected : _borderColorDefault);
    30.             Debug.Log($"{name} Setting Outline Colors, selected: {selected}."); //This calls when expected!
    31.         }
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    It's been a while but the last time I tried to do something similar, I had to instantiate a copy of the material on the image because of the way the UI batches shared materials and by default material property blocks weren't (aren't?) supported. Give something like this a whirl:
    Code (csharp):
    1.  
    2. private Material _localMaterial;
    3.  
    4. private void Awake()
    5. {
    6.    // copy it and assign it back
    7.    _localMaterial = Instantiate<Material>(_baseImage.material);
    8.  
    9.    _baseImage.material = _localMaterial;
    10.  
    11.    // replace the rest of your _baseImage.material code with _localMaterial including in SetOutlineColors
    12. }
    13.  
    This will break batching, so there will be performance implications, but I never noticed anything too bad.
     
  3. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    You're right that has to be done as well for UI materials, but I was already creating a copy of the material for each element and that unfortunately didn't help this problem.