In another tip here, Eduardo Dias da Costa showed us how to customize materials for different game objects. This is a great practice that can save a lot of work in creating the materials. However, in cases where the objects will frequently be generated and later destroyed, this technique could lead to leaking, as Unity generates a new instance of the material every time it’s modified and assigned to a Mesh Renderer. In this case, if you will reuse materials with the same parameters, it’s useful to use a Pool of Materials. I usually start with this generic class, and later customize if needed:
/// <summary>
/// Generic material pool
/// </summary>
/// <typeparam name="T">A class or struct that contains the parameters for identifying and constructing a new material</typeparam>
public class MaterialPool<T> {
// The definition of a function that given a T will return a new material.
// This is used when the material is inserted in the pool for the first time.
public delegate Material MaterialGenerator(T t);
private Dictionary<T, Material> pool;
private MaterialGenerator generator;
public MaterialPool(MaterialGenerator generator) {
this.pool = new Dictionary<T, Material>();
this.generator = generator;
}
public Material GetMaterial(T t) {
Material mat;
if (!pool.TryGetValue(t, out mat)) {
mat = generator(t);
pool[t] = mat;
}
return mat;
}
}
It requires two additional, customized, elements: a material generator function, and a type that provides the parameters for customizing the material:
/// be careful when overriding both Equals and GetHashCode methods so that it will work correctly
/// as a key in the dictionary
struct MaterialDef {
...
}
/// generate a new material from the definition
private Material MaterialGenerator(MaterialDef matDef) {
...
}
/// in Start or Awake instance the material pool
matPool = new MaterialPool<MaterialDef>(MaterialGenerator);
/// later in the code, when needing a new material, just request it from the pool
gameObject.GetComponent<MeshRenderer>().material = matPool.GetMaterial(new MaterialDef( ... ));
A big benefit of this approach is that it enables you to separate different concerns:
- The pooling.
- The identification of what makes one material different from the other.
- The material generation itself.
You can replace each one of these without worrying (much) about the others. Another advantage of this approach is that it allows you just to query for a material from the pool without caring whether it will generate a new one or reuse an existing one.