スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Android SurfaceViewを使ってグラフィック処理

グラフィック機能を活用したアプリケーション

・グラフィック機能を利用するには、描画処理を別スレッドで処理できるSurfaceViewを利用します。
【関連項目】
・SurfaceView
 →別スレッドを描画処理専用として割り当てることが可能となる。
・SurfaceHolder
 →SurfaceViewへアクセスするためのインターフェース。
 →SurfaceViewのgetHolderメソッドでSurfaceHolderを取得することが可能。
 →SurfaceHolder.Callbackインタフェースは、SurfaceViewが生成されたとき、変更されたとき、破棄されたとき
  の3つのイベントをハンドルすることが可能

■SurfaceViewを使ってグラフィック処理を行う  
・自身と敵のグラフィックオブジェクトを描画し、画面上で動作させる。
 敵オブジェクトはランダムに計算した値によって、自動で移動させるが
 自機オブジェクトは画面イベントでのonTouchEventにより座標を取得、タッチした場所へ移動させる。
 自機と敵オブジェが接触した場合にアクションを起こす。
 簡単なアクションゲームを作成する。


○SurfaceCircleViewクラス

package com.example.game;

import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class SurfaceCircleView extends SurfaceView {①
/** コールバッククラス */
private CircleSurfaceHolderCallback cshc = null; // このクラスはコールバックをインタフェースしている

/**
* コンストラクタ

* @param context
*/
public SurfaceCircleView(Context context) {
super(context);

// SurfaceHolderを取得
SurfaceHolder holder = getHolder();②

// コールバッククラスを生成しSurfaceHolderに追加する
this.cshc = new CircleSurfaceHolderCallback();③
holder.addCallback(this.cshc);④
}

/**
* タッチイベントメソッド

* @see android.view.View#onTouchEvent(android.view.MotionEvent)
*/@Override
public boolean onTouchEvent(MotionEvent event) {⑤
// タッチされた座標を取得
float y = event.getY();
float x = event.getX();

// コールバッククラスにタッチ座標を渡す
this.cshc.setKeyX(x);
    // タッチイベントでの座標をコールバッククラスへ設定する(CircleSurfaceholderCallback)
this.cshc.setKeyY(y);

// ログ出力
Log.d( " SurfaceCircleView ", " x: " + event.getX() + " y: " + event.getY());
return true;
}
}

①SurfaceViewを実装します。ActivityクラスではこのクラスをsetContentViewします
②自身(SurfaceView)の生成、変更、破棄時のコールバック処理を行うためのSurfaceHolderを取得する。
③コールバック処理を実装したクラス(CircleSurfaceHolderCallback)を生成する
④自身(SurfaceView)のaddCallbackでコールバックを実装したクラスを実装する
⑤タッチイベントにより、座標を取得、コールバッククラスへ値を設定する。


○SurfaceCircleViewクラス

package com.example.game;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;

