概ねタイトル通りです。
今回はViewの中でもかなり特殊なView、「SurfaceView」を使ってみます。
どこらへんが特殊かというと、スレッドを別にできるというところらしいんですが…。
なんだか難しそうです(;^ω^)
百聞は一見にしかず、サンプルを見ながら説明してみます。
【参考にした書籍とサイト様】
・愚鈍なプログラマーの独り言 「グラフィックス(2)-SurfaceViewによる描画」
・@IT 「SurfaceViewなら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をセットしてさっそく動かしてみましょう。
【実行結果】

何も表示されていませんが、
ログでちゃんと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);
}
}
}
【実行結果】

スリープ処理を入れてゲームの速度を調整する
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) {
}
}
}
}
これでゲームプログラムに必要な基幹部分は大体できました。
大雑把な説明ですみません。
次の記事ではプレイヤーからの入力の処理を入れてゲームっぽいサンプルを作ってみます。

ピンバック: 【Android】SurfaceViewを使ってゲームっぽいアプリを作ってみる(入力編) | 桜花満開
ピンバック: 【Android】カメラ機能を使ってプレビューをSurfaceViewに表示する方法 | 桜花満開
ピンバック: 【Android】SurfaceViewを使ってゲームっぽいアプリを作ってみる(ゲーム内容編) | 桜花満開