Androidプログラミング日記 (仮)  

シューティング作るぞ!> 第九回 パワーアップアイテム&整理

あなたは

人目のプログラマーだよ。

Androidプログラミング日記 (仮).

 

 

 

 

パワーアップアイテム&整理

ちょっと次の段階へ進む前に、現段階でできるだけのコードの整理とやっておきたいことがあったのでやってみました。敵機を撃墜するとパワーアップアイテムが出現し、弾の発射種類が変更されることの実装。タップしつづけると移動&弾発射。コードの整理(なるべくメソッド化)をしました。

コードがごちゃごちゃしてくると、変更もどこを変更をすればいいかとかわからなくなってきたりしますよね。なるべくメソッド化とかすれば塊りとして探しやすくなるかなぁと思い整理してみました。

いまから掲載するクラスファイル以外に変更はあったのですが、変数の数値を1箇所変えただけなのでここに書いておくことにしました。変更はObjectクラスをextendsさせたクラス、Jiki、JikiTama、Tekki、Bomのobsyuruiの数値でそれぞれ

Jiki obsyurui = 0

JikiTama obsyurui = 1

Tekki obsyurui = 2

Bom obsyurui = 3

と変更しています。

 

ファイル名「MainLoop.java」

/*
 * SurfaceViewをextendsさせたクラス
 * メインループなどはここにあります
 * 基本的にSurfaceViewを使用した時には決まった変数の使用
 * などがあるので、そういう変数やメソッドの末尾に
 * ”お決まり”と書いておきます
 */
