ここでは自分のandroidプログラミングのスタイルを書き連ねていこうかと思っています。入門程度の難易度になるかと思うけど参考になる人もいるかもしれないと思う。現在の仕事はandroid向けのゲームを作るのが主になっているので、ここでもゲームプログラミングの内容が多くなる予定です。
1. プログラムの起動まで
ここでは、よくあるようにHello Worldを作りますが
1. ViewはSurfaceViewを使う
2. プログラムの起動時にスレッドを作っておく
SurfaceViewを使うのはandroid.widget以下のViewを使うより速いからです。もうひとつ、android.widget以下のViewだと描画はメインスレッドで行われなくてはならないのですが、ゲームの処理はゲーム用のスレッド(以下ゲームスレッドと呼びます)でも行ないますし、描画もゲームスレッドで行いたいからです。ただし、メインスレッドとゲームスレッドで描画が競合しないように作らなくてはいけません。
スレッドについてですが、これから作っていくプログラムではデータのローディングから処理のほとんどを自分で生成したゲームスレッドで行います。長い時間の掛かる描画やデータのロードをメインスレッドで行うようにすると、ANR (Application Not Responding)で落ちる場合があります。時間の掛かる処理はメインスレッドでは何をするかの指示をするだけで実際の処理はゲームスレッドで行うようにします。
サンプルコードを書きます。
package jp.co.mekira.android.examples.helloworld1;
import android.os.Bundle;
import android.app.Activity;
import android.view.Window;
import android.util.Log;
public class HelloWorld1Activity extends Activity {
private GameView gameView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameView = new GameView(this); // <-- ここは遅くちゃダメ
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(gameView);
}
@Override
public void onDestroy() {
if (gameView != null) {
gameView.finish();
}
super.onDestroy();
}
}
最初にactivityの生成。 onCreate()をオーバーライドしてactivityを作る数行の処理ですが、ここでの肝はonCreate()は速く終了するという事。その為に14行目のGameVew()のインスタンスを作る処理は速く終わる必要があります。
andoridに限らずdocomoのiアプリでも、Windowシステム向けのアプリケーション(X Window SystemやMacintoshやWindows)でもこの部分は速く終わらせてシステムのメインループに戻す必要があります。
やってはいけない代表的な事がネットワークを介してデータを読み込む(少しでもダメです)とか,大量のデータを読み込む(ディスクなどの速い媒体からでも)事です。
package jp.co.mekira.android.examples.helloworld1;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.util.Log;
public class GameView extends SurfaceView implements
SurfaceHolder.Callback,
Runnable {
private SurfaceHolder holder;
private boolean surfaceCreated;
private Thread thread;
private int status;
private Paint bgPaint;
private Paint textPaint;
private int textSize;
public static final int StatusNOP = 0;
public static final int StatusDraw = 0;
public GameView(Activity activity) {
super(activity);
init();
thread = new Thread(this);
thread.start();
}
private void init() {
holder = getHolder();
holder.addCallback(this);
surfaceCreated = false;
thread = null;
status = StatusNOP;
bgPaint = new Paint();
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setARGB(0xff,0xff,0xff,0xff); //背景は白
textSize = 24;
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setARGB(0xff,0,0,0); //文字の色は黒
textPaint.setTextSize(textSize);
}
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
surfaceCreated = true;
repaint();
}
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceCreated = false;
}
public void run() {
while (thread != null) {
if (status == StatusDraw) {
repaint();
} else {
synchronized (this) {
try {
wait(); //止めてしまう
} catch (Exception e) {
}
}
}
}
}
public void finish() {
thread = null;
wakeup();
}
private void wakeup() {
synchronized(this) {
notifyAll();
}
}
public void repaint() {
Canvas canvas = null;
if (!surfaceCreated) {
//surfaceCreated()より前に呼び出された場合は何もしない
return;
}
try {
canvas = holder.lockCanvas();
synchronized (holder) {
paint(canvas);
}
} catch (Exception e) {
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
}
protected void paint(Canvas c) {
//背景を塗りつぶす
c.drawRect(0,0,getWidth(),getHeight(),bgPaint);
String str = "ハローワールド";
Rect bounds = new Rect();
int xx,yy;
//文字列の描画範囲を取得する
textPaint.getTextBounds(str,0,str.length(),bounds);
xx = (getWidth() - bounds.width()) / 2;
yy = (getHeight() - textSize) / 2;
c.drawText(str,xx,yy,textPaint);
}
}
SurfaceViewを継承したGameViewは、初期化時にゲームスレッドを生成します。今回はプログラムの骨組みを作るだけなのでゲームスレッドは何もしないでいきなりwait()を呼び出して止めてしまいます。用がないのにCPUを使いたくありませんから、電池も消耗しますし。yield()やsleep()ではなくてwait()で完全に止めます。プログラム終了時にはnotifyAll()メソッドを呼び出しスレッドを起こして終了させます。
描画にはrepaint()を呼び出しますが、この名前はjava.awtやjavax.microedition.lcdui.Canvasクラスと同じ名前を使っているだけです。
ソースコード一式はここ
githubにもおいてあります。 git clone git@github.com:michiro/HelloWorld.git でソースコード一式取得できます。