エイリアンプログラム

主にゲームプログラミングに関するブログです。

動的に生成したTextureとSpriteはしっかり管理しよう!

お久しぶりです。
随分長いことブログを放置していました。
さて今回はアプリ開発やゲーム開発で頭を悩ませるメモリリークについてです。
メモリリークとは、プログラムのゴミがどんどん蓄積されてメモリが「もう満帆無理っす!」ということでアプリが落ちてしまうバグです。
詳細な説明はWiki先生に記載されています。

メモリリーク - Wikipedia

私が経験したメモリリークの中で最近苦労したことは、Texture2DとSpriteを動的に生成する処理は、コンポーネントしているGameObjectがDestroy(破棄)されてもTexture2DとSpriteが残ってしまうということです。

public class CreateTexture : MonoBehaviour {

    public int m_createCount = 100;
    private List<Texture2D> texture2dList = new List<Texture2D>();
    private List<Sprite> spriteList = new List<Sprite>();


    // Use this for initialization
    void Start () {
        for(int i =0; i < m_createCount; i++)
        {
            Texture2D texture2D = new Texture2D(1000, 1000);
            Sprite sprite = Sprite.Create(texture2D, new Rect(0.0f, 0.0f, 1000, 1000), Vector2.zero);
            texture2dList.Add(texture2D);
            spriteList.Add(sprite);
        }
        
        DestroyTexture2D();
    }
    

    private void DestroyTexture2D()
    {
        Destroy(this.gameObject);
    }
}


上記のソースコードを実行させてProfilerのMemoryのName>Scene Memorで確認すると…

f:id:alien_program:20171112122905p:plain
GameObjectだけ破棄した結果

何と生成したTexture2DとSpriteが残っているではないか!?
では処理を少し修正した状態でProfilerを確認してみましょう。


public class CreateTexture : MonoBehaviour {

    public int m_createCount = 100;
    private List<Texture2D> texture2dList = new List<Texture2D>();
    private List<Sprite> spriteList = new List<Sprite>();


    // Use this for initialization
    void Start () {
        for(int i =0; i < m_createCount; i++)
        {
            Texture2D texture2D = new Texture2D(1000, 1000);
            Sprite sprite = Sprite.Create(texture2D, new Rect(0.0f, 0.0f, 1000, 1000), Vector2.zero);
            texture2dList.Add(texture2D);
            spriteList.Add(sprite);
        }

        DestroyTexture2D();
    }
    

    private void DestroyTexture2D()
    {
        foreach(var tex in texture2dList)
        {
            Destroy(tex);
        }

        foreach(var spr in spriteList)
        {
            Destroy(spr);
        }
        Destroy(this.gameObject);
    }
}

f:id:alien_program:20171112123802p:plain
Texture2DとSpriteの破棄させた結果

今度はTexture2DとSpriteが消えている事を確認することができましたね。
どうやらUnityの仕様上、GameObjectが破棄されても生成されたTexture2DとSpriteが残ってしまうようです。
これは知らないと修正作業が少しめんどくさくなりますね。
後、Resources.UnloadUnusedAssetsで使わなくなったAsset(今回で言うところのTexture2DとSprite)を解放することができます。
ただしResources.UnloadUnusedAssetsは多様すると良くないらしいです…。


public class CreateTexture : MonoBehaviour {

    public int m_createCount = 100;
    private List<Texture2D> texture2dList = new List<Texture2D>();
    private List<Sprite> spriteList = new List<Sprite>();


    // Use this for initialization
    void Start () {
        for(int i =0; i < m_createCount; i++)
        {
            Texture2D texture2D = new Texture2D(1000, 1000);
            Sprite sprite = Sprite.Create(texture2D, new Rect(0.0f, 0.0f, 1000, 1000), Vector2.zero);
            texture2dList.Add(texture2D);
            spriteList.Add(sprite);
        }

        DestroyTexture2D();
    }
    

    private void DestroyTexture2D()
    {
        Destroy(this.gameObject);
    }


    private void OnDestroy()
    {
        Resources.UnloadUnusedAssets();
    }
}


なので皆さん、くれぐれもAssetの管理には気をつけましょう!


※参考サイトは以下になります。

tsubakit1.hateblo.jp