package and.roid.shooting2;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.MediaPlayer;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainLoop extends SurfaceView implements SurfaceHolder.Callback,Runnable{
	private SurfaceHolder holder;//お決まり
	private Thread thread;//お決まり

	//どのActivityを使用しているかのための変数
	private Shooting2Activity s2a;
	private Mesod ms;
	private float disp_w,disp_h;
	private Bitmap jikibit,tamabit;
	//弾用、連続で重ならないようにの変数
	private boolean tamaflg;
	private static final int TAMATIMES = 2;
	private int tamatime;
	//音源用3種&爆発音
	private MediaPlayer jitama1s,jitama2s,jitama3s,boms;

	//敵機画像用
	private Bitmap tekibit;

	//爆発画像用
	private Bitmap[] bombit = new Bitmap[4];
	private boolean bomflg=false;

	//写真撮影用ポーズボタン
	private Rect pose;
	private boolean poseflg;

	//背景画像用
	//背景をいくつで分割するか
	private static final int HAIKEI = 10;
	//背景スクロールスピード
	private static final int BUCKSCROOLSP = 1;
	//背景分割画像用
	private ArrayList <Drawable> buck = new ArrayList();
	//背景分割画像座標用
	private ArrayList <Integer> by = new ArrayList();

	//パワーアップアイテム用
	private Bitmap powbit;

	//タップ状態用
	private int tx,ty;
	private boolean tapstate;


	/*
	 * 画面に表示させるオブジェクトは自機と弾、敵機、爆発です
	 * 全然違うオブジェクトですがObjectクラスをextends
	 * することで同じような変数として扱うことができます。
	 * 自機は一つのオブジェクトだけですが、弾はたくさん
	 * 表示します。しかし数は決まっていません。なので
	 * ArrayListを使用することにしました。
	 * 普通の配列変数では、いくつ配列を使用するかを決めないと
	 * いけませんが、ArrayListの場合は使用したいときに配列的な
	 * ものをいくつでも作成することができます。
	 */
	private ArrayList<Object> object = new ArrayList();

	//コンストラクタが二つあるけど気にしないように
	//こちらのコンストラクタは、自前でViewを実装するときに
	//呼ばれるコンストラクタっぽい
	public MainLoop(Context context) {
		super(context);
		init(context);
	}
	//こちらはxml方式でViewを呼び出すときに呼ばれるぽい
	public MainLoop(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	public void init(Context context){
		holder = getHolder();//お決まり
		holder.addCallback(this);//お決まり
		holder.setFixedSize(getWidth(), getHeight());//お決まり
		/*
		 * s2a = (Shooting2Activity)context;
		 * よくわからないけども、私的には使用している(今現在
		 * 画面に表示させている)アプリの何か色々なものをcontext
		 * として受け取り、それがいまどのActivityのやつかとか
		 * そんな感じの雰囲気と思います。(キニシテナイ)
		 */
		s2a = (Shooting2Activity)context;
		ms = new Mesod();
		disp_w = s2a.disp_w;
		disp_h = s2a.disp_h;

		Resources resources = context.getResources();//画像登録準備
		//ビットマップ方式で画像取り込み
		//ビットマップで取り込む理由として、使用したい大きさなどに変換できるので
		Bitmap img= BitmapFactory.decodeResource(resources,and.roid.shooting2.R.drawable.jiki);
		//ここで画像分割
		//わざわざ画像の大きさをgetWidthgetHeightを使用するのは
		//確実に大きさを図って分割するため
		jikibit = Bitmap.createBitmap(img,0,0,img.getWidth()/4,img.getHeight());
		tamabit = Bitmap.createBitmap(img,img.getWidth()/4,0,img.getWidth()/4,img.getHeight());

		/*
		 * Onjectクラスではインスタンス(実装)できないので
		 * ObjectクラスをextendsさせたJikiクラスを実装
		 * ArrayListを使用しているため、addでインスタンスしています
		 * メソッドなどを使用する場合はget(インデックス).でメソッドなど
		 * 色々呼び出したりします。
		 * 今回はArrayListの0番目の要素に自機が入っています
		 */
		object.add(new Jiki(disp_w,disp_h));
		object.get(0).Oint(jikibit, 240, 800, 0, 0, jikibit.getWidth(), jikibit.getHeight(),0);

		tamaflg = true;
		tamatime = 1;


		//4種類の音源を取り込み使用準備
		jitama1s = MediaPlayer.create(context,R.raw.jitama1);
		jitama2s = MediaPlayer.create(context,R.raw.jitama2);
		jitama3s = MediaPlayer.create(context,R.raw.jitama3);
		boms = MediaPlayer.create(context,R.raw.bom);
		try{
			jitama1s.prepare();
			jitama2s.prepare();
			jitama3s.prepare();
			boms.prepare();
		}catch (Exception e){}

		//敵機画像登録&作成
		tekibit = Bitmap.createBitmap(img,img.getWidth()/4*2,0,img.getWidth()/4,img.getHeight());

		//ついでなのでランダムで敵機10機作成
		Random r = new Random(new Date().getTime());
		for(int i=0;i<5;i++){
		int x = r.nextInt((int) (disp_w-50));
		int y = r.nextInt((int) (disp_h/2));
		object.add(new Tekiki(disp_w,disp_h));
		object.get(object.size()-1).
			Oint(tekibit, x, y, 0, 0, tekibit.getWidth(), tekibit.getHeight(),0);
		}

		//爆発画像用意
		img= BitmapFactory.decodeResource(resources,and.roid.shooting2.R.drawable.bom);
		for(int i=0;i<2;i++){
			for(int j=0;j<2;j++){
		bombit[j+i*2] = Bitmap.createBitmap(
				img,
				j*(img.getWidth()/2),i*(img.getHeight()/2),
				img.getWidth()/2,img.getHeight()/2);
		}
		}

		//ポースボタン
		pose = new Rect(0,0,(int)disp_w,50);
		poseflg=false;

		//背景画像登録
		img= BitmapFactory.decodeResource(resources,and.roid.shooting2.R.drawable.buck);
		for(int i=0;i<HAIKEI;i++){
		Bitmap buckbit = Bitmap.createBitmap(img,
				0,i*(img.getHeight()/HAIKEI),img.getWidth(),(img.getHeight()/HAIKEI));
		buck.add(new BitmapDrawable(buckbit));
		by.add(i*(int)(disp_h/(HAIKEI-1))-(int)(disp_h/(HAIKEI-1)));
		buck.get(i).setBounds(0, by.get(i), (int)disp_w, (int)(by.get(i)+(disp_h/HAIKEI-1)));
		}


		//パワーアップアイテム画像用意&登録
		img= BitmapFactory.decodeResource(resources,and.roid.shooting2.R.drawable.jiki);
		powbit = Bitmap.createBitmap(img,img.getWidth()/4*3,0,img.getWidth()/4,img.getHeight());

		tapstate=false;
	}

	//implements Runnableを実装するとこのメソッドが自動追加
	//ここがメインループとなります
	public void run() {//お決まり
		Canvas c;
		Paint p = new Paint();
		p.setAntiAlias(true);

		while(thread != null){
			c = holder.lockCanvas();//お決まり

			//ポース用
			if(poseflg==false){
			c.drawColor(Color.BLACK);

			Haikei(c);
			//タップ中なら弾発射
			if(tapstate==true) Tamahassya();
			/*
			 * 自機も弾も同じObjectの要素を持っているので
			 * インスタンス(実装)時に作成したいクラスを
			 * 指定しておけば、同じObjectクラスとして使用
			 * することができます。わざわざ各オブジェクトの
			 * メソッドを呼ぶのではなく、共通しているObjectの
			 * メソッドを呼ぶことで解決しています
			 */
			for(int i=0;i<object.size();i++){
				object.get(i).ODraw(c);
				object.get(i).OMove();
				ms.Atarihantei(object,i);
				BomDraw(i);
				PowDraw(i);
				if(object.get(i).Ogetdead()==true) object.remove(i);
			}
			TamaTime();
			}
			holder.unlockCanvasAndPost(c);//お決まり

			try {
				Thread.sleep(50);//お決まり
			} catch (Exception e){}
		}
	}



	public boolean onTouchEvent(MotionEvent event){
		int action = event.getAction();
		tx = (int)event.getX();
		ty = (int)event.getY();
		switch(action){
		case MotionEvent.ACTION_DOWN:
			//タップ中ですよフラグオン
			tapstate=true;
			Pose();

			break;
		case MotionEvent.ACTION_UP:
			//タップ中ですよフラグオフ
			tapstate = false;
			break;
		case MotionEvent.ACTION_MOVE:
			if(ms.RectTap(tx, ty, object.get(0).OgetTapRect()) == true) object.get(0).OMove(tx, ty);

			break;
		}
		return true;
	}

	/*
	 * 弾を1發打ったら連続で打てないようになり、タイマー
	 * が0になったら打てるようにしています。
	 */
	private void TamaTime(){
		if(tamaflg == false){
			--tamatime;
			if(tamatime < 0){
				tamatime = TAMATIMES;
				tamaflg = true;
			}
		}
	}
	/*
	 * 爆破オブジェクトであり、それが消える状態なら
	 * 今度はパワーアップアイテムを出現させる
	 */
	private void PowDraw(int i){
		if(object.get(i).obsyurui==3){
			if(object.get(i).dead ==true){
			object.add(new Pow(disp_w,disp_h));
			object.get(object.size()-1).Oint(
					powbit, object.get(i).cx, object.get(i).cy,
					0, 0, powbit.getWidth(), powbit.getHeight(),0);
		}
			}
	}

	//敵機と弾が当たったら爆発オブジェクト生成
	private void BomDraw(int i){
		if(object.get(i).bomsflg==true){
			object.add(new Bom(disp_w,disp_h));
			object.get(object.size()-1).Oint(
					bombit, object.get(i).cx, object.get(i).cy,
					0, 0, bombit[0].getWidth(), bombit[0].getHeight(),0);
			//爆発音追加
			ms.playSound(boms);
		}
	}

	/*
	 * 背景表示&スクロール
	 * setBoundsはセットさせたい画像を短形の範囲で表示させるメソッドです
	 * 表示させたい座標にも表示もできますが、後半2つの引数(right,bottom)の
	 * 設定で画像の大きさも変更させることができます。
	 * 今回は背景画像を定数で割っているのでそのまま表示させるとただ表示するだけなので
	 * スクロールさせるために割った画像の1つを画面外にだしたいために
	 * 画面表示は残りの画像で表示させたいわけです。
	 * なので本当の画像の大きさではなく、定数より1つ少なくした数で画像の大きさを
	 * 調整しています。
	 * 本当の画像よりちょっと縦に伸びた感じで表示されますが、たいした
	 * 画像ではないのでちゃんと表示されます。
	 *
	 * 画像移動座標はArrayListのbyで設定しているのですが、直接座標を
	 * 書き換えることができません。なのでbyset(index,書き換え内容)で
	 * 設定しています。
	 *
	 * 座標(top)が画面外にでたら0座標から分割画像一つ分上の座標にセット
	 */
	public void Haikei(Canvas c){
		for(int i=0;i<HAIKEI;i++){
			buck.get(i).setBounds(0, by.get(i), (int)disp_w, (int)(by.get(i)+(disp_h/(HAIKEI-1))));
			buck.get(i).draw(c);
			by.set(i, by.get(i)+BUCKSCROOLSP);
			if(by.get(i) > disp_h) by.set(i, (int)(0-(disp_h/(HAIKEI-1))));
		}
	}

	/*
	 * ポーズボタン用
	 * 今回から画面上部タップでポースするようにしました
	 */
	private void Pose(){
		if(ms.RectTap(tx, ty, pose)==true){
			if(poseflg==true){poseflg=false;}else{poseflg=true;}
		}
	}
	private void Tamahassya(){
		/*
		 * 弾を打っていいよ状態であり自機画像範囲にタップしていれば
		 * Jitama(弾クラス)を作成する
		 */
		if(tamaflg == true && ms.RectTap(
				tx, ty, object.get(0).OgetTapRect()) == true){
			/*
			 * 弾を出すような操作をすると
			 * どんな弾を作成するかのメソッドを
			 * 呼び出します
			 */
			Tamajoutai();
			tamaflg = false;
		}
	}


	/*
	 * このメソッドも他のファイルに移したい所ですが
	 * MainLoopで使用している変数を多く使用しているため
	 * 渡す変数が多いならここでもいいんじゃないかな・・・とか
	 * 思っちゃってます。どうしたものか
	 *
	 * Objectクラスに弾の状態を表す変数を用意しています
	 * それを外部から操作して変化させることでここのif文
	 * の分岐に割り当てられます
	 */
	public void Tamajoutai(){
		//通常の1発だけ
		if(object.get(0).tamajoutai == 0){
			object.add(new JiTama(disp_w,disp_h));
			object.get(object.size()-1).Oint(
					tamabit, object.get(0).cx, object.get(0).cy-jikibit.getHeight(),
					0, 30, tamabit.getWidth(), tamabit.getHeight(),0);
			//音源再生
			ms.playSound(jitama1s);
		}
		//2発並んで
		if(object.get(0).tamajoutai == 1){

			object.add(new JiTama(disp_w,disp_h));
			object.get(object.size()-1).Oint(
					tamabit, object.get(0).cx-20, object.get(0).cy-jikibit.getHeight(),
					0, 30, tamabit.getWidth(), tamabit.getHeight(),0);
			object.add(new JiTama(disp_w,disp_h));
			object.get(object.size()-1).Oint(
					tamabit, object.get(0).cx+20, object.get(0).cy-jikibit.getHeight(),
					0, 30, tamabit.getWidth(), tamabit.getHeight(),0);
			//音源再生
			ms.playSound(jitama3s);
		}
		/*
		 * 36度づつ自機の周りに10発一気に出ます
		 *
		 * ちょっと改造しました。自機を中心にちょっと離れた場所
		 * から弾が発射されるようにするために、弾の初期位置の角度
		 * (360/10)からその角度にsinでx座標cosでy座標に進む座標
		 * の最小値が出るので、一定の数値を掛ける(55)、そして
		 * 自機の中心座標にそれぞれ足したりすることで自機の中心から
		 * 少し離れた場所から発射されるようになりました。
		 */
		if(object.get(0).tamajoutai == 2){
			for(int i=0;i<10;i++){
			int r = i*(360/10);
			float rx = (float) (Math.sin(ms.toRadian(r))*55);
			float ry = (float) (Math.cos(ms.toRadian(r))*55);
			object.add(new JiTama(disp_w,disp_h));
			object.get(object.size()-1).Oint(
					tamabit, object.get(0).cx+rx, object.get(0).cy-ry,
					30, 30, tamabit.getWidth(), tamabit.getHeight(),r);
			}
			//音源再生
			ms.playSound(jitama2s);
		}
	}

	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}//お決まり

	public void surfaceCreated(SurfaceHolder arg0) {thread = new Thread(this);thread.start();}//お決まり

	public void surfaceDestroyed(SurfaceHolder arg0) {thread = null;}//お決まり

}

						

