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

シューティング作るぞ!> 第四回 弾の種類

あなたは

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

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

 

 

 

 

弾の種類

今回は弾の種類をふやしてみようと思いました。通常の1発だけでる弾、一度に2発出る弾、一度に360度10発出る弾の3種類ほど作ってみました。ちょっとだけ考えてしまうことが今回作成していてありました。弾自体を持つべきは本当は自機なのではないのかと。なので自機クラス内に弾クラスを作成させたほうがいいのかな?とか思ったのですが、敵機のことや、判定、なによりもややこしくなりそうなので「イヤ、ソンナコトハナイ」と言い聞かせてなっとくしました。

それと弾クラス内で弾の種類を作成したほうがいいのかとかも思いましたが、よく考えたら表示も移動も自機と一緒にやっています。当然敵機も敵機の弾も一気に表示することになるでしょう。そうすると同じ画面のオブジェクトとして弾を扱う方が、いろいろと判定とかが簡単になると思ったのでこういうやりかたにしました。

今回も結構色々と追加変更があります。

この回では、特別新しいコードなどは書いていません。

追加といっても弾の種類を作成させるメソッドくらいが目新しいメソッドです。それと弾の移動方向を変化させたいので、角度による移動の計算式に変更しました。

 

ファイル名「MainLoop.java」


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

import java.util.ArrayList;

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.Drawable;
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 Drawable jikiimg,tamaimg;
	private Bitmap jikibit,tamabit;
	//弾用、連続で重ならないようにの変数
	private boolean tamaflg;
	private int tamatime;
	//弾変化ボタン用
	private Rect tamabtn;


	/*
	 * 今回画面に表示させるオブジェクトは自機と弾です
	 * 全然違うオブジェクトですが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()/2,img.getHeight());
		tamabit = Bitmap.createBitmap(img,img.getWidth()/2,0,img.getWidth()/2,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, 425, 0, 0, jikibit.getWidth(), jikibit.getHeight(),0);

		tamaflg = true;
		tamatime = 5;

		//弾ボタン用座標
		tamabtn = new Rect(50,50,100,100);

	}

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

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

			c.drawColor(Color.BLACK);

			//弾変化ボタン
			p.setColor(Color.BLUE);
			c.drawRect(tamabtn, p);
			p.setTextSize(30);
			c.drawText("tama:"+object.get(0).tamajoutai, 50, 150, p);

			/*
			 * 自機も弾も同じObjectの要素を持っているので
			 * インスタンス(実装)時に作成したいクラスを
			 * 指定しておけば、同じObjectクラスとして使用
			 * することができます。わざわざ各オブジェクトの
			 * メソッドを呼ぶのではなく、共通しているObjectの
			 * メソッドを呼ぶことで解決しています
			 */
			for(int i=0;i<object.size();i++){
				object.get(i).ODraw(c);
				object.get(i).OMove();
				/*
				 * 弾が画面外に出たらオブジェクト要素を消去します
				 */
				if(object.get(i).Ogetdead()==true) object.remove(i);
			}
			/*
			 * 弾を1發打ったら連続で打てないようになり、タイマー
			 * が0になったら打てるようにしています。
			 */
			if(tamaflg == false){
				--tamatime;
				if(tamatime < 0){
					tamatime = 5;
					tamaflg = true;
				}
			}


			holder.unlockCanvasAndPost(c);//お決まり

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

	public boolean onTouchEvent(MotionEvent event){
		int action = event.getAction();
		int x = (int)event.getX();
		int y = (int)event.getY();
		switch(action){
		case MotionEvent.ACTION_DOWN:
			/*
			 * 弾を打っていいよ状態であり自機画像範囲にタップしていれば
			 * Jitama(弾クラス)を作成する
			 */
			if(tamaflg == true && ms.RectTap(
					x, y, object.get(0).OgetTapRect()) == true){
				/*
				 * 今回は弾を出すような操作をすると
				 * どんな弾を作成するかのメソッドを
				 * 呼び出します
				 */
				Tamajoutai();
				tamaflg = false;
			}
			/*
			 * 弾状態を変化させるボタン
			 * 本来ならアイテムなどで弾の飛び方などの
			 * 変化をさせるものでしょうけども、今回は
			 * テストということで青短形タップで変化します
			 */
			if(ms.RectTap(x, y, tamabtn)==true){
				++object.get(0).tamajoutai;
				object.get(0).tamajoutai = (object.get(0).tamajoutai+3)%3;
			}
			break;
		case MotionEvent.ACTION_UP:
			break;
		case MotionEvent.ACTION_MOVE:
			/*
			 * 一応自機の移動もできますが、移動と弾発射は同時にはまだ
			 * できません
			 */
			if(ms.RectTap(x, y, object.get(0).OgetTapRect()) == true) object.get(0).OMove(x, y);

			break;
		}
		return true;
	}

	/*
	 * 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);
		}
		//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);
		}
		/*
		 * 36度づつ自機の周りに10発一気に出ます
		 */
		if(object.get(0).tamajoutai == 2){
			for(int i=0;i<10;i++){
			object.add(new JiTama(disp_w,disp_h));
			object.get(object.size()-1).Oint(
					tamabit, object.get(0).cx, object.get(0).cy,
					30, 30, tamabit.getWidth(), tamabit.getHeight(),i*(360/10));
			}
		}
	}

	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;}//お決まり

}



						

