オブジェクト指向がまだちゃんと理解できていないのですが

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

オブジェクト指向がまだちゃんと理解できていないのですが

投稿記事 by せんちゃ » 14年前

※注意※

この日記はプログラムもオブジェクト指向も初めて間もない人間が
初心者丸出しのことばかり書いてます。



今はオブジェクト指向でシューティングゲーム製作中です。

しかしクラスや仮想関数、演算子オーバーロードなどの
C++の使い方はわかっていてもそれだけではオブジェクト指向を
理解できたわけではない、という結論に(今更ながら)到達…

オブジェクト指向の理念というか思想というか、
考え方といいますか…

これがよくわからなくて苦戦していましたが、
最近自分なりの答えを見つけてきました。

自分自身で考えた末に出た答えとして、
・クラスだけでプログラムを組もうとは考えないこと
というのが一つの答えでした。

学校の授業でやったことのひとつとして

クラスはあくまでも設計図であり、
これでプログラムを動かすわけではない

ということを学んだので、

このように書いてみました

CODE:

//自機のクラス
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
class PlayerClass{
private:
	double x;		 //x座標
	double y;		 //y座標
	double speed;	 //速度
	double range;	 //当たり範囲
	int g_dir;		 //絵の表示番号
	int   dir;		 //移動方向
	int   cnt;		 //カウンター
	int muteki_time; //無敵時間
	int        flag; //フラグ
	int  play_times; //残機数
	int  Handle[24]; //画像ハンドル
public:
//コンストラクタ
	PlayerClass( void )
	{
		x = 200;
		y = 200;
		g_dir = 0;
		dir   = 0;
		cnt   = 0;
		range = 5;
		muteki_time = 0;
		flag  = 1;
		play_times = 5;
	}
	void Load( void )
	{
		LoadDivGraph( "dat/img/1.png" , 24 , 6 , 4 , 384/6 , 254/4 , Handle ); 
	}
	void Draw( void )
	{
		cnt++;
		DrawGraphF( (float)x-32 , (float)y-32 , Handle[((cnt%24)/8) + g_dir ] , TRUE );
	}

	int Graphic( void )
	{
		if( Key[KEY_INPUT_RIGHT] >= 1 )     { dir = RIGHT; g_dir = 3; }
		else if( Key[KEY_INPUT_DOWN ] >= 1 ){ dir = DOWN;  g_dir = 6; }
		else if( Key[KEY_INPUT_LEFT ] >= 1 ){ dir = LEFT;  g_dir = 0; }
		else if( Key[KEY_INPUT_UP   ] >= 1 ){ dir   = UP;  g_dir = 9; }
		else{ dir = NO_MOVE; g_dir = 3; }
		if( Key[KEY_INPUT_RIGHT] >= 1 && Key[KEY_INPUT_UP   ] >= 1 ) dir = ( RIGHT^  UP );
		if( Key[KEY_INPUT_RIGHT] >= 1 && Key[KEY_INPUT_DOWN ] >= 1 ) dir = ( RIGHT^DOWN );
		if( Key[KEY_INPUT_LEFT ] >= 1 && Key[KEY_INPUT_UP   ] >= 1 ) dir = ( LEFT ^  UP );
		if( Key[KEY_INPUT_LEFT ] >= 1 && Key[KEY_INPUT_DOWN ] >= 1 ) dir = ( LEFT ^DOWN );
		return 0;
	}

	void Moving( void )
	{
		if( dir == 0xAAAA ){ x += 10 - speed; }
		if( dir == 0x55AA ){ x -= 10 - speed; }
		if( dir == 0x0050 ){ y += 10 - speed; }
		if( dir == 0x0005 ){ y -= 10 - speed; }
		if( dir == 0xAAAF ){ x += 10 - speed; y -= 10 - speed; g_dir =  9; }
		if( dir == 0xAAFA ){ x += 10 - speed; y += 10 - speed; g_dir =  6; }
		if( dir == 0x55AF ){ x -= 10 - speed; y -= 10 - speed; g_dir = 15; }
		if( dir == 0x55FA ){ x -= 10 - speed; y += 10 - speed; g_dir = 12; } 

		x > X_MAX ? x = X_MAX : NULL;
		x  Y_MAX ? y = Y_MAX : NULL;
		y = 60 && muteki_time  120 ){
			muteki_time = 0; //タイムは0に
			flag        = 1; //フラグを1にします。
		}
	}


そして次にショットのクラス

CODE:

