今回は残念ながらActivityファイル以外すべてのファイルの変更がありました;; 大変ですが掲載したいとおもいます;; |
ファイル名「MainLoop.java」 /* * SurfaceViewをextendsさせたクラス * メインループなどはここにあります * 基本的にSurfaceViewを使用した時には決まった変数の使用 * などがあるので、そういう変数やメソッドの末尾に * ”お決まり”と書いておきます */ package and.roid.shooting2; import java.util.ArrayList; import java.util.Date; 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.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 int tamatime; //弾変化ボタン用 private Rect tamabtn; //音源用3種 private MediaPlayer jitama1s,jitama2s,jitama3s; //敵機画像用 private Drawable tekiimg; private Bitmap tekibit; /* * 今回画面に表示させるオブジェクトは自機と弾です * 全然違うオブジェクトですが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 = 5; //弾ボタン用座標 tamabtn = new Rect(0,0,50,50); //3種類の音源を取り込み使用準備 jitama1s = MediaPlayer.create(context,R.raw.jitama1); jitama2s = MediaPlayer.create(context,R.raw.jitama2); jitama3s = MediaPlayer.create(context,R.raw.jitama3); try{ jitama1s.prepare(); jitama2s.prepare(); jitama3s.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<10;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); } } //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, 50, p); /* * 自機も弾も同じObjectの要素を持っているので * インスタンス(実装)時に作成したいクラスを * 指定しておけば、同じObjectクラスとして使用 * することができます。わざわざ各オブジェクトの * メソッドを呼ぶのではなく、共通しているObjectの * メソッドを呼ぶことで解決しています */ for(int i=0;i<object.size();i++){ object.get(i).ODraw(c); object.get(i).OMove(); //当たり判定 Atarihantei(i); /* * 弾が画面外に出たらオブジェクト要素を消去します */ 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; } /* * メインループで表示移動メソッドが呼び出されているオブジェクト * をナンバー(i)で受け取り、その他と総当りで判定 * 同じオブジェクト以外と同じ種類のオブジェクト以外なら判定 * この場合自機と自機弾は同じ種類としています * Mesodクラスの短形同士の当たり判定がtrueなら比べたオブジェクト * 両方共に消す */ public void Atarihantei(int i){ for(int j=0;j<object.size()-1;j++){ if(i!=j && object.get(i).obsyurui != object.get(j).obsyurui){ if(ms.RectRect(object.get(i).atarir, object.get(j).atarir)==true){ object.get(i).dead=true; object.get(j).dead=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); //音源再生 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;}//お決まり } ファイル名「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 Rect atarir; //オブジェクトの種類 public int obsyurui; 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;}//表示していいかどうかを返す /* * オブジェクトすべてに、オブジェクトごとの当たり判定 * を作成しました。それはゲーム中は見えないもののため * わかりやすくするために可視化しています。 */ public void OdrawRect(Canvas c){ Paint p=new Paint(); p.setColor(Color.RED); //c.drawRect(atarir, p); } } ファイル名「Jiki.java」 /* * 自機クラス * Objectクラスをextendsしています */ package and.roid.shooting2; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; 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; atarir = new Rect((int)cx-30,(int)cy-30,(int)cx+30,(int)cy+30); obsyurui = 0; } 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); OdrawRect(c); } } /* * 今回はここは使用していません */ public void OMove(int x, int y) { float cxx = cx; float cyy = cy; cx = x; cy = y; //当たり判定更新 atarir = new Rect((int)cx-30,(int)cy-30,(int)cx+30,(int)cy+30); 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); OdrawRect(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; //当たり判定更新 atarir = new Rect((int)cx-10,(int)cy-10,(int)cx+10,(int)cy+10); /* * 範囲外にでたら弾消してくださいの合図 */ 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; atarir = new Rect((int)cx-10,(int)cy-10,(int)cx+10,(int)cy+10); obsyurui = 0; } @Override public void Oint(Bitmap imgb, float x, float y, float sx, float sy, int w,int h) {} } 今回の追加ファイル ファイル名「Tekiki.java」 package and.roid.shooting2; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; public class Tekiki extends Object{ public Tekiki(){} public Tekiki(float dw,float dh){ super(dw,dh); } @Override 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); OdrawRect(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 = 1; } @Override public void OMove() {} @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) {} } ファイル名「Mesod.java」 /* * 無理やり作った汎用メソッド群 * なんども使用するようなメソッドをかためて置いて * おきたかったので無理やりつくったクラス */ package and.roid.shooting2; 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; //渡された短形と短形の当たり判定。重なっていれば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));} }
|
無事成功しました! |
画像が2枚あるのは、左は通常で右がオブジェクトの当たり範囲を短形で表示させているものです。ObjectクラスのOdrawRectメソッドのところのDrawRectの「//」を消したら右の画像になります。 まだシューティングゲームをやる上で必要なものが揃っていません。テストやなんやら試したりするのは今のうちですよね。でもちょっと1ファイルのコードが長くなってくるとそろそろしっかりファイル分けとかしたほうがいいのかな・・・とか思っています。 |