【Unity】Renderer.materialで取得したマテリアルは自分で破棄しないとリークする話

Renderer.materialで取得したマテリアルは自分で破棄しないとリークするというお話です。

Unity2018.4.6

Renderer.material?

Renderer.materialにアクセスするとレンダラのマテリアルを取得できます。
このとき、マテリアルがこのオブジェクト用に複製されます。

docs.unity3d.com

Renderer.materialsも同様です。
なお、代わりにRenderer.sharedMaterialを使うとマテリアルが複製されず元のマテリアルを参照します。

自分で破棄しないとリークする

さてこのようにRenderer.materialを通して取得したマテリアルは、自分で破棄しないとリークします。
マニュアルにも以下のように明記されています。

It is your responsibility to destroy the automatically instantiated mesh when the game object is being destroyed

したがって、Renderer.materialを使った場合にはそのマテリアルをOnDestroyなどで破棄する必要があります。

using UnityEngine;

public class Example : MonoBehaviour
{
    private Material _material;

    private void Start()
    {
        _material = GetComponent<Renderer>().material;
    }

    private void OnDestroy()
    {
        if (_material != null) {
            Destroy(_material);
        }
    }
}

実用性を考えると次のようなコンポーネントをRendererを持つオブジェクトにアタッチするのがいいかもしれません。

using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class Example : MonoBehaviour
{
    private void OnDestroy()
    {
        // 複製されたマテリアルをすべて破棄する
        foreach (var material in GetComponent<Renderer>().materials) {
            Destroy(material);
        }
    }
}

ちなみにResources.UnloadUnusedAssets()を使うことでも解放されますが、重い処理なので推奨はされていないようです。

MeshFilter.meshも同様

Renderer.materialと同様にMeshFilter.meshで取得したメッシュにも注意が必要です。
こちらも同様、自分で破棄しないとリークしてしまいます。

docs.unity3d.com

破棄されていることを確認する

マテリアルがきちんと破棄されていることを確認するためにはProfilerを使います。
メモリのProfilerを監視するとメモリに乗っているマテリアルやメッシュの数が見れるので、これらが正常に減っていれば破棄できています。

f:id:halya_11:20190814115554p:plain