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. Dismiss Notice

correct way to edit materials in an editor script?

Discussion in 'Scripting' started by JoeStrout, Dec 22, 2016.

  1. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    I've got a script with [ExecuteInEditMode] that configures a material based on some public properties. There are a lot of possible configurations of this material, so I don't really want to store the result in the project; I want to just create a material on the fly.

    That's fine at runtime, but when running in edit mode, I'm a little uncertain how to properly get the material to modify.
    Code (CSharp):
    1. Material mat = rend.material;
    throws an error: 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. But no, I'm pretty sure I don't want to use renderer.sharedMaterial, because that would potentially change some material stored in the project and used on lots of other objects. I only want to change this particular instance.

    So, I could just create a new material on the spot:

    Code (CSharp):
    1. Material mat = new Material(rend.sharedMaterial);
    But this means creating a new material every time, even if no other object is referencing this one. Seems a little heavy-handed, especially at runtime.

    What I really want is some sort of copy-on-write semantics, that gets me a new instance if the old instance was shared, but if the old instance was not shared, just returns that to me directly. In other words, pretty much what renderer.material appears to do, except that I'm not allowed to use that in edit mode.

    Maybe I should just have my script keep its own material reference, instantiating (by copying rend.sharedMaterial) only when its own reference is null, and then using that thereafter. I suppose that would work.

    But since I've come this far, I may as well hit "Post" and see if somebody has a brighter idea!

    Thanks,
    - Joe
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    If you only modify the material via OnValidate, it shouldn't have any runtime implications?
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,184
    This works both at runtime and at editor time, and isn't reliant on the order of any operations. It's the best choice.
     
    JoeStrout likes this.
  4. Zephni

    Zephni

    Joined:
    Apr 23, 2015
    Posts:
    100
    Hi guys, I know this is mega old. Could I just ask how I should implement the above? Is this suggesting I should have the script keep a reference to the main "global" material, and also a reference to an instance of it that is created on the fly. Which one of those two should be using the .sharedMaterial? Surely if I have a reference to the main material I want to copy then that is the "shared" or "global" version of it anyway?... It's hard to get my head around for some reason, it feels like this should be much simpler than I'm making it out to be :(. When would be best to create the instance, just on awake? Could I have a little prod in the right direction please :) :)

    Below is a cut down example of what is calling the warning. If I change .material to .sharedMaterial it will change the base material which is not desired.
    Code (CSharp):
    1.     public Material parallax2DSpriteMaterial;
    2.     public Texture2D texture2D;
    3.  
    4.     void Awake()
    5.     {
    6.         GetComponent<Renderer>().material = new Material(parallax2DSpriteMaterial);
    7.     }
    8.  
    9.     void Update()
    10.     {
    11.         GetComponent<Renderer>().material.SetTexture("_Texture2D", texture2D);
    I have also tried the blow (and setting the material to the prefab):

    Code (CSharp):
    1.    
    2.     private Material material;
    3.  
    4.     void Update()
    5.     {
    6.         if(material == null) {
    7.             material = GetComponent<Renderer>().sharedMaterial;
    8.             GetComponent<Renderer>().sharedMaterial = new Material(material);
    9.         }
    10.  
    11.         GetComponent<Renderer>().sharedMaterial.SetTexture("_Texture2D", texture2D);
    12.  
    But in this case rather than changing the base material, it changes the one that has now been created and shared between each instance of the object. How do I make it so that each instance "borrows" the Material / Shader and then I can modify the properties for each one individually?
     
    Last edited: Apr 13, 2020
  5. Zephni

    Zephni

    Joined:
    Apr 23, 2015
    Posts:
    100
    Ok, I eventually got this working without the error message in this hacky kind of way. The solution was to get the shared material, and then set a local variable to a new version of that material if it is null, and finally use that local material to set the property values. Seems hacky, but if anyone has a better way please let me know :)

    Code (CSharp):
    1.  
    2.     public Vector2 coefficient;
    3.  
    4.     private Material sharedMaterial;
    5.     private Material material;
    6.  
    7.     void Awake()
    8.     {
    9.         sharedMaterial = GetComponent<Renderer>().sharedMaterial;
    10.         material = GetComponent<Renderer>().material = new Material(sharedMaterial);
    11.     }
    12.  
    13.     void Update()
    14.     {
    15.         material.SetVector("_Coefficient", coefficient);
    16.     }
    17.  
     
    Last edited: Apr 13, 2020