Viewのまとめなのでまじめな匠によるAndroid講座第4回です。

今回はsurfaceviewを使ったゲームアプリの例を作っていきたいです。
そして今回の例はせっかく前回たくさんのviewによる図形の描画をやったので
それを活躍させられる「あの楽器」の画面エフェクトを作っていきたいと思います。

始めにプロジェクトを作成します
プロジェクト名: touchTest
ターゲット:Android2.2
アプリケーション名:あの楽器
パッケージ名:aokai.app.touchTest
Create Actvity:Main
としました。

あの楽器にする前にsurfaceviewのひな形を作ります。
その前にSurfaceviewを簡単に説明すると、描画専用のスレッドを作成しそのスレッドを
ループさせることによりパラパラ漫画のように描かれているものを少しずつ動かしていくことでアニメーションに見せるものです。
サボった例
#ref(001.png)
頑張った例
#ref(test .swf)

そしてsurfaceviewとゲームループをつかった例の雛形
 package aokai.app.touchTest;
 
 import android.content.Context;
 import android.graphics.Canvas;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 
 
 public class MyView extends SurfaceView implements SurfaceHolder.Callback,
 		Runnable {
 
 	private Canvas canvas;
 	// スレッドクラス
 	private Thread thread;
 	private SurfaceHolder holder;
 	public boolean tread_flag;
 
 	//コンストラクタ
 	public MyView(Context context) {
 		super(context);
 		tread_flag = true;
 		// サーフェイスホルダーの作成
 		holder = getHolder();
 		holder.addCallback(this);
 		holder.setFixedSize(getWidth(), getHeight());
 	}
 	
 //ディスプレイタッチ時に呼ばれる
 	@Override
 	public boolean onTouchEvent(MotionEvent event) {
 		return true;
 	}
 
 	public void surfaceCreated(SurfaceHolder holder) {
 		thread = new Thread(this);
 		//ここでrun()が呼ばれる
 		thread.start();
 	}
 
 	public void run() {
 		while (tread_flag) {
 			UpDate();
 			Draw();
 		}
 	}
 	private void UpDate(){
 		//TODO 更新内容
 	}
 	public void Draw() {
 		canvas = getHolder().lockCanvas();
 		//TODO 描画処理
 		holder.unlockCanvasAndPost(canvas);
 	}
 
 	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
 	}
 
 	public void surfaceDestroyed(SurfaceHolder holder) {
 		tread_flag = false;
 	}
 } 
 
このMyviewクラスには
コンストラクタのMyview(自動生成)
surfaceviewが作られた時に呼ばれるsurfaceCreated(自動生成)
surfaceviewのサイズなどが変更されたときに呼ばれるsurfaceChanged(自動生成)
surfaceviewがなくなる時に呼ばれるsurfaceDestroyed(自動生成)
画面がタッチされたときに呼ばれるonTouchEvent

 public void surfaceCreated(SurfaceHolder holder) {
 		thread = new Thread(this);
 		//ここでrun()が呼ばれる
 		thread.start();
 	}
のthread.start();で新たなスレッドとして呼ばれるrun(自動生成)

 while (tread_flag) {
 			UpDate();
 			Draw();
 		}
で無限ループさせてゲームの数値達を更新させるUpDate(自作)
描写させるDraw(自作)

これを元にしてアプリを作っていきます。

まずAndroidとは関係ないあの楽器のエフェクト達の座標を管理するクラスを作ります。
ここではShapesという名前にしました。
今回使うエフェクトは
 円	直線	三角	四角	星
です。
そしてすべて透明度がフレームごとで下がっていき0になると消えるようにします
また、図形によっては回転、拡大させます。
まずはクラスを作ります
 Class Shapes{
 	public int shapeID;//図形の種類を保存
 	public int rad;//現在の図形の角度
 	public int size;//現在の図形の大きさ
 	public int x;//タッチした座標(x)
 	public int y;//タッチした座標(y)
 	public boolean flag;//現在使われているかどうか
 	public int Alpha;//透明度
 	private int wid;//画面サイズ(x)
 	private int hei;//画面サイズ(y)
 	public point[] point = new point[10];//図形の座標を保存
 	//コンストラクタ画面サイズを代入
 	public Shapes(int w, int h) {
 		wid = w;
 		hei = h;
 		for (int i = 0; i < 10; i++) {
 			point[i] = new point();
 		}
 	}
 	public class point {
 		double x;
 		double y;
 	}
 } 
