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に投稿しなかったのはこの辺りが理由。

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

libGDX触ってる

なんかやることコロコロ変わってる感あるの、ほんとアレ…。 あんまり時間が無いので、メモ程度にまとめておく。。

libGDX

手の付け方みたいなの

あ、今触って見てるのは2D系です。

僕はゲーム制作ちゃんとやったことなくて、触ったことあるのは吉里吉里NScripterくらい。ほとんどノベルゲーに特化したもので、ゲーム制作という程のものは使ったことなかった。Spriteとか名前しか聞いたこと無いよみたいな。iOS7でSpriteKitってでましたね。 なので、今はすごいコード書いては消してみたいなの繰り返してます。

現段階で、手をつけるキーワード的なのは以下。すごい雑多…

        Group root = stage.getRoot();
        SequenceAction sequence = Actions.sequence();
        sequence.addAction(Actions.fadeOut(1.f));
        sequence.addAction(Actions.run(new Runnable() {
            @Override
            public void run() {
                // フェードアウト完了時
            }
        }));
        root.addAction(sequence);
  • リソース名散らからないようにしておいたほうが良いと思う。
    • 色んな所でファイル名指定することになるのですごいカオスになりやすいため
    • 僕は以下の様なシェルスクリプトで、リソースへのパスをJavaの定数として吐き出してる(AndroidのRクラス的な位置づけ)。そうすることで、使っているかどうかわかるし、リソースが消えた時はビルドエラーになってくれるから問題が判明しやすい。
#!/usr/bin/env bash
# Print Header
###########################################################
cat <<_EOT_
package myapp;

/**
 * AUTO-GENERATED: `date +"%Y-%m-%d %H:%M:%S"`
 */
public interface RDef {
_EOT_

# Print General data
###########################################################
cd "./assets"
echo "    // Files"
for rawPath in `find . |grep -v ".DS_Store" |grep -v "^./atlas/"`
do
  if [ -f $rawPath ]; then

