Search Unity

Changing Material Array Via C#

Discussion in 'Scripting' started by Studio_Akiba, Oct 13, 2016.

  1. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,426
    I am trying to assign a variable material to an object in an array via a C# script, I have changed things like the emissive value of a material before, but never needed to change the actual material of an object with multiple material channels, which I think is where I am getting issues.

    I have a script with 2 variable materials at the top:
    Code (CSharp):
    1. public Material lightboxActiveMaterial;
    2. public Material lightboxInactiveMaterial;
    And further down the script I have this:
    Code (CSharp):
    1. if (lightIsActive[lightCounter]) {
    2.                         //lights [lightCounter].GetComponent<Renderer> ().material.SetColor( "_EmissionColor",  2f * Color.white);
    3.                         lights[lightCounter].GetComponent<MeshRenderer>().materials[0] = lightboxActiveMaterial;
    4.                     }
    5.                     else
    6.                     {
    7.                         //lights [lightCounter].GetComponent<Renderer> ().material.SetColor( "_EmissionColor",  Color.black);
    8.                         lights[lightCounter].GetComponent<MeshRenderer>().materials[0] = lightboxInactiveMaterial;
    9.                     }
    Most of this is related to other stuff but lines 3 and 8 are the important ones, which as far as I can see, should be working.

    The lines that have been commented out (2 and 7) both work so I can't think of any reason the new ones wouldn't.

    The object in question (lights) has 2 materials, the first in the array in the MeshRenderer component is the light which I want to control via these lines, the second is just a frame.

    I think my issue stems from the fact I can't use the standard <Renderer>().material as I have 2 materials, not 1, but I can't be sure.

    I have also heard over the years that not having unused (not currently in-scene) materials and whatnot in the Resources folder can lead to issues when instantiating stuff like this, but I have never used this method, and thought I should ask before messing with where stuff is stored.
     
  2. imDanOush

    imDanOush

    Joined:
    Oct 12, 2013
    Posts:
    368
    You didn't write "new" keyword there, You also can change the first material without using the mat array, example is written below.
    Code (CSharp):
    1. if (lightIsActive[lightCounter]) {
    2.                             lights[lightCounter].GetComponent<MeshRenderer>().material = new Material(lightboxActiveMaterial);
    3.                         }
    4.                         else
    5.                         {
    6.                             lights[lightCounter].GetComponent<MeshRenderer>().material = new material (lightboxInactiveMaterial);
    7.                         }
    8. }
    If there is an array of materials, then I guess you should assign a new array to it (and not assigning one element of the array). For example if you want to change the Material[1] , you should first make a whole new material array and assign it to the renderer's material array. Something like the code below.
    Code (CSharp):
    1. Material[] m = new material[100]; // assuming the number of materials of the renderer is 100.
    2. m = lights[lightCounter].GetComponent<MeshRenderer>().materials; //get the materials.
    3. m[1] = new Material(lightboxActiveMaterial); // change the material.
    4. if (lightIsActive[lightCounter])
    5.     {
    6.          lights[lightCounter].GetComponent<MeshRenderer>().materials = m; //assign it this way.
    7.     }
    8. }
    I wrote the codes on the fly (so I didn't test them) but they should work.
    Hope this was helpful.
     
    Last edited: Oct 13, 2016
  3. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,426
    The first one doesn't seem to work for me, I have changed all my changing materials to that syntax but it doesn't seem to work.
    Code (CSharp):
    1. if (lightIsActive[lightCounter]) {
    2.                         //lights [lightCounter].GetComponent<Renderer> ().material.SetColor( "_EmissionColor",  2f * Color.white);
    3.                         lights[lightCounter].GetComponent<Renderer>().materials[0] = new Material(lightboxActiveMaterial);
    4.                     }
    5.                     else
    6.                     {
    7.                         //lights [lightCounter].GetComponent<Renderer> ().material.SetColor( "_EmissionColor",  Color.black);
    8.                         lights[lightCounter].GetComponent<Renderer>().materials[0] = new Material(lightboxInactiveMaterial);
    9.                     }
    In another script, I have this as well, which is also being assigned when the others are, but this isn't working either.
    Code (CSharp):
    1. public void ChangeMaterial(Material newMaterial)
    2.     {
    3.         for (int i = 0; i < meshes.Count; i++)
    4.         {
    5.             meshes[i].GetComponent<Renderer>().material = new Material(newMaterial);
    6.         }
    7.     }
     
  4. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    You are changing the array copy you get back from materials (info).

    e.g.
    Code (CSharp):
    1. Renderer r = meshes[i].GetComponent<Renderer>();
    2. material[] mats = r.materials;  // copy of materials array.
    3.  
    4. mats[n] = newMaterial; // set new material
    5.  
    6. r.materials = mats; // assign updated array to materials array
    But as you are only changing element [0] you could just assign it to material or sharedMaterial property.
     
    thurst0n and MrKarp like this.
  5. imDanOush

    imDanOush

    Joined:
    Oct 12, 2013
    Posts:
    368
    Strange, I just tested it and it works fine. I think maybe there is another problem. And take note that these statements never change the materials parmanently, they only change at runtime.

    Here is the result of my code:

    Here is the code I've used, suggest you to run it in a new project and study it.​
    Attach it to a cube and assign a new material to the script, then run the project.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class changeMat : MonoBehaviour
    5. {
    6.  
    7.     public Material myNewMaterial; //I want this new mat to be used instead of the old mat
    8.  
    9.  
    10.     void Start()
    11.     {
    12.         if (myNewMaterial == null)
    13.         {
    14.             Debug.Log("No new material assigned in the inspector to this script");
    15.             return;
    16.         }
    17.         else
    18.         {
    19.             Renderer rend = this.GetComponent<Renderer>();
    20.  
    21.             if (rend != null)
    22.                 rend.material = new Material(myNewMaterial); //This code changes the old mat to the new one
    23.             else {
    24.                 Debug.Log("No renderer is found");
    25.                 return;
    26.             }
    27.         }
    28.     }
    29. }
    30.  

    Maybe if you write more details (about the new materials, more details of the code you've written), people might help you better.

    That was all I could help :) .
     
  6. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,426
    Update:

    The original script I pasted (lights) now works, but I am still having trouble with the other.

    It has been updated to use materials on its own component instead of another, and uses a boolean to switch:
    Code (CSharp):
    1. public void ChangeMaterial(bool isActive)
    2.     {
    3.         for (int i = 0; i < meshes.Count; i++)
    4.         {
    5.             if (isActive)
    6.             {
    7.                 meshes[i].GetComponent<Renderer>().material = new Material(activeMaterial);
    8.             }
    9.             else
    10.             {
    11.                 meshes[i].GetComponent<Renderer>().material = new Material(inactiveMaterial);
    12.             }
    13.         }
    14.     }
    But this is giving me these errors:

    NullReferenceException: Object reference not set to an instance of an object
    LightStripParent.ChangeMaterial (Boolean isActive) (at Assets/Scripts/Light Strips/LightStripParent.cs:71)
    GateChangeLightStrip.Update () (at Assets/Scripts/Puzzles/Switch Puzzle/GateChangeLightStrip.cs:17)

    The first line it is talking about is this (line 11 from the script above):
    Code (CSharp):
    1. meshes[i].GetComponent<Renderer>().material = new Material(inactiveMaterial);
    And the second is this:
    Code (CSharp):
    1. lightStrip.ChangeMaterial(false);
    Again, I can't see anything different with this, the only thing I can think of is that I am somehow losing the meshes array but I can't be as I can still use this:
    Code (CSharp):
    1. public void DeleteMesh()
    2.     {
    3.         for (int i = 0; i < meshes.Count; i++)
    4.         {
    5.             DestroyImmediate(meshes[i]);
    6.         }
    7.     }
    Which is what the script at the top of this post was based off of, so I can see no reason it shouldn't work.
     
  7. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Normally meshes are separate objects, e.g. an element in the hierarchy only has one mesh.

    How are you populating "meshes[]" ?
     
  8. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,426
    Code (CSharp):
    1. GameObject pointPlane = GameObject.CreatePrimitive(PrimitiveType.Quad);
    2.                 pointPlane.transform.parent = this.gameObject.transform;
    3.  
    4.                 pointPlane.transform.localScale = Vector3.one * 0.1f;
    5.                 pointPlane.name = "PointPlane" + " | " + i + " - " + n;
    6.  
    7.                 pointPlane.transform.position = Vector3.Lerp(p1, p2, n / (float)howManyCanFit);
    8.  
    9.                 pointPlane.transform.LookAt(pointPlane.transform.TransformPoint(nodes[i].GetComponent<LightStripNode>().lookDir));
    10.  
    11.                 //pointPlane.transform.LookAt(nodes[i].GetComponent<LightStripNode>().lookDir);
    12.  
    13.                 pointPlane.GetComponent<Renderer>().material = inactiveMaterial;
    14.  
    15.                 meshes.Add(pointPlane);
    The meshes are created via script, and added to the array.
     
  9. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    pointPlane.GetComponent should probably be pointPlane.AddComponent since you're creating everything from scratch.
     
  10. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,426
    It is being created by using the CreatePrimative method, so the Renderer is already part of it, when it is created it has a MeshRenderer.
     
    KelsoMRK likes this.
  11. Undume

    Undume

    Joined:
    Jun 2, 2016
    Posts:
    14
    Hey,

    I was trying to solve the same that happened to OC, probably he already solved it but I let it here for future people.

    Code (CSharp):
    1. foreach (var renderer in GetComponentsInChildren<Renderer>())
    2. {
    3.     var materials = render.materials;
    4.     for (var i = 0; i < materials.Length; i++)
    5.     {
    6.         materials[i] = ReferencedMaterialFromTheEditor;
    7.     }
    8.  
    9.     render.materials = materials;
    10. }
    renderer.materials is a getter that gives a copy of the array, not of the materials indivually. You were changing the copied array.

    You have to set the full array, the reference to the copied array with, now yes, the modified parameters or changed materials.