Set tint for multiple objects in Unity


In some engines like Phaser you can set tint for group of child objects with single line of code. Unfortunately, this is not possible in Unity. Neither for SpriteRenderers, nor for UI graphic elements.
This post covers how to do it in a convenient way for both SpriteRenderers and UI Graphic objects. More, it will save the original tint of the object, so un-tinting will be easy too.

MassTinter class

Start with empty class MassTinter:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MassTinter {

}

Put following interface and generic abstract class inside:

public class MassTinter {

    private interface IMassTinterItem {

        public bool IsObject(Object obj);
        public void SetTint(Color tint);
    }

    private abstract class MassTinterItem<T> : IMassTinterItem where T : class {

        public T obj;
        public Color savedColor;

        public bool IsObject(Object obj) => (obj as T) == this.obj;
        public abstract void SetTint(Color tint);
    }
}

MassTinterItem is generic abstract class and we can write specific implementation for SpriteRenderer and for Graphic. Graphic is parent class for UI elements including TextMeshPro texts.
In MassTinterItem class we are storing component through which we will set tint and also its original color to easy un-tint.
Add following lines:

public class MassTinter {

        :
        :

    private class MassTinterItemGraphic : MassTinterItem<Graphic> {

        public override void SetTint(Color tint) => obj.color = savedColor * tint;
    }

    private class MassTinterItemSpriteRenderer : MassTinterItem<SpriteRenderer> {

        public override void SetTint(Color tint) => obj.color = savedColor * tint;
    }
}

In MassTinerItemGraphic and MassTinterItemSpriteRenderer we added specific implementation for Graphic and SpriteRenderer. These two components don’t have common ancestor with color property, so we had to write two separate classes.

Add following lines to class itself:

public class MassTinter {

             :
             :

    private readonly List<IMassTinterItem> _items = new();

    // -------------------------------------------------------------------------
    private bool HasItem(Object obj) {
        
        foreach (IMassTinterItem item in _items) {
            if (item.IsObject(obj)) {
                return true;
            }
        }

        return false;
    }

    // -------------------------------------------------------------------------
    private void AddItem(IMassTinterItem item) {

        _items.Add(item);
    }
}

These private methods are here to add item into the list of IMassTinterItem items and to check if item is already in list.

Now, we have to write public methods to add SpriteRenderer(s) or Graphic(s) into the list. Add these methods into MassTinter class:

public class MassTinter {

               :
               :

    // -------------------------------------------------------------------------
    public void Add(params Graphic[] elementsArray) {

        foreach (Graphic element in elementsArray) {
            Add(element);
        }
    }

    // -------------------------------------------------------------------------
    public void Add(Graphic graphic) {

        if (!HasItem(graphic)) {

            MassTinterItemGraphic newItem = new() {
                obj = graphic,
                savedColor = graphic.color
            };

            AddItem(newItem);

        } else {
            Debug.LogWarning($"Graphics {graphic.name} already in list");
        }
    }

    // -------------------------------------------------------------------------
    public void Add(params SpriteRenderer[] elementsArray) {

        foreach (SpriteRenderer element in elementsArray) {
            Add(element);
        }
    }

    // -------------------------------------------------------------------------
    public void Add(SpriteRenderer spriteRenderer) {

        if (!HasItem(spriteRenderer)) {

            MassTinterItemSpriteRenderer newItem = new() {
                obj = spriteRenderer,
                savedColor = spriteRenderer.color
            };

            AddItem(newItem);

        } else {
            Debug.LogWarning($"SpriteRenderer {spriteRenderer.name} already in list");
        }
    }
}

Finally, add SetTint() method:

public class MassTinter {

               :
               :

    // -------------------------------------------------------------------------
    public void SetTint(Color tint) {

        foreach (IMassTinterItem item in _items) {
            item.SetTint(tint);
        }
    }
}

Test

For testing create test script TintTest.cs with the following code:

using UnityEngine;

public class TintTest : MonoBehaviour {

    private MassTinter _tinter = null;

    // -------------------------------------------------------------------------
    private void Awake() {
        
        _tinter = new MassTinter();

        SpriteRenderer[] spriteRenderers = GetComponentsInChildren<SpriteRenderer>();
        _tinter.Add(spriteRenderers);
    }

    // -------------------------------------------------------------------------
    private void Update() {
        
        if (Input.GetKey(KeyCode.T)) {
            _tinter.SetTint(Color.grey);
        }

        if (Input.GetKey(KeyCode.U)) {
            _tinter.SetTint(Color.white);
        }
    }
}

Create new scene in Unity and place several sprites into it. Make them all child objects of single GameObject. In image below I named the main object ‘Block’ and put under it house, two trees and trash bin. Add TintTest script on the main object.

BTW, sprites I used are by Ansimuz.

Now, you can run the scene. Press T for tint and U for un-tint. Test script is applying grey tint on pressing T and white tint on pressing U.

In next step we will expand the scene a little. Add Canvas under ‘Block’ game object and set it to World Space. Then add TextMeshPro text for title above the house. See image below for canvas setup:

Make small adjustments to the TintTest script:

  1. add using for TMPro
  2. add SerializedField for TextMeshProUGUI text
  3. adjust Awake() method
using TMPro;                                                   // ===> 1.

public class TintTest : MonoBehaviour {

    [SerializeField] private TextMeshProUGUI _title = null;    // ===> 2.

               :
               :

    // -------------------------------------------------------------------------
    private void Awake() {

               :
               :

        _tinter.Add(_title);                                  // ===> 3.
    }

               :
               :
}

In Unity, drag and drop TextMeshPro component reference into empty field on ‘Block’object.

Run the game again. When you press T, whole scene including the title is tinted. When you press U, all is un-tinted again.


Leave a Reply

Your email address will not be published. Required fields are marked *