    filename=${rawPath##*/}
    filenameNoExt=${filename%.*}
    ext=${filename##*.}
    path=${rawPath%/*}
    extension=${rawPath##*.}
    pathNoDot=${path:2}

    variable=`echo "$pathNoDot/${filenameNoExt}" |tr '[a-z/\-]' '[A-Z__]'`
    echo "    String $variable = \"$rawPath\";"
  fi
done

# print footer
###########################################################
cat <<_EOT_
}
_EOT_

以下のように生成される。

package myapp;

/**
 * AUTO-GENERATED: 2015-04-14 00:09:15 
 */
public interface RDef {
    // Files
    String BGM_BGM = "./bgm/bgm.ogg";
    String FONTS_YASASHISA = "./fonts/yasashisa.ttf";
    String IMG_CHARACTERS_BOY_RAKU = "./img/characters/boy/raku.png";
}
  • MicrosoftXNAもちょいちょい引っかかる時がある。Frameworkとしては似たようなところがある(SpriteBatchとか)みたいなので、コードの組み方とかクラスの設計とかは参考になるかもしれないです
  • Music/soundは未調査

よく分からないところ

  • 画面遷移どうするのがベストなのかなぁというところ。 Screen インターフェースやScene2Dの Stage がキーワードになる感じだけど、どうするのがやりやすいのかまだ試行錯誤中。 libGDX入門 その04 画面遷移|ざる魂にも解説ある
  • Stage をdisposeするのはいいけど、なんかサンプルとか見てると Texture#dispose してないんじゃね?みたいなところがある。してなくて平気なのか、それとも解説のために省いてるのか、忘れているのか、よく分からない。。自分で確かめてみないといけないかなあ
  • 文章を一文字ずつ表示していくのどう実装するのが良いのかな。。今のところ Actor につけれる Actions という仕組みを使ってできないか見てるところ。なるべくScene2DのAPIを使って実装したい。速度とかも変更しやすくしたい。

参考ドキュメント

以下のドキュメントがすごい参考になってる

ゲーム作れるようになりたいなあ。がんばるんば。

  • (2015-04-25 LibGDX -> libGDXに表記変更)
  • (2015-04-30 BitmapFontについて、BMFont以外の選択肢書いてるところ追加)

TypeScript触った

所感

  • すごい手触りが良い。JavaScript好きだったらそこまで勉強しなくてもなんでも書けるような気がしてくる
  • まだちょっとしか触ってなくて文法あんまりだけど、Java+JavaScript+ES4=TypeScript な感じ。TypeScriptのmoduleもJavaScriptのmodule触ってないからかもしれないけど。
  • スクランナー系は必須(gruntとか)。てか少なくともaltJS使う場合は全部そうか。。
  • 出力されるソースがきれいって聞いてたけど本当。(これはCoffeeScriptもだけど)
  • 型安全にする仕組みはすごい整備されてる(DefinitelyTyped/tsd, DefinitelyTyped/grunt-tsd)。
  • 自分で勉強した内容とかは 自分のGitHubに置いていってる。まだあんまり無い

選んだ理由

どれかには手を出そうと思いながら、結婚したのもあって、全然時間とれてなくて。ようやくちょっと落ち着いてきたので、一番気になっていたTypeScriptに手を出した。 なんでこれを選んだのかというと、静的型付け魅力的だったのと、JavaScriptに類似していて学習コストが低そうだったことが理由。

僕はFlash/ActionScript3が青春だった ので、一度はHaxe使おうと思っていたけど(参考:altJS勉強会「Haxeすごいからみんな使え!」)、僕はES4が好きではなかったのと、 package とか import がどうも受け入れられなかった。IDE整備されまくったJavaだと良いんだけれど。あと、 以前PHPのnamespaceが良いらしいから使ったら、SPL autoloadの仕組みがかっちりしすぎてて辛い目にあったこともトラウマなのです。柔軟なWebには合わないと思ってしまった。 WebはJavaScriptのように柔軟だからいろんなブラウザがでてもなんとか緩衝してこれたと思ってます。緩衝しなかったほうがまだよかったとか言えたほうがよかったとかもあると思うけれど、それよりも僕は、こうして共通プラットフォームが整備されてきたのは嬉しく思ってます。

JSXは、出た時すげえ!ってなったけど、僕はゲームはまだそんなに作ってなかったのとメンテナンス考えると辛いだろうなっていうことで、据え置きだった。流行ったら使おうかなという感じ。

Coffee Scriptは、そこまで魅力感じなかった。altJSって名前がなかった頃から出てたのもあって、「最終的にJavaScriptにになる」「C/C#/Java/JavaScriptとかしか見てこなかったペーペーなので空白開きすぎてるの変な感じがする」「コンパイルめんどくさ」みたいな今でいうと意味不明理論と、「JavaScriptの知識が結局必要になる」「文法ちょっと覚えるのめんどくさそう」っていう考えがいろいろ交錯して、「JavaScriptもっと詳しくなって綺麗にコードかけるようになろう」みたいになったので選ばなかった。

まあ結局のところJavaScriptが好きだったんだと思う。

今後

もっと勉強しようと思います!!!!!!!!!!!!!!! わかめさんプルリク捌きがんばって!!!!!

あとどうでもいいけどpackage.jsonやらbower.jsonやらtsd.jsonやらGruntfile.jsonやら作るのめんどくさい。コマンドラインが大体npmベースなのが救い。Yeomanとかwebpackとか使ったこと無いんだけどそういう環境の整備ってかなり助けてくれるんだろうか…よくわかってない…

Mac OS XにTmux powerline入れた

f:id:esperia:20150107204654p:plain

会社のパソコン新しいの使うようになったのでターミナルもかっこよくしたくなったので! 例によって Homebrew は入ってること前提です。

Powerlineのインストール

$ brew install python
$ pip install git+git://github.com/Lokaltog/powerline

Installation on OS X — Powerline beta documentation あたりに書いてあります。

powerline/fonts のインストール

$ git clone https://github.com/powerline/fonts.git
$ cd fonts
$ ./install.sh

上記でOK、次にターミナルで利用するフォントを変更。 僕はiTerm使ってるので以下の様な感じ。

f:id:esperia:20150107204544p:plain

Source Code Pro for Powerline を選択すればおkです。

Tmux statusline のインストール

Tmux statuslineのページを見ると、tmuxの設定ファイル( ~/.tmux.conf )へ、 source "{repository_root}/powerline/bindings/tmux/powerline.conf" を追加すれば良いみたいに書いてる。 以下を実行して、powerlineのインストール先を調べて、

$ pip show powerline-status
---
Name: powerline-status
Version: dev-4b4964013be7f5205382b4a0a8595e2e0df19eb7
Location: /usr/local/lib/python2.7/site-packages
Requires:

{repository_root} を、 Location: のところに出たパスで置き換えた文字列を、 .tmux.conf へ追記。

$ echo 'source "/usr/local/lib/python2.7/site-packages/powerline/bindings/tmux/powerline.conf"' >> .tmux.conf

あと、なんか以下のパッケージもないとtmux起動した時にエラー出るので入れる。

$ pip install psutil

起動!

$ tmux

かっこいい!!ステキ!!

会社で、Mac→Macへデータをrsyncで移行してたんだけど

モノとしてrsync使うとデータ移行楽なの知ってたんですけど、社内PCのデータ移行として利用するの初めてでビクビクしてたけど、とっても便利でした!ファイルのパーミッションもそのままで同期してくれるのが最高に良い。ほとんど元のまま次のPCでも使える。

なんかいくつか思った点があったので、日記にかいとく。

Thunderbirdのデータ移行の例

メーラーThunderbird使ってるけど、公式のバックアップ方法を見るとプロファイルのフォルダをそのまま移行すれば新しいPCでもそのまま利用できそうでした。ああ、じゃあここでrsync使えるかなと思ったので、下記でプロファイルのデータ送って、

$ rsync -av /Users/from/Library/Thunderbird/Profiles/ to@192.168.xxx.xxx:/Users/to/Library/Thunderbird/Profiles/

そいで、一度新しいPCでThnderbirdを開く→閉じるってして、公式の復元方法見ながらプロファイルのフォルダ名を合わせたら全部移行完了でした。 移行に際して、空いてるハードディスクとかなかったし、パーミッション関係心配だったので、すごい便利でした。

半角空白がパスにあると変な場所に送られた

ただちょっとハマったこと。 以下のコマンド使ってFirefoxのプロファイルを送ってたんだけど、なんか一発で指定したパスへ配置してくれなかった。

$ rsync -av /Users/from/Library/Application\ Support/Firefox/Profiles/ to@192.168.xxx.xxx:/Users/to/Library/Application Support/Firefox/Profiles/

上記でファイル送れるんだけど、実際には /Users/to/Library/Application/ へデータが送られた。パスを"(ダブルクォート)で囲んでもダメ。回避する方法あるのかな? 最後は手動でプロファイルフォルダ移動させました。

あとrsync全然関係ないけど古い資料のアーカイブ

僕は全部、お仕事のディレクトリの階層って、 /path/to/work/<お客さんのとこの名前>/<案件の名前>/<いろんなファイル> ってしてるんです。分かりやすいので。 ただ、終わった案件のファイルって大抵使わなくて、でも捨てるとあとあと面倒なことになるので、アーカイブにしておこうと思いました。 ただ、案件のフォルダがまためちゃめちゃ多いので、 /path/to/work/ に以下のシェルスクリプト作って実行、一括アーカイブしました。

#!/usr/bin/env bash

echo "Start archive..."

for comp in `ls`
do
    echo
    if [ ! -d $comp ]; then
        echo "$comp is a file. Skip."
        continue
    fi  

    echo "$comp"
    cd $comp

    for targetDir in `ls`
    do  
        if [ ! -d $targetDir ]; then
            echo "$targetDir is not a directory. Skip."
            continue
        fi  
        if [ -e $targetDir.tar.gz ]; then
            echo "$targetDir is already compressed. Skip."
            continue
        fi  

        echo -n "Start compress $targetDir ..."
        tar zcf $targetDir.tar.gz $targetDir
        rm -rf $targetDir
        echo "Compressed."
    done
    echo
    echo "Finished $comp."
    cd ../ 
done

載せるほどじゃないんですけどね…なんにせようまくアーカイブできてよかった。

今年はあまり各方面で顔を出さなかったけれど、ぼちぼちやってるので、また来年はアウトプット増やせるように頑張るます。

コマンドラインにて、iOSのプロジェクトに設定したバージョン番号を取得する

コマンドラインからiOSプロジェクトをビルドしてipaファイルを作成した際に、作成後のファイルにバージョンを自動的に入れたかったので、バージョンを取得する方法を調査した。

$ cd path/to/ios_project
$ xcrun agvtool mvers -terse1
1.0.0

path/to/ios_projectは*.xcworkspaceとかがあるディレクトリ。 スクリプト内で取得したい場合は appVersion=$(cd $projectPath; xcrun agvtool mvers -terse1) で良いと思ふ。