//ショットクラス
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
class shot_class{
	double     x;
	double     y;
	double     r;
	double  flag;
	double   ang;
	double    sp;
public:
	shot_class( void )
	{
		x	  = 0;
		y     = 0;
		r     = 0;
		flag  = 0;
		ang   = 0;
		sp    = 0;
	}
	void SetData( PlayerClass p , double set_range , double set_speed , double set_angle )
	{
		x   = p.return_data(X);
		y   = p.return_data(Y);
		r   = set_range;
		sp  = set_speed;
		ang = get_angle( set_angle );
	}
	//値を得る関数、外部からメンバ変数に値を入れたい場合はこれを使ってください
	double GetData( VARIABLE GetDataNumber , int get )
	{
		switch( GetDataNumber ){
			case X     : x     = get; break;
			case Y     : y     = get; break;
			case RANGE : r     = get; break;
			case FLAG  : flag  = get; break;
			case ANGLE : ang   = get; break;
			case SPEED : sp    = get; break;
		}
		return 0;
	}
	//値を返す関数、外部から値を受け取ったり、値を使った判定を行いたい場合はここで
	double return_data( VARIABLE return_number )
	{
		switch( return_number ){
			case X			: return      x; break;
			case Y			: return      y; break;
			case RANGE		: return      r; break;
			case FLAG		: return   flag; break;
			case ANGLE      : return    ang; break;
			case SPEED		: return     sp; break;
		}
		return 0;
	}
	void Draw( void )
	{
		DrawCircle( x , y , r , GetColor(0,255,0) , TRUE );
	}
	void Move( void )
	{
		x += cos( ang ) * sp;
		y += sin( ang ) * sp;
	}
	int CheckWindowOut( void )
	{
		//画面外へ出たら
		if( x >= FIELD_MAX_X || x = FIELD_MAX_Y || y = 1 && Key[KEY_INPUT_Z] % 5 == 0 ){
					sc[i].GetData( FLAG , 1 );
				}
			}
			if( sc[i].return_data( FLAG ) == 1 ){
				sc[i].SetData( pl    , 10 , 25 , 0 );
				sc[i].GetData( FLAG  ,  2          );
				break;
			}
		}
		for( int i = 0; i < SHOT_MAX ; i++ ){
			if( sc[i].return_data( FLAG ) == 2 ){
				sc[i].Draw();
				sc[i].Move();
			}
			if( sc[i].CheckWindowOut() == 1 ){
				sc[i].GetData( FLAG , 0 );
			}
		}
		//-------------------------------------------------//
	}


それぞれのクラスを使う中で難しいのはプライベートメンバの中身をどうやって持ってくるか、(フラグとか)

そして外部からprivateの変数をいじるにはどうすればいいか
を考えた結果、

値を渡す関数と値を返す関数を作ってみることにしました。

CODE:

//値渡し、返しの列挙型
enum VARIABLE{
	X = 0 ,
	Y = 1 ,
	SPEED = 2 ,
	RANGE = 3 ,
	G_DIR = 4 ,
	DIR   = 5 ,
	CNT   = 6 ,
	MUTEKI_TIME = 7 ,
	FLAG        = 8 ,
	PLAY_TIMES  = 9 ,
	E_PATTERN     = 10 ,
	ANGLE         = 11 ,
	HITFLAG       = 12 
};



	//値を返す関数
	double return_data( VARIABLE return_number ){
		switch( return_number ){
			case X			: return		   x; break;
			case Y			: return		   y; break;
			case SPEED		: return	   speed; break;
			case RANGE		: return	   range; break;
			case G_DIR		: return	   g_dir; break;
			case DIR		: return		 dir; break;
			case CNT		: return         cnt; break;
			case MUTEKI_TIME: return muteki_time; break;
			case FLAG		: return        flag; break;
			case PLAY_TIMES : return  play_times; break;
		}
		return 0;
	}
	double get_Data( VARIABLE GetDataNumber , int get ){
		switch( GetDataNumber ){
			case X			: return x			 = get;	break;
			case Y			: return y			 = get;	break;
			case SPEED		: return speed		 = get;	break;
			case RANGE		: return range		 = get;	break;
			case G_DIR		: return g_dir		 = get;	break;
			case DIR		: return dir		 = get;	break;
			case CNT		: return cnt		 = get;	break;
			case MUTEKI_TIME: return muteki_time = get; break;
			case FLAG		: return flag		 = get; break;
			case PLAY_TIMES : return play_times  = get; break;
		}
		return 0;
	}



列挙型で作った番号を引数として渡し、
その番号に合わせて値を返すプログラムです。

これを使っていってメンバ変数の値を取り出したり、
操作したりします。


しかしこれもオブジェクト指向の考え方的な
プログラムといえるのだろうか、個人的にかなり疑問ではあります^^;


当たり判定を行うときとか、
敵のクラスと自機のクラスで分けると
それぞれのデータはprivateに入ってしまうので

どちらからもアクセスすることができない、
でも当たり判定のクラスを作りそこでオブジェクトの中身を操作
したい!

どうすればいいんだぁ!

というときにオブジェクトから値を返す関数を作り、
それを参照で渡せば当たり判定もできるぞヤッター!


という感じでオブジェクト指向でゲーム制作

でもオブジェクトやクラスの設計の仕方が果たしてこんな感じで
いいのだろうかと疑問に思いつつ。

ISLe
記事: 2650
登録日時: 15年前

Re: オブジェクト指向がまだちゃんと理解できていないのですが

投稿記事 by ISLe » 14年前

ゲッタ/セッタで検索してみてください。
getAngle/setAngleといったメソッド(メンバ関数)を作れば良いと思います。
漏れで「なぜ0しか返らない?」といったバグを未然に防げます。
setAngle関数を作れば2πに丸めてメンバ変数にセットとかできます。
get_Data関数だと例外処理がごちゃごちゃしてしまいます。

speed←→sp
range←→r
と同じ用途のメンバ変数の名前が違うのはなぜでしょう。

CODE:

class CharBase {
    double x;
    double x;
    double speed;
    double range;
    virtual getX();
    virtual setX(double x);
    // 続く
};
class Player : public CharBase {
    // 追加
};
class Shot : public CharBase {
    // 追加
};
というふうにすると統一感が出ると思います。

ISLe
記事: 2650
登録日時: 15年前

Re: オブジェクト指向がまだちゃんと理解できていないのですが

投稿記事 by ISLe » 14年前

連投すみません。
オブジェクト指向は関連するオブジェクトをまとめるのも大切ですが、それ以上に関係の無いオブジェクトを絡めないのが大切です。

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: オブジェクト指向がまだちゃんと理解できていないのですが

投稿記事 by せんちゃ » 14年前

ISLeさん

コメント、アドバイスありがとうございます
m(_ _)m

>speed←→sp
>range←→r
>と同じ用途のメンバ変数の名前が違うのはなぜでしょう

これは完全にボクの注意力の無さが起こしたミスです^^;
いままではrange,speedと書いていたけど長いから
もっと短くしようと思いr,spと書いていたのですが、

今までのクセでついついrange,speedと
書いてしまっていたようです。

>getAngle/setAngleといったメソッド(メンバ関数)を作れば良いと思います。

なるほど…
ひとつひとつメンバを得る関数を作ったほうが効率は良いということですね!

これからはゲット、セットという概念を考えながら
プログラミングしてみようと思います。

アバター
SAI
記事: 115
登録日時: 14年前

Re: オブジェクト指向がまだちゃんと理解できていないのですが

投稿記事 by SAI » 14年前

オブジェクト指向って言葉、参考書でよく見たんですが私は未だに???って感じです。
それで結局投げて適当にやった結果がああなったと。
ついでにカプセル化も投げました。ヒドイですね。
やっちゃいけないとわかりつつ、全部publicにしちゃったり。
get/setなんてメンドイことやってられるかー
なんて昔は思ってました。
今まで何事もないのが奇跡です。
うん、次は勉強しなおそう。


あと、弾の画面外判定で

CODE:

if( x >= FIELD_MAX_X || x = FIELD_MAX_Y || y <= 0 )
とされていますが、これはフィールドの一番左上の座標が(0,0)だった場合、弾が画面上に見えるところで消えてしまいます。特に大きい弾だとそれが顕著にわかっちゃいます。(細かいことですが。)
フィールドの左上が(20,20)とかだったら何言ってんだコイツと思って聞き流してください。(^_^;)

アバター
DVDM
記事: 38
登録日時: 15年前

Re: オブジェクト指向がまだちゃんと理解できていないのですが

投稿記事 by DVDM » 14年前

>>せんちゃさん
オブジェクト指向は結構難しいですよね・・・奥も深いです。
自分も、オブジェクト指向のつもりで組んでいてもいつの間にか面倒のあまり機能を盛り込みすぎたりして
残念な設計になっている事が多いです。
最近は少しでも機能を分けるように取り組むようにしています。

「一つのクラスに一つの機能を実装する」というのを目安にしてみるといいかもしれません。
例えば弾を発射するクラスで発射、得点を加算する、描画をする、などを担当してしまうとそれは一機能ではなくなってしまいます。
なので、発射クラスと得点クラス、描画クラスに分けられれば(恐らく)いいのだと思います。

この時の設計もまた難しいと思ってますが、そこが楽しいと言えば楽しいです。
とりあえずがっつり一つのクラスに入れてしまって、
後から分けれそうなところを分けるだけでも一つのクラスがスッキリするのではないでしょうか。

パーツ分けをしていくと後から使いやすく、流用できるクラスになっているかもしれません。
後々リファクタリングもやりやすいと思います。
私もまだまだ勉強中の身ですがお互い頑張りましょう><