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

ちょっとだけポリモーフィズム

あなたは

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

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

 

 

 

 

レベルアップを図るべく

プログラム効率をレベルアップさせるべく、ポリモーフィズムを勉強してみました。”多様性”と言われるこの仕組みなのですが、仕組みは簡単なんですよね。しかし仕組みとかそういうのはどうでもいいのです。これを勉強してわからないことはみな同じだと思います。そうです、使うと何が便利になるのか、その利便性がわからないのです。わたしですか?ワカルワケアリマセン。なので無理やりプログラムにしてみました。

以前やったバウンディングボックスを改造しています。単純なプログラムで、似たような複数のオブジェクトを使用し、他のアプリに応用が効きやすそうだったからです。

今回は、今回のアプリ仕様を先に書いてみますね。

 

 

大改造バウンディングボックス仕様

敵味方の2種類の四角形オブジェクトがあります。

オブジェクト1=青色系、敵より大きな四角で移動速度も敵より速い。移動方向は初期設定でランダムに設定で変更なし。

オブジェクト2=赤系、敵より小さな四角で移動速度も敵より遅い。移動方向は、一番近くにいる敵にうまいこと向かう。

ファイル名「ShootingActivity.java」

package and.roid.shooting;

import android.app.Activity;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.Display;
import android.view.Window;
import android.view.WindowManager;

public class ShootingActivity extends Activity {
	public float disp_w,disp_h;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFormat(PixelFormat.TRANSLUCENT);
        Window window = getWindow();
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        WindowManager manager = window.getWindowManager();
        Display disp = manager.getDefaultDisplay();
        disp_w = disp.getWidth();
        disp_h = disp.getHeight();
        setContentView(new MainLoop(this));
    }
}

ファイル名「MainLoop.java」

/*
 * 今回は無理やりぽりもーふぃずむぽいことを試してみようと
 * こんなことをやってみました。
 * もっとObjectクラスをextendsしたクラスを作成したり、
 * 大規模なプログラムにすればこのぽりりんの意義も見えてくるはず
 * なのですが、メンドーなので、小さなプログラムで試してみました。
 *
 * 今回は以前やったバウンディングボックスを使用しています。
 * しかし前と違うのは、ぽりもーふぃずむを使用したおかげか
 * 変数なども少なくてすみました。
 * しかしぽりもーふぃずむの真価はこんなことではないと思います。
 *
 * ただ、こういうクラスの運用方法をしたらこうなったよって感じ
 * で今回は見て下さい。
 *
 */