ファイル名「Object.java」




/*
 * ほとんどのメソッドをabstractにしました
 * これによって自機クラス弾クラスと違うオブジェクト
 * によってメソッド名は同じでそれぞれ違う固有の命令
 * を書くことができます
 *
 * このクラスは画面に表示させるオブジェクト
 * を作成させるためのクラスです
 * このクラス単体ではインスタンス(オブジェクト化、実体化)
 * はできず、このクラスをextendsさせて使うようにしています
 * 理由としてはこのゲームを作成していくうちにわかってきますが
 * コード表記がラクチンになります
 */
package and.roid.shooting2;

import android.graphics.Bitmap;
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;

public abstract class Object {
	public Mesod ms = new Mesod();
	public float disp_w,disp_h;
	//オブジェクトの画像
	public Drawable img;
	//オブジェクトの中心座標
	public float cx,cy;
	//向かおうとしている座標
	public float vx,vy;
	//オブジェクトの移動スピード
	public float spx,spy;
	//オブジェクトの大きさ
	public int imgw,imgh;
	//オブジェクトを消去させるための変数
	public boolean dead;
	//弾の状態
	public int tamajoutai;
	//弾画像の角度
	public int tamar;

	public Object(){}
	public Object(float dw,float dh){
		disp_w = dw;
		disp_h = dh;
	}
	/*
	 * このクラスに各オブジェクトに必要なメソッドを書いても
	 * いいのですが、どのオブジェクトにどのメソッドを使用して
	 * いるかなどの管理も大変なので、こういうメソッドを持って
	 * いますよ的なことだけかいておきます(abstract)
	 * 作ろうとしている弾クラスには、タップ座標はいらないので
	 * メソッドのオーバーライドで登録
	 */
	//画像表示
	public abstract void ODraw(Canvas c);
	//初期設定
	public abstract void Oint(Bitmap imgb,float x,float y, float sx,float sy,int w,int h);
	public abstract void Oint(Bitmap imgb,float x,float y, float sx,float sy,int w,int h,int tj);
	public abstract void OMove();
	public abstract void OMove(int x,int y);
	public abstract Rect OgetTapRect();
	/*
	 * オブジェクトによって調べたい範囲が違うので引数で指定して調べるように変更
	 */
	public boolean OsotoX(int ww){return (cx-ww<0 || cx+ww>disp_w);}
	public boolean OsotoY(int hh){return (cy-hh<0 || cy+hh>disp_h);}
	public boolean Ogetdead(){return dead;}//表示していいかどうかを返す
}

						

ファイル名「Jiki.java」



/*
 * 自機クラス
 * Objectクラスをextendsしています
 */
package and.roid.shooting2;

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

public class Jiki extends Object{