//コールバックとスレッドをインタフェースしている
public class CircleSurfaceHolderCallback implements SurfaceHolder.Callback,
Runnable {

/** サーフェースホルダ */
private SurfaceHolder holder = null;

/** スレッド */
private Thread thread = null;

/** スレッド実行フラグ */
private boolean threadRun = true;

/** 敵オブジェクト */
private Circle enemyCircle = null;①

/** 自機オブジェクト */
private Circle myCircle = null;

/**
* コンストラクタ
*/
public CircleSurfaceHolderCallback() {
this.enemyCircle = new Circle(0, 0, 20, Color.RED); // 敵オブジェ作成 ②
this.myCircle = new Circle(0, 0, 20, Color.WHITE); // マイオブジェ作成
}

/** 画面タッチY座標 */
private float keyY = 0;

/** 画面タッチX座標 */
private float keyX = 0;

/** 画面高さ */
private int height;

/** 画面横幅 */
private int width;

/**
* @param keyY ③
*/
public void setKeyY(float keyY) {
synchronized(this.holder) { // setメソッドは別スレッドから呼ばれるため同期させる
this.keyY = keyY;
}
}

/**
* @param keyX
*/
public void setKeyX(float keyX) {
synchronized(this.holder) { // setメソッドは別スレッドから呼ばれるため同期させる
this.keyX = keyX;
}
}

/**
* サーフェース生成時

* @see android.view.SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)
*/
public void surfaceCreated(SurfaceHolder holder) {④
this.holder = holder; // インスタンスを自身に格納
this.thread = new Thread(this); // 描画用スレッドを新しく生成
this.thread.start(); // スレッドを実行する(Runnable)
}

/**
* サーフェース変更時 ⑤

* @see android.view.SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder,
*      int, int, int)
*/
public void surfaceChanged(SurfaceHolder holder, int format, int width, // 端末を傾けたときの画面切り替わりの処理
int height) {
synchronized(this.holder) { // 同期処理にして、敵オブジェ、マイオブジェの位置を再設定する
this.width = width;
this.height = height;

this.enemyCircle.setCx(width * 1 / 2);
this.enemyCircle.setCy(height / 4);
this.myCircle.setCx(width / 2);
this.myCircle.setCy(height * 3 / 4);
this.keyX = width / 2;
this.keyY = height * 3 / 4;
}
}

/**
* サーフェース破棄時

* @see android.view.SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)
*/
public void surfaceDestroyed(SurfaceHolder holder) {⑥
boolean retry = true;

synchronized(this.holder) { // 同期処理
this.threadRun = false; // メンバ変数 終了フラグを立てる
}

while (retry) {
try {
this.thread.join(); // 別スレッドが終了するまで待つ
retry = false;
} catch (InterruptedException e) {}
}

this.thread = null; // スレッド終了
}

/**
* スレッド処理
*/
public void run() {⑦
// レベル
int level = 1;
// 最大レベル
int levelMax = 1;
// レベルアップまでのカウント
long count = 0;
// 自機の情報
float myX;
float myY;
float myR;
// 敵の情報
float eneX;
float eneY;
float eneR;
// 画面の幅
int width;

// ペイントオブジェクト
Paint paintEne = new Paint(); // 敵paint
Paint paintMy = new Paint(); // マイpaint
Paint paintLevel = new Paint(); // レベル表示領域

// 描画色の設定
paintEne.setColor(this.enemyCircle.getColor()); // 敵オブジェのColor設定
paintMy.setColor(this.myCircle.getColor()); // マイオブジェのColor設定
paintLevel.setColor(Color.WHITE);

// 初期の敵移動量設定
this.enemyCircle.setMoveX(-1 - (int) Math.ceil(Math.random() * (this.enemyCircle.getMoveXMax() / 2))); // 敵オブジェの移動量をランダム生成
this.enemyCircle.setMoveY(1 + (int) Math.ceil(Math.random() * (this.enemyCircle.getMoveYMax() / 2)));

// メインループ
while (this.threadRun) { // メンバbool スレッド制御フラグ参照
// 敵移動
moveEnemy(this.enemyCircle);⑧
// 自機移動
moveMyShip(this.myCircle);

synchronized(this.holder) { // スレッドの同期
// 自機の座標取得
myX = this.myCircle.getCx();
myY = this.myCircle.getCy();
myR = this.myCircle.getRadius();
// 敵の座標取得
eneX = this.enemyCircle.getCx();
eneY = this.enemyCircle.getCy();
eneR = this.enemyCircle.getRadius();

// 画面幅取得
width = this.width;
// レベルに応じた最大移動量設定
this.enemyCircle.setMoveXMax(level / 2 + 5);
this.enemyCircle.setMoveYMax(level / 2 + 5);

}

// Canvas取得
Canvas canvas = this.holder.lockCanvas(); // スレッドのキャンパス取得 ⑨

// 背景色の設定
canvas.drawColor(Color.BLACK); // 背景を設定

// 敵の描画
canvas.drawCircle(eneX, eneY, eneR, paintEne); // 敵paintを設定

// 自機の描画
canvas.drawCircle(myX, myY, myR, paintMy); // マイpaintを設定

// レベル表示
canvas.drawText("Level:" + level, 2, 12, paintLevel);
canvas.drawText("MaxLevel:" + levelMax, width / 2, 12, paintLevel);

// Canvasアンロック
this.holder.unlockCanvasAndPost(canvas); // キャンバスの開放・出力 ⑩

// 衝突判定
if (collisionJudgment(this.enemyCircle, this.myCircle)) {
count = 0;
level = 1;
try {
Thread.sleep(500); // 衝突時に0.5秒まつ
} catch (InterruptedException e) {}
}

// レベルアップ判定
count++;
if (count > 100) {
count = 0;
level++;
if (level > levelMax) {
levelMax = level;
}
}

// ウェイト
try {
Thread.sleep(10); // 再描画のウェイト
} catch (InterruptedException e) {}
}
}

/**
* 敵移動処理 自動移動処理

* @param canvas

* @param circle
*/
private void moveEnemy(Circle circle) {
synchronized(this.holder) {
// 新しい座標を計算
float cx = circle.getCx();
float cy = circle.getCy();

// 移動
circle.setCx(cx - circle.getMoveX()); // 移動は設定している移動量となる
circle.setCy(cy - circle.getMoveY());

float rad = circle.getRadius() / (float) 1.1;

// 壁衝突処理(x座標 左側)
if (circle.getCx() < rad) {
circle.setMoveX(-1 - (int) Math.ceil(Math.random() * (circle.getMoveXMax() / 2)));
}

// 壁衝突処理(x座標 右側)
if (circle.getCx() > this.width - rad) {
circle.setMoveX(1 + (int) Math.ceil(Math.random() * (circle.getMoveXMax() / 2)));
}

// 壁衝突処理(y座標 上側)
if (circle.getCy() < rad) {
circle.setMoveY(-1 - (int) Math.ceil(Math.random() * (circle.getMoveYMax() / 2)));
}

// 壁衝突処理(y座標 下側)
if (circle.getCy() > this.height - rad) {
circle.setMoveY(1 + (int) Math.ceil(Math.random() * (circle.getMoveYMax() / 2)));
}
}
}

/**
* 自機移動処理

* @param canvas

* @param circle
*/
private void moveMyShip(Circle circle) {
synchronized(this.holder) {
// 新しい座標を計算
float cx = circle.getCx();
float cy = circle.getCy();
circle.setCx(cx - (cx - this.keyX) / 30); // 移動は上記setメソッドで設定された移動量となる
circle.setCy(cy - (cy - this.keyY) / 30);

// 画面境界移動制限
float rad = circle.getRadius() / (float) 1.1;
if (circle.getCx() < rad) {
circle.setCx(rad);
}
if (circle.getCx() > this.width - rad) {
circle.setCx(this.width - rad);
}

if (circle.getCy() < rad) {
circle.setCy(rad);
}
if (circle.getCy() > this.height - rad) {
circle.setCy(this.height - rad);
}
}
}

/**
* 衝突判定

* @param enemy
* @param my
* @return
*/
private boolean collisionJudgment(Circle enemy, Circle my) {
float myX;
float myY;
float eneX;
float eneY;
float eneR;
float eneMinX;
float eneMaxX;
float eneMinY;
float eneMaxY;

synchronized(this.holder) {
// 敵の座標取得
eneX = enemy.getCx();
eneY = enemy.getCy();
eneR = enemy.getRadius();

// 自機の座標取得
myX = my.getCx();
myY = my.getCy();
}

// 敵の当たり判定範囲
eneMinX = eneX - (eneR * 3 / 4);
eneMaxX = eneX + (eneR * 3 / 4);
eneMinY = eneY - (eneR * 3 / 4);
eneMaxY = eneY + (eneR * 3 / 4);

// 衝突判定
if ((myX >= eneMinX && myX <= eneMaxX) && (myY >= eneMinY && myY <= eneMaxY)) {
return true;
}

return false;
}
}