次に新たに図形が作られたとき(画面がタッチされたとき)に呼ぶようにする
図形の初期化をする関数を作ります
 	public void set(int setx, int sety) {
 		flag = true;
 		// Randomクラスのインスタンス化
 		Random rnd = new Random();
 		shapeID = rnd.nextInt(5);
 		rad = rnd.nextInt(180);
 		size = 50;
 		x = setx;
 		y = sety;
 		Alpha = 255 * 2 / 3;
 		switch (shapeID) {
 		case 0:
 			// ☆の初期化
 			break;
 		case 1:
 			// 四角形の初期化
 			break;
 		case 2:
 			// 三角形の初期化
 			break;
 		case 3:
 			// 直線の初期化
 		}
 	}
 
Switchでかかれた所に各図形の初期化を書いていきます。
ちなみに4は円としていて、頂点がないのでここではありません。


まず画面固定になる直線エフェクトの初期化を書きます。
#ref(002.png) 
直線を描くためには始点と終点が必要なので求めます。
描くに当たって条件
	タッチした点を通る
	傾きは乱数で決めた値
にそって求めます。
#ref(003.png)  
基本の y = ax + b
さらに傾きは tanθ、タッチされた点XYより
Y = tanθ * X + b
となり
b = Y &#8211; tanθ * Xとなり
結果 y = tanθ * x + Y &#8211; tanθ * X
となります

これよりx = 0 or y = 0 の場所が始点となり
X = 画面端ッ!! or Y = 画面端ッ!!の時が終点となり

それらをプログラムにすると以下のようになりset()での直線の初期化に書きます
 point[0].x = 0;
 point[0].y = y - Math.tan(rad) * x;
 if (point[0].y < 0) {	
 	point[0].x = (-point[0].y) / Math.tan(rad);
 	point[0].y = 0;
 } 
 point[1].x = wid;
 point[1].y = Math.tan(rad) * wid + y - Math.tan(rad) * x;
 if (point[1].y > hei) {
 	point[1].x = (hei - y + Math.tan(rad) * x) / Math.tan(rad);
 	point[1].y = hei;
 } 
となります。

次に三角の初期化
#ref(004.png)  
今回の条件は
	中心はタッチした点
	だんだん拡大
	正三角形
	回転する
