1
\$\begingroup\$

I have a class Edge which may change to multiple colors - materials in this case. I need to switch a Material on MeshRenderer.

In order to do this, I'd need to make multiple prefabs public variables like

class Edge : Mono{

    public Material red, blue, green;

    public void SwitchColor(SomeColorEnum param){
        switch(param)
           // change
           // this.renderer.mat = something;
    }

}

This is a bad solution for this, cause I'd need at least 3 material references in each Edge object. Is there any way to make a material static, assign it from a prefab and then access it statically?

\$\endgroup\$
7
  • \$\begingroup\$ cant you just change the color property of the material? \$\endgroup\$
    – OC_RaizW
    May 17, 2017 at 9:57
  • \$\begingroup\$ If I change the color property of one material, I'd get a color change on all the edges with that material set. \$\endgroup\$
    – Jacob
    May 17, 2017 at 9:58
  • 2
    \$\begingroup\$ You'll want to take a look at docs.unity3d.com/ScriptReference/MaterialPropertyBlock.html .. This tutorial as well: thomasmountainborn.com/2016/05/25/materialpropertyblocks \$\endgroup\$ May 17, 2017 at 10:03
  • \$\begingroup\$ If you change it in the Inspector you change the "Shared Material" property for every Object. Just change the color in the Script in runtime. After you change something in the script, your object gets an instance of the Material. \$\endgroup\$
    – OC_RaizW
    May 17, 2017 at 10:06
  • \$\begingroup\$ Josh, +69 for the MPB. Post an answer please. Thanks guys! \$\endgroup\$
    – Jacob
    May 17, 2017 at 10:14

3 Answers 3

2
\$\begingroup\$

Quite often I'll use a ScriptableObject to hold this kind of shared data.

It's similar to OC_RaizW's suggestion of a MonoBehaviour, but doesn't require a GameObject to host this content - a ScriptableObject can exist as its own asset:

// This attribute makes the asset show up in the editor's Assets -> Create menu
// and when right-clicking in your Project window and choosing Create
[CreateAssetMenu(filename = "New Material Library", menuName = "Custom/Material Library")]
public class MaterialLibrary:ScriptableObject {

    // You could also expose individual named fields, or provide a public method
    // to retrieve a material based on an enum key or other input...
    public Material[] materials;

}

Now all your edges can share a reference to this MaterialLibrary.

I often use this for collecting up pools of foley sounds that many characters use, or for sharing configuration parameters used by multiple player/enemy instances. This gives me a single place to make changes to tune all instances currently running or that have yet to be spawned. This is a form of the flyweight pattern.

Another trick I like to use for materials in particular is to prepare a general-purpose material variant cache:

public static class MaterialCache {

    struct Variant {
        public Material originalMaterial;
        public Color32  color;
    }

    static Dictionary<Variant, Material> _cache;

    public static Material GetMaterialVariant(Material originalMaterial, Color32 color) {
        var key = new Variant{originalMaterial = originalMaterial, color = color};

        Material output;
        if(_cache.TryGetValue(key, out output) == false)
        {
            output = Instantiate<Material>(originalMaterial);
            output.color = color;
            _cache.Add(key, output);
        }
        return output;
    }

    // You may also want to provide a Flush method to destroy cached materials,
    // in case your game ever enters a state where you don't need them anymore.
}

Now anything that wants to modify its display colour can do so like so...

public class ColorChanger : MonoBehaviour {
    Material _originalMaterial;
    Renderer _renderer;

    void Start() {
        _renderer = GetComponent<Renderer>();
        _originalMaterial = _renderer.sharedMaterial;
    }

    public void ChangeColor(Color32 color) {
        var mat = MaterialCache.GetMaterialVariant(_originalMaterial, color);
        _renderer.sharedMaterial = mat;
    }
}

If I'm using only a handful of easily-standardized colours like Color.Red, then every instance with the same base material asking for a red variant will get a reference to the same red material instance, rather than creating copies all over the place. And best of all, I don't have to manually create and assign all those near-duplicate materials - they're just created on demand. :)

\$\endgroup\$
2
  • \$\begingroup\$ Change [CreateAssetMenu(filename = "New Material Library", menuName = "Custom/Material Library"] to [CreateAssetMenu(fileName = "New Material Library", menuName = "Custom/Material Library")] and we're good to go! Thanks Greg :) \$\endgroup\$
    – Jacob
    May 21, 2017 at 10:21
  • 1
    \$\begingroup\$ For little typos like a missing parenthesis, you can simply make the edit. ;) \$\endgroup\$
    – DMGregory
    May 21, 2017 at 12:27
1
\$\begingroup\$

Unfortunately Unity can't serialize static members in inspector.

Though, as a workaround, you can use a singleton class that derives from Monobehaviour, as an asset reference database. So you can assign references to this singleton instance, instead of class directly.

That being said, having a couple of excess members in all instances won't effect anything in a noticeable manner. Especially when you need to use workarounds.

Before optimizing your program, always, always, always profile beforehand. Optimization without profiling is called "Premature Optimization", and is one of the most discouraged acts in programming.

\$\endgroup\$
0
\$\begingroup\$
  1. If you only want to change some properties, do it via Runtime in code.

    Material mat; mat.GetComponent().color = Color.red;

Then you will get a new fresh instance of one material.

2. If you have different Materials with different Shaders though, you have to change the materials.

For this just make a "BaseClass" Mono Behaviour with public Materials.

public class BaseMaterials : MonoBehaviour
{
public Material redMat;
public Material greenMat;
public Material blueMat;
}

and in your Edge class:

class Edge : Mono{

public BaseMaterials baseMats;

public void SwitchColor(SomeColorEnum param){
    switch(param)
       // change
       this.renderer.mat = baseMats.redMat;
}

Now you only need to assign the "baseMats" reference.

There are now multiple solutions to this.

Do it in Start() with GetComponent() or drag and Drop it in the Inspector or do it via Dependency Injection and Interfaces

The biggest advantage here is : There is only 1 reference instead of 3

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .