【Unity】Editモードでマテリアルのプロパティをスクリプトから変更する

UnityエディターのEditモードでマテリアルのプロパティをスクリプトから変更したい場合、少し工夫がいります。

まず、良くないパターンです。以下のコードは`MeshRenderer#material`で取得したマテリアルの`_BaseColor`プロパティを`Update`内で設定しています。`[ExecuteAlways]`属性を付与しているので、Editモードでも`Update`が呼ばれます。

```c#
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
[RequireComponent(typeof(MeshRenderer))]
public class MaterialPropertyChanger01 : MonoBehaviour
{
    [SerializeField] Color color = Color.white;
    MeshRenderer meshRenderer;
    Material material;

    void Start()
    {
        meshRenderer = GetComponent<MeshRenderer>();
    }

    void Update()
    {
        if (material == null)
        {
            material = meshRenderer.material;
        }
        material.SetColor("_BaseColor", color);
    }

    void OnDestroy()
    {
        if (material != null)
        {
#if UNITY_EDITOR
            if (EditorApplication.isPlaying)
            {
                Destroy(material);
            }
            else
            {
                DestroyImmediate(material);
            }
#else
            Destroy(material);
#endif
        }
    }
}
```

今回の記事とは直接関係ないですが、`MeshRenderer.material`を取得した場合は自分で破棄する必要があり、またEditモードでは`Destroy`ではなく`DestroyImmediate`を使わなければいけないのでコードが冗長になっています。
[【Unity】Renderer\.materialで取得したマテリアルは自分で破棄しないとリークする話 \- LIGHT11](https://light11.hatenadiary.com/entry/2019/11/03/223241)


このスクリプトでもマテリアルのプロパティを更新できますが、コンソールに以下のエラーが出るようにメモリリークが発生します。

:::note alert
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.
:::

エラーの指示に従って、`MeshRenderer#sharedMaterial`を使ってみます。

```c#
using UnityEngine;

[ExecuteAlways]
[RequireComponent(typeof(MeshRenderer))]
public class MaterialPropertyChanger02 : MonoBehaviour
{
    [SerializeField] Color color = Color.white;
    MeshRenderer meshRenderer;

    void Start()
    {
        meshRenderer = GetComponent<MeshRenderer>();
    }

    void Update()
    {
        meshRenderer.sharedMaterial.SetColor("_BaseColor", color);
    }
}
```

この場合エラーはでませんが、マテリアルアセット自体のプロパティが書き換わってしまい、同じマテリアルを複数のオブジェクトで使いまわすことが難しくなります。

エラーを出さずに、かつマテリアルアセット自体のプロパティを書き換えたくない場合は次のようにします。全体的に冗長ですが見てほしいのは`Update`の`else`内で、自分で`MeshRenderer#sharedMaterial`からマテリアルのインスタンスを作成して、それを新たな`MeshRenderer#sharedMaterial`に設定しています。

```c#
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
[RequireComponent(typeof(MeshRenderer))]
public class MaterialPropertyChanger03 : MonoBehaviour
{
    [SerializeField] Color color = Color.white;
    MeshRenderer meshRenderer;
    Material material;

    void Start()
    {
        meshRenderer = GetComponent<MeshRenderer>();
    }

    void Update()
    {
        if (material == null)
        {
#if UNITY_EDITOR
            if (EditorApplication.isPlaying)
            {
                material = meshRenderer.material;
            }
            else
            {
                material = new Material(meshRenderer.sharedMaterial);
                meshRenderer.sharedMaterial = material;
            }
#else
            material = meshRenderer.material;
#endif
        }
        material.SetColor("_BaseColor", color);
    }

    void OnDestroy()
    {
        if (material != null)
        {
#if UNITY_EDITOR
            if (EditorApplication.isPlaying)
            {
                Destroy(material);
            }
            else
            {
                DestroyImmediate(material);
            }
#else
            Destroy(material);
#endif
        }
    }
}
```


---

以下の記事を参考にしました。

- [Material Leak in Editor \- Unity Answers](https://answers.unity.com/questions/283271/material-leak-in-editor.html?childToView=322397#answer-322397)