【Android】SurfaceViewを使ってゲームっぽいアプリを作ってみる(基幹編)


概ねタイトル通りです。

今回はViewの中でもかなり特殊なView、「SurfaceView」を使ってみます。
どこらへんが特殊かというと、スレッドを別にできるというところらしいんですが…。
なんだか難しそうです(;^ω^)

百聞は一見にしかず、サンプルを見ながら説明してみます。


【参考にした書籍とサイト様】
・愚鈍なプログラマーの独り言 「グラフィックス(2)-SurfaceViewによる描画
・@IT 「SurfaceViewならAndroidで高速描画ゲームが作れる」
やさしいAndroidプログラミング (やさしいシリーズ)やさしいAndroidプログラミング (やさしいシリーズ)



SurficeViewを継承したクラス「SampleSurficeView」を作成


ではさっそく、SurficeViewを作成しましょう。
SurficeViewを継承したクラスを用意します。
今回は「「SampleSurficeView」」という名前にしてみましょう。

新規->クラス->名前「SampleSurficeView」に設定->完了

SampleSurficeView.java
import android.content.Context;
import android.view.SurfaceView;

public class SampleSurficeView extends SurfaceView{
	public SampleSurficeView(Context context) {
		super(context);

	}
}




まだ中身は空っぽですが、これがSurficeViewそのものになります。

SurficeHolderインターフェースを実装したクラス「SampleHolderCallBack」を作成、無限ループの開始と停止を作る

SurficeViewの中で、SurficeHolderインターフェースを実装したクラスを用意します。
今回は「SampleHolderCallBack」という名前で作成してみます。
このクラスには
 ・SruficeHolderインターフェース
 ・Runnableインターフェース
の2つのインターフェースを作ります。

Eclipseで関数を自動生成すると楽ですね。

・SampleHolderCallBack.java
import android.view.SurfaceHolder;

public class SampleHolderCallBack implements SurfaceHolder.Callback, Runnable{

	private SurfaceHolder holder = null;
	private Thread thread = null;
	private boolean isAttached = true;

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,	int height) {
		// TODO 自動生成されたメソッド・スタブ
		// 使わない(´・ω・`)
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// TODO 自動生成されたメソッド・スタブ
		this.holder = holder;
		thread = new Thread(this);
		thread.start(); //スレッドを開始
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		// TODO 自動生成されたメソッド・スタブ
	       isAttached = false;
	       thread = null; //スレッドを終了
	}

	@Override
	public void run() {
		// TODO 自動生成されたメソッド・スタブ
		// メインループ(無限ループ)
		while( isAttached ){
			Log.w("テスト", "ループなう");
		}
	}
}



2つのインターフェースの関数には
       
SruficeHolderインターフェースsurficeCreated関数
surficeChanged関数
surficeDestroyed関数
Runnableインターフェースrun関数
といった関数が用意されていて、
役割ととしては、

run関数 : 別スレッドで動き続ける、無限ループを作る

surficeCreated関数 :スレッドを開始
surficeDestroyed関数:スレッドを停止
surficeChanged関数 :今回使わない(´・ω・`)

という感じになります。

このクラスを、先ほどのSampleSurficeViewを修正してコールバックに登録します。

・SampleSurficeView.java
public class SampleSurficeView extends SurfaceView{

	private SampleHolderCallBack cb;

	public SampleSurficeView(Context context) {
		super(context);
		SurfaceHolder holder = getHolder();
		cb = new SampleHolderCallBack();
		holder.addCallback(cb);
	}
}



メインのアクティビティでSampleSurficeViewをセットしてさっそく動かしてみましょう。

【実行結果】
SurficeView実行結果

何も表示されていませんが、
ログでちゃんとrun関数がループしているのが確認できますね(^ω^)

無限ループ内でCanvasを描画する


無限ループの中に
画面内を動きまわる丸を描画するプログラムを作ってみます。

描画処理は holder.lockCanvas() を使って取得したCanvasに対して行います。
描画処理が終わったら holder.unlockCanvasAndPost でCanvasを開放します。

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.util.Log;
import android.view.SurfaceHolder;

public class SampleHolderCallBack implements SurfaceHolder.Callback, Runnable{

	private SurfaceHolder holder = null;
	private Thread thread = null;
	private boolean isAttached = true;

	  private float dx = 10, dy = 10;
      private float width, height;
      private int   circle_x, circle_y;

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,	int height) {
		// TODO 自動生成されたメソッド・スタブ
		this.width = width;
		this.height = height;
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// TODO 自動生成されたメソッド・スタブ
		this.holder = holder;
		thread = new Thread(this);
		thread.start(); //スレッドを開始
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		// TODO 自動生成されたメソッド・スタブ
	       isAttached = false;
	       thread = null; //スレッドを終了
	}

	@Override
	public void run() {
		// TODO 自動生成されたメソッド・スタブ
		// メインループ(無限ループ)
		while( isAttached ){
			//丸の表示位置を動かす
			if( circle_x < 0 || circle_x > this.width ){
				dx = -dx;
			}
			if( circle_y < 0 || circle_y > this.height ){
				dy = -dy;
			}

			circle_x += dx;
			circle_y += dy;

			//描画処理を開始
			Canvas canvas = holder.lockCanvas();
			canvas.drawColor(0,PorterDuff.Mode.CLEAR );
			Paint paint = new Paint();
	        paint.setColor(Color.WHITE);
	        canvas.drawCircle( circle_x, circle_y, 50, paint);

			//描画処理を終了
			holder.unlockCanvasAndPost(canvas);

		}
	}

}



【実行結果】
SurficeViewイメージ、Canvasで丸を描画

スリープ処理を入れてゲームの速度を調整する


run関数での無限ループを速度を調整しないと、
スマートフォンの性能によりゲーム内の時間がまちまちになってしまいます。

そこで、一秒間でループが60回繰り返すようにスリープ処理を入れます。
多くのゲームでは1ループを「1フレーム」と呼び、
1秒間あたり60フレーム(60fps)で動くように調整されています。

・SampleHolderCallBack.java
private long t1 = 0, t2 = 0; // スリープ用変数



	@Override
	public void run() {
		// TODO 自動生成されたメソッド・スタブ
		// メインループ(無限ループ)
		while( isAttached ){
			t1 = System.currentTimeMillis();

			//丸の表示位置を動かす
			if( circle_x < 0 || circle_x > this.width ){
				dx = -dx;
			}
			if( circle_y < 0 || circle_y > this.height ){
				dy = -dy;
			}

			circle_x += dx;
			circle_y += dy;

			//描画処理を開始
			Canvas canvas = holder.lockCanvas();
			canvas.drawColor(0,PorterDuff.Mode.CLEAR );
			Paint paint = new Paint();
	        paint.setColor(Color.WHITE);
	        canvas.drawCircle( circle_x, circle_y, 50, paint);

			//描画処理を終了
			holder.unlockCanvasAndPost(canvas);

			// スリープ
			t2 = System.currentTimeMillis();
			if(t2 - t1 < 16){ // 1000 / 60 = 16.6666
				try {
					Thread.sleep(16 - (t2 - t1));
				} catch (InterruptedException e) {
				}
			}
		}
	}



これでゲームプログラムに必要な基幹部分は大体できました。
大雑把な説明ですみません。

次の記事ではプレイヤーからの入力の処理を入れてゲームっぽいサンプルを作ってみます。


関連記事