①Circleオブジェクトを宣言
②2つのCircleオブジェクトを生成。
③onTouchEventから呼び出され、座標を設定する
④SurfaceHolder生成時にCall。描画用スレッドを実行する
⑤画面を傾けたるなどでCall。オブジェクトの現在位置を計算し、再配置を行う。
⑥SurfaceHolder破棄時にCall。後始末を行う。
⑦スレッド処理
⑧敵移動はランダム計算。自機移動はonTouchEventにて設定された座標を下に計算
⑨holder.lockCanvas()でCanvasオブジェクトを取得
 .drawCircleにて円形を描画する。
⑩unlockCanvasAndPost()メソッドにて実際に画面へ描画する


○SurfaceActivityアクティビティクラス

package com.example.game;

import android.app.Activity;
import android.os.Bundle;

public class SurfaceActivity extends Activity {
/** Called when the activity is first created. */@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.main);
setContentView(new SurfaceCircleView(this));①
}
}


①setContentViewに、SurfaceViewを実装しているSurfaceCircleViewクラスを設定する。


○Circleクラス
描画するグラフィックオブジェクトの半径、座標などの設定値を保持するクラス

package com.example.game;

public class Circle {
/** x座標 */
private float cx = 0;

/** y座標 */
private float cy = 0;

/** 円の半径 */
private float radius = 50;

/** 色 */
private int color = 0;

/** x方向移動量 */
private int moveX = 0;

/** y方向移動量 */
private int moveY = 0;

/** x方向最大移動量 */
private int moveXMax = 0;

/** y方向最大移動量 */
private int moveYMax = 0;

/**
* コンストラクタ

* @param cx
*            x座標
* @param cy
*            y座標
* @param radius
*            円の半径
* @param color
*            色
*/
public Circle(float cx, float cy, float radius, int color) {
this.cx = cx;
this.cy = cy;
this.radius = radius;
this.color = color;
}

/**
* x座標を取得する

* @return cx
*/
public float getCx() {
return cx;
}

/**
* x座標を設定する

* @param cx
*            セットする cx
*/
public void setCx(float cx) {
this.cx = cx;
}

/**
* y座標を取得する

* @return cy
*/
public float getCy() {
return cy;
}

/**
* y座標を設定する

* @param cy
*            セットする cy
*/
public void setCy(float cy) {
this.cy = cy;
}

/**
* 半径を取得する

* @return radius
*/
public float getRadius() {
return radius;
}

/**
* 半径を設定する

* @param radius
*            セットする radius
*/
public void setRadius(float radius) {
this.radius = radius;
}

/**
* 色を取得する

* @return color
*/
public int getColor() {
return color;
}

/**
* 色を設定する

* @param color
*            セットする color
*/
public void setColor(int color) {
this.color = color;
}

/**
* @return moveXMax
*/
public int getMoveXMax() {
return moveXMax;
}

/**
* @param moveXMax
*            セットする moveXMax
*/
public void setMoveXMax(int moveXMax) {
this.moveXMax = moveXMax;
}

/**
* @return moveYMax
*/
public int getMoveYMax() {
return moveYMax;
}

/**
* @param moveYMax
*            セットする moveYMax
*/
public void setMoveYMax(int moveYMax) {
this.moveYMax = moveYMax;
}

/**
* @return moveX
*/
public int getMoveX() {
return moveX;
}

/**
* @param moveX
*            セットする moveX
*/
public void setMoveX(int moveX) {
this.moveX = moveX;
}

/**
* @return moveY
*/
public int getMoveY() {
return moveY;
}

/**
* @param moveY
*            セットする moveY
*/
public void setMoveY(int moveY) {
this.moveY = moveY;
}
}



■稼動イメージ




・常に赤い○が移動している。別スレッドにて処理している。
・画面のパネルタッチイベントにて座標を取得。その位置へ白い玉を移動させる。
スポンサーサイト

テーマ : android
ジャンル : コンピュータ

コメントの投稿

非公開コメント

おすすめアプリ
カテゴリ
最新記事
リンク
アクセスカウンター
アクセス解析
imobile
i-mobile
i-mobile
i-mobile
i-mobile
i-mobile
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
Amazon
Androidお勧め参考書
EC studio
商品
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。