2011年11月24日木曜日

「androidで動くゲームプログラミング入門」 その1

[はじめに]
今回は「androidで動くゲームプログラミング入門」というタイトルで書いてみます。少し長くなりそうなので何回かに分けて書く予定です。
ちょっと普通のアプリと作り方が違うかと思いますのでSurfaceViewを使ったパズルゲームを作って、そのコードを追いながらandroidの基本的なゲーム・プログラミングのやり方を解説していきたいと思います。作るのはスライドパズルです。なぜスライドパズルかというと作るのが簡単だからという理由です。(笑) ゲームといえばOpenGLを使うケースが多いかと思いますが、今回はCanvasを使います。Canvasでもスライドパズルなら充分な速度が出ます。
ソースコードはgithubに置いておきます。
linuxとMacOSな人は
git clone git@github.com:michiro/SlidePuzzle.git でソースコード一式取得できます。
windowsな他人はリポジトリをgit@github.com:michiro/SlidePuzzle.gitとしてください。
質問、コメントや間違いの指摘を寄せていただいたら感謝します。

[ゲームの処理とマルチスレッド]
ゲームの処理をどこで行うか考えてみます。ゲームですので画面の表示はユーザが何かをした時以外にも変わってきます。例えばアクションゲームではユーザが何もしなくてもゲームはどんどん進んでいきます、それに伴い画面もどんどん描き変えてきます。こういった長い処理はメインスレッドでは行うことが出来ません。androidの場合はメインスレッドを5秒以上止めてしまいますとANR(Application Not Responding)エラーが出てプログラムが終了させられます。android以外のシステムだとしても、例えばwindowシステム(WindowsやX-Window Sysetmなど)のメインループを止めてしますような事をしますと画面が書き換えられなかったり、プログラムが反応しなくなったりします。
そこでゲームの主な処理はメインスレッドを専有しないようにしなくてはいけません。方法としてはタイマーを使って一定間隔でゲーム処理部分を呼び出す方法や別のスレッドを作ってそこで処理を行う方法があります。今回のプログラムは別のスレッドを作る方法で書いて行きます。以後の説明では、この別のスレッドをゲームスレッドと呼ぶことにします。

[Viewについて]
今回作るゲームではSurfaceViewを使います。理由はViewクラスを使うより速いからです。もう一つ、androidのUI操作はスレッドセーフに作られていません。したがって描画はメインスレッドで行わなければならないのです。具体的にはViewクラスでは描画はview.invalidate()を呼ぶのですが、これはメインスレッドから呼ばなくてはなりません。メインスレッドから呼ぶには、下記のようなコードになります。
public void repaint() {
    new Thread(new Runnable() {
            @Override public void run() {
                handler.post(new Runnable() {
                        @Override public void run() {
                            view.invalidate()
                        }
                    });
            }
        }).start();
}

protected onDraw(Canvas canvas) {
    //ここで描画をする
}

この repaint()メソッド(Java2MEと同じメソッド名にしました)はメインスレッドの待ち行列に処理を登録するだけで、すぐに実行されるわけではありません。
repaint()を呼んだ後にonDraw()が呼び出されるので、そこで描画を行います。下記のコードではrepaint()で描画しているのですが、おそらく描画が完了する前にnextProcess()が呼び出されます。遅くなりますし、描画の終了を知るのにひと工夫要ります。
public void myFunction() {
    repaint();
    nextProcess(); //描画が完了する前に、ここに来てしまう
}

androidアプリのUIがシングルスレッドという事に関してはadamrockerさんがblogに詳しく書かれています。

これに対してSurfaceViewを使った場合は、自分でonDraw()を呼び出し描画をします。コードは下記のようになります。
public void repaint() {
    Canvas canvas = null;
    try {
        canvas = holder.lockCanvas();
        synchronized (holder) {
            onDraw(canvas);
        }
    } catch (Exception e) {
    } finally {
        if (canvas != null) {
            holder.unlockCanvasAndPost(canvas);
        }
    }
}

この場合はrepaint()を呼び出せばすぐに描画が始まり。repaint()が終了したときは描画も終わっています。こちらの方が速く、repaint()が帰ってきた時には描画は終わっています。
注意する点はメインスレッドとゲームスレッド両方で色々な処理をする事になると思いますので競合が起きないようにしないように注意深くプログラミングする必要があります。


次回はコードの説明に入ります。
「androidで動くゲームプログラミング入門」 その1
「androidで動くゲームプログラミング入門」 その2
「androidで動くゲームプログラミング入門」 その3



0 件のコメント:

コメントを投稿