Spica*

プログラミングの話。

libGDXのテクスチャをdisposeするためだけにフィールド変数作るのめんどくさいので、管理クラスあってもいいんじゃないかなあ、という話

追記2(2015-06-18 14:47)

AssetManagerを使うと、本稿の問題は解決できました!僕が管理クラスの存在を知らなかっただけでした。

調べたのでサンプルコードを載せておきます。

public class MyGdxGame extends ApplicationAdapter {

    // 読み込むファイルのパス
    private static String FILE_MYFILE = "myfile.png";
    private AssetManager am = new AssetManager();

    private SpriteBatch batch;
    private Animation anim;
    private float time = 0.f;

    @Override
    public void create() {
        batch = new SpriteBatch();

        // 読み込ませる
        am.load(FILE_MYFILE, Texture.class);
        // 非同期で読込み中であるため、読込みが全て終わるまで待つ
        am.finishLoading();

        // 読み込んだテクスチャを取得
        Texture texture = am.get(FILE_MYFILE, Texture.class);
        TextureRegion[][] split = TextureRegion.split(texture, 64, 64);
        anim = new Animation(0.05f, split[0]);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        time += Gdx.graphics.getDeltaTime();
        batch.begin();
        batch.draw(anim.getKeyFrame(time), 10, 10);
        batch.end();
    }

    @Override
    public void dispose() {
        am.dispose(); // 破棄
    }

}

AssetManagerを使うと、テクスチャの読み込みから破棄までを管理してくれるようです。 さらに、読み込みを非同期で行う仕組みがあり、テクスチャの読み込みに時間がかかる場合にも使えそうな感じです。

どうやってAssetManager#dispose()時に各クラスのdispose()メソッド呼んでるのかなと思ってソース見ると、Disposableインターフェースを継承してる場合はdispose()メソッド呼ぶみたいな処理になってました。なるほど。

ついでに会社ブログの実装の方も修正しておこう…

追記(2015-06-17 14:24)

AssetsManager知らなかった。。標準であるもんですね!まだちょっと使ってみてはいないんですが、僕の欲しいもの+αっぽいです。 使ったら追記します!

本題

使えるかどうか分かんないんですが。

libGDXで遊んでて思ってたんですけど、TextureとかBitmapFontとかって、newして他のクラス渡したり、TextureRegionへ分割したりした後、最後にdispose(破棄)するためだけにフィールド変数に持ってる場合ってわりとある気がしてるんですね。

例えばこんな感じ。

public class MyGdxGame extends ApplicationAdapter {

    private Texture texture; // disposeのためだけに持ってる
    private SpriteBatch batch;
    private Animation anim;
    private float time = 0.f;

    @Override
    public void create() {
        batch = new SpriteBatch();

        texture = new Texture(Gdx.files.internal("myfile.png")); // 読み込み
        TextureRegion[][] split = TextureRegion.split(texture, 64, 64);
        anim = new Animation(0.05f, split[0]);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        time += Gdx.graphics.getDeltaTime();
        batch.begin();
        batch.draw(anim.getKeyFrame(time), 10, 10);
        batch.end();
    }

    @Override
    public void dispose() {
        texture.dispose(); // 破棄
    }
}

そうすると、dispose()呼ぶためだけに、Textureインスタンスをフィールド変数に保持しておかなきゃならない。だったら、それらをまとめて配列みたいなので管理させておいて、disposeするべきタイミングで一気にdispose、ってのもありなんじゃないのかなあと思ったりしたのです。

実装してみる

例えば、こんなクラスを作っておいて。

package com.esperia09.libgdx.utils;

import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;

public class DisposeRegister implements Disposable {

    private final Array<Disposable> disposables = new Array<Disposable>();

    public <T extends Disposable> T reg(T obj) {
        disposables.add(obj);
        return obj;
    }

    @Override
    public void dispose() {
        for (Disposable d : disposables) {
            d.dispose();
        }
        disposables.clear();
    }
}

そして、上記クラスをこう使います。

public class MyGdxGame2 extends ApplicationAdapter {

    private DisposeRegister dr; // 追加
    private SpriteBatch batch;
    private Animation anim;
    private float time = 0.f;

    @Override
    public void create() {
        batch = new SpriteBatch();
        dr = new DisposeRegister(); // 追加

        Texture texture = dr.reg(new Texture(Gdx.files.internal("myfile.png"))); // 変更
        TextureRegion[][] split = TextureRegion.split(texture, 64, 64);
        anim = new Animation(0.05f, split[0]);

    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        time += Gdx.graphics.getDeltaTime();
        batch.begin();
        batch.draw(anim.getKeyFrame(time), 10, 10);
        batch.end();
    }

    @Override
    public void dispose() {
        dr.dispose(); // 変更
    }
}

こうすると、new Texture(...)dr.reg(new Texture(...))みたいに囲むだけで、配列に登録し、任意の箇所で一気にdisposeできます。上記ではTexture一つだけですが、複数になると結構コード量変わってくると思います。

こんな感じで、disposeさせるためにインスタンス管理するのってありなんじゃないかなあと思っている次第です。

一抹の不安

ただ、これがベストなんだったら、標準で用意されてたり、他でもこういうの普通にされてるんじゃないかなと思ったりはします。なので、僕がありかなと思ってるだけで、実はタブーだったり、Textureの管理の仕方がヘタクソだったりするのかなあとも思ってます。ちなみにQiitaに投稿しなかったのはこの辺りが理由。

シンプルなコード書くの難しいですね。