0
\$\begingroup\$

I created a component called "AphaGroup" (an analog of the existing CanvasGroup for UI) which should be able to change alpha of the whole game object tree. My component finds all the MeshRenderer components, takes their material.color and changes its alpha. It should work both in play- & edit- modes. When it's run in edit-mode there is an error in the console when the material is copied for the first time:

Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.

I've read most of the similar questions on the Internet but still haven't found the answer on how to manage this situation properly. How to avoid both: this error message and memory leaks?

I tried Renderer.material, Renderer.materials and Renderer.GetMaterials, but all they cause the same error.

\$\endgroup\$
  • \$\begingroup\$ Did you try the one thing suggested in the error message: renderer.sharedMaterial ? I give an overview of options for avoiding the .material getter in this answer. \$\endgroup\$ – DMGregory Apr 2 at 11:56
  • \$\begingroup\$ @DMGregory sharedMaterial doesn't suit because it will change transparency for all the objects which use the same material in the scene. I will read it. \$\endgroup\$ – Sergey Apr 2 at 12:12
  • \$\begingroup\$ No it won't, if you use it correctly. There's a reason it's being recommended in the error message \$\endgroup\$ – DMGregory Apr 2 at 12:13
  • \$\begingroup\$ If I create a copy via Instantiate(material) then I should set it to the renderer.material, in this case, who is responsible for destroying the previous instance of renderer.material which is replaced with a new one? \$\endgroup\$ – Sergey Apr 2 at 15:11
  • \$\begingroup\$ If you want a material to be destroyed, you are responsible for destroying it. But usually for a case like this, the previous value was an asset that you do not want to destroy. It's OK for it to simply sit unreferenced by any active game objects, but still exist in your game asset files. \$\endgroup\$ – DMGregory Apr 2 at 15:25
0
\$\begingroup\$

My original goal was to create a component similar to CanvasGroup in the UI system, but which would work for Sprites. Then I needed it to work with MeshText components as well - that's where I encountered the error described in my question.

Given my original goal, the code was tested on those components (sprites & text) and I can't guarantee that it works correctly with any other materials. For example, transparency of some materials wouldn't be changed if their RenderingMode is not Fade or Transparent, or their shader is not "Transparent/Diffuse" (it can be changed with material.shader = Shader.Find("Transparent/Diffuse"); or in the Inspector).

AlphaGroup.cs

using System.Linq;
using UnityEditor;
using UnityEngine;

/// <summary>
/// Controls the transparency of the entire object (including all children) as `CanvasGroup` does in the UI system.
/// Executes both in play- & edit- modes.
/// </summary>
[DisallowMultipleComponent]
[ExecuteAlways]
public class AlphaGroup : MonoBehaviour
{
    [Range(0f, 1f)]
    [SerializeField]
    private float _alpha = 1f;

    private float _prevAlpha;
    private Renderer[] _renderers;

    private void Awake()
    {
        _renderers = GetComponentsInChildren<Renderer>();
    }

    private void Update()
    {
        UpdateAlpha();
    }

    private void UpdateAlpha()
    {
        if (_prevAlpha == _alpha)
            return;

        for (int i = 0; i < _renderers.Length; i++)
        {
            var renderer = _renderers[i];

            if (renderer is SpriteRenderer spriteRenderer)
            {
                var color = spriteRenderer.color;
                color.a = _alpha;
                spriteRenderer.color = color;
            }
            else if (renderer is MeshRenderer meshRenderer)
            {
                IsCopyMaterial isCopy = renderer.gameObject.GetComponent<IsCopyMaterial>();
                Material[] materials;

                if (isCopy)
                {
                    materials = meshRenderer.sharedMaterials;
                }
                else
                {
                    materials = meshRenderer.sharedMaterials.Select(m => Instantiate(m)).ToArray();

                    for (int j = 0; j < materials.Length; j++)
                    {
                        var material = materials[j];
                        material.name = meshRenderer.sharedMaterials[j].name + " (copy)";
                    }

                    meshRenderer.sharedMaterials = materials;
                    meshRenderer.gameObject.AddComponent<IsCopyMaterial>(); // Create a copy of the renderer materials only once.
                }

                foreach (Material material in materials)
                {
                    if (!material.HasProperty("_Color"))
                        continue;

                    var color = material.color;
                    color.a = _alpha;
                    material.color = color;
                }
            }
        }

        _prevAlpha = _alpha;
    }
}

IsCopyMaterial.cs

/// <summary>
/// An indicator that the material has been copied.
/// </summary>
[DisallowMultipleComponent]
public class IsCopyMaterial : MonoBehaviour
{
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.