Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

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

Discussion in 'Scripting' started by ECampese, Apr 1, 2022.

  1. ECampese

    ECampese

    Joined:
    Jun 20, 2020
    Posts:
    8
    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.

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. public class MaterialSwitcher : MonoBehaviour
    5. {
    6.     [SerializeField]
    7.     int indexToSwitch = 0;
    8.  
    9.     [SerializeField]
    10.     Material newMaterial;
    11.  
    12.  
    13.     private Material GetMeshMaterialAtIndex(int index)
    14.     {
    15.         return GetComponent<Renderer>().materials[index];
    16.     }
    17.  
    18.     public void SwitchMaterial()
    19.     {
    20.         Material currentMaterial = GetMeshMaterialAtIndex(indexToSwitch);
    21.      
    22.         Debug.Log("Collison worked!");
    23.  
    24.         if(String.Equals(currentMaterial.name, newMaterial.name))
    25.         {
    26.             Debug.Log("Material already swapped!");
    27.         }
    28.         else
    29.         {
    30.             GetComponent<Renderer>().materials[indexToSwitch] = newMaterial;
    31.             Debug.Log("Material swapped succesfully!");
    32.         }
    33.     }
    34. }
    35.  
    The function that should swap the material is called from another gameObject ( the player ) like so:
    Code (CSharp):
    1.  private void OnCollisionEnter(Collision other)
    2.     {
    3.          if(!other.gameObject.GetComponent<MaterialSwitcher>()) return;
    4.          other.gameObject.GetComponent<MaterialSwitcher>().SwitchMaterial();
    5.     }
    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.

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. public class MaterialSwitcher : MonoBehaviour
    5. {
    6.     [SerializeField]
    7.     int indexToSwitch = 0;
    8.  
    9.     [SerializeField]
    10.     Material newMaterial;
    11.  
    12.     [SerializeField]
    13.     Material[] newMaterials;
    14.  
    15.     void Start()
    16.     {
    17.         // CHECK FOR ARRAY SWAP
    18.         GetComponent<Renderer>().materials = newMaterials;
    19.     }
    20.    
    21.     private Material GetMeshMaterialAtIndex(int index)
    22.     {
    23.         return GetComponent<Renderer>().materials[index];
    24.     }
    25.  
    26.     public void SwitchMaterial()
    27.     {
    28.         Material currentMaterial = GetMeshMaterialAtIndex(indexToSwitch);
    29.        
    30.         Debug.Log("Collison worked!");
    31.  
    32.         if(String.Equals(currentMaterial.name, newMaterial.name))
    33.         {
    34.             Debug.Log("Material already swapped!");
    35.         }
    36.         else
    37.         {
    38.             GetComponent<Renderer>().materials[indexToSwitch] = newMaterial;
    39.             Debug.Log("Material swapped succesfully!");
    40.         }
    41.     }
    42. }
    43.  
    It is driving me nuts! Help guys!

    Thank you for your time.
     
    Last edited: Apr 1, 2022
  2. ZBerm

    ZBerm

    Joined:
    Jan 12, 2017
    Posts:
    61
    If you have a look at the docs here https://docs.unity3d.com/ScriptReference/Renderer-materials.html 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.

    Code (CSharp):
    1.         else
    2.         {
    3.             Material[] materials = GetComponent<Renderer>().materials;
    4.             materials[indexToSwitch] = newMaterial;
    5.             GetComponent<Renderer>().materials = materials;
    6.             Debug.Log("Material swapped succesfully!");
    7.         }
    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.
     
  3. ECampese

    ECampese

    Joined:
    Jun 20, 2020
    Posts:
    8
    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!