概ねタイトル通りです。
今回は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を使ってゲームっぽいアプリを作ってみる(ゲーム内容編) | 桜花満開