package and.roid.shooting;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
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{
	public static final int GAME_START = 0;//ゲーム状態固定値
	public static final int GAME_PLAY = 1;//ゲーム状態固定値
	public static final int GAME_OVER = 2;//ゲーム状態固定値
	private int game_state;//ゲーム状態決定変数
	private Thread thread;
	private SurfaceHolder holder;
	private float disp_w,disp_h;//画面の幅高さ
	private ShootingActivity shoot;//Activityクラス登録
	private int sleep;//遅延時間

	//変数設定

	//今回設定した変数はこれだけ
	private ArrayList <Object> object = new ArrayList();
	//変数設定ここまで

	public MainLoop(Context context) {
		super(context);
		init(context);
	}
	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());
		shoot = (ShootingActivity)context;
		disp_w = shoot.disp_w;//画面幅取得
		disp_h = shoot.disp_h;//画面高さ取得
		game_state = 1;//ゲーム状態

		//ここから初期化

		this.setSleep(50);//ループ中遅延時間


		//初期化ここまで

	}


	public void run() {
		Canvas c;
		Paint p = new Paint();
		p.setAntiAlias(true);

		while(thread != null){
			c = holder.lockCanvas();

			//ゲーム状態によってスイッチ処理
			switch(game_state){
			case GAME_START:
				StartDraw(c);
				break;
			case GAME_PLAY:
				PlayDraw(c,p);
				break;
			}
			holder.unlockCanvasAndPost(c);

			try {
				Thread.sleep(sleep);
			} 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:
			switch(game_state){
			case GAME_START:
				break;
			case GAME_PLAY:
				break;
			}
			break;
		case MotionEvent.ACTION_UP:
			switch(game_state){
			case GAME_START:
				break;
			case GAME_PLAY:
				/*
				 * タップした場所が画面半分より上なら敵
				 * 下なら味方オブジェクトを作成します。
				 * 敵も味方も同じObjectをextendsして作成されているので
				 * ここの変数で設定したobjectで作成できます
				 */
				if(y < disp_h / 2){
					object.add(new Teki(disp_w,disp_h,x,y));
					object.get(object.size()-1).initObject();
				}else{
					object.add(new Mikata(disp_w,disp_h,x,y));
					object.get(object.size()-1).initObject();
				}
				break;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			switch(game_state){
			case GAME_START:
				break;
			case GAME_PLAY:
				break;
			}
			break;
		}
		return true;
	}

	public boolean RectTap(int x,int y,Drawable[] gazou,int keystate){
		return gazou[keystate].getBounds().left < x && gazou[keystate].getBounds().top < y &&
		gazou[keystate].getBounds().right > x && gazou[keystate].getBounds().bottom > y;
	}

	public void StartDraw(Canvas c){
	}
	public void PlayDraw(Canvas c,Paint p){
		//プレイ画面表示
		p.setColor(Color.WHITE);
		c.drawRect(0, 0, disp_w, disp_h, p);

		/*
		 * 敵も味方も同じObjectをextendsして作成されているので
		 * 敵も味方も同じ変数objectでも呼び出すことができます。
		 * ということで一気に表示させることができました。
		 */
		for(int i=0;i<object.size();i++){
			object.get(i).drawObject(c, p);
			object.get(i).enemy = ChoiceObject(i);
			object.get(i).moveObject(object.get(object.get(i).enemy));
			object.get(i).ChoiceObject(object.get(object.get(i).enemy), c, p);
		}

	}
	/*
	 * どのオブジェクトに向かっていくかを設定するメソッド
	 * メソッドの内容は
	 * 受け取ったオブジェクトをすべてのオブジェクトを参照して
	 * それが敵味方の参照であれば
	 * そのオブジェクトとの距離を図り、前回測った距離より短いなら
	 * そのオブジェクトに向かうように設定
	 *
	 * このメソッドは今回Tekiクラスにのみ必要なメソッドですが
	 * TekiクラスにあるmoveメソッドなどをMikataにコピーすれば
	 * 同じような動きをすることができます。
	 */
	public int ChoiceObject(int i){
		int nline=0,oline=1000,ob=0;
			for(int j=0;j<object.size();j++){
				if(i!=j && object.get(i).tekimikata!=object.get(j).tekimikata){
					nline = (int)Math.abs(Math.sqrt(
							(object.get(i).py-object.get(j).py)*(object.get(i).py-object.get(j).py)+
							(object.get(i).px-object.get(j).px)*(object.get(i).px-object.get(j).px)));
					if(oline > nline) {
						oline = nline;
						ob=j;
					}
				}
		}
		return ob;
	}
	public void setSleep(int s){sleep=s;}
	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」


package and.roid.shooting;

import java.util.Date;
import java.util.Random;

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

/*
 * 表示させるオブジェクトの元になるクラス
 * 抽象クラスとなっており、このクラス自体を
 * オブジェクト化させる事はできない。
 * 他のオブジェクトにextendsさせなくては使えない
 */
public abstract class Object {
	public float disp_w,disp_h;//ディスプレイの大きさ
	public int tekimikata;//敵か味方かどうか
	public float px,py;//オブジェクト中心座標
	public double vx,vy;//オブジェクト進行方向ベクトル
	public int speedx,speedy;//オブジェクトの進む力
	public double angle;//オブジェクト進行角度
	public int w,h;//オブジェクトの大きさ
	public int col;//オブジェクトの色

	public int enemy;//どのオブジェクトに向かっているか

	public Object(){}
	public Object(float dw,float dh,int x,int y){
		//初期設定
		Random r = new Random(new Date().getTime());//ランダム値使用準備
		disp_w = dw;
		disp_h = dh;
		px = x;
		py = y;
		w = 50;//r.nextInt(90)+10;
		h = w;
		angle = r.nextInt(360);
		speedx = 5;
		speedy = speedx;
		vx = (float) Math.cos(toRadian(angle));
		vy = (float) Math.sin(toRadian(angle));

		enemy =0;
	}
	//オブジェクト表示
	public void drawObject(Canvas c,Paint p){
		p.setAntiAlias(true);
		p.setColor(col);
		c.drawRect((int)(px-w/2), (int)(py-h/2), (int)(px+w/2), (int)(py+h/2), p);
	}
	public double toRadian(double ang){return (ang * 3.141592 / 180);}//360度>ラジアン値
	public double toAngle(double ang){return (ang * 180 / 3.141592);}//ラジアン値>360度
	public abstract void initObject();//抽象メソッド
	public abstract void moveObject(Object ob);//抽象メソッド
	public abstract void ChoiceObject(Object ob,Canvas c,Paint p);//抽象メソッド
}

ファイル名「Mikata.java」

package and.roid.shooting;

import java.util.Date;
import java.util.Random;

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

/*
 * Objectクラスを継承させたクラス
 * Objectクラスで設定したabstractメソッドは
 * 自動的に実装される
 * このクラスのメソッドはObjectクラスで設定
 * してあるので、必要がないかぎりabstractメソッド
 * の中身を書くだけにしています
 */
public class Mikata extends Object{

	public Mikata(){}
	public Mikata(float dw, float dh, int x, int y) {
		super(dw, dh, x, y);
	}

	/*
	 * Objectクラスでも初期設定はしているけど
	 * 変更したい初期設定をここで上書きしています
	 * オブジェクトの種類を表すtekimikata
	 * 色は青っぽくなるように調整
	 * 進む力をも変更
	 */
	public void initObject() {
		tekimikata = 0;
		Random r = new Random(new Date().getTime());
		col = Color.rgb(0, 50, r.nextInt(115)+125);
		speedx = 10;
		speedy = speedx;
	}

	public void moveObject(Object ob){
		/*
		 *オブジェクト座標に進行方向ベクトルに進む力を掛け合わせた
		 *ものをプラスして、画面からはみでようとしたら進む力にマイナス
		 *を掛け合わせ、進行方向を反転させています。
		 */
		float oldx = px;
		float oldy = py;
		px += vx * speedx;
		if(px-w/2 < 0 || px+w/2 > disp_w) {
			speedx *= -1;
			px = oldx;
		}
		py += vy * speedy;
		if(py-h/2 < 0 || py+h/2 > disp_h) {
			speedy *= -1;
			py = oldy;
		}
	}
	//このメソッドはこのクラスでは使用しないので空にしています。
	public void ChoiceObject(Object ob,Canvas c,Paint p){}
}

ファイル名「Teki.java」

package and.roid.shooting;

import java.util.Date;
import java.util.Random;

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

/*
 * Mikataクラスとほぼ同じだけど、動きと色、ライン表示
 * などがMikataクラスとはちがいます。
 *
 * Objectクラスを継承させたクラス
 * Objectクラスで設定したabstractメソッドは
 * 自動的に実装される
 * このクラスのメソッドはObjectクラスで設定
 * してあるので、必要がないかぎりabstractメソッド
 * の中身を書くだけにしています
 */
public class Teki extends Object{

	public Teki(float dw, float dh, int x, int y) {
		super(dw, dh, x, y);
	}

	/*
	 * Objectクラスでも初期設定はしているけど
	 * 変更したい初期設定をここで上書きしています
	 * オブジェクトの種類を表すtekimikata
	 * 色は赤っぽくなるように調整
	 * 進む力をも変更
	 * 大きさも変更
	 */
	public void initObject() {
		tekimikata = 1;
		Random r = new Random(new Date().getTime());
		col = Color.rgb(r.nextInt(115)+125, 50, 0);
		speedx = 5;
		speedy = speedx;
		w =10;
		h=w;
	}

	/*
	 * 自分の現在の進行方向の角度から、今から向いたい角度
	 * の差を出すメソッド
	 */
	public double angObject(double oldrad,Object ob){
		double rad = Math.atan2(ob.py-py, ob.px-px);
		double vx1 = Math.cos(rad-oldrad);
		double vy1 = Math.sin(rad-oldrad);
		return Math.atan2(vy1,vx1);
	}
	/*
	 * 向いたい相手が自分より左にいるかどうか
	 */
	public boolean angleLeftObject(double oldrad,Object ob){
		if(angObject(oldrad,ob) < 0) {return true;}else{return false;}
	}
	/*
	 * 向いたい相手が自分の進行角度の90度以内にいるかどうか
	 */
	public boolean angleFrontObject(double oldrad,Object ob){
		if(toAngle(angObject(oldrad,ob)) < 90) {return true;}else{return false;}
	}
	public void moveObject(Object ob) {
		//ここから
		double addang = 0;
		double oldrad = Math.atan2((py+vy*speedy)-py, (px+vx*speedx)-px);
		/*
		 * もし向いたい相手が自分の進行方向の90度以内にいるなら
		 *     そんでもって相手が自分の左にいるならー3度違うなら+3度
		 * 90度以内にいないなら
		 *     そんでもって相手が自分の左にいるならー5度違うなら+5度
		 */
		if(angleFrontObject(oldrad,ob) == true){
			if(angleLeftObject(oldrad,ob) == true){addang = -3;}else{addang = 3;}
		}else{
			if(angleLeftObject(oldrad,ob) == true){addang = -5;}else{addang = 5;}
		}
		/*
		 * 上で出した角度を元の進行方向角度にプラス
		 */
		vx = Math.cos(oldrad+toRadian(addang));
		vy = Math.sin(oldrad+toRadian(addang));
		//ここまでがMikataクラスとちがいます

		float oldx = px;
		float oldy = py;
		px += vx * speedx;
		if(px-w/2 < 0 || px+w/2 > disp_w) {
			speedx *= -1;
			px = oldx;
		}
		py += vy * speedy;
		if(py-h/2 < 0 || py+h/2 > disp_h) {
			speedy *= -1;
			py = oldy;
		}
	}
	//どのオブジェクトに進行しているかラインを引いています
	public void ChoiceObject(Object ob,Canvas c,Paint p){
		p.setColor(Color.CYAN);
		c.drawLine(px, py, ob.px,ob.py, p);
	}

}

 
実行結果
無事に表示できました。

今回のコードを無理やりextendsを使用したりしてみました。はっきりいってこの程度ではポリモーフィズの恩恵はあまりうけられません。しかしこのプログラム自体まだまだ改造の余地があります。

まずはオブジェクト思考をちゃんと使ってコードを書くということです。どういうことかというと、オブジェクト思考は思考だけで終わらせてはだめで、一応こういうふうな設計をしなさい的なものがあります。

カプセル化とか汎用性とか色々ですね。たとえば、色々なクラスで使われているメソッドを別のクラスで作成してどのクラスからでも使用できるようにしてみたり、そのクラスのメンバ変数をprivateで設定して、参照するにはよく使われるgetやsetなどで出し入れしたり、誰でもわかるような設計にしたり。

でもわたしのモットーは、動けば勝ち!です。そんな大規模なものを設計しているわけでもありませんし、わかりにくいコードも説明でおぎないますし・・・;;

要するにあれですね、無理やりポリモーフィズム的な設計思考で作成しなくてもアプリは完成できます。

ただ今回作成して利点もありました。

MainLoopクラスでの、相手との距離を図るメソッドですが、もし2種類のオブジェクトともにそのメソッドを使用する場合で、今まで通り2つのオブジェクトが違うクラスであれば、引数にオブジェクトを渡さなければいけません。どのオブジェクトとどのオブジェクトを比べるのかというコードも付け加える必要があります。オブジェクトが5種類になれば、表示などの部分も5種類になり、同じようなメソッドが5種類並びます。書くのもメンドーです。

今回のような方法であれば何種類であろうと1種類分書けばすみますし、距離を図るメソッドもそのままです。ラクチンですね。

いままでと違い、ラクチンな方法が見つかれば、それがなんであれ考える必要はなく使用すればいいと思います。

問題は、新しい方法を使用すれば、それに伴って新しいコードを書く事になるということです。しかしそれによって・・・

レベルアップスルノデス!!!!

 

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