ファイル名「Mesod.java」


/*
 * 無理やり作った汎用メソッド群
 * なんども使用するようなメソッドをかためて置いて
 * おきたかったので無理やりつくったクラス
 */

package and.roid.shooting2;

import java.util.ArrayList;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.MediaPlayer;

public class Mesod {
	/*
	 * わたくしの環境がXPERIAでその画面幅でアプリを作成
	 * しているのでそれから各アプリの画面幅に合うように調整
	 * させるための変数
	 */

	static public final float XPERIA_W = 480f;
	static public final float XPERIA_H = 854f;
	//せっかくなので0も固定値に
	static public final float ZERO = 0f;
	private static final double PIE = 3.1415926;


	/*
	 * Objectクラスから移動してきた当たり範囲表示メソッド
	 *
	 * オブジェクトすべてに、オブジェクトごとの当たり判定
	 * を作成しました。それはゲーム中は見えないもののため
	 * わかりやすくするために可視化しています。
	 */
	public void OdrawRect(Rect rec,Canvas c){
		Paint p=new Paint();
		p.setColor(Color.RED);
		//c.drawRect(rec, p);
	}
	/*
	 * 種類の違うオブジェクト自機、弾、敵機、爆発、アイテム
	 * をそれぞれ区別できるように変更したのでちょっと改造
	 * 弾と敵機の判定(敵機と弾の順番では判定なし)と自機と
	 * アイテムの判定の2つの場合のみの判定になっています。
	 */
	public void Atarihantei(ArrayList<Object> object,int i){
		for(int j=0;j<object.size()-1;j++){
			//弾と敵機の当たり判定
			if(i!=j &&
					object.get(i).obsyurui == 1 &&
					object.get(j).obsyurui == 2){
				//当たっていればオブジェクトを消す準備&爆発呼び出し準備
				if(RectRect(object.get(i).atarir, object.get(j).atarir)==true){
					object.get(i).dead=true;
					object.get(i).bomsflg=true;
					object.get(j).dead=true;
				}
			}
			//自機とアイテムの判定
			if(i!=j &&
					object.get(i).obsyurui == 0 &&
					object.get(j).obsyurui == 4){
				//当たっていれば自機の弾状態を変化0-2を周回&アイテム消去準備
				if(RectRect(object.get(i).atarir, object.get(j).atarir)==true){
					object.get(j).dead = true;
					object.get(i).tamajoutai = object.get(i).tamajoutai + 1;
					object.get(i).tamajoutai = (object.get(i).tamajoutai + 3)%3;
				}
			}

		}
	}
	//渡された短形と短形の当たり判定。重なっていればtrue
	public boolean RectRect(Rect oa,Rect ob){
		return oa.left < ob.right && ob.left < oa.right && oa.top < ob.bottom && ob.top < oa.bottom;
	}
	//サウンド再生用
	public void playSound( MediaPlayer mp){
		   mp.seekTo(0);
		   mp.start();
	}
	/*
	 * sin,cosなどを使用するときに入れ込む数値は
	 * 3.14を半周とした数値を180で割ったラジアン値
	 * というものを使用しなければなりません。
	 * (1周3.14×2=6.28を360で割った数値)
	 * 角度設定などは度数で出したほうが簡単なので設定は
	 * 度数でして、使用するときにここのメソッドでラジアン値
	 * に変換しています
	 */
	public double toRadian(double deg){return (deg * PIE / 180);}
	/*
	 * 受け取ったxy座標と調べたい短形範囲が重なっているかいないか
	 */
	public boolean RectTap(int x,int y,Rect gazou){
		return gazou.left < x && gazou.top < y && gazou.right > x && gazou.bottom > y;
	}
	/*
	 * この2行で各座標を実装機種の画面比に合わせます
	 */
	public int setSizeX(float disp_w,float zahyou){return (int) (zahyou * (disp_w / XPERIA_W));}
	public int setSizeY(float disp_h,float zahyou){return (int) (zahyou * (disp_h / XPERIA_H));}
}

						