	public Jiki(){}
	public Jiki(float dw,float dh){
		super(dw,dh);
	}
	//初期設定
	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;
	}
	public void ODraw(Canvas c){
		/*
		 * 画像を表示させていいかどうかdead状態を調べて表示
		 * cx,cyは中心座標のため、画像の中心にちゃんとcx,cyがくるように調整
		 */
		if(dead == false){
			img.setBounds((int)(cx-imgw/2),(int)(cy-imgh/2),
					(int)(cx+imgw/2),(int)(cy+imgh/2));
			img.draw(c);
		}
	}
	/*
	 * 今回はここは使用していません
	 */
	public void OMove(int x, int y) {
		float cxx = cx;
		float cyy = cy;
		cx = x;
		cy = y;

		if(OsotoX(imgw/2)==true) cx = cxx;
		if(OsotoY(imgh/2)==true) cy = cyy;
	}
	public void OMove() {}

	/*
	 * タップ範囲にオブジェクトがあると移動できるようにしてます。。
	 * しかしタップして動かしてみると、指の動きにオブジェクトが
	 * ついてこれない場合があります。そのためオブジェクトの大きさを
	 * タップ時のみ大きくしてある程度オブジェクトから離れても
	 * ついてこれるようにごまかしています。
	 */
	public Rect OgetTapRect(){
		Rect taprect = new Rect(
				img.getBounds().left-50,img.getBounds().top-50,
				img.getBounds().right+50,img.getBounds().bottom+50);
		return taprect;
		}
	@Override
	public void Oint(Bitmap imgb, float x, float y, float sx, float sy, int w,int h) {}
}


						

ファイル名「JiTama.java」



/*
 * 弾の種類で後方や横など飛び方が異なる
 * 飛び方もさせたいので、飛ぶ方向を角度
 * で設定。それに合わせて画像の角度も変化
 * させるようにしました
 */
package and.roid.shooting2;

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

public class JiTama extends Object{
	/*
	 * 弾と画像の角度
	 */
	public int tamakakudo;

	public JiTama(){}
	public JiTama(float dw,float dh){
		super(dw,dh);
	}
	public void ODraw(Canvas c){
		/*
		 * 画像を回転させるにあたり、rotateのみでやろうとすると
		 * 別の画像も一緒に回転してしまいおかしくなります。
		 * そこでsave()でいったん以前の画像を保存しておき、表示
		 * し終わったらrestore()で開放すると手法で、ちゃんと表示
		 * するようになります
		 *
		 * 画像を表示させていいかどうかdead状態を調べて表示
		 * cx,cyは中心座標のため、画像の中心にちゃんとcx,cyがくるように調整
		 */
		if(dead == false){
			c.save();
			img.setBounds((int)(cx-imgw/2),(int)(cy-imgh/2),
					(int)(cx+imgw/2),(int)(cy+imgh/2));
			c.rotate(tamar, cx, cy);
			img.draw(c);
			c.restore();
		}
	}
	/*
	 * 角度で弾を飛ばすようにしています
	 * 画像角度と移動角度は-90度ほどの誤差があるので
	 * 調整
	 * 例えば角度0度でここもそのままの角度0度にすると
	 * 画像表示は正常だが飛ぶ方向が右方向に・・・
	 */
	public void OMove() {
		cx += (float) Math.cos(ms.toRadian(tamar-90)) * spx;
		cy += (float) Math.sin(ms.toRadian(tamar-90)) * spy;
		/*
		 * 範囲外にでたら弾消してくださいの合図
		 */
		if(OsotoX(-imgw/2)==true) dead = true;
		if(OsotoY(-imgh/2)==true) dead = true;
	}

	public void OMove(int x, int y) {}
	public Rect OgetTapRect() {return null;}
	//初期設定
	@Override
	public void Oint(Bitmap imgb, float x, float y, float sx, float sy, int w,int h, int r) {
		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;
		tamar = r;
	}
	@Override
	public void Oint(Bitmap imgb, float x, float y, float sx, float sy, int w,int h) {}

}


						

ファイル名「Mesod.java」


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

package and.roid.shooting2;

import android.graphics.Rect;

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;


	/*
	 * 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));}
}

						

 

無事成功しました!

 

無事に打ち分けができました。

これの応用で、画像を変えたり、以前作成したちょっとだけポリモーフィズムの応用で誘導弾にしたりもできますね。

今回思ったことは、クラス作成てしっかりしていないとグダグダになるってことですね。もうすでにグダグダなんですが、自分で思っているクラス要素、何を持たせるかなどわけがわからなくなります。今回のことでいえば、弾の種類はどこで作成するかってとこで迷いました。普通に考えると弾の種類だから弾クラスに・・いやいや弾を持ってて操作するのは自機なので自機で・・とか考えて、たどりついたのが「色々と一番簡単に書ける場所に書こう」ということで今回のMainLoopに書く事になりました。

まぁあまだテスト段階ですし・・・あとで直せばいいですよね・・・;;

 

<戻る   次へ>

 

 

 

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