How to change a single material at an object which has multiple materials.

Hello guys, I tried this script to swap a material of a game object on collision. Problem is that the collision happens correctly but the material is not swapped at all.

using UnityEngine;
using System;

public class MaterialSwitcher : MonoBehaviour
{
    [SerializeField]
    int indexToSwitch = 0;

    [SerializeField]
    Material newMaterial;

  
    private Material GetMeshMaterialAtIndex(int index)
    {
        return GetComponent<Renderer>().materials[index];
    }

    public void SwitchMaterial()
    {
        Material currentMaterial = GetMeshMaterialAtIndex(indexToSwitch);
      
        Debug.Log("Collison worked!");

        if(String.Equals(currentMaterial.name, newMaterial.name))
        {
            Debug.Log("Material already swapped!");
        }
        else
        {
            GetComponent<Renderer>().materials[indexToSwitch] = newMaterial;
            Debug.Log("Material swapped succesfully!");
        }
    }
}

The function that should swap the material is called from another gameObject ( the player ) like so:

 private void OnCollisionEnter(Collision other)
    {
         if(!other.gameObject.GetComponent<MaterialSwitcher>()) return;
         other.gameObject.GetComponent<MaterialSwitcher>().SwitchMaterial();
    }

And finally, this is the console output:

One funny thing is that If I create a public variable with an array of two materials, and I try to swap the current materials with the one in the array, it works! So the problem is with INDIVIDUAL material. If a game object has multiple materials, you can only change all of them or none at all.

using UnityEngine;
using System;

public class MaterialSwitcher : MonoBehaviour
{
    [SerializeField]
    int indexToSwitch = 0;

    [SerializeField]
    Material newMaterial;

    [SerializeField]
    Material[] newMaterials;

    void Start()
    {
        // CHECK FOR ARRAY SWAP
        GetComponent<Renderer>().materials = newMaterials;
    }
   
    private Material GetMeshMaterialAtIndex(int index)
    {
        return GetComponent<Renderer>().materials[index];
    }

    public void SwitchMaterial()
    {
        Material currentMaterial = GetMeshMaterialAtIndex(indexToSwitch);
       
        Debug.Log("Collison worked!");

        if(String.Equals(currentMaterial.name, newMaterial.name))
        {
            Debug.Log("Material already swapped!");
        }
        else
        {
            GetComponent<Renderer>().materials[indexToSwitch] = newMaterial;
            Debug.Log("Material swapped succesfully!");
        }
    }
}

It is driving me nuts! Help guys!

Thank you for your time.

If you have a look at the docs here Unity - Scripting API: Renderer.materials you’ll see this line:

This means that the following line in your code is getting a new copy of the materials list, not a reference to the materials list on the object. When you swap the material it’s only changing the new copy that you have.

GetComponent<Renderer>().materials[indexToSwitch] = new Material(newMaterial);

So the solution is to store a reference to the returned list, modify that reference, and then set it back to the renderer.

        else
        {
            Material[] materials = GetComponent<Renderer>().materials;
            materials[indexToSwitch] = newMaterial;
            GetComponent<Renderer>().materials = materials;
            Debug.Log("Material swapped succesfully!");
        }

I would also recommend you store a Renderer varible in your MaterialSwitcher script that is set in Start() so that you only have to call GetComponent<> once.

6 Likes

Amazing! Thank you so much for your explanation, you right it works now and I understood why. Didn’t know at all about this thing!

1 Like