ファイル名「Pow.java」


package and.roid.shooting2;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;

public class Pow extends Object{

	public Pow(){}
	public Pow(float dw,float dh){
		super(dw,dh);
	}
	@Override
	public void ODraw(Canvas c) {
		if(dead == false){
			img.setBounds((int)(cx-imgw/2),(int)(cy-imgh/2),
					(int)(cx+imgw/2),(int)(cy+imgh/2));
			img.draw(c);
			ms.OdrawRect(atarir,c);
		}
	}

	@Override
	public void Oint(Bitmap imgb, float x, float y, float sx, float sy, int w,
			int h, int tj) {
		img = new BitmapDrawable(imgb);
		cx = ms.setSizeX(disp_w, x);
		cy = ms.setSizeY(disp_h, y);
		spx = sx;
		spy = sy;
		imgw = w;
		imgh = h;
		dead = false;
		tamajoutai = tj;
		atarir = new Rect((int)cx-30,(int)cy-30,(int)cx+30,(int)cy+30);
		obsyurui = 4;
	}
	@Override
	public void OMove() {
		++cy;
		atarir = new Rect((int)cx-30,(int)cy-30,(int)cx+30,(int)cy+30);

		if(OsotoX(-imgw/2)==true) dead = true;
		if(OsotoY(-imgh/2)==true) dead = true;
	}
	@Override
	public void OMove(int x, int y) {}
	@Override
	public Rect OgetTapRect() {return null;}
	@Override
	public void Oint(Bitmap imgb, float x, float y, float sx, float sy, int w,
			int h) {}
	@Override
	public void Oint(Bitmap[] imgb, float x, float y, float sx, float sy,
			int w, int h, int tj) {}

}

						

 

 

無事成功しました!

 

だいぶ整理はされたもののまだごちゃついてます;;

なんとかしてメソッド自体は別ファイルに移したいものです。

 

 

<戻る   次へ>

 

 

 

Androidプログラミング日記 (仮) | サイトマップ | 個人情報保護方針 | 応援メールテヘペロ | ©2012 Japan  相互リンク大募集中です