です
頂点を求めるために今回は極座標の考え方を使います
回転行列を使ってもいいのですがこの方法だと工程がすごく多くなってしまううえに
他の図形に大様がきかないので(ただ単にうまくいかなかっただけww…誰か教えて…)
ちなみに下のが失敗した回転行列を用いた計算
**********************間違った例です********************** [#tceedd98]
 point[0].x = x;
 point[0].y = y + size * 2 * triangle;
 point[1].x = x + size;
 point[1].y = y - size * 2 * triangle;
 point[2].x = x - size;
 point[2].y = y - size * 2 * triangle;
 // 回転
 point temp = new point();
 for (int i = 0; i < 3; i++) {
 	temp = point[i];
 	temp.x -= x;
 	temp.y -= y;
 	double trad = -Math.toRadians(rad);
 	point[i].x = temp.x * Math.cos(trad) - temp.y * Math.sin(trad);
 	point[i].y = temp.x * Math.sin(trad) + temp.y * Math.cos(trad);
 	point[i].x += x;
 	point[i].y += y;
 } 
**********************間違った例です********************** [#laec068f]
#ref(005.png)  
各点は中心より3等分に角度が分けられ距離はsizeとすると
θは次の点に映るたびに180度たし次のような計算式になります
 x = X + size * cosθ
 y = Y + size * sinθ
となります
これをプログラムにすると以下のようになります
**********************正しい例です********************** [#tceedd98]
 double trad;
 for (int i = 0; i < 3; i++) {
 trad = Math.toRadians(rad + i * 120); 
 	point[i].x = x + (size * 2 * triangle) * Math.cos(trad);
 	point[i].y = y + (size * 2 * triangle) * Math.sin(trad);
 }
**********************正しい例です********************** [#tceedd98]
ちなみにこれは足す角度とループ回数を変えることにより多角形になり
Sizeを毎回変えてやれば星のような図形もできます。
なのでソースを後ほど張って省かせていただきます

毎フレームごとに呼ばれる図形の更新についてです
サイズ角度を増し、透明度を下げます
そして図形の座標の更新をいますが初期化のと同じでいけます
そして透明度が0以下になったらflag;//現在使われているかどうかを
Falseに変えます
以下最終的なsurfaceviewを継承したクラスです
 package aokai.app.touchTest;
 
 import java.util.Random; 
  
 public class Shapes {
	public int shapeID;
	public int rad;
	public int size;
	public int x;
	public int y;
	public boolean flag;
	public int Alpha;
	private int wid;
	private int hei;
	public point[] point = new point[10];
 
	public Shapes(int w, int h) {
		wid = w;
		hei = h;
		for (int i = 0; i < 10; i++) {
			point[i] = new point();
		}
	}
 
 	public void set(int setx, int sety) {
 		flag = true;
 		// Randomクラスのインスタンス化
 		Random rnd = new Random();
 		shapeID = rnd.nextInt(5);
 		rad = rnd.nextInt(180);
 		size = 50;
 		x = setx;
 		y = sety;
 		Alpha = 255 * 2 / 3;
 		switch (shapeID) {
 		case 0:
 			// ☆の初期化
 			star_math();
 			break;
 
 		case 1:
 			// 四角形の初期化
 			rect_math();
 			break;
 		case 2:
 			// 三角形の初期化
 			triangle_math();
 			break;
 		case 3:
 			// 直線の初期化
 			point[0].x = 0;
 			point[0].y = y - Math.tan(rad) * x;
 			if (point[0].y < 0) {
 				point[0].x = (-point[0].y) / Math.tan(rad);
 				point[0].y = 0;
 			}
 			point[1].x = wid;
 			point[1].y = Math.tan(rad) * wid + y - Math.tan(rad) * x;
 			if (point[1].y > hei) {
 				point[1].x = (hei - y + Math.tan(rad) * x) / Math.tan(rad);
 				point[1].y = hei;
 			}
 			break;
 		}
 	}
 
 	public boolean update() {
 		if (flag == true) {
 			size += 10;
 			if (shapeID != 3) {
 				rad += 6;
 				if (rad > 360) {
 					rad -= 360;
 				}
 			}
 			Alpha -= 6;
 			if (Alpha < 0) {
 				flag = false;
 				return false;
 			}
 			switch (shapeID) {
 			case 0:
 				star_math();
 				break;
 			case 1:
 				rect_math();
 				break;
 			case 2:
 				triangle_math();
 				break;
 			}
 			return true;
 		} else {
 			return false;
 		}
 	}
 
 	private void star_math() {
 		double trad;
 		for (int i = 0; i < 10; i++) {
 			trad = Math.toRadians(rad + i * 36);
 			if (i % 2 == 0) {
 				point[i].x = x + (size * 0.8) * Math.cos(trad);
 				point[i].y = y + (size * 0.8) * Math.sin(trad);
 			}
 			if (i % 2 == 1) {
 				point[i].x = x + (size * 0.8 * 0.4) * Math.cos(trad);
 				point[i].y = y + (size * 0.8 * 0.4) * Math.sin(trad);
 			}
 		}
 	}
 
 	private void rect_math() {
 		double trad;
 		for (int i = 0; i < 4; i++) {
 			trad = Math.toRadians(rad + i * 90);
 			point[i].x = x + (size) * Math.cos(trad);
 			point[i].y = y + (size) * Math.sin(trad);
 		}
 	}
 
 	private void triangle_math() {
 		double trad;
 		for (int i = 0; i < 3; i++) {
 			trad = Math.toRadians(rad + i * 120);
 			point[i].x = x + (size * 2 ) * Math.cos(trad);
 			point[i].y = y + (size * 2 ) * Math.sin(trad);
 		}
 	}
 	public class point {
 		double x;
 		double y;
 	}
 } 
 
あとはsurfaceviewを継承したクラスを作り
Shapeクラスのインスタンスを配列で作り座標にあった描写をします
描写については前回のviewで説明したので省きます
以下は上の雛形をと描写をあわせたソースコードです

 package aokai.app.touchTest;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.MotionEvent;
 
 public class TouchView extends SurfaceView implements SurfaceHolder.Callback,
		Runnable {
	private Canvas canvas;
 
	// スレッドクラス
	private Thread thread;
	private SurfaceHolder holder;
	public boolean tread_flag;
	public Shapes[] shapes;
 	private int objectmax = 10;
 	private int pointerCount;
 	private Paint paint = new Paint();
 
 	public TouchView(Context context) {
 		super(context);
 		// setBackgroundColor(Color.WHITE);
 		setFocusable(true);
 		tread_flag = true;
 		pointerCount = 0;
 		paint.setAntiAlias(true);
 		paint.setStrokeCap(Paint.Cap.ROUND);
 		shapes = new Shapes[objectmax];
 
 		// サーフェイスホルダーの作成
 		holder = getHolder();
 		holder.addCallback(this);
 		holder.setFixedSize(getWidth(), getHeight());
 	}
 
 	public void run() {
 		for (int i = 0; i < objectmax; i++) {
 			shapes[i] = new Shapes(getWidth(), getHeight());
 		}
 		while (tread_flag) {
 			Draw();
 		}
 	}
 
  	public void Draw() {
 		canvas = getHolder().lockCanvas();
 		// 背景の描画
 		canvas.drawColor(Color.BLACK);
 		Path path = new Path();
 		for (int i = 0; i < objectmax; i++) {
 			if (shapes[i].update()) {
 				paint.setStyle(Paint.Style.FILL);
 				paint.setColor(Color.argb(shapes[i].Alpha, 20, 255, 1));
 				switch (shapes[i].shapeID) {
 				case 0:
 					// ☆形の描写
 					paint.setStrokeWidth(20);
 					paint.setStyle(Paint.Style.STROKE);
 					path.moveTo((int) shapes[i].point[0].x,
 							(int) shapes[i].point[0].y); // 始点
 					for (int j = 1; j < 10; j++) {
 						path.lineTo((int) shapes[i].point[j].x,
 								(int) shapes[i].point[j].y);
 					}
 					path.lineTo((int) shapes[i].point[0].x,
 							(int) shapes[i].point[0].y); 
                                        // 終点から始点へパ スをつなぐ
 					canvas.drawPath(path, paint);
 					break;
 				case 1:
 					// 四角形の描写
 					paint.setStrokeWidth(20);
 					paint.setStyle(Paint.Style.STROKE);
 					path.moveTo((int) shapes[i].point[0].x,
 							(int) shapes[i].point[0].y); // 始点
 					path.lineTo((int) shapes[i].point[1].x,
 							(int) shapes[i].point[1].y);
 					path.lineTo((int) shapes[i].point[2].x,
 							(int) shapes[i].point[2].y);
 					path.lineTo((int) shapes[i].point[3].x,
 							(int) shapes[i].point[3].y);
 					path.lineTo((int) shapes[i].point[0].x,
 							(int) shapes[i].point[0].y); 
                                        // 終点から始点へパ スをつなぐ
 					canvas.drawPath(path, paint);
					break;
 				case 2:
 					// 三角形の描写
 					paint.setStrokeWidth(20);
 					paint.setStyle(Paint.Style.STROKE);
 					path.moveTo((int) shapes[i].point[0].x,
 							(int) shapes[i].point[0].y); // 始点
 					path.lineTo((int) shapes[i].point[1].x,
 							(int) shapes[i].point[1].y);
 					path.lineTo((int) shapes[i].point[2].x,
 							(int) shapes[i].point[2].y);
 					path.lineTo((int) shapes[i].point[0].x,
 							(int) shapes[i].point[0].y); 
                                        // 終点から始点へパ スをつなぐ
  					canvas.drawPath(path, paint);
 					break;
 				case 3:
 					// 直線の描写
 					paint.setStrokeWidth(15);
 					canvas.drawLine((float) shapes[i].point[0].x,
 							(float) shapes[i].point[0].y,
 							(float) shapes[i].point[1].x,
 							(float) shapes[i].point[1].y, paint);
 					break;
 				case 4:
 					// 円の描写
 					paint.setStyle(Paint.Style.STROKE);
 					paint.setStrokeWidth(30);
 					canvas.drawCircle(shapes[i].x, shapes[i].y, shapes[i].size,
 							paint);
 					break;
 				}
 			}
 		}
 		holder.unlockCanvasAndPost(canvas);
 	}
 
 
 	@Override
 	public boolean onTouchEvent(MotionEvent event) {
 		for (int i = pointerCount; i < event.getPointerCount(); i++) {
 			for (int j = 0; j < objectmax; j++) {
 				if (shapes[j].flag == false) {
 					shapes[j].set((int) event.getX(i), (int) event.getY(i));
 					break;
 				}
 			}
 		}
 		pointerCount = event.getPointerCount();
 		if (event.getAction() == MotionEvent.ACTION_UP) {
 			pointerCount = 0;
 		}
 		return true;
 	}
 
 	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
 		// TODO 自動生成されたメソッド・スタブ
 
 	}
 
 	public void surfaceCreated(SurfaceHolder holder) {
 		thread = new Thread(this);
 		thread.start();
 
 	}
 
 	public void surfaceDestroyed(SurfaceHolder holder) {
 		// TODO 自動生成されたメソッド・スタブ
 
 	}
 } 
 
これでMainの
setContentViewの引数をsurfaceviewを継承したクラスにしておわりです!!
 package aokai.app.touchTest;
 
 import android.app.Activity;
 import android.os.Bundle; 
 
 public class Main extends Activity {
 	TouchView view;
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         view = new TouchView(this);
         setContentView(view);
	}
 }
これが実行結果です。
#ref(006.png)

今回で説明しきれなかったので多分動くがアプリを終了したときエラると思います。
次回の予定はもう決めてないのでいつになるか分かりません…
内容はlayoutについてだとおもいますが…

#vote(余裕だぜ!![0],普通ですぅ[0],ソースくせぇだよ!![0],  ぐへへへぇ[16])
|余裕だぜ!!|0|
|普通ですぅ|0|
|ソースくせぇだよ!!|0|
|  ぐへへへぇ|16|

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS