変数のカプセル化を守りながらのデータの受け渡しについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
SDD

変数のカプセル化を守りながらのデータの受け渡しについて

#1

投稿記事 by SDD » 11年前

毎度お世話になっております、SDDという者です。
今まではC++に関する質問をさせていただいていたのですが、やはり、基礎のCがまだまだできていないと感じたため、
以前私の立てたトピックでご指摘をいただいた通り、まず、C言語でのオブジェクト指向「的」プログラミングから入ろうと思います。


まず、環境は 
Windows7
VC++ 2008EE、DXライブラリ使用
です。

C言語はまだまだ初心者です。

明確に苦手なのは、変数の隠蔽をしながら(グローバル変数を多用しない・あるいは使用しない)
処理を書くのが苦手、というかわかっていない感じがしたため、前述の通り、
C++の前に、Cでオブジェクト指向的な練習をしていこうと思っています。

質問なのですが、上記のように隠蔽しながらのデータの受け渡しのことです。
かなり苦手意識の強い問題なので、こちらでのご協力を得ながら克服したいです。

龍神録の11章を参考にしながら質問させていただきます。
例えば、龍神録11章から出てくるvoid load_storyという関数で、enemy_order[n]という構造体を使うのですが、
龍神録では、このenemy_orderという構造体はグローバル変数として用意されています。
そのため、以下のようにファイルの違うenemy.cppの関数でもアクセスできるようになっています。

コード:

//敵情報を登録
void enemy_enter(){//敵の行動を登録・制御する関数
        int i,j,t;
        for(t=0;t<ENEMY_ORDER_MAX;t++){
                if(enemy_order[t].cnt==stage_count){//現在の瞬間がオーダーの瞬間なら
                        if((i=enemy_num_search())!=-1){
                                enemy[i].flag   =1;//フラグ
                                enemy[i].cnt    =0;//カウンタ
                                enemy[i].pattern=enemy_order[t].pattern;//移動パターン
                                enemy[i].muki   =1;//向き
                                enemy[i].knd    =enemy_order[t].knd;//敵の種類
                                enemy[i].x      =enemy_order[t].x;//座標
                                enemy[i].y      =enemy_order[t].y;
                                enemy[i].sp     =enemy_order[t].sp;//スピード
                                enemy[i].bltime =enemy_order[t].bltime;//弾の発射時間
                                enemy[i].blknd  =enemy_order[t].blknd;//弾幕の種類
                                enemy[i].blknd2 =enemy_order[t].blknd2;//弾の種類
                                enemy[i].col    =enemy_order[t].col;//色
                                enemy[i].wait   =enemy_order[t].wait;//停滞時間
                                enemy[i].hp     =enemy_order[t].hp;//体力
                                enemy[i].hp_max =enemy[i].hp;//体力最大値
                                enemy[i].vx     =0;//水平成分の速度
                                enemy[i].vy     =0;//鉛直成分の速度
                                enemy[i].ang    =0;//角度
                                for(j=0;j<6;j++)
                                        enemy[i].item_n[j]=enemy_order[t].item_n[j];//落とすアイテム
                        }
                }
このenemy_order構造体をグローバル変数として使うのをやめたい(スタティックな変数にしたい。クラスでいうところのプライベートな変数)
のですが、そうした時、どのようにファイルの違う関数であるenemy_enterに渡すのが得策でしょうか。
ポインタとか引数や戻り値を使うとか、ぼんやりとした考えは浮かんでくるのですが、結局なかなかいい手が浮かびません・・・。

質問の丸投げと言われても否定できないのですが、どうしても取っ掛かりがほしいです。
ヒントやアドバイスをいただきたく、トピックを立てました。
ご協力いただけたら幸いです。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#2

投稿記事 by Rittai_3D » 11年前

enemy_orderのポインタを引数にとるようにして、enemy_orderを隠してしまえば良いのでは?
しばらく龍神録プログラミングの館もコードも見てないのですが、

なんちゃら.h

コード:

// インクルードガードは面倒なので書いてないですが、必ず書いてください
// この下の構造体の内容は外部には公開しない。
// こういう構造体はあるけど、メンバ変数は外から見えないよ、ってことです
struct enemy_t;
struct enemy_order_t;

void enemy_enter( struct enemy_order_t* pOrder, struct enemy_t* pEnemy );
なんちゃら.cpp

コード:

// cpp/cの中で構造体の中身を書く
typedef struct enemy_t
{
// 略
} enemy_t;

typedef struct enemy_order_t
{ 
// ほにゃらら
} enemy_order_t;

// ここのファイルの中では構造体のメンバ変数が見えている

/*!
* 敵の情報をcsvから読み取って格納する
* @param pOrder : 読み取る情報のポインタ
* @param pEnemy : 設定する敵のポインタ
*/
void enemy_enter( struct enemy_order_t* pOrder, struct enemy_t* pEnemy )
{
// 略
}
こんな感じで、構造体の内容を隠してしまい、外部には構造体があることだけを教える。そうするとカプセル化になっているように思います。
自分も初心者なので間違えた事を言っていたらすいません。自分はこうやって変数を隠しています。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#3

投稿記事 by SDD » 11年前

3D_3Dさん、毎度ご協力ありがとうございます。
しばらく参考として読ませていただいていたのですが、やはり相当苦手みたいでよくわからない
のが現状みたいです。

とりあえず今回は、例にあげた「loadstory()」という関数は、敵のためのロード関数ということなので、敵関係のファイルに
その関数をつかってやることにして、enemy_enter()と同じファイルにしました。そうすることで、enemy_orderという構造体をそのファイルだけの変数にし、
一応隠蔽を守りました。

この場はこれでいいと思うのですが、必ず今回質問したことの理解がすぐに必要になるので、そのときまた改めて例を出して質問させていただきたいと思います。
それまで、もう少し理解を進めたいと思います。

そのため、ご迷惑をおかけしますが、未解決とします。毎度すみません。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#4

投稿記事 by Rittai_3D » 11年前

具体的にどの辺りがわからないのでしょうか?

構造体のメンバ変数を公開しないで、こういう構造体があるよ、というものがわからないのでしょうか?それなら、構造体 前方宣言 で調べて見て下さい。

それとも引数のポインタですか?それでしたら、引数に渡された構造体の中身をムリヤリ書き換える、と考えると良いと思います。自分はそのように理解していますがあっているかどうかはわかりません。すいません。

あとは、共通化できる構造体は共通化してしまうとか、色々方法はあると思います。
オフトピック
勘違いしていたら申し訳ないのでofftopicで…
http://dixq.net/rp/0.htmlにある通り、グローバル変数をたくさん使って設計されていますので、最初から自分で設計してみる方が良いかもしれません。いや、それを今やってるよ!とおっしゃるのでしたらすいません。
また、龍神録プログラミングの館の一番下にC++verの龍神録もあります(オブジェクト指向で設計されています)ので、ダウンロードされてみてはいかがでしょうか?これも、既にダウンロード済みでしたらすいません。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#5

投稿記事 by SDD » 11年前

3D_3Dさん、ご迷惑おかけします。
前方宣言は知ってはいたのですが、まだ使ったことがないので慣れない感じです。
そこももっと調べてみます。

また、関数の引数に構造体変数のアドレスを与えると関数内で直接いじれるというのもセオリーとしては分かるのですが、
実際にやるということになると難しいようです。


具体的にわからないところは、なんというか言葉にして言うのが難しいのですが、私に読解力がなくて、教えていただいたことの流れ(?)というか
全体像をつかめなくて・・・。なんちゃらcpp(ここでは、enemy.cpp)内でここでいうload_story()を行って、それをenemy_enterに与えてる・・・というぼんやり
した感じになってしまうのです。あまり手取り足取り伺ってダイレクトな答えを教えていただくのも気が咎めるため、うまくつたえることができないのですが・・・。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#6

投稿記事 by SDD » 11年前

補足ですが、只今龍神録をベースにしながらオブジェクト指向的なプログラミングでシューティングゲームを作ろうとしています。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#7

投稿記事 by Rittai_3D » 11年前

SDD さんが書きました: 前方宣言は知ってはいたのですが、まだ使ったことがないので慣れない感じです。
そこももっと調べてみます。

また、関数の引数に構造体変数のアドレスを与えると関数内で直接いじれるというのもセオリーとしては分かるのですが、
実際にやるということになると難しいようです。
わたしは習うより慣れよ、と言いたいです。
わたしも前方宣言は最初は理解出来ませんでした。

そんなに難しいのでしょうか?
わたしは逆で理解出来ないけど使い方はわかってるという状況でした。
SDD さんが書きました:具体的にわからないところは、なんというか言葉にして言うのが難しいのですが、私に読解力がなくて、教えていただいたことの流れ(?)というか
全体像をつかめなくて・・・。なんちゃらcpp(ここでは、enemy.cpp)内でここでいうload_story()を行って、それをenemy_enterに与えてる・・・というぼんやり
した感じになってしまうのです。あまり手取り足取り伺ってダイレクトな答えを教えていただくのも気が咎めるため、うまくつたえることができないのですが・・・。
個人の感想としては、ダイレクトに伝えて下さった方が説明し易いですw

プログラムの流れは引数のpEnemyにpOrderのデータを代入しているだけです。
構造体のメンバ変数を隠したからポインタを渡す、という簡単なことです。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#8

投稿記事 by SDD » 11年前

習うより慣れろというのは本当にその通りだと思います。
ただ、一つ思ったのですが、構造体の前方宣言の使用は必須なのでしょうか・・・。
なるべく初歩的なやり方から慣らしていきたくて、悩んでいます。

現在の状態なのですが、前述の通り、今回の質問の例でだした、「load_story()」という関数は敵のロード関数のため、enemy_enter()と同じファイルに持ってくることで
カプセル化を守れた(と思っています。)ので、とりあえずのところまでは大丈夫なのですが、今後敵の弾幕の制御や当たり判定のことを考えると、違うファイル間での
データの受け渡しは必ずすることになると思うので、解決ボタンを押していないという感じです。(質問に直接使った問題はとりあえず解決したが、根本の問題が残っている)


一応貼ります。テスト段階なのでかなりきたないですが、

コード:

//enemy.h
#ifndef __ENEMY_H__
#define __ENEMY_K__

//初期化、ロード
void Enemy_Init();
//自機の計算
void Enemy_Updata();
//描画
void Enemy_Draw();
//終了処理
void Enemy_Final();


#endif

コード:



static enemy_order_t enemy_order[ENEMY_ORDER_MAX];
static enemy_t enemy[ENEMY_MAX];
static int m_img;


//敵の出現情報をエクセルから読み込んで格納する関数
void load_enemy_order(){
	int n,num,i,fp;
	char fname[]={"story1.csv"};
	int input[64];
	char inputc[64];

	fp=FileRead_open(fname);//ファイル読み込み

	if(fp==NULL){
		printfDx("csvを読み込めてません\n");
		return;
	}
	for(i=0;i<2;i++){//最初の二行読み飛ばし
		while(FileRead_getc(fp)!='\n');
	}

	n=0,num=0;
	while(1){
		for(i=0;i<64;i++){
			inputc[i]=input[i]=FileRead_getc(fp);//1文字取得する
			if(inputc[i]=='/'){//スラッシュがあれば
				while(FileRead_getc(fp)!='\n');//改行までループ
				i=-1;//カウンタを最初に戻して
				continue;
			}
			if(input[i]==',' || input[i]=='\n'){//カンマか改行なら
				inputc[i]='\0';//そこまでを文字列とし
				break;
			}
			if(input[i]==EOF){//ファイルの終わりなら
				goto EXFILE;//終了
			}
		}
		switch(num){
						case 0: enemy_order[n].cnt      =atoi(inputc);break;
						case 1: enemy_order[n].pattern  =atoi(inputc);break;
						case 2: enemy_order[n].knd      =atoi(inputc);break;
						case 3: enemy_order[n].x        =atof(inputc);break;
						case 4: enemy_order[n].y        =atof(inputc);break;
						case 5: enemy_order[n].sp       =atof(inputc);break;
						case 6: enemy_order[n].bltime   =atoi(inputc);break;
						case 7: enemy_order[n].blknd    =atoi(inputc);break;
						case 8: enemy_order[n].col      =atoi(inputc);break;
						case 9: enemy_order[n].hp       =atoi(inputc);break;
						case 10:enemy_order[n].blknd2   =atoi(inputc);break;
						case 11:enemy_order[n].wait     =atoi(inputc);break;
						case 12:enemy_order[n].item_n[0]=atoi(inputc);break;
						case 13:enemy_order[n].item_n[1]=atoi(inputc);break;
						case 14:enemy_order[n].item_n[2]=atoi(inputc);break;
						case 15:enemy_order[n].item_n[3]=atoi(inputc);break;
						case 16:enemy_order[n].item_n[4]=atoi(inputc);break;
						case 17:enemy_order[n].item_n[5]=atoi(inputc);break;
		}
		num++;
		if(num==18){
			num=0;
			n++;
		}
	}
EXFILE:
	FileRead_close(fp);
}



//空いている敵番号を検索
int enemy_num_search(){
	for(int i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==0){
			return i;//空いている番号を返す
		}
	}
	return -1;
}



//敵の移動パターン0での移動制御
void enemy_pattern0(int i){
    if(enemy[i].cnt<60){
        enemy[i].y+=2.0;
    }
    if(enemy[i].cnt>60+240){
        enemy[i].y-=2.0;
    }
}



//敵情報を登録
void enemy_enter(){//敵の行動を登録・制御する関数
	 int i,j,t;
	// if(boss.flag!=0)return;
	 for(t=0;t<ENEMY_ORDER_MAX;t++){
		  if(enemy_order[t].cnt==stage_cnt){//現在の瞬間がオーダーの瞬間なら
			   if((i=enemy_num_search())!=-1){
					enemy[i].flag   =1;//フラグ
					enemy[i].cnt    =0;//カウンタ
					enemy[i].pattern=enemy_order[t].pattern;//移動パターン
					enemy[i].back_col=GetRand(4);
					enemy[i].knd    =enemy_order[t].knd;//敵の種類
					enemy[i].x      =enemy_order[t].x;//座標
					enemy[i].y      =enemy_order[t].y;
					enemy[i].sp     =enemy_order[t].sp;//スピード
					enemy[i].bltime =enemy_order[t].bltime;//弾の発射時間
					enemy[i].blknd  =enemy_order[t].blknd;//弾幕の種類
					enemy[i].blknd2 =enemy_order[t].blknd2;//弾の種類
					enemy[i].col    =enemy_order[t].col;//色
					enemy[i].wait   =enemy_order[t].wait;//停滞時間
					enemy[i].hp     =enemy_order[t].hp;//体力
					enemy[i].hp_max =enemy[i].hp;//体力最大値
					enemy[i].vx     =0;//水平成分の速度
					enemy[i].vy     =0;//鉛直成分の速度
					enemy[i].ang    =0;//角度
					for(j=0;j<6;j++)
						 enemy[i].item_n[j]=enemy_order[t].item_n[j];//落とすアイテム
			   }
		  }
	 }
}


//敵の行動制御
void enemy_act(){
	int i;
	for(i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==1){//その敵のフラグがオンになってたら
			if(0<=enemy[i].pattern && enemy[i].pattern<ENEMY_PATTERN_MAX){
				enemy_pattern0(i);
				enemy[i].x+=cos(enemy[i].ang)*enemy[i].sp;
				enemy[i].y+=sin(enemy[i].ang)*enemy[i].sp;
				enemy[i].x+=enemy[i].vx;
				enemy[i].y+=enemy[i].vy;
				enemy[i].cnt++;
				enemy[i].img=(enemy[i].cnt%30)/11;
				//敵が画面から外れたら消す
				if(enemy[i].x<-50 || F_M_X+20<enemy[i].x || enemy[i].y<-50 || F_M_Y+20<enemy[i].y)
					enemy[i].flag=0;
			}
			else
				printfDx("enemy[i].patternの%d値が不正です。",enemy[i].pattern);

		}
	}
}



void Enemy_Init(){
	memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX);
	memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);
	m_img=LoadGraph("キャラクター/雑魚1.png");
	load_enemy_order();
}





void Enemy_Updata(){
	enemy_enter();
	enemy_act();
}


void Enemy_Draw(){
	for(int i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==1){
			DrawGraph(enemy[i].x+F_X,enemy[i].y+F_Y,m_img,TRUE);
		}
	}
}



void Enemy_Final(){
}

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#9

投稿記事 by Rittai_3D » 11年前

とりあえず、気になるところがありますので、書かせていただきます。

インクルードガードが無意味である
インクルードガードに二連アンダースコアは使用しない方が良い(http://www.wdic.org/w/TECH/予約済み識別子)
オブジェクト指向で設計したいのであれば、敵の移動とデータ読み込みとショットパターンは分離すべきです。この辺りは練習しないとわけがわからないと思います。

わたしもオブジェクト指向を完璧に理解しているわけではないですが、一度プログラムから離れて、ゲームのスクリーンショットからオブジェクトを抜き出す練習をしてみるとよろしいかと。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#10

投稿記事 by SDD » 11年前

本当でした。よくこういううち損じをしてしまいます。これでは意味がありませんね・・・汗
また、アンダースコアからはじまるのや、二連アンダースコアはいけないのですね。
以前参考にしていた書籍が二連アンダースコアを使用していたので、普通に使っていました。訂正します。

ショットパターンはまだ未実装で、それは敵そのものと分離するつもりです。(そのため、データの受け渡しが必要かなと考えておりました)
ただ、敵の移動計算と敵のパターン読み込みを分離すべきというのが、正直あまりわかりません・・・。
どちらも同じ「敵」に関することならと思い、一つのファイルでまとめ、Init関数とUpdata関数に分けたのですが、分けることで管理がしやすくなるのでしょうか。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#11

投稿記事 by Rittai_3D » 11年前

"敵の移動"と"敵のショット"は"敵"ではないですよね。"敵"のオプションみたいなものですよね。だから、分離すべきです。

敵に関するものは敵管理部を作り、そこで全て管理すべきです。
わたしなら、Init関数もUpdate関数も各オブジェクトごとに持ち、上に書いた管理部で呼び出す設計にします。

管理をしやすくするならオブジェクトごとにInit関数やUpdate関数を持った方が、どのオブジェクトの初期化や更新処理をしているのかが一発でわかるので、わたしはそのようにしています。
オフトピック
わたしの考えるオブジェクト指向は、オブジェクトごとに管理することによりバグの発見がしやすくなる設計と勝手に解釈しているので、あまり参考にならないと思います...
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#12

投稿記事 by SDD » 11年前

なるほど。
ただ、そうなると「敵」から移動やデータ読み込みやショットを取ったら、「敵」とはなんなんだ?って感じになってしまいました。
「敵」は何を持っているべきなのでしょうか。

C++をとりあえずやめてCからやりなおそうと思った理由としては、やはり「データの受け渡し」が苦手でオブジェクト間でやり取りすることが
わからなかったということと、具体的に「管理部」はどのように「敵」と関わらせれば良かったのかわからなかった(管理部と「敵」とのデータの受け渡し?)
からなので、やはりここで克服すべきですね。

少し考えてみることにします。ただやはり、今やれるようになりたいことは、カプセル化を守ったままでの外部とのデータの受け渡しなので
そちらのほうをとりあえず重点的にもう少し考えていこうと思います。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#13

投稿記事 by Rittai_3D » 11年前

わたしはそこまで「"敵"は何を持つべきか」というようなことは考えていなかったので、少し返答に困りますが…

もし、敵が雑魚だとしたら、移動とショットの命令を出す、とでも言えばいいのでしょうか。
わたし的には、雑魚の管理部は、雑魚の画像ハンドルを持ていて、移動やショットを任意のタイミングで呼び出すだけの物だと考えています。言葉にしにくいです。

#ここでの雑魚は、雑魚全体ではなく、雑魚一体の話です。
オフトピック
# 追記 ショットも移動も敵の画像ハンドルはいりませんよね。管理部(というか雑魚1体)が持てば良い情報で、オプション部分は必要無いですよね。ショットも移動も、呼び出されたら、呼び出し元の敵だけを移動させますよね。
敵の移動は、なにも考えず敵の座標を変える
敵のショットは、なにも考えず書かれた通りのショットを出す

なにも考えず、という部分が大切で、上に書いたことは管理部で命令を出すのです。そして、その命令が来た時だけショットを出したり移動させます。(わたしの語彙力がないため伝わりにくいと思います…すいません)

擬似コードだと

コード:

# ZakoMove
# 敵の移動
func MovePat000
{
// 移動
}

# ZakoShot
# 雑魚のショット
func Shot000
{
// ショット
}

# Zako
# 雑魚
func Zako
{
//雑魚一体の管理部
    if( 移動させたい ) MovePat000
    if( ショットを打たせたい ) Shot000
}
こんな感じでしょうか。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#14

投稿記事 by SDD » 11年前

自分なりに考えてみて、正常に作動するようにもできたのですが、いまいち冴えない気がします。
一応、今回の質問の肝である外部へのデータの受け渡しはできるようになったのですが・・・。

コード:

#ifndef DEF_ENEMYMGR_H
#define DEF_ENEMYMGR_H


//enemymgr_h

void EnemyMgr_Init();

void EnemyMgr_Updata();

void EnemyMgr_Draw();

void EnemyMgr_Final();


#endif

コード:

//enemymgr.cpp


static enemy_order_t enemy_order[ENEMY_ORDER_MAX];//敵のパターン格納
static enemy_t enemy[ENEMY_MAX];//敵の実態
static int m_img;//敵の画像ハンドル




//敵の出現情報をエクセルから読み込んで格納する関数
void load_enemy_order(){
	int n,num,i,fp;
	char fname[]={"story1.csv"};
	int input[64];
	char inputc[64];

	fp = FileRead_open(fname);//ファイル読み込み
	if(fp == NULL){
		printfDx("read error\n");
		return;
	}
	for(i=0;i<2;i++)//最初の2行読み飛ばす
		while(FileRead_getc(fp)!='\n');

	n=0 , num=0;
	while(1){
		for(i=0;i<64;i++){
			inputc[i]=input[i]=FileRead_getc(fp);//1文字取得する
			if(inputc[i]=='/'){//スラッシュがあれば
				while(FileRead_getc(fp)!='\n');//改行までループ
				i=-1;//カウンタを最初に戻して
				continue;
			}
			if(input[i]==',' || input[i]=='\n'){//カンマか改行なら
				inputc[i]='\0';//そこまでを文字列とし
				break;
			}
			if(input[i]==EOF){//ファイルの終わりなら
				goto EXFILE;//終了
			}
		}
		switch(num){
						case 0: enemy_order[n].cnt      =atoi(inputc);break;
						case 1: enemy_order[n].pattern  =atoi(inputc);break;
						case 2: enemy_order[n].knd      =atoi(inputc);break;
						case 3: enemy_order[n].x        =atof(inputc);break;
						case 4: enemy_order[n].y        =atof(inputc);break;
						case 5: enemy_order[n].sp       =atof(inputc);break;
						case 6: enemy_order[n].bltime   =atoi(inputc);break;
						case 7: enemy_order[n].blknd    =atoi(inputc);break;
						case 8: enemy_order[n].col      =atoi(inputc);break;
						case 9: enemy_order[n].hp       =atoi(inputc);break;
						case 10:enemy_order[n].blknd2   =atoi(inputc);break;
						case 11:enemy_order[n].wait     =atoi(inputc);break;
						case 12:enemy_order[n].item_n[0]=atoi(inputc);break;
						case 13:enemy_order[n].item_n[1]=atoi(inputc);break;
						case 14:enemy_order[n].item_n[2]=atoi(inputc);break;
						case 15:enemy_order[n].item_n[3]=atoi(inputc);break;
						case 16:enemy_order[n].item_n[4]=atoi(inputc);break;
						case 17:enemy_order[n].item_n[5]=atoi(inputc);break;
		}
		num++;
		if(num==18){
			num=0;
			n++;
		}
	}
EXFILE:
	FileRead_close(fp);
}



//空いている敵番号を検索
int enemy_num_search(){
	for(int i=0;i<ENEMY_MAX;i++){//フラグのたって無いenemyを探す
		if(enemy[i].flag==0){
			return i;//使用可能番号を返す
		}
	}
	return -1;//全部埋まっていたらエラーを返す
}


//敵情報を登録
void enemy_enter(){//敵の行動を登録・制御する関数
	int i,j,t;
	for(t=0;t<ENEMY_ORDER_MAX;t++){
		if(enemy_order[t].cnt==stage_cnt){//現在の瞬間がオーダーの瞬間なら
			if((i=enemy_num_search())!=-1){
				enemy[i].flag   =1;//フラグ
				enemy[i].cnt    =0;//カウンタ
				enemy[i].pattern=enemy_order[t].pattern;//移動パターン
				enemy[i].back_col=GetRand(4);
				enemy[i].knd    =enemy_order[t].knd;//敵の種類
				enemy[i].x      =enemy_order[t].x;//座標
				enemy[i].y      =enemy_order[t].y;
				enemy[i].sp     =enemy_order[t].sp;//スピード
				enemy[i].bltime =enemy_order[t].bltime;//弾の発射時間
				enemy[i].blknd  =enemy_order[t].blknd;//弾幕の種類
				enemy[i].blknd2 =enemy_order[t].blknd2;//弾の種類
				enemy[i].col    =enemy_order[t].col;//色
				enemy[i].wait   =enemy_order[t].wait;//停滞時間
				enemy[i].hp     =enemy_order[t].hp;//体力
				enemy[i].hp_max =enemy[i].hp;//体力最大値
				enemy[i].vx     =0;//水平成分の速度
				enemy[i].vy     =0;//鉛直成分の速度
				enemy[i].ang    =0;//角度
				for(j=0;j<6;j++)
					enemy[i].item_n[j]=enemy_order[t].item_n[j];//落とすアイテム
			}
		}
	}
}





void EnemyMgr_Init(){
	memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX);
	memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);
	m_img=LoadGraph("キャラクター/雑魚1.png");
	load_enemy_order();
	for(int i=0;i<ENEMY_MAX;i++){
		Enemy_Init(&enemy[i],m_img);
	}
}


void EnemyMgr_Updata(){
	enemy_enter();
	for(int i=0;i<ENEMY_MAX;i++){
		Enemy_Updata(&enemy[i]);
	}
}


void EnemyMgr_Draw(){
	for(int i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==1){
			Enemy_Draw(&enemy[i]);
		}
	}
}



void EnemyMgr_Final(){
}

コード:

#ifndef DEF_ENEMY_H
#define DEF_ENEMY_H


//enemy.h


//初期化、ロード
void Enemy_Init(enemy_t *enemy,int img);
//自機の計算
void Enemy_Updata(enemy_t *enemy);
//描画
void Enemy_Draw(enemy_t *enemy);
//終了処理
void Enemy_Final();


#endif

コード:

//enemy.cpp


//敵の行動制御
void enemy_act(enemy_t *enemy){
	if(enemy->flag==1){//その敵のフラグがオンになってたら
		if(0<=enemy->pattern && enemy->pattern<ENEMY_PATTERN_MAX){


			//とりあえずの移動制御パターン
			int t=enemy->cnt;
			if(t==0){
				enemy->vx=3;
				enemy->vy=2;//下がってくる
			}
			if(t==60){
				enemy->vy=0;//止まる
				enemy->vx=0;
			}
			if(t==60+enemy->wait)//登録された時間だけ停滞して
				enemy->vy=-2;//上がっていく



			enemy->x+=cos(enemy->ang)*enemy->sp;
			enemy->y+=sin(enemy->ang)*enemy->sp;
			enemy->x+=enemy->vx;
			enemy->y+=enemy->vy;
			enemy->cnt++;
			//敵が画面から外れたら消す
			if(enemy->x<-50 || F_M_X+20<enemy->x || enemy->y<-50 || F_M_Y+20<enemy->y)
				enemy->flag=0;
		}
		else
			printfDx("enemy[i].patternの%d値が不正です。",enemy->pattern);

	}
}




void Enemy_Init(enemy_t *enemy,int img){
	enemy->img_enemy[0]=img;
}


void Enemy_Updata(enemy_t *enemy){
	enemy_act(enemy);
}


void Enemy_Draw(enemy_t *enemy){
	DrawGraph(enemy->x+F_X,enemy->y+F_Y,enemy->img_enemy[0],TRUE);
}


void Enemy_Final(){
}
この程度の分離になりました。
一応、enemy.cppが敵全体のことをしっている必要がないようにしたつもりなのですが・・・。
もっとかしこい方法があるとは思うのですが、やはり初歩的な感じから慣らしていきたいのですが、
それでもいまいちな感じがあります。ご指摘などをどなたかいただけないでしょうか。

アバター
ookami
記事: 214
登録日時: 14年前
住所: 東京都

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#15

投稿記事 by ookami » 11年前

横からすいません。細かいツッコミではありますが一応...
オブジェクト指向における「隠蔽」とはデータや振る舞い等を外部からアクセスできないようにすることです。

例えば、

コード:

class myClass {
private:
	int hoge;
public:
	int getHoge(void) {
		return hoge;
	}
};
というクラスにおいて、「int hoge」変数は外部からは「隠蔽」されています。
これに対し、「enemy_order[n].cnt」のように「cnt」メンバ変数に直接アクセスできるのは「隠蔽」ではありません。
上記でされているのは、ファイルを分離して読みやすくなった、あるいは他のcppファイルからもインクルードできるようになった、ということかと。

ただ、全体としては「隠蔽」が目的ではなくて、ざっくり「正しい設計」的なものを模索していると思いますので、ついでの理解と思っていただければと。
(本題の設計へのツッコミができなくてすいません)

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#16

投稿記事 by Rittai_3D » 11年前

大分よくなっていると思います。

ただ、ちょっと気になったのが、マネージャ部が構造体のメンバ変数を知る必要が本当にあるのでしょうか?マネージャ部は呼び出すだけの設計の方がわたしは綺麗だと思います。

あと、構造体が見当たらないのですが、struct.hに書きっぱなしですか?必要のない情報は隠すべきです。カプセル化とは、そういうことです。
オフトピック
わたしなら、オーダー構造体(enemy_order_t)のポインタを返すような関数を用意します。
初期値(読み取った値)を引数に取り、その関数内で値を設定し、設定した構造体を返すようにしますが、自分のやり方もどうなんでしょう。
読み込み部も独立しておく方がいい気がします。ここは、C++版龍神録が参考になるかと思います。
※わたしも初心者なので間違えたことを言っているかもしれません。

以前わたしが質問した構造体の前方宣言が敵の移動について書いてあるはずですので見てみると良いかもしれません。
ただ、本当に酷いコードなので参考にしないでください。
初心者です

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#17

投稿記事 by usao » 11年前

オフトピック
本題と全然関係ないですが, update ではないでしょうか.

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#18

投稿記事 by SDD » 11年前

usaoさん、毎度ご協力ありがとうございます。
本当でした。訂正しておきます。ご指摘ありがとうございます。


3D_3Dさん、何度もすみません。
直すところが多そうなので、とりあえず今直せるところから直していこうと思います・・・汗

構造体の定義はその構造体を使うファイルに置く・・・ということでしょうか。
また、C++龍神録での読み込み部分はみたことがあるのですが、自分は、敵に関してのロード処理は敵のマネージャがすること
ではないかと考えてしまうのですが、あまりよくないのでしょうか。
3D_3Dさんの「構造体の前方宣言」のトピックも拝読しようと思います。


ookamiさん、毎度ご協力ありがとうございます。
すみません。どうにも隠蔽を少し誤解しているのかもしれません。
自分が今思っているのは、enemymgr.cpp全体を、仮に、enemymgrクラスと見立てたとして、スタティックなenemy_order構造体は
enemymgr.cpp以外ではアクセスできないようにしている(はず)のですが、それでも隠蔽できていないのでしょうか・・・。
仰るとおりで、正直なところ、完璧に隠蔽したいというよりかは少しずつ学びながら進めていきたい、すこしずつ設計を整えたいというのが本音です・・・。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#19

投稿記事 by Rittai_3D » 11年前

直すくらいなら1から設計した方が楽な気もしますが…。
龍神録プログラミングの館はここにある文を引用しますと、
効率化、カプセル化、または、汎用性・拡張性などは意識せず、とにかく難しい構文を使わずに実装することを目指しています。

よってほとんどの変数をグローバル変数として利用しており、本館の設計は大きなコードの設計をする上で適切な手法ではありません。
とあります。館の設計に囚われずに設計してみる方が良いかもしれません。
SDD さんが書きました:3D_3Dさん、何度もすみません。
直すところが多そうなので、とりあえず今直せるところから直していこうと思います・・・汗

構造体の定義はその構造体を使うファイルに置く・・・ということでしょうか。
また、C++龍神録での読み込み部分はみたことがあるのですが、自分は、敵に関してのロード処理は敵のマネージャがすること
ではないかと考えてしまうのですが、あまりよくないのでしょうか。
3D_3Dさんの「構造体の前方宣言」のトピックも拝読しようと思います。
構造体はヘッダファイルに実装は書かずに、前方宣言だけします。
ヘッダファイル

コード:

#pragma once
struct Hoge_t;
int getNumber( struct Hoge_t* pHoge );
実装ファイル(わたしはcppファイルを実装ファイルと呼びますが、一般的ではないと思いますので参考にしないでください)

コード:

#include "Hoge.h"

struct Hoge_t
{
   int val;
};

int getNumber( struct Hoge_t* pHoge )
{
   return pHoge->val;
}
#初期化処理は書いておりません。突っ込まないで下さい。
このようにして隠します。ヘッダをインクルードしても直接valの値は見れません。
getNumber() を呼び出さないと使えないようにします。

画像につきましては、わたし個人としてはあまり好みませんが、人によってはそれがいいという場合もあるのでなんとも言えません。
SDD さんが書きました:すみません。どうにも隠蔽を少し誤解しているのかもしれません。
自分が今思っているのは、enemymgr.cpp全体を、仮に、enemymgrクラスと見立てたとして、スタティックなenemy_order構造体は
enemymgr.cpp以外ではアクセスできないようにしている(はず)のですが、それでも隠蔽できていないのでしょうか・・・。
仰るとおりで、正直なところ、完璧に隠蔽したいというよりかは少しずつ学びながら進めていきたい、すこしずつ設計を整えたいというのが本音です・・・。
ookamiさんではないですが…
enemy_order構造体はヘッダに書いているんですよね?そうでしたら全てに公開するようになっています。ここで、変数を隠すために使うのが前方宣言です。まずは、構造体を隠すことからはじめましょう。
初心者です

アバター
ookami
記事: 214
登録日時: 14年前
住所: 東京都

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#20

投稿記事 by ookami » 11年前

SDDさんが「隠蔽した」と仰ってたのは「別cppから見たenemy_order(構造体)変数」のことだったのですね。
てっきり「enemy_order(構造体)変数以外から見たcntなどのメンバ」かと思ってました... (オブジェクト指向と言われたのでつい構造体の「中身」に目が行ってしまった)

前者は確かに、隠蔽に似た効果があると思います。後者は、3D_3Dさんも仰っていますが、「公開」されています。
(というか、ここまで寄せているならもうC++で書いた方がよい気がしますが...?)

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#21

投稿記事 by ISLe() » 11年前

先に、配列に対して各要素を回している部分と、各要素に対して更新かけている部分、を分離すれば良いのではないでしょうかね。

例えば各要素(以後「個」と称する)ごとにポインタを引数に取り、個を処理する関数に分けます。
一要素ごとへのポインタであれば、配列ではない変数でも、vectorやlistに格納された要素でも、同様に使えます。
各要素を回す「管理部」の実装に影響を受けなくなります。

で、enemy_order構造体/変数は、「個」の初期化でしか使いません。
ならば自ずと、なのではないでしょうか。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#22

投稿記事 by Rittai_3D » 11年前

疑問に思いましたが、テスト段階でcsvから読み込む必要はあるのでしょうか?
csv読み込み部はとりあえず放っておいて、まずはしっかりと設計をした方が良いのではないでは?
まずは一体だけで試してみて、段々と数を増やし、そして読み込み部を実装すればよろしくない設計にはならないと思いますが、どうでしょう?なにかcsvから読み込まなくてはいけないというような感じがしたので書かせて頂きました。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#23

投稿記事 by SDD » 11年前

すみません、返信が遅れてしまいました。

3D_3Dさん
お恥ずかしいのですが、まだまだ初心者なため、1から作り直すには難しく、今はまだ龍神録をベースに学んでいくしかないというのが現状です・・・。
館の設計を無視して自分の力のみで設計する力はまだ無いのだと思います・・・。

外部から構造体変数にアクセスできないということだけでは隠蔽は完璧ではなく、ヘッダに書いてある以上
構造体の中身はアクセスなどはできずとも隠蔽はされていないということですね。「公開」はされてしまっているんですね。
「画像につきましては、わたし個人としてはあまり好みませんが、人によってはそれがいいという場合もあるのでなんとも言えません。」
というのは、「敵の読み込み部は独立させるかどうか」のことでしょうか。私自身、今のところやはり敵管理部が持ってるほうがいいような気がします・・。
プログラムは、個人の性格が出るのがまた難しいのですね。


ookamiさん
外部からアクセスすることはできなくても、「公開」はされているということですね。
3D_3Dさんが仰ったように、構造体を本当に隠蔽するためには、ヘッダではなく実装ファイルに書くべきということですね。
たしかに、もう半分C++みたいなものなのかもしれませんが、僕がC++で躓いた理由は、隠蔽の問題だけでなく、オブジェクトという考え方がまだすこし難しかったということもあって、
やはりプログラミングそのものに慣れが足りていないと思います・・・。
ふと単純に興味本位で思ったのですが、龍神録のように、グローバルなやり方はあまり良くなく、かと言って隠蔽を強めるとC++的なゲームプログラミングになるなら、
純粋にC言語的なゲームってどういうコードになるんだ?とか思ってしまいました。C言語が手続き型というのはよく聞きますが・・・。


ISLeさん
すみません・・・。しばらく考えていたのですが、やはり、まだポインタなどに対する理解が足りていないようで、難しく感じてしまいました・・・。
配列に対して各要素を回している部分というのは、load_enemy_order関数で、各要素に更新をかけている部分というのは、enemy_enter関数のことでしょうか。
enemyの一体一体のポインタを引数に取り、for文で回して複数の処理をするのではなく、一体ごとに操作する関数を作ればよいということでしょうか。
読解力がなくてすみません。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#24

投稿記事 by Rittai_3D » 11年前

SDD さんが書きました: 3D_3Dさん
お恥ずかしいのですが、まだまだ初心者なため、1から作り直すには難しく、今はまだ龍神録をベースに学んでいくしかないというのが現状です・・・。
館の設計を無視して自分の力のみで設計する力はまだ無いのだと思います・・・。

外部から構造体変数にアクセスできないということだけでは隠蔽は完璧ではなく、ヘッダに書いてある以上
構造体の中身はアクセスなどはできずとも隠蔽はされていないということですね。「公開」はされてしまっているんですね。
「画像につきましては、わたし個人としてはあまり好みませんが、人によってはそれがいいという場合もあるのでなんとも言えません。」
というのは、「敵の読み込み部は独立させるかどうか」のことでしょうか。私自身、今のところやはり敵管理部が持ってるほうがいいような気がします・・。
プログラムは、個人の性格が出るのがまた難しいのですね。
わたしの言う画像話は、「初期化関数の引数に画像ハンドルを渡し、敵の画像を設定する」のではなく、「enemy構造体の中に画像ハンドル用の整数型変数を用意し、初期化関数内で読み込み、敵の画像を設定する」ということです。人のよっては前者のが良い、後者のが良いと言うので、どちらかが正しいということはないと思います。

前にも書きましたが、龍神録プログラミングの館は、難しい話は無しにして、とりあえず分かりやすく、簡単に、をメインに置いて作られています。なので、適切な設計ではないのです。
新・ゲームプログラミングの館を参考にしながら設計してみるとよいでしょう。
http://dixq.net/g/d_01.htmlここに設計のお話があるので参考にして見てはどうでしょう。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#25

投稿記事 by SDD » 11年前

3D_3Dさん
そういうことでしたか。
ハンドル周りについては、とりあえず今のところそういう風にしているという感じです。

新・ゲームプログラミングの館も参考にさせていただいております。
そのため、大まかな骨格などはそのようにするつもりなのですが、内部の処理を
龍神録的なやり方+なるべく隠蔽するという感じでやっています。

只今、struct.hに構造体定義を書くのをやめて、前方宣言を試している途中です。
少し気になったのですが、このまま前方宣言で進めたら、訂正しない限り、3D_3Dさんからリンクを貼っていただいた、
「構造体の前方宣言」のトピックの、ソフト屋さんが仰っていた「Enemy.cppしか知らないはずの構造体のメンバ情報にEnemy_MovePat.cppがアクセスしているのでエラーになります。」
ということと、僕のenemy.cppのenemy_act(enemy_t *enemy)関数はまったく同じ状況になってしまうということですよね?今まではstruct.hをどのファイルでもインクルードしていたから正常に
動いていたと思うので・・・。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#26

投稿記事 by Rittai_3D » 11年前

SDD さんが書きました:只今、struct.hに構造体定義を書くのをやめて、前方宣言を試している途中です。
少し気になったのですが、このまま前方宣言で進めたら、訂正しない限り、3D_3Dさんからリンクを貼っていただいた、
「構造体の前方宣言」のトピックの、ソフト屋さんが仰っていた「Enemy.cppしか知らないはずの構造体のメンバ情報にEnemy_MovePat.cppがアクセスしているのでエラーになります。」
ということと、僕のenemy.cppのenemy_act(enemy_t *enemy)関数はまったく同じ状況になってしまうということですよね?今まではstruct.hをどのファイルでもインクルードしていたから正常に
動いていたと思うので・・・。
そういうことです。だから、動的確保する必要があります。
たとえば、こんな感じになると思います。
ヘッダ

コード:

#pragma once
struct Hoge_t;

struct Hoge_t* createHogeInstance();
void deleteHogeInstance( struct Hoge_t* p );

void initHoge( struct Hoge_t* p );
void printHoge( struct Hoge_t* p );
実装ファイル

コード:

#include <stdio.h>
#include "hoge.h"
struct Hoge_t{ int val; char name[10]; };

struct Hoge_t* createHogeInstance()
{
    struct Hoge_t* p = nullptr;
    p = ( Hoge_t* )calloc( 1, sizeof( struct Hoge_t ) );//エラーチェックしてません。
    // すいません、pだと落ちます。
    return p;
}
void deleteHogeInstance( struct Hoge_t* p )
{
    free( p );
}

void initHoge( struct Hoge_t* p )
{
    p->val = 40;
    strcpy( p->name, "Foo" ); //ここ間違えてるかも…
}

void printHoge( struct Hoge_t* p )
{
    printf( "val = %d¥nname = %s¥n", p->val, p->name );
}
呼び出す

コード:

#include "hoge.h"

int main( void )
{
    struct Hoge_t* pHoge; // 外部に公開されていないからここから直接はいじれない。関数を呼ぶ必要がある。
    pHoge = createHogeInstance();

    initHoge( pHoge );
    printHoge( pHoge );

    deleteHogeInstance( pHoge );
    return 0;
}
スマホから頑張って打ちました。コンパイルが通るかどうか知りませんが、形としてはこんな感じにわたしならします。
最後に編集したユーザー Rittai_3D on 2014年8月27日(水) 22:10 [ 編集 3 回目 ]
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#27

投稿記事 by SDD » 11年前

3D_3Dさん
なんの義理もないのに、親身にしていただいて、本当に感謝しています。本当に助かっています。
わざわざスマホで打っていただいたコード、やっていることは理解できたのですが、どうして、今回の、『「構造体の前方宣言」のトピックの、ソフト屋さんが仰っていた「Enemy.cppしか知らないはずの構造体のメンバ情報にEnemy_MovePat.cppがアクセスしているのでエラーになります。」ということと、僕のenemy.cppのenemy_act(enemy_t *enemy)関数はまったく同じ状況になってしまうということですよね?今まではstruct.hをどのファイルでもインクルードしていたから正常に動いていたと思うので・・・。」』ということで、動的確保が必要になるのでしょうか・・・。飲み込みが悪くて申し訳ないです。


また、いろんな方から色々教えていただき、参考になるのですが、少し、混乱してきたため、的を絞ろうと思います。
初心者らしい、少し醜いコードになるかもしれませんが、教わったことをある程度絞って、身の丈にあったコードから始めようと思います。
まず、参考書やこのトピックで教わったこととで、にらめっこしながら考えてみます。とりあえず敵周りのコードを訂正して、再び投稿できるレベルにしたいと思います。

アバター
ookami
記事: 214
登録日時: 14年前
住所: 東京都

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#28

投稿記事 by ookami » 11年前

SDDさん、
あ、収束ぽいですかね^^;
一応聞かれたところ書いておきます。

>龍神録のように、グローバルなやり方はあまり良くなく、かと言って隠蔽を強めるとC++的なゲームプログラミングになるなら、
>純粋にC言語的なゲームってどういうコードになるんだ?とか思ってしまいました。C言語が手続き型というのはよく聞きますが・・・。

仰る通り「グローバルなやり方」を取らないといけないことが多かったですね。
その分、コード規約を徹底するなど、人力に頼る部分が多かったように思います。
今SDDさんがされているように、「関数は 『名詞_動詞』 という命名にしてポインタで渡そう」とか。
「どういうコード」というか、今思えば「動けばいいコード」になりがちな時代だったというか...
ちなみにC++も手続型です。

- - -

あともう1点、
上で「C++でいいのでは...」と書いた補足です。

「あくまでC言語」なら私も3D_3Dさんのような書き方になると思いますが、
例えばSDDさんのソースの、

コード:

// --------------------------------------- enemy.h
struct enemy_t {
	int x;
	int y;
	int img_enemy[IMGMAX];
};

コード:

// --------------------------------------- enemy.cpp
(略)
void Enemy_Draw(enemy_t *enemy){
    DrawGraph(enemy->x+F_X,enemy->y+F_Y,enemy->img_enemy[0],TRUE);
}
というのを、

コード:

// --------------------------------------- enemy.h
class enemy_t {
private:
	int x;
	int y;
	int img_enemy[IMGMAX];
public:
	void draw();
};

コード:

// --------------------------------------- enemy.cpp
(略)
void enemy_t::draw(){
    DrawGraph(x+F_X,y+F_Y,img_enemy[0],TRUE);
}
こうすると、C++のオブジェクト指向らしくなります。(呼び出し側も書き換えないといけませんが...)
文法の違いだけで( _ を :: に置き換える程度で )オブジェクト指向になるということは、
すでに考え方はオブジェクト指向に近づいてると思ったので、C++でいいんじゃないかなと思った次第です。

「①CかC++か」「②オブジェクト指向かどうか」「③設計がすっきりしない」という問題がごっちゃになってしまっているような気がするので、
「①CかC++か」「②オブジェクト指向かどうか」があまり関係ないことを確認する効果もあるかなと思いました。
ご参考まで。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#29

投稿記事 by Rittai_3D » 11年前

SDD さんが書きました:3D_3Dさん
なんの義理もないのに、親身にしていただいて、本当に感謝しています。本当に助かっています。
わざわざスマホで打っていただいたコード、やっていることは理解できたのですが、どうして、今回の、『「構造体の前方宣言」のトピックの、ソフト屋さんが仰っていた「Enemy.cppしか知らないはずの構造体のメンバ情報にEnemy_MovePat.cppがアクセスしているのでエラーになります。」ということと、僕のenemy.cppのenemy_act(enemy_t *enemy)関数はまったく同じ状況になってしまうということですよね?今まではstruct.hをどのファイルでもインクルードしていたから正常に動いていたと思うので・・・。」』ということで、動的確保が必要になるのでしょうか・・・。飲み込みが悪くて申し訳ないです。
あのトピックでは今回の様な敵一体ごとの制御ではなく、龍神録的な設計なのでよろしくないです。
余計に混乱させてしまったようです。ごめんなさい。

今回は、オブジェクト指向を目指しているので、あのトピックの趣旨とは異なります。
あちらは、敵の移動をさせたいが、何故か変数にアクセス出来ない、どうしよう、という内容です。

あちらのトピックでは、(一番最後のわたしの投稿したコードで話します)敵のインスタンス(実体)はEnemy構造体の中身が書いてあるところに書いてあります。あの様な書き方はオブジェクト指向を意識したものではなく、とりあえず動けばいいや、動かし方がわかればいいや、という考えで書きました。

今回は、敵一体一体を意識した(で通じるかな?)設計なのです。つまり、Enemy構造体の中身が書いてある場所ではインスタンスは作りません。つまり、管理部でインスタンスを作る必要があります。しかし、管理部は構造体の中身が見えていない…。では、どうするか。上の、わたしが必死にスマホで打ったコードでいうcreateHogeInstance()を呼び出します。前方宣言してある構造体はポインタならインスタンスを作れる(若干違う気がしますが、説明のためこのように書きます)ので、createHogeInstance()でメモリを動的確保します。不完全な型なのでこうするしか方法が無いのです。
► スポイラーを表示
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#30

投稿記事 by SDD » 11年前

ookamiさん
すみません、まだ収束とはいえないです・・・。まだトピックを解決にすることはできません(皆さんにはご迷惑をおかけします。)
わざわざ教えていただき、ありがとうございます。
純粋なC的なゲームプログラミングのことは前々から疑問に思っていたのですが、そのような感じだったのですか。
だからprivate一つで隠蔽できるC++は比較的画期的なのですね。C++をやめてからクラスの価値がわかってきた・・・。

本当ですね。C++もほんの少しかじったので、クラスも作ったことがあるのですが、そうやって改めて見てみると、ほとんどクラスと紙一重に・・・。
確かに、境界がぼやけてきてしまっていますが、ほぼC++であっても、やはりCを学んでからのC++という順序に従おうと思います。


3D_3Dさん
offtopicも読ませていただきました。そうでしたか。そこまで考えていただき、少し感動してしまいました。
少し時間の問題であせっていて、ある程度は「動けばいい」という結論になってしまいがちなのですが、
その中でもできる限り、完成度を高めたいと思います。

本題なのですが、つまり、今教えていただいているのは、本当にC++的な、敵一体をオブジェクトとしていじくるような感じということ・・・なのでしょうか。
何を教わっているのかすらあまりわかっていなかったようです・・・。

つまり、今回の僕のコードで言うなら、隠蔽を守るため、敵の構造体の定義(中身)はenemy.cppに書き、enemymgrに敵を管理させる。
しかし、隠蔽のため、enemymgr.cppはそのenemy構造体の定義を知らないため、enemymgr.cppでは直接実体(enemy構造体変数)をつくることができない。
そのために教えていただいた方法が必要になるという解釈で正しいでしょうか。おかげさまで少しずつですが理解ができてきてる気がします。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#31

投稿記事 by Rittai_3D » 11年前

SDD さんが書きました:本題なのですが、つまり、今教えていただいているのは、本当にC++的な、敵一体をオブジェクトとしていじくるような感じということ・・・なのでしょうか。
何を教わっているのかすらあまりわかっていなかったようです・・・。

つまり、今回の僕のコードで言うなら、隠蔽を守るため、敵の構造体の定義(中身)はenemy.cppに書き、enemymgrに敵を管理させる。
しかし、隠蔽のため、enemymgr.cppはそのenemy構造体の定義を知らないため、enemymgr.cppでは直接実体(enemy構造体変数)をつくることができない。
そのために教えていただいた方法が必要になるという解釈で正しいでしょうか。おかげさまで少しずつですが理解ができてきてる気がします。
そういうことです。オブジェクト指向はオブジェクトごとに分割してバグの発見を簡単にする、コードの可読性を上げるものだとわたしは考えます。

隠蔽の部分は、そうです!その通りです。
そこが分かれば、コードをその通りに書いて行くだけです。
また、csv読み込み部はまだ作る必要がないと思いますので、無しの方向でまずはやってみましょう。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#32

投稿記事 by SDD » 11年前

3D_3Dさん
解釈が正しかったようで安心しました。
おかげさまです。とりあえず、テスト的なプロジェクトを作り、敵周りを新しく作り直そうと思います。
完成したら、あるいは詰まってしまったときにコードを載せようと思います。

ただ、仮に今回のことができたとしても、今後やらなくてはならない、とりあえず無しにする複数の敵のCSVのロードとその管理、弾幕の制御、ボスの制御、当たり判定、エフェクトなどのことを
考えると隠蔽を守りきる自信がないです・・・。結局はやはり龍神録的(C言語的?)な感じに落ち着いてしまうような気がして怖いですが、とりあえず今は目の前のことをします。

コードを載せるのにすこし時間がかかるかもしれませんが、もしお暇でしたら、そのときもご協力いただけたら幸いです。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#33

投稿記事 by ISLe() » 11年前

とりあえず構造化だけを進めるのではダメなんでしょうかね。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#34

投稿記事 by ISLe() » 11年前

「管理部」と「個」を分けるというのは、例えばenemy_enter関数なら…

コード:

//敵情報を登録
void enemy_enter(){//敵の行動を登録・制御する関数
    int i,j,t;
    for(t=0;t<ENEMY_ORDER_MAX;t++){
        if(enemy_order[t].cnt==stage_cnt){//現在の瞬間がオーダーの瞬間なら
            if((i=enemy_num_search())!=-1){

                // ここから
                enemy[i].flag   =1;//フラグ
                enemy[i].cnt    =0;//カウンタ
                enemy[i].pattern=enemy_order[t].pattern;//移動パターン
                enemy[i].back_col=GetRand(4);
                enemy[i].knd    =enemy_order[t].knd;//敵の種類
                enemy[i].x      =enemy_order[t].x;//座標
                enemy[i].y      =enemy_order[t].y;
                enemy[i].sp     =enemy_order[t].sp;//スピード
                enemy[i].bltime =enemy_order[t].bltime;//弾の発射時間
                enemy[i].blknd  =enemy_order[t].blknd;//弾幕の種類
                enemy[i].blknd2 =enemy_order[t].blknd2;//弾の種類
                enemy[i].col    =enemy_order[t].col;//色
                enemy[i].wait   =enemy_order[t].wait;//停滞時間
                enemy[i].hp     =enemy_order[t].hp;//体力
                enemy[i].hp_max =enemy[i].hp;//体力最大値
                enemy[i].vx     =0;//水平成分の速度
                enemy[i].vy     =0;//鉛直成分の速度
                enemy[i].ang    =0;//角度
                for(j=0;j<6;j++)
                    enemy[i].item_n[j]=enemy_order[t].item_n[j];//落とすアイテム
                // ここまでを関数化する

            }
        }
    }
}
ということです。

enemy_orderを「個」でしか使っていないというのは勘違いでした。
「管理部」と「個」の両方に公開する必要がありますね。

構造体定義も宣言もインクルードしなければふつうにはアクセスできないわけなのでそのレベルで工夫するのも良いと思いますが。

あとenemy_num_searchのような配列であることを前提としている部分を汎用化すると良いですね。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#35

投稿記事 by ISLe() » 11年前

先ほど思い出したのですが、諸悪の根源たるC言語版龍神録のヘッダファイルはすべて書き直したのでしょうか。
あれは記述方法としては一片たりとも残してはいけないレベルのひどいものです。
元のプロジェクトからヘッダファイルを適切に書き直すだけでもいろいろと学べるものがあるのではないでしょうかね。

これまでSDDさんが提示されたコードを見る限り「個」と「全体(管理部)」の概念がごっちゃになっている印象です。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#36

投稿記事 by Rittai_3D » 11年前

SDD さんが書きました: ただ、仮に今回のことができたとしても、今後やらなくてはならない、とりあえず無しにする複数の敵のCSVのロードとその管理、弾幕の制御、ボスの制御、当たり判定、エフェクトなどのことを
考えると隠蔽を守りきる自信がないです・・・。結局はやはり龍神録的(C言語的?)な感じに落ち着いてしまうような気がして怖いですが、とりあえず今は目の前のことをします。

コードを載せるのにすこし時間がかかるかもしれませんが、もしお暇でしたら、そのときもご協力いただけたら幸いです。
設計をしていくうちに改善されていきます。
また、最初からうまく行くわけがないので、まずは自分なりに設計し、指導して貰う。これが一番だとわたしは思います。一度ミスをすれば同じミスはしなくなるので、どんどんミスをしましょう。

C言語でもオブジェクト指向設計はできます。龍神録の設計は、何度も言いますが、簡単に、をメインに置いています。つまり、設計としては不適切なのです。
これだけは言っておきたいので一言。龍神録の設計とC言語での設計は全く違います。
► スポイラーを表示
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#37

投稿記事 by SDD » 11年前

ISLe()さん

構造化ということについて、構造化プログラミングというのを調べてみたのですが、いまいちぱっとしません。goto文などを使わないということは分かったのですが・・・。
switchもだめなのでしょうか。オブジェクト指向とはまた別なんでしょうか。

enemy_enter()で言うなら、

コード:

  int i,j,t;
    for(t=0;t<ENEMY_ORDER_MAX;t++){
        if(enemy_order[t].cnt==stage_cnt){//現在の瞬間がオーダーの瞬間なら
            if((i=enemy_num_search())!=-1){
の部分までを管理部関数とし、(つまりenemymgr.cppの関数)

コード:

      enemy[i].flag   =1;//フラグ
                enemy[i].cnt    =0;//カウンタ
                enemy[i].pattern=enemy_order[t].pattern;//移動パターン
                enemy[i].back_col=GetRand(4);
                enemy[i].knd    =enemy_order[t].knd;//敵の種類
                enemy[i].x      =enemy_order[t].x;//座標
                enemy[i].y      =enemy_order[t].y;
                enemy[i].sp     =enemy_order[t].sp;//スピード
                enemy[i].bltime =enemy_order[t].bltime;//弾の発射時間
                enemy[i].blknd  =enemy_order[t].blknd;//弾幕の種類
                enemy[i].blknd2 =enemy_order[t].blknd2;//弾の種類
                enemy[i].col    =enemy_order[t].col;//色
                enemy[i].wait   =enemy_order[t].wait;//停滞時間
                enemy[i].hp     =enemy_order[t].hp;//体力
                enemy[i].hp_max =enemy[i].hp;//体力最大値
                enemy[i].vx     =0;//水平成分の速度
                enemy[i].vy     =0;//鉛直成分の速度
                enemy[i].ang    =0;//角度
                for(j=0;j<6;j++)
                    enemy[i].item_n[j]=enemy_order[t].item_n[j];//落とすアイテム

ここを個の関数(つまりenemy.cppの関数)として、このもともと一つの関数を分けるということですか。

また、
「構造体定義も宣言もインクルードしなければふつうにはアクセスできないわけなのでそのレベルで工夫するのも良いと思いますが。」
とは、構造体の前方宣言に拘らずとも、インクルードしない限りは使えないので、とりあえず構造体の扱いは
このままで良いということでしょうか。(つまり、「管理部と個の両方に公開する必要がある」ということは、enemy_order構造体と
enemy構造体はenemymgr.cppとenemy.cpp両方に必要なため、とりあえずはそのレベルでの隠蔽で良いということでしょうか。)

また、本当にきいてばかりですみませんが、「enemy_num_searchのような配列であることを前提としている部分を汎用化すると良いですね。」
との、部分を汎用化するとはどのようなことなのでしょうか。プログラマの方が仰ることが、まだなかなか聞きなれなくて...すみません。


龍神録のヘッダファイルを直すというのは、つまり、GV.hというヘッダにグローバル変数をおいて、さらにそのGV.hでdefine.hなどをインクルードして、
さらにそのdefine.hでstruct.hをインクルードしている、というのが「その一片たりとも残してはいけないレベルのひどいもの」で、それを直すべきということですね?
それも含めて書き直します。それほどまでに良くない書き方なんですね・・・。

質問ばかりですみません。どうかご協力お願いします。


3D_3Dさん
了解です。「習うより慣れろ」という風に、まずやってみたいと思います。当方は学生なのですが、昨日は用事があり、コードを作れませんでしたが、早い内に公開したいと思います。
ただ、いろんな方の教えをいただいているため、結局、自分にあったやり方になり、3D_3Dさんの意図するところに落ち着くかはまだわからないです・・・。
龍神録がかなり良くないというのが良く分かりました・・・汗

また私のこれから投稿するコードが3D_3Dさんの意図するところと違っていても、参考としていただきたいです。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#38

投稿記事 by ISLe() » 11年前

構造化プログラミングとは…
『抽象化したプログラム命令(ステートメント)を用意して』それを単純な制御文を使って呼び出すように組み上げることです。
世の中にはもっとも重要な部分が欠落した解説があふれているので注意してください。

例えば「敵の全体を処理する」という命令の中身は「敵の個を処理する」という命令の繰り返しです。
抽象化したステートメントの実装にどのような命令を使うかは問題ではありません。
gotoを使わないというのも本質ではありません。

オブジェクト指向プログラミングは構造化プログラミングを発展させたものです。

既存のコードに対して構造化を進める作業としては、ひと続きのものをひとつの命令に置き換える。
それを階層的に繰り返す、ということになります。
ときには大幅な変更が必要になる可能性もあります。


ヘッダファイルは、敵全体を処理するモジュール、敵を個別に処理するモジュールといった、モジュールごとに必要な構造体の定義や宣言をまとめます。
そしてそのモジュールの機能を必要とするソースファイルがそれをインクルードします。
公開範囲が限定される以外にも、変更時にコンパイルしなければいけない対象が絞られるので時間の節約にもなります。


enemy_num_searchは配列の空いている要素の添字を返しますが、enemy_num_search関数の中と外で同じ配列にアクセスしなければいけないという制約があります。
一部のコードを抜き出して移動しただけで、命令として独立していないので、構造化できていないということです。

この場合、配列の空き要素へのポインタを返す関数に置き換えるのが良いと思います。
ポインタが苦手とのようなことを書いておられましたが、ポインタを使いこなせないのはC/C++言語でコードを整理する上で大きなハンデになります。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#39

投稿記事 by Rittai_3D » 11年前

SDD さんが書きました: 3D_3Dさん
了解です。「習うより慣れろ」という風に、まずやってみたいと思います。当方は学生なのですが、昨日は用事があり、コードを作れませんでしたが、早い内に公開したいと思います。
ただ、いろんな方の教えをいただいているため、結局、自分にあったやり方になり、3D_3Dさんの意図するところに落ち着くかはまだわからないです・・・。
龍神録がかなり良くないというのが良く分かりました・・・汗

また私のこれから投稿するコードが3D_3Dさんの意図するところと違っていても、参考としていただきたいです。

ゆっくりで大丈夫ですよ。また、自分のやり方で問題ないです。決まりはありませんので。
オフトピック
3D_3Dって入力が面倒でしょうから、3Dで大丈夫です。
初心者です

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#40

投稿記事 by SDD » 11年前

3Dさん
了解です!自分のレベルでがんばって見ます。ありがとうございます。


ISLeさん
いろいろ教えていただき、ありがとうございます。
抽象化するということは汎用性を高める、という感じなのでしょうか。

とりあえず、ヘッダは龍神録のからは直して、その定義が必要なモジュールだけがそれをインクルードするようにしました。

しばらく考えていたのですが、構造化するということは、その関数の汎用性を高める、命令として独立させる、ということを考えて、
この場合の、

コード:

//空いている敵番号を検索
int enemy_num_search(){
	for(int i=0;i<ENEMY_MAX;i++){//フラグのたって無いenemyを探す
		if(enemy[i].flag==0){
			return i;//使用可能番号を返す
		}
	}
	return -1;//全部埋まっていたらエラーを返す
}

を、

コード:

Enemy *enemy_num_search(enemy_t *enemy){
	return (&enemy[i]);
}


//enemy_num_search呼び出し部分
for(int i=0;i<ENEMY_MAX;i++){
	if(enemy.flag==0){
		enemy_num_search(&enemy[i]);
	}
}
という風に、仮に書きました。
まだ実際にコードにしてはいないので、文法がおかしくてエラーが出るかもしれませんが・・・
抽象化というのはこういうことなのでしょうか・・・。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#41

投稿記事 by Rittai_3D » 11年前

なんとなくですが、変数の隠蔽がまだ出来ていない気がします。
まずは、雑魚一体一体で分ける練習をしましょう。
3D_3D さんが書きました:
SDD さんが書きました:只今、struct.hに構造体定義を書くのをやめて、前方宣言を試している途中です。
少し気になったのですが、このまま前方宣言で進めたら、訂正しない限り、3D_3Dさんからリンクを貼っていただいた、
「構造体の前方宣言」のトピックの、ソフト屋さんが仰っていた「Enemy.cppしか知らないはずの構造体のメンバ情報にEnemy_MovePat.cppがアクセスしているのでエラーになります。」
ということと、僕のenemy.cppのenemy_act(enemy_t *enemy)関数はまったく同じ状況になってしまうということですよね?今まではstruct.hをどのファイルでもインクルードしていたから正常に
動いていたと思うので・・・。
そういうことです。だから、動的確保する必要があります。
たとえば、こんな感じになると思います。
ヘッダ

コード:

#pragma once
struct Hoge_t;

struct Hoge_t* createHogeInstance();
void deleteHogeInstance( struct Hoge_t* p );

void initHoge( struct Hoge_t* p );
void printHoge( struct Hoge_t* p );
実装ファイル

コード:

#include <stdio.h>
#include "hoge.h"
struct Hoge_t{ int val; char name[10]; };

struct Hoge_t* createHogeInstance()
{
    struct Hoge_t* p = nullptr;
    p = ( Hoge_t* )calloc( 1, sizeof( struct Hoge_t ) );//エラーチェックしてません。
    // すいません、pだと落ちます。
    return p;
}
void deleteHogeInstance( struct Hoge_t* p )
{
    free( p );
}

void initHoge( struct Hoge_t* p )
{
    p->val = 40;
    strcpy( p->name, "Foo" ); //ここ間違えてるかも…
}

void printHoge( struct Hoge_t* p )
{
    printf( "val = %d¥nname = %s¥n", p->val, p->name );
}
呼び出す

コード:

#include "hoge.h"

int main( void )
{
    struct Hoge_t* pHoge; // 外部に公開されていないからここから直接はいじれない。関数を呼ぶ必要がある。
    pHoge = createHogeInstance();

    initHoge( pHoge );
    printHoge( pHoge );

    deleteHogeInstance( pHoge );
    return 0;
}
スマホから頑張って打ちました。コンパイルが通るかどうか知りませんが、形としてはこんな感じにわたしならします。
わたしが書いたこのコードのpHogeを配列にするとほとんど敵の処理っぽくなるのではないでしょうか?
初心者です

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#42

投稿記事 by ISLe() » 11年前

抽象化を進めるというのは、言わば「どれだけ短い言葉で内容を表すことができるようにするか」ってことです。
もちろん表現する言葉を無理矢理削るってことではなく、プログラムの中身を表現に合わせます。

うまく抽象化すればプログラムの構造はシンプルになり汎用性も高まり開発効率も上がります。

抽象化は仕様設計レベルの話なので既存のコードをいじくりまわしてどうなるというものでもありません。
既存のコードはいったんおいといてどうあるべきかを考えるということです。

enemy_num_searchの機能は、空き要素を1個選択することですから、それに含まれるループは構造化における繰り返しとは違います。
分解する必要もありません。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#43

投稿記事 by SDD » 11年前

ISLeさん

構造化についてですが、仰ってることが少しずつですが、理解できてきた気がします。

とりあえず、enemy_enterを分離するなどしたのですが、どうしても、enemy_num_searchに
対してのご指摘がなかなか理解できず、あやふやになってしまっています。
enemy_num_searchで言うなら、「一部のコードを抜き出して移動しただけで、命令として独立していないので、構造化できていないということです」
とのことですが、「この場合、配列の空き要素へのポインタを返す関数に置き換えるのが良いと思います。」
とすれば、構造化ということになるのですか。
分解する必要がないなら、普通に

コード:

Enemy *enemy_num_search(){
	for(int i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==0){
			return (&enemy[i]);
		}
	}
}
といった感じにするということでしょうか。

ご迷惑は重々承知ですが、よろしければもう少し、ご教示いただきたいです。

一応、今のところのコードを貼ります。なにかご指摘いただければと思います。

コード:

//about_enemy.h

#ifndef DEF_ABOUT_ENEMY_H
#define DEF_ABOUT_ENEMY_H


#include "define.h"


//敵に関する構造体
typedef struct{
	//フラグ、カウンタ、移動パターン、向き、敵の種類、HP最大値、落とすアイテム、画像、背景色
	int flag,cnt,pattern,knd,hp,hp_max,item_n[6],img,back_col;
	//座標、速度x成分、速度y成分、スピード、角度
	double x,y,vx,vy,sp,ang;
	//弾幕開始時間、弾幕の種類、弾の種類、色、状態、待機時間、停滞時間
	int bltime,blknd,blknd2,col,state,wtime,wait;
	//画像ハンドル
	int img_enemy[3];
}enemy_t;



typedef struct{
	//カウンタ、移動パターン、敵の種類
	int cnt,pattern,knd;
	//初期座標と移動スピード
	double x,y,sp;
	//弾幕開始時間、弾幕の種類、色、体力、弾の種類、停滞時間、アイテム(6種類)
	int bltime,blknd,col,hp,blknd2,wait,item_n[6];
}enemy_order_t;



static enemy_order_t enemy_order[ENEMY_ORDER_MAX];//敵のパターン格納
static enemy_t enemy[ENEMY_MAX];//敵の実態


#endif

コード:

//enemymgr.cpp


#include "DxLib.h"
#include "about_enemy.h"
#include "enemy.h"
#include "define.h"
#include "GV.h"



static int m_img;//敵の画像ハンドル




//敵の出現情報をエクセルから読み込んで格納する関数
void load_enemy_order(){
	int n,num,i,fp;
	char fname[]={"story1.csv"};
	int input[64];
	char inputc[64];

	fp = FileRead_open(fname);//ファイル読み込み
	if(fp == NULL){
		printfDx("read error\n");
		return;
	}
	for(i=0;i<2;i++)//最初の2行読み飛ばす
		while(FileRead_getc(fp)!='\n');

	n=0 , num=0;
	while(1){
		for(i=0;i<64;i++){
			inputc[i]=input[i]=FileRead_getc(fp);//1文字取得する
			if(inputc[i]=='/'){//スラッシュがあれば
				while(FileRead_getc(fp)!='\n');//改行までループ
				i=-1;//カウンタを最初に戻して
				continue;
			}
			if(input[i]==',' || input[i]=='\n'){//カンマか改行なら
				inputc[i]='\0';//そこまでを文字列とし
				break;
			}
			if(input[i]==EOF){//ファイルの終わりなら
				goto EXFILE;//終了
			}
		}
		switch(num){
						case 0: enemy_order[n].cnt      =atoi(inputc);break;
						case 1: enemy_order[n].pattern  =atoi(inputc);break;
						case 2: enemy_order[n].knd      =atoi(inputc);break;
						case 3: enemy_order[n].x        =atof(inputc);break;
						case 4: enemy_order[n].y        =atof(inputc);break;
						case 5: enemy_order[n].sp       =atof(inputc);break;
						case 6: enemy_order[n].bltime   =atoi(inputc);break;
						case 7: enemy_order[n].blknd    =atoi(inputc);break;
						case 8: enemy_order[n].col      =atoi(inputc);break;
						case 9: enemy_order[n].hp       =atoi(inputc);break;
						case 10:enemy_order[n].blknd2   =atoi(inputc);break;
						case 11:enemy_order[n].wait     =atoi(inputc);break;
						case 12:enemy_order[n].item_n[0]=atoi(inputc);break;
						case 13:enemy_order[n].item_n[1]=atoi(inputc);break;
						case 14:enemy_order[n].item_n[2]=atoi(inputc);break;
						case 15:enemy_order[n].item_n[3]=atoi(inputc);break;
						case 16:enemy_order[n].item_n[4]=atoi(inputc);break;
						case 17:enemy_order[n].item_n[5]=atoi(inputc);break;
		}
		num++;
		if(num==18){
			num=0;
			n++;
		}
	}
EXFILE:
	FileRead_close(fp);
}



//空いている敵番号を検索
int enemy_num_search(){
	for(int i=0;i<ENEMY_MAX;i++){//フラグのたって無いenemyを探す
		if(enemy[i].flag==0){
			return i;//使用可能番号を返す
		}
	}
	return -1;//全部埋まっていたらエラーを返す
}


//敵情報を登録
void enemy_enter(){//敵の行動を登録・制御する関数
	int i,j,t;
	for(t=0;t<ENEMY_ORDER_MAX;t++){
		if(enemy_order[t].cnt==stage_cnt){//現在の瞬間がオーダーの瞬間なら
			if((i=enemy_num_search())!=-1){
				enemy_set(&enemy[i],&enemy_order[t],t);
			}
		}
	}
}





void EnemyMgr_Init(){
	memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX);
	memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);
	m_img=LoadGraph("キャラクター/雑魚1.png");
	load_enemy_order();
	for(int i=0;i<ENEMY_MAX;i++){
		Enemy_Init(&enemy[i],m_img);
	}
}


void EnemyMgr_Update(){
	enemy_enter();
	for(int i=0;i<ENEMY_MAX;i++){
		Enemy_Update(&enemy[i]);
	}
}


void EnemyMgr_Draw(){
	for(int i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==1){
			Enemy_Draw(&enemy[i]);
		}
	}
}



void EnemyMgr_Final(){
}

コード:

//enemy.cpp

#include "DxLib.h"
#include "about_enemy.h"
#include "define.h"
#include "math.h"




void enemy_set(enemy_t *enemy,enemy_order_t *enemy_order,int t){
	enemy->flag   =1;//フラグ
	enemy->cnt    =0;//カウンタ
	enemy->pattern=enemy_order->pattern;//移動パターン
	enemy->back_col=GetRand(4);
	enemy->knd    =enemy_order->knd;//敵の種類
	enemy->x      =enemy_order->x;//座標
	enemy->y      =enemy_order->y;
	enemy->sp     =enemy_order->sp;//スピード
	enemy->bltime =enemy_order->bltime;//弾の発射時間
	enemy->blknd  =enemy_order->blknd;//弾幕の種類
	enemy->blknd2 =enemy_order->blknd2;//弾の種類
	enemy->col    =enemy_order->col;//色
	enemy->wait   =enemy_order->wait;//停滞時間
	enemy->hp     =enemy_order->hp;//体力
	enemy->hp_max =enemy->hp;//体力最大値
	enemy->vx     =0;//水平成分の速度
	enemy->vy     =0;//鉛直成分の速度
	enemy->ang    =0;//角度
	for(int i=0;i<6;i++)
		enemy->item_n[i]=enemy_order->item_n[i];//落とすアイテム
}




//敵の行動制御
void enemy_act(enemy_t *enemy){
	if(enemy->flag==1){//その敵のフラグがオンになってたら
		if(0<=enemy->pattern && enemy->pattern<ENEMY_PATTERN_MAX){


			//とりあえずの移動制御パターン
			int t=enemy->cnt;
			if(t==0){
				enemy->vx=3;
				enemy->vy=2;//下がってくる
			}
			if(t==60){
				enemy->vy=0;//止まる
				enemy->vx=0;
			}
			if(t==60+enemy->wait)//登録された時間だけ停滞して
				enemy->vy=-2;//上がっていく



			enemy->x+=cos(enemy->ang)*enemy->sp;
			enemy->y+=sin(enemy->ang)*enemy->sp;
			enemy->x+=enemy->vx;
			enemy->y+=enemy->vy;
			enemy->cnt++;
			//敵が画面から外れたら消す
			if(enemy->x<-50 || F_M_X+20<enemy->x || enemy->y<-50 || F_M_Y+20<enemy->y)
				enemy->flag=0;
		}
		else
			printfDx("enemy[i].patternの%d値が不正です。",enemy->pattern);

	}
}




void Enemy_Init(enemy_t *enemy,int img){
	enemy->img_enemy[0]=img;
}


void Enemy_Update(enemy_t *enemy){
	enemy_act(enemy);
}


void Enemy_Draw(enemy_t *enemy){
	DrawGraph(enemy->x+F_X,enemy->y+F_Y,enemy->img_enemy[0],TRUE);
}


void Enemy_Final(){
}


3Dさん
すみません、とりあえず、現状では、構造体変数はenemy_mgr.cpp、enemy.cpp両方に公開されている感じにしています。
根本の方法をまず確立させたいので、敵もまだ複数体のままにしています。僕のためにわざわざコードまで用意して頂き、
ご親切にしていただいているのに、飲み込みと要領が悪くてすみません・・・。

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#44

投稿記事 by Rittai_3D » 11年前

いくつか疑問があります。

なぜ、CSVの読み込みが現段階で必要なのでしょうか。
なぜ、カプセル化を目指しているのに構造体を公開しているのでしょうか。

設計を適切にすれば、構造化は簡単に出来る気がします。
龍神録の設計を完璧に忘れましょう。忘れないと、おそらく設計は出来ません。

構造体の内部を隠さないとカプセル化にはなりません。
現在、スマホから返信をしているのでこの返信ではコードを貼れません。
すぐに貼りますので、少々お待ちください。
オフトピック
コードを貼ることは本当にその人の為になるのでしょうか?
#すいません、コードは少し遅れます。
初心者です

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#45

投稿記事 by softya(ソフト屋) » 11年前

そうですね。
正しい姿にこだわり過ぎると、行き詰まる人をよく見かけます。
今できる中で、やれることをやってバグを減らすことを目指されてはどうでしょうか。

ロジックの正確さと、カプセル化、バグの少ないコーディングを一度に達成するのはもう少し経験を積んでからのほうが良いように思います。
いっぺんに完成形を目指すのは延々に完成しない目標になりかねません。
再設計やリファクタリングという形で完成度を上げていけば良いんです。

目的と手段がすり替わらない様に気をつけてください。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#46

投稿記事 by ISLe() » 11年前

構造化というのは、先に書いたとおり、プログラム全部を、シンプルに表現できるステートメントで組み上げることです。
なので1つの関数を変えただけで構造化が完成することはありません。

ポインタを返せば良いと書いたのは、その後はそのポインタを使うという構想がわたしにはあるからです。
少なくともその前後も構造化を考えて繋いでいかなければ動くものになりません。
XXXX_InitとかXXXX_Updateとか(...以下略)はただのラッパのようであり構造化を阻害しています。

とりあえず既存のコードのどこからどこまでをどんな命令に置き換えられるか母国語で考えてみてはいかがでしょうか。

それとenemy_num_search関数に限らず中と外で同じ変数にアクセスしている箇所がたくさんあります。
それがどこなのか明確にするといったこともやったほうが良いと思います。
闇雲にコードを分割・移動してもグローバル変数はなくなりませんよ。

わたしは悪いところを直すというのはいちばん勉強になると思っているので、このまま続けても良いと思います。
先に定義を隠そうとしても何も分からずにっちもさっちもいかないでしょう。
定義を隠すことができる構造にもっていくことが先だと思います。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#47

投稿記事 by SDD » 11年前

皆さん、何度もすみません。

どなたの仰るやり方・順番で進めていったらいいかをすこし迷ってしまいました・・・。

正直なところ、ソフト屋さんの仰るように、一度にすべてをやろうとすると、折れてしまいそうです。
正直、本当に仰るとおりに、ずっと目標が叶わないという風になってしまうかもしれません。
実際に目標と手段がすり替わりかけていました。

ただ、みなさんには続いてご迷惑をおかけすることになるのですが、もう少し粘ってみようと思います。
時間のリミットがあるため、焦っていて、どこまで悪いところを直せるかはわかりませんが、行き詰る寸前までは努力します。
どうしても無理だった際の最後の手段として、今の自分に見合ったやり方でゲームを作ることにして、トピックを解決させることにします。

3Dさんの、定義の隠蔽→構造と、ISLeさんの構造→定義の隠蔽のどちらかなのですが、とりあえず後者の順序でやってみます。



3Dさん
ご質問に対してのことなのですが、お恥ずかしいのですが、今回のことには期限があり、焦りが強く、CSV(敵の複数の処理)→一体の処理という風にランクダウンさせるというか、
逆戻りにすることに気が引けたという理由です。後者も先を急いだためです。
龍神録の設計から完全に離れて、設計しなおすことは、正直今の僕には全くどうしたらいいか検討がつかないのです・・・。
コードはできることなら見せて頂きたいですが、3Dさんが見せるべきではないというのでしたら、それ無しでがんばってみます。
ありがとうございます。


ソフト屋さん
ご忠告、ありがとうございます。
仰るとおり、経験も知識もたりていないですね・・・。
正直なところでは、今できるやり方で進んでいきたいのですが、時間の許す限り、もう少し苦しんで粘ってみようと思います。
励ましていただき、だいぶ楽になりました。ありがとうございます。


ISLeさん
ラッパーは構造化を妨げるのですか・・・。
逆にそれらがシンプルさを失わせているのですね。

確かに、どこの関数の外でも内でもenemy_orderなどに干渉していますね・・・。自分ですこしややこしくなっていました。
もう少し粘ってみようと思います。ありがとうございます。

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#48

投稿記事 by usao » 11年前

オフトピック
期限的な話があるなら
(内側のコードの形は,現状で悩まずに済む形の書き方で)とりあえず動くもの
を先に保険として作ってしまった方が良いかもしれませんね.
その後で,期限までに 内側を(なんらかの意味で)良い形 にした版 ができるかどうかにチャレンジ,という.

アバター
ookami
記事: 214
登録日時: 14年前
住所: 東京都

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#49

投稿記事 by ookami » 11年前

「迷ってしまい」とのことなのでよけい混乱させたらすいませんが、別の視点になるかと思い、参考までに書いてみます。

どういう固まりでテストしたいか、ということなんですが...
例えば「敵だけを一体生成し、登場から退場までのテスト」をしているとします。

期待する結果としては、真っ暗な背景に、敵が一体だけ表示されているイメージです。
しかし、実際には画面に何も表示されなかったとします。
すると、今のソースだと可能性として、
  ① story1.csvが正しくない
  ② csvの読み込み処理が正しくない
  ③ enemy_order構造体への格納が正しくない
  ④ enemy_num_search関数の戻り値が正しくない
  ⑤ enemy構造体への格納が正しくない
  ⑥ Draw関数が正しくない
など、疑うポイントが多くなってしまいます。

これに対し、
void test_MakeEnemy() {
if( Zキーが押されたとき...) {
Enemy_Init(&enemy[0],100,200 ............なんやかんや ); // enemy配列の0番に、座標(100,200) 。。。。。。。。なんやかんやで、敵を生成
}
}
のように書けるとしたら(今の設計では書けませんが...)、
少なくとも①~④までを疑う必要がなくなります。

さらに、この時、Enemy_Init関数の引数がなるべく少なくなるように考えてみます。

元のソースでは、enemy_enter関数で
  フラグ、カウンタ、移動パターン、敵の種類、座標、スピード、弾の発射時間、弾幕の種類、弾の種類、色、停滞時間、体力、体力最大値、水平成分の速度、鉛直成分の速度、角度

...をenemyにセットしていますが、この中で...

①固定値を設定しているものは、Enemy_Initの引数でなくてよい。
  フラグ、カウンタ、水平成分の速度、鉛直成分の速度、角度 は固定値が設定されている。

②「Aが決まればBが決まる」という依存関係があれば、BはEnemy_Initの引数でなくてよい。
  (想像込みですが)
  敵の種類 が決まれば 体力、体力最大値、弾の発射時間、弾幕の種類、弾の種類、色 が決まる
  移動パターン が決まれば スピード、停滞時間 が決まる

すると Enemy_Init 関数の引数は、上記の中で残った 座標、移動パターン、敵の種類 (あと最初のポインタ) となります。
で、それだけの引数で動くように、関数の中身を記述していきます。

というか、この考え方でいけば、関数の中身の記述は後まわしでも大丈夫です。
敵だけでなく、敵マネージャ、プレーヤー、シーン、当たり判定、などを考えていく間に、
「やっぱり敵にはこの引数も必要だわ」ということが分かるので、手直しの手間が減ります。

これを進めていくと、敵のテストを、「敵以外が何もできていなくても」できるようになります。
また、「これだけはグローバル変数じゃないとダメなやつ」も見えやすくなるかと。

まぁ...他の方も仰っていることを別の切り口で言ってるだけなのかもしれませんが...^^;
また、やるとしたらかなりの修正になりますので、締切があるようでしたらよく検討されたし。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#50

投稿記事 by SDD » 11年前

usaoさん
そうかもしれません・・・。
まだまったく時間がないということではないので、すくなくともこの一週間を目処にして、粘ろうと思います。
それまでご迷惑をおかけします。

ookamiさん
ご提案ありがとうございます。
一応、今後の方針の選択肢として参考にさせていただきます。
そうですね、検討してみます。何度もすみません・・・。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#51

投稿記事 by ISLe() » 10年前

構造化は少しずつ進めることもできます。
例えばenemy_enterの第一歩。

コード:

//敵情報を登録
void enemy_enter() { //敵の行動を登録・制御する関数
	int i,j,t;
	// if(boss.flag!=0)return;
	for(t=0; t<ENEMY_ORDER_MAX; t++) {
		if(enemy_order[t].cnt==stage_cnt) { //現在の瞬間がオーダーの瞬間なら
			if((i=enemy_num_search())!=-1) {
				{
					enemy_t *enemy = &enemy[i];

					enemy->flag   =1;//フラグ
					enemy->cnt    =0;//カウンタ
					enemy->pattern=enemy_order[t].pattern;//移動パターン
					enemy->back_col=GetRand(4);
					enemy->knd    =enemy_order[t].knd;//敵の種類
					enemy->x      =enemy_order[t].x;//座標
					enemy->y      =enemy_order[t].y;
					enemy->sp     =enemy_order[t].sp;//スピード
					enemy->bltime =enemy_order[t].bltime;//弾の発射時間
					enemy->blknd  =enemy_order[t].blknd;//弾幕の種類
					enemy->blknd2 =enemy_order[t].blknd2;//弾の種類
					enemy->col    =enemy_order[t].col;//色
					enemy->wait   =enemy_order[t].wait;//停滞時間
					enemy->hp     =enemy_order[t].hp;//体力
					enemy->hp_max =enemy[i].hp;//体力最大値
					enemy->vx     =0;//水平成分の速度
					enemy->vy     =0;//鉛直成分の速度
					enemy->ang    =0;//角度
					for(j=0; j<6; j++)
						enemy->item_n[j]=enemy_order[t].item_n[j];//落とすアイテム
				}
			}
		}
	}
}
enemy_t *enemy = &enemy;
では文法として右辺が配列の要素でなくても代入できることは分かりますよね。
これで下位の部分にenemy配列縛りが無くなってひとつ抽象化できました。
このように抽象化を進めていって最後に関数化するという方法もあります。

このあとenemy_order[t]をどうするかは悩みどころです。
新しく作ったブロックの外と中の両方にあるからです。
どういうふうに変えたら良いと思いますか?

この関数の中にあるループはすべて選択するループですからきちんと構造化すれば不要になるものです。
この関数はいくつかのシンプルな命令で表現できます。
(日本語で)どう表現できるか考えてみてください。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#52

投稿記事 by SDD » 10年前

ISLeさん
結局ヒントをいただいてしまってすみません・・・。
正直に言うと、ここ数日にらめっこがつづいたり、ポインタの理解が足りていないため、
参考書を読み漁ったりといった感じだったので、ヒントをいただけたのは正直かなり助かります。
いただいたヒントを考えたり、この際なので、参考書を一通りちゃんと理解しようと思います。
しばらくそんな感じで続いてしまうと思いますが、改善できた際にはコードを載せますので、
引き続き、お暇なときに私のお相手をしていただけたら助かります。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#53

投稿記事 by SDD » 10年前

すこしわかったきがします。(間違えているかもしれませんが・・・)
もう今日明日はここに来る時間があまりないので、コードを載せるのは明日の遅くかもしれませんが、
考えたコードを載せます。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#54

投稿記事 by SDD » 10年前

すみません、やはり思いついたものでは、教えられたことを実現することはまだできませんでした。時間がかかってしまっていますが、もう少し自分で考えます。只今、同時にCの基礎も本をちゃんと
読んで勉強しているところで、少しもたついてしまっていますが、まだあきらめていません。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#55

投稿記事 by SDD » 10年前

コードを実際に打てる環境になったため、しばらく調べていたのですが、

コード:

//敵情報を登録
void enemy_enter() { //敵の行動を登録・制御する関数
    int i,j,t;
    // if(boss.flag!=0)return;
    for(t=0; t<ENEMY_ORDER_MAX; t++) {
        if(enemy_order[t].cnt==stage_cnt) { //現在の瞬間がオーダーの瞬間なら
            if((i=enemy_num_search())!=-1) {
                {
                    enemy_t *enemy = &enemy[i]; //ここでエラーが出る
 
                    enemy->flag   =1;//フラグ
                    enemy->cnt    =0;//カウンタ
                    enemy->pattern=enemy_order[t].pattern;//移動パターン
                    enemy->back_col=GetRand(4);
                    enemy->knd    =enemy_order[t].knd;//敵の種類
                    enemy->x      =enemy_order[t].x;//座標
                    enemy->y      =enemy_order[t].y;
                    enemy->sp     =enemy_order[t].sp;//スピード
                    enemy->bltime =enemy_order[t].bltime;//弾の発射時間
                    enemy->blknd  =enemy_order[t].blknd;//弾幕の種類
                    enemy->blknd2 =enemy_order[t].blknd2;//弾の種類
                    enemy->col    =enemy_order[t].col;//色
                    enemy->wait   =enemy_order[t].wait;//停滞時間
                    enemy->hp     =enemy_order[t].hp;//体力
                    enemy->hp_max =enemy[i].hp;//体力最大値
                    enemy->vx     =0;//水平成分の速度
                    enemy->vy     =0;//鉛直成分の速度
                    enemy->ang    =0;//角度
                    for(j=0; j<6; j++)
                        enemy->item_n[j]=enemy_order[t].item_n[j];//落とすアイテム
                }
            }
        }
    }
}

enemy_t *enemy = &enemy;の部分で「Run-Time Check Failure #3 - The variable 'enemy' is being used without being initialized.」
というエラーが出てしまいます。
コードは、とりあえず第一歩として教えていただいた、上記のenemy_enter()以外変わっていません。
enemy構造体変数は初期化系関数で要素数すべてゼロクリアしているはずなのに初期化していないといわれてしまいます。
しばらく考えていたのですが、全く検討がつかないです・・・。どうしてなのでしょうか・・・。本題からそれてしまいすみません。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#56

投稿記事 by SDD » 10年前

エディタ内で変数にカーソルを近づけ、それがどんな変数なのかチェックすることができると思いますが、
enemy_t *enemy = &enemy;という文のenemy_t *enemyを調べると「static enemy_t enemy[30(現状の敵の数)]」と出て、&enemyを調べると「enemy_t *enemy」と
出てきました。逆ではないのかと思い、とりあえず、ポインタ変数の名を、ためしにenemy_t *enemy_pとしたところ、正常に作動した上に、上記のような変数の詳細も正しくなっていました。

同じ名前だったのが原因なのでしょうか・・・。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#57

投稿記事 by SDD » 10年前

連投ですみません。
単純に日本語で表現すると、「ループ処理」、「カウント比較」、「空き要素チェック」、「データセット」と
いった感じでしょうか。

やはり、いまだに構造化というのがぼんやりとしかわかっておらず、考えてもなかなか思い浮かびません。
引数を与えたり、enemy_enter()に呼び出し側に条件わけをつけて、この関数を呼び出したり、ということはしないで、
この関数内だけで解決できるのでしょうか。

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#58

投稿記事 by usao » 10年前

なんかまた余計なことを書くと混乱を招きそうですが…
(どうにかうまいこと取捨選択してください.不要なら丸ごと無視してOK)


(A)
「enemy_t, enemy_order_t という構造体がどんなものなのかさえ分かってれば書ける部分」

と,

(B)
enemy_order_t enemy_order[ENEMY_ORDER_MAX];//敵のパターン格納
enemy_t enemy[ENEMY_MAX];//敵の実態
という
「2つの配列が存在することも知らないと書けない部分」


という風に分けて考えてみたらどうでしょうか.
(プログラム全体には,さらに,(C)「それらをどっちも知らなくて良い部分」というのもあるけれども
 とりあえずここでは話を簡単にするために
 今見ている「敵に関する処理のあたり」だけについて着目するのだとして.)

で,(今,この話の中では,わかりやすく)
(A)に属するものと(B)に属するものは,それぞれ別のcppに実装するのだとしましょうか.

こうすると2つの配列の定義は(B)側のB.cppの中にあれば済みますね.
(なぜならば,これらの配列の存在を知らないと書けないコードはみんなこのB.cpp内にあるという話にしたから)

こうすると A.cpp側に書く処理は,配列にダイレクトにアクセスしなく(できなく)なるので
当初の目的に近づくのではないでしょうか.


非常にざっくり言うと,

(B)側は,配列データ全体を眺めて
個々のデータについて今何をすべきかを判断したりする,
で,データに対してすべきことが決まったら,
「このデータについて こういう処理を してよ」という形で
(A)側にその作業を下請けに出す感じ,ですかね.

(A)側からすれば「このデータ」がどっから出てきたのかは不明ですが
とにかく言われたことだけやればよい,みたいな感じ.

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#59

投稿記事 by ISLe() » 10年前

SDD さんが書きました:同じ名前だったのが原因なのでしょうか・・・。
そのとおりです。
先の投稿は考え方を示すことを急いだために内容の精査を怠ってしまいました。
申し訳ありません。
#思いがけずグローバル変数を使うことの危険性を具体的に示す結果になったわけですが。
SDD さんが書きました:単純に日本語で表現すると、「ループ処理」、「カウント比較」、「空き要素チェック」、「データセット」と
いった感じでしょうか。
それは実装寄りの説明ですね。
「ループ処理」と書いてしまったあとからループを使わずにシンプルに書く方法が見付かったらどうするのですか?
この関数の機能はそれで変わってしまうのでしょうか。

わたしなら、こんな感じに表現します。

コード:

void enemy_enter() {
    // 敵キャラ用のメモリ領域を新規に取得する
    // 前段で取得した敵キャラ用のメモリ領域を初期化する
}
実際にコードを書くときもこんなふうにコメントを書いてから埋めていってます。
コメントの内容は抽象化した普遍的なものです。
リファクタリングでいくらコードを書き直そうがコメントまで書き直すような無駄は発生しません。

#手を付けていないところに TODO とか書いておくといまどきの統合開発環境だとそれを自動的にリストアップしてくれる機能があったりします。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#60

投稿記事 by ISLe() » 10年前

また見落としがありました。ごめんなさい。

コード:

void enemy_enter() {
    // 敵キャラの出現オーダーからタイミングの一致するものを選択(複数あり)
    // 以下を選択されたオーダーごとに繰り返す
    {
        // 敵キャラ用のメモリ領域を新規に取得する
        // 前段で取得した敵キャラ用のメモリ領域を初期化する
    }
}

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#61

投稿記事 by SDD » 10年前

usaoさん
お気遣いありがとうございます。
参考にします。ただ、とりあえず、ひとつの関数から直していこうと思います。混乱しやすくてすみません(汗)気を使っていただいてしまって・・・。


ISLeさん
なるほど・・・。その関数の根本の役目というか、本来の機能を言えということですね。ループなどはそのためのたまたまの
手段でしかないですね。すみません、もう一つヒントをいただきたいのですが、この関数における構造化のための改造は、
この関数内だけの改造で解決するのでしょうか。外部から引数を取ったりするようにしたり、外部のループの中にこの関数
を置く、ということにはしないでよいのでしょうか。何度もすみません。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#62

投稿記事 by SDD » 10年前

エラーチェックはしていないのですが、こういうことではないんですよね。難しいです・・・。

コード:


//敵情報を登録
void enemy_enter(enemy_order_t *enemy_order_p) { //敵の行動を登録・制御する関数
    int i;

	//敵キャラの出現オーダーからタイミングの一致するものを選択
	//敵の数だけ以下の処理を繰り返す

        if(enemy_order_p->cnt==stage_cnt) { //現在の瞬間がオーダーの瞬間なら
            if((i=enemy_num_search())!=-1) {
                {
					//選択された敵一体のポインタを確保

                    enemy_t *enemy_p = &enemy[i];

					//取得したポインタを使って敵情報をセット

                    enemy_p->flag   =1;//フラグ
                    enemy_p->cnt    =0;//カウンタ
                    enemy_p->pattern=enemy_order_p->pattern;//移動パターン
                    enemy_p->back_col=GetRand(4);
                    enemy_p->knd    =enemy_order_p->knd;//敵の種類
                    enemy_p->x      =enemy_order_p->x;//座標
                    enemy_p->y      =enemy_order_p->y;
                    enemy_p->sp     =enemy_order_p->sp;//スピード
                    enemy_p->bltime =enemy_order_p->bltime;//弾の発射時間
                    enemy_p->blknd  =enemy_order_p->blknd;//弾幕の種類
                    enemy_p->blknd2 =enemy_order_p->blknd2;//弾の種類
                    enemy_p->col    =enemy_order_p->col;//色
                    enemy_p->wait   =enemy_order_p->wait;//停滞時間
                    enemy_p->hp     =enemy_order_p->hp;//体力
                    enemy_p->hp_max =enemy[i].hp;//体力最大値
                    enemy_p->vx     =0;//水平成分の速度
                    enemy_p->vy     =0;//鉛直成分の速度
                    enemy_p->ang    =0;//角度
                    for(j=0; j<6; j++)
                        enemy_p->item_n[j]=enemy_order_p->item_n[j];//落とすアイテム
                }
            }
        }
    }
}



ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#63

投稿記事 by ISLe() » 10年前

日本語で書いたことは理想的な最終形です。

ブロックで囲ったのは、ギリギリまで見通しを優先するための手順です。

既存のコードを構造化する作業はボトムアップになるので、理想はあくまで理想。
現状を少しずつ改善し、その都度問題点を洗い出し、変えるべきところは大胆に変えていかなければいけません。

構造化は抽象化したステートメントでプログラムを構成します。
抽象化したステートメントというのはC/C++で言えば関数のことです。
構造化を進めるということは、新しい関数をいくつも作って置き換えていくということです。


No.62のenemy_enter関数は、元のenemy_enter関数にあった、複数オーダーに対応するという仕様が欠落していますね。

新たに囲んだブロックの外と中にあるenemy_orderをどうすれば良いか、の答えが、引数にする、なのでしょうか。
その根拠は?
enemy_orderがどこに関連しているのか整理してみましたか。
抽象化する際には、いま目の前にあるものをいったん忘れることも必要ですよ。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#64

投稿記事 by SDD » 10年前

ISLeさん
回答ありがとうございます。
一度enemy_enterや、enemy_orderなどの状況を紙などに書いて整理してみます。

ただ、もう少しだけ、具体的なヒントをいただけないでしょうか。
正直、この関数をよりよくするために、どこからどこまでかえればいいのか判断できず、
この関数だけでとりあえず改良できるのか、今、外部までも改良しなければいけないのかわからないのです。
もしかしたら、今までの言葉をちゃんと理解していたらすぐにできることなのかもしれませんが、なかなか難しいです。
甘えてすみません。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#65

投稿記事 by SDD » 10年前

NO.51の「この関数の中にあるループはすべて選択するループですからきちんと構造化すれば不要になるものです。」
ということは、ループをなくしたら、「t」でアクセスすることができなくなるから、enemy_enterはただ敵情報を初期化するだけになってしまう・・・(?)
かといってそうしたら、最終の理想であるコメントの表現から外れてしまう・・・。


enemy_orederはenemymgr.cppでのみ宣言され、敵情報ロード関数で一回、enemy_enter()のカウントチェックで一度、ブロックの先で
もう一度使われています。j関連を考えたのですが、これ以上のものは思いつきません・・・。


実際では、他人からヒントが与えられるということは少なく、甘くないのはわかるのですが、
取っ掛かりが欲しいです。正直、お手上げになってしまいました・・・。

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#66

投稿記事 by usao » 10年前

オフトピック
<< 的外れであれば無視してください >>


そもそも今現在 何をされているのでしょうか?

>この関数をよりよくするために…

「よくする」とは,【具体的に(詳細に)】どういうことを指しているのか,を説明できますか?
(逆に言えば,今現在までで動いているバージョンのコード では何がどう良くないのか)

こんなことを書くと話を進めておられる ISLe() さんに怒られてしまいそうですが…

「何をしようとしているのか」「目指す形は何か」をご自身できちんと把握されていないのだとすれば
コードをいじくりまわすよりも,まずそれをしっかりと把握することを先に行うべきです.
>お手上げ
になってしまうのは,上記のような 動機(? と言えばいいかな?) の部分で既に躓いてしまっているから
であるように見受けます.

#要するに
 言われるがままにやってみるけれども,やってる本人が何やってるかわかってないパターン
 みたいなのに はまってなければいいのですが… ってことです.

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#67

投稿記事 by SDD » 10年前

usaoさん
的はずれではなく、痛いところを突かれてしまっているのですが・・・、
龍神録はポインタなどを使わないために、あまり良くない(美しくない?)コードになっており、
さらに明瞭な、全体の構造化を進めてみるため、とりあえずひとつの関数から進めている。と言う感じです。

C言語が構造化プログラミングだ、とかもネットで見たことがあるのですが、現状の自分のコードはあまりそんな気がしないです・・・。

ただ、どうにも未だに「構造化」というのが頭の中で概念的になりすぎていて、実際の今のenemy_enterや、それをとりまくコードをど実際に構造化するとどうなるのか
がイメージできない状況です。

正直、どうまとめられるのかが気になって、興味本位でやっている部分もあります。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#68

投稿記事 by ISLe() » 10年前

日本語で表現したものを実際のコードっぽくしてみます。
関数名は日本語表現に沿っています。
ただし、これはまだ不完全です。
ヒントになるでしょうか。

コード:

void enemy_enter() {
    enemy_order_t *enemy_order_ptr;
    // 敵キャラの出現オーダーからタイミングの一致するものを選択(複数あり)
    // 以下を選択されたオーダーごとに繰り返す
    while (enemy_order_ptr = get_next_enemy_order(stage_cnt))
    {
        enemy_t *enemy_ptr;
        // 敵キャラ用のメモリ領域を新規に取得する
        enemy_ptr = get_enemy_buffer();
        if (enemy_ptr == NULL) continue;
        // 前段で取得した敵キャラ用のメモリ領域を初期化する
        init_enemy_buffer(enemy_ptr, enemy_order_ptr);
    }
}
get_next_enemy_order関数は呼び出されるたびに引数と一致するカウンタのオーダーへのポインタを戻り値にします。
一致するオーダーがなくなるとNULLを返します。
get_enemy_buffer関数とinit_enemy_buffer関数は日本語で書いてあることそのままですね。

enemy_tもenemy_ordert_tもここではポインタしか使ってません。
そうなるとこのenemy_enter関数に対して構造体の中身を公開する必要がありません。

構造体の中身を知っている必要があるのはどの関数なのか。
それを整理するとどの関数同士てまとめると良いかが見えてくると思います。



実際書いてみるとこうしたほうがスマート。

コード:

void enemy_enter() {
    enemy_order_t *enemy_order_ptr;
    // 敵キャラの出現オーダーからタイミングの一致するものを選択(複数あり)
    // 以下を選択されたオーダーごとに繰り返す
    while (enemy_order_ptr = get_next_enemy_order(stage_cnt))
    {
        enemy_t *enemy_ptr;
        // 出現オーダーを使って敵キャラを生成
        enemy_ptr = create_enemy(enemy_order_ptr);
        //if (enemy_ptr == NULL) continue;
    }
}

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#69

投稿記事 by SDD » 10年前

ISLeさん
すみません、これほどのヒントをいただいてしまって。
ですが、ようやく構造化の片鱗がわかってきました。
正直、教えていただけてなかったら、こういうところまで至らなかったと思い
内心はずかしいのですが、まずは目的を達成しようと思います。

実際に実装などをしてみて、コードを投稿いたします。
長い間おつきあいさせてすみません。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#70

投稿記事 by ISLe() » 10年前

SDDさんの投稿からはやる気が感じられるのでこちらも有意義に思っています。

抽象化は正解がないので、プログラミングを続ける限りずっと向き合っていかなければいけない問題です。

ここをおろそかにすると、汎用性も保守性も低い糞コードを量産するハメになるのでじっくり取り組んでください。
こればかりになってもいけませんが。

オブジェクト指向でも抽象化は重要です。
オブジェクト指向を取り入れさえすれば良くなるということはありません。

構造化をきちんとおさえておけばそこで身に付けたものがこの先もきっと役に立ってくれると思います。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#71

投稿記事 by SDD » 10年前

ISLeさん
うれしいです。ありがとうございます。今回のことは本当に勉強になっています。構造化だけでなく、苦手なポインタ関係の
実践も一緒に練習できてくると思っているので・・・。
夏季休暇が終わり、大学が始まったので、今までよりは投稿のペースが落ちてしまっているのですが、
最後まで、やります。もう少しお付き合いいただけたら幸いです。

以前あった話ですが、たしかに、こういう風に考えてみると、構造化したときに、~Init()や~Updateのようなラッパはちょっと逆に分かりづらい
なと感じます。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#72

投稿記事 by SDD » 10年前

構造体はポインタしか使っていないから、このenemymgr.cppでは前方宣言だけでよくなるということでしょうか。
そうすると、最終的には3D_3Dさんが仰っていたように、ゆくゆくは構造体を隠蔽できる、という解釈でよいのかな・・・。
そうすると、仮に、enemy.cppに構造体の定義をおき、enemy.cppのみが構造体を知っているなら、
enemy_enterの部品になっている関数たちははenemymgr.cppではなく「個」であるenemy.cppとヘッダに、定義と宣言を
おくことになる・・・?となると、enemy_enterと同じくenemymgr.cppにある「load_enemy_order」のenemy_orderに対する初期化部分も
関数化して、enemy.cppにその定義を置く、という風にしたほうがよい・・・?
そうしたら、enemymgr.cppは完全に構造体をポインタしか知らなくてよくなる・・・?

すみません、ISLeさんが仰っていたことも、3D_3Dさんが仰っていたことも大分掴みかけている気はするのですが・・・。


まだラッパーや、隠蔽は進めていないのですが、とりあえずいただいたヒントを元にした
コードを貼ってみます。
ただ、このコードを実行すると、最初の敵が100カウント目に出てくるのですが、その直前の99カウントで
フリーズしてしまうのです。関数の定義のどこかが悪いのは明白ですが、どこに問題があるのかがわかりません・・・。

コード:

//enemymgr.cpp
#include "DxLib.h"
#include "about_enemy.h"
#include "enemy.h"
#include "define.h"
#include "GV.h"


static enemy_order_t enemy_order[ENEMY_ORDER_MAX];//敵のパターン格納
static enemy_t enemy[ENEMY_MAX];//敵の実態
static int m_img;//敵の画像ハンドル




//敵の出現情報をエクセルから読み込んで格納する関数
void load_enemy_order(){
	int n,num,i,fp;
	char fname[]={"story1.csv"};
	int input[64];
	char inputc[64];

	fp = FileRead_open(fname);//ファイル読み込み
	if(fp == NULL){
		printfDx("read error\n");
		return;
	}
	for(i=0;i<2;i++)//最初の2行読み飛ばす
		while(FileRead_getc(fp)!='\n');

	n=0 , num=0;
	while(1){
		for(i=0;i<64;i++){
			inputc[i]=input[i]=FileRead_getc(fp);//1文字取得する
			if(inputc[i]=='/'){//スラッシュがあれば
				while(FileRead_getc(fp)!='\n');//改行までループ
				i=-1;//カウンタを最初に戻して
				continue;
			}
			if(input[i]==',' || input[i]=='\n'){//カンマか改行なら
				inputc[i]='\0';//そこまでを文字列とし
				break;
			}
			if(input[i]==EOF){//ファイルの終わりなら
				goto EXFILE;//終了
			}
		}
		switch(num){
						case 0: enemy_order[n].cnt      =atoi(inputc);break;
						case 1: enemy_order[n].pattern  =atoi(inputc);break;
						case 2: enemy_order[n].knd      =atoi(inputc);break;
						case 3: enemy_order[n].x        =atof(inputc);break;
						case 4: enemy_order[n].y        =atof(inputc);break;
						case 5: enemy_order[n].sp       =atof(inputc);break;
						case 6: enemy_order[n].bltime   =atoi(inputc);break;
						case 7: enemy_order[n].blknd    =atoi(inputc);break;
						case 8: enemy_order[n].col      =atoi(inputc);break;
						case 9: enemy_order[n].hp       =atoi(inputc);break;
						case 10:enemy_order[n].blknd2   =atoi(inputc);break;
						case 11:enemy_order[n].wait     =atoi(inputc);break;
						case 12:enemy_order[n].item_n[0]=atoi(inputc);break;
						case 13:enemy_order[n].item_n[1]=atoi(inputc);break;
						case 14:enemy_order[n].item_n[2]=atoi(inputc);break;
						case 15:enemy_order[n].item_n[3]=atoi(inputc);break;
						case 16:enemy_order[n].item_n[4]=atoi(inputc);break;
						case 17:enemy_order[n].item_n[5]=atoi(inputc);break;
		}
		num++;
		if(num==18){
			num=0;
			n++;
		}
	}
EXFILE:
	FileRead_close(fp);
}




enemy_order_t *get_next_enemy_order(int cnt){
	int i;
	for(i=0;i<ENEMY_ORDER_MAX;i++){
		if(cnt==enemy_order[i].cnt){
			return &enemy_order[i];
		}
	}
	return NULL;
}




//ISLeさんがNO:38で「この場合、配列の空き要素へのポインタを返す関数に置き換えるのが
//良いと思います。」と仰ってたのはこういうことでしょうか・・・?
enemy_t *get_enemy_buffer(){
	int i;
	for(i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==0){
			return &enemy[i];
		}
	}
	return NULL;
}


void init_enemy_buffer(enemy_t *enemy,enemy_order_t *enemy_order){
	int i;
	enemy->flag   =1;//フラグ
	enemy->cnt    =0;//カウンタ
	enemy->pattern=enemy_order->pattern;//移動パターン
	enemy->back_col=GetRand(4);
	enemy->knd    =enemy_order->knd;//敵の種類
	enemy->x      =enemy_order->x;//座標
	enemy->y      =enemy_order->y;
	enemy->sp     =enemy_order->sp;//スピード
	enemy->bltime =enemy_order->bltime;//弾の発射時間
	enemy->blknd  =enemy_order->blknd;//弾幕の種類
	enemy->blknd2 =enemy_order->blknd2;//弾の種類
	enemy->col    =enemy_order->col;//色
	enemy->wait   =enemy_order->wait;//停滞時間
	enemy->hp     =enemy_order->hp;//体力
	enemy->hp_max =enemy->hp;//体力最大値
	enemy->vx     =0;//水平成分の速度
	enemy->vy     =0;//鉛直成分の速度
	enemy->ang    =0;//角度
	for(i=0; i<6; i++){
		enemy->item_n[i]=enemy_order->item_n[i];//落とすアイテム
	}

}


void enemy_enter() { //敵の行動を登録・制御する関数
	enemy_order_t *enemy_order_ptr;
	//敵キャラの出現オーダーからタイミングの一致するものを選択
    // 以下を選択されたオーダーごとに繰り返す
	while(enemy_order_ptr=get_next_enemy_order(global_stage_cnt)){
		enemy_t *enemy_ptr;
		//敵キャラ用のメモリ領域を新規に取得する
		enemy_ptr=get_enemy_buffer();
		if(enemy_ptr==NULL){
			continue;
		}
		//前段で取得した敵キャラ用のメモリ領域を初期化する
		init_enemy_buffer(enemy_ptr,enemy_order_ptr);
	}
}














void EnemyMgr_Init(){
	memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX);
	memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);
	m_img=LoadGraph("キャラクター/雑魚1.png");
	load_enemy_order();
	for(int i=0;i<ENEMY_MAX;i++){
		Enemy_Init(&enemy[i],m_img);
	}
}


void EnemyMgr_Update(){
	enemy_enter();
	for(int i=0;i<ENEMY_MAX;i++){
		Enemy_Update(&enemy[i]);
	}
}


void EnemyMgr_Draw(){
	for(int i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==1){
			Enemy_Draw(&enemy[i]);
		}
	}
}



void EnemyMgr_Final(){
}




アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#73

投稿記事 by みけCAT » 10年前

SDD さんが書きました:ただ、このコードを実行すると、最初の敵が100カウント目に出てくるのですが、その直前の99カウントで
フリーズしてしまうのです。関数の定義のどこかが悪いのは明白ですが、どこに問題があるのかがわかりません・・・。
get_next_enemy_order()で敵を取得した後、cnt(global_stage_cnt)またはその他のフラグを更新していないので、
同じ敵が無限回取得され、無限ループになっているのだと思います。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Rittai_3D
記事: 525
登録日時: 12年前

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#74

投稿記事 by Rittai_3D » 10年前

オフトピック
ISLe()さんの方法をとられるということと、高校が始まり、なかなか趣味の時間に使えなくなったために手を引いていました。

個人的な感想なので無視して下さっても構いません。思ったことを言わせていただきますと、なぜ一体から始めないのでしょうか?

まず一体だけの処理を書き、それがうまくいったら、配列などを用いて敵の数を増やしてやれば良いと思います。

前にも書きましたが、わたしが載せたコードのmain()に書いたpHogeを配列にすれば敵の処理っぽくなると思うのですが。

あまり言うとISLeさんに怒られてしまうの邪魔をしてしまうので、不快に感じたら無視してくださって構いません。これ以降は投稿しませんので、構造化頑張って下さい。
#一部文章を変更、追加しました
最後に編集したユーザー Rittai_3D on 2014年9月06日(土) 19:06 [ 編集 1 回目 ]
初心者です

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#75

投稿記事 by ISLe() » 10年前

ハングアップの原因はみけCATさんの指摘どおりで、get_next_enemy_order関数が同じオーダーを返し続けてしまうせいです。
関数名が表すとおりに、次に一致するオーダーを返すようにしてください。
get_next_enemy_order関数の中を変えるだけでは実現できません。
考えてみてください。

InitやUpdateのようなフレームワーク的な実装を見据えた抽象化が無駄なわけではありません。
ただし、それはずっと上層です。
構造化は、階層を成すものです。
作るときは上からでも下からでも途中からでもそれはできるのですが、いまは既存のコードをリファクタリングしているので一気に変えるとゴールが見えにくくなってしまいます。
分かりやすいところから順番に手を付けるのが良いと思います。

どのソースファイルに属するのが良いかは、実際にやってみてください。
いまはまだ構造化の途中なので試行錯誤を繰り返すことになるでしょうけど、いずれ腑に落ちる瞬間が来るはずです。

提示されたコードは、良い感じに構造化が進んでいると思います。
まだまだ先は長いですが。

無理矢理作ったEnemyMgr_*関数が構造化を妨げていますのでそちらを先に片付けたほうが良いと思います。
enemy配列やenemy_order配列などとつぜん出てきてますよね。
整理してみてください。


既に動いているものを変更することには、間違いにすぐ気付くことができる、モチベーションを維持できるというメリットがあると思います。
他人が書いたコードをベースにした作業は、単に知識を得るだけでなく、貴重な体験となるはずです。
オフトピック
わたしが『怒る』から云々などと勝手に人格を作り上げて批判するのはいいかげんやめていただきたいのですが。
不満があるのなら、質問者さんに対して認められるように、反対意見でも何でも主張して戦ってください。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#76

投稿記事 by SDD » 10年前

みけCATさん
アドバイスありがとうございます。
確かにそのようでした。今はとりあえず応急処置として、enemy_enterの最後にreturn文を置いています。
最終的には、*get_next_enemy_orderを直します。


3Dさん
お忙しいのにすみません・・・。
理由は以前も書いたとおりです。稚拙な理由ではあるのですが・・・。
また、どうせなら、目的に近い方での実践練習を見ていただきながら練習したいという気持ちもあります。
「隠蔽ができる構造」を理解できたら、3Dさんが提示してくださったコードの理解もさらに進むと考えています。
どの手段をとるかの最終判断はまだですが、「より良きコード」よりも「より良き構造」の方から勉強したかったのです。
3Dさんの回答者への熱意は大事なものだと考えます。どうかまたご教示ください。へたくそなりにですが、僕はISLeさんが
仰っていることと、3Dさんが仰ってくれたことの両方を、なるべく租借しながら勉強しています。



ISLeさん
get_next_enemy_order関連の修正、ソースファイルの配置、考えます。
たしかに、今はenemy_enter関連で精一杯なので、全体のラッパー構造はとりあえず保留にします。
「他人が書いたコードをベースにした作業は、単に知識を得るだけでなく、貴重な体験となるはずです。」
というのは励みになります。コードを教わることに少し罪悪感があるので・・・。

すみません、「無理矢理作ったEnemyMgr_*関数」とは、このenemymgr.cppの一連の関数のことでしょうか・・・。

また、現状の私のコードでは不可能なことが一つあることに気付いたのですが、「同じカウントで出てくる敵」の出現が
現状では不可能なのですが、これも、構成を直せば実現可能になるでしょうか。

これも含めて考えます。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#77

投稿記事 by ISLe() » 10年前

SDD さんが書きました:すみません、「無理矢理作ったEnemyMgr_*関数」とは、このenemymgr.cppの一連の関数のことでしょうか・・・。
そうです。
現状のenemy_enter関数は、その機能から見て、管理部のものであることにはお気付きでしょうか。
EnemyMgr_*関数がenemy配列やenemy_order配列を直接参照しているために『管理部』として独立するのを妨げているわけです。

ここは既存のコードをいじってもうまくいかないでしょう。
考え方をひとひねりする必要があります。
設計レベルでどうあるべきかという視点で考えてみてください。
どうしても分からなければ質問してください。
SDD さんが書きました:また、現状の私のコードでは不可能なことが一つあることに気付いたのですが、「同じカウントで出てくる敵」の出現が
現状では不可能なのですが、これも、構成を直せば実現可能になるでしょうか。
先に書いたとおり、get_next_enemy_order関数の中身だけでは対応できません。
『管理部』に属する情報として、どこまでオーダーを進めたか記憶する場所を用意して、それを使うようにしてみてください。

このような何かを順に列挙する仕組みはアプリケーションプログラムを作るときによく出てくるので覚えておくと良いと思います。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#78

投稿記事 by SDD » 10年前

ISLeさん
いつもありがとうございます。
「現状のenemy_enter関数は、その機能から見て、管理部のものであることにはお気付きでしょうか。
EnemyMgr_*関数がenemy配列やenemy_order配列を直接参照しているために『管理部』として独立するのを妨げているわけです。」
とは具体的には *get_next_enemy_order関数、*get_enemy_buffer関数のことですよね?
私は、この関数をenemy.cppに持っていけばいい(そうすれば、enemymgr.cppは配列を直接知らなくて済む・・・)のかなと思っていたのですが、
そういうレベルの話ではない、ということでしょうか・・・。

後者の「同じカウントの敵のオーダー」についても、管理部次第なのですね。
その仕組みはアプリケーションにも役立つと・・・。
少し考えてます。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#79

投稿記事 by ISLe() » 10年前

get_next_enemy_order関数とget_enemy_buffer関数だけを指しているわけではないです。

enemy配列やenemy_order配列を直接参照する必要もないのに直接参照しているせいで、適切に分類できなくなっていることを問題にしています。
分類する対象は、関数定義や構造体定義だけではありません。
外部変数にも注目してください。

もちろんenemy.cppに移動したほうが良いと思ったものはどんどん移動していってください。
ところでget_next_enemy_order関数は、enemy.cppが相応しいでしょうか。

現段階で、どのソースファイルにどの定義(あるいは宣言)が収まるか、箇条書きにして眺めてみるのも良いかもしれません。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#80

投稿記事 by SDD » 10年前

ISLeさん
すみません。
コードを載せるペースや書き込みのペースが滞ってしまっていますが、ここだけは理解したいので、
時間をおかけしてしまいますが、これだけは最後までやります。どうしても分からなければ、そのときはヒントを頂こうと思います。

get_next_enemy_orderは「オーダー」というくらいですから、「個」の範疇ではありませんね・・・。

C++をすこしかじったときも、どうにも「管理部」はどうあるべき、どう実装すべきで、「個」はどうあるべきか、みたいなことに悩んでしまいました。
そういう意味でも、今回のトピックを解決することは勉強になります。(管理部や個をとりあえずは掴めるようになる、抽象化、構造化や隠蔽を学べるため)

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#81

投稿記事 by SDD » 10年前

現状の問題をとりあえず、二つにまとめました。
一つは、「ハングアップ対策」、「同じカウントの敵の制御のための構造にする」
これは、管理部のオーダーがどこまで進めたかを記憶する関数を用意する?もので、
これはとりあえず後回しにします。

もう一つは、enemymgr.cppの関数が直接、配列の要素にアクセスしているため、
管理部の独立、構造化を妨げているというもの。
まずこちらから解決します。


「ここは既存のコードをいじってもうまくいかないでしょう。
考え方をひとひねりする必要があります。設計レベルでどうあるべきかという視点で考えてみてください。」
とのことでしたが、やはり難しいです。できる限りは自分で考える癖をつけたいので、少しだけでいいので、ヒントだけを頂きたいです。
まったく形から変えてしまうべきなのでしょうか。どのように一ひねりすべきなのでしょうか・・・。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#82

投稿記事 by SDD » 10年前

追記
とりあえず、管理部と個を分別しようと思い、考えたのですが、*get_next_enemy_orderはやはり「個」に移動させるべきではないし、
init_enemy_bufferも、「敵を初期化する」というのは「個」の役割ではないような気がして、これも動かせない。
enemy_enterは言うまでも無く「管理部」だし、結局、「個」である、enemy.cppに移動させたのは*get_enemy_bufferでした。
ただ、*get_enemy_bufferが「「空いている番号の敵のポインタを返す役目を持つ」と考えると、これすらも「個」の範疇ではないような気がしてきました・・・。
どうもやはり「管理」と「個」がわかっていないです。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#83

投稿記事 by ISLe() » 10年前

バッファとオブジェクトを別と考えるかどうかは難しい部分ですね。
実装に関することなので、とりあえず器として同じものという扱いで良いのではないかと思います。
全体として配列という実装に依存しないようにすることで、その辺も解決するはずです。

前に構造化のイメージを具体的なコードっぽくしたときに、こちらのほうがスマートと書いたものがありますね。
あちらだと、バッファが表に出ません。
構造としてどうあるべきかがより強く出ていると思います。


ひとひねり、という言い方が変だったかもしれません。
新しいenemy_enter関数では、enemy_t*しか出てこないので、それが配列の要素であることは分かりません。
むしろ、抽象化するということは、配列の要素であることに依存してはいけないということです。

EnemyMgr_*関数は、配列に対して要素を順番に回していて、思いっきり依存しています。

管理部が独自にリストを用意して、enemy_enter関数で得たenemy_t*を登録していくことで、配列に対する依存をなくすことができます。
ただし、これだと管理部のリストでの順番に敵が処理されるので、元の動作と変わってしまう可能性があります。

もうひとつは、配列であることを隠して、配列の要素順にenemy_t*を返すようにする方法です。
こちらはget_next_enemy_order関数と同じ考え方が使えます。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#84

投稿記事 by SDD » 10年前

ISLeさん
回答ありがとうございます。
まだ整理し切れていないのですが、問題の解決として、後者の
「配列であることを隠して、配列の要素順にenemy_t*を返すようにする方法」をとりたいのですが、「get_next_enemy_order関数と同じ考え方が使えます」
とのことですが、今のget_next_enemy_orderは配列に依存しているため、まずそういうところからなおさなくてはいけないのですよね。
お手間をおかけしてすみません。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#85

投稿記事 by ISLe() » 10年前

設計が配列に依存しているのと、実装が配列に依存しているのとでは、意味が違います。

get_next_enemy_order関数を利用する側はenemy_order_t*をひとつずつ受け取ります。
その際、その実装が配列であるかどうかは知る由もないことなのです。

実装が現時点で配列を用いているとしても、設計としては配列に依存していないのです。
配列以外の方法に実装を変更しても、get_next_enemy_order関数を利用している箇所は変更する必要がありません。

現時点では、get_next_enemy_order関数がひとつずつオーダーを返すようにはなっていないようですが、それは実装が配列であることとは関係ありません。
配列のままで、get_next_enemy_order関数という抽象化されたステートメントは実装できます。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#86

投稿記事 by SDD » 10年前

ISLeさん

なるほど・・・。配列がどうというよりか、管理部であるenemy_enterが
配列だとかどうとかを何も知らずにただ受け取ることが大事なのですね。

また、私がNO.81で書いた一つ目の問題に起因する、「get_next_enemy_order関数がひとつずつオーダーを返すようにはなっていない」というの
に関しては、それは配列がどうこうとはまったく関係ないのですね。



「get_next_enemy_order関数を利用する側はenemy_order_t*をひとつずつ受け取ります。
その際、その実装が配列であるかどうかは知る由もないことなのです。

実装が現時点で配列を用いているとしても、設計としては配列に依存していないのです。
配列以外の方法に実装を変更しても、get_next_enemy_order関数を利用している箇所は変更する必要がありません。」

というのは理解できたのですが、「enemy配列やenemy_order配列を直接参照している」のが問題なら、結局get_next_enemy_orderなどは
配列を直接参照して、「配列に対して要素を順番に回して」いるから、get_next_enemy_orderなどの現状はあまり正しくない状態なのですよね?

直接配列に参照する必要もないのに、そうやってしまっているとのことですが、他にいい手段が思い浮かびません・・・。

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#87

投稿記事 by ISLe() » 10年前

実装として配列を使うかどうかは自由なので、配列を使っていること自体は悪ではありません。

配列を、『直接』参照すべきではないのに参照している箇所があるとコードの汎用性が著しく損なわれ応用が効かなくなります。

enemy_order配列に関しては、get_next_enemy_order関数とload_enemy_order関数の内側で参照しますが、それ以外には隠すことができるはずです。

ところが、EnemyMgr_Init関数にenemy_order配列を直接参照して中身をクリアするコードがあるためにそれができません。

どうすれば良いかというと、get_next_enemy_order関数やload_enemy_order関数が含まれるモジュールに、オーダーの初期化関数を追加して、そこにenemy_order配列をクリアするコードを移動し、追加した関数を呼び出すコードに置き換えます。
実装が見えないように「オーダーを初期化する」という抽象化したステートメントに置き換えて構造化するわけです。

あるいは、load_enemy_order関数にenemy_order配列をクリアする処理を含めてしまっても良いかと思います。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#88

投稿記事 by SDD » 10年前

なるほど・・・。ついenemy_enterやそれに内包する関数ばかりに気を取られて、EnemyMgr_Initなどを全然見ていませんでした。
とりあえず、現状のコードを貼ります。

コード:

#ifndef DEF_ENEMYMGR_H
#define DEF_ENEMYMGR_H


//enemymgr_h

void EnemyMgr_Init();

void EnemyMgr_Update();

void EnemyMgr_Draw();

void EnemyMgr_Final();


#endif

コード:

#include "DxLib.h"
#include "about_enemy.h"
#include "enemy.h"
#include "define.h"
#include "GV.h"


//enemymgr.cpp



typedef struct{
	//カウンタ、移動パターン、敵の種類
	int cnt,pattern,knd;
	//初期座標と移動スピード
	double x,y,sp;
	//弾幕開始時間、弾幕の種類、色、体力、弾の種類、停滞時間、アイテム(6種類)
	int bltime,blknd,col,hp,blknd2,wait,item_n[6];
}enemy_order_t;




static enemy_order_t enemy_order[ENEMY_ORDER_MAX];//敵のパターン格納





//敵の出現情報をエクセルから読み込んで格納する関数
void load_enemy_order(){



	//enemy_orderをゼロクリア
    memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX);




	int n,num,i,fp;
	char fname[]={"story1.csv"};
	int input[64];
	char inputc[64];

	fp = FileRead_open(fname);//ファイル読み込み
	if(fp == NULL){
		printfDx("read error\n");
		return;
	}
	for(i=0;i<2;i++)//最初の2行読み飛ばす
		while(FileRead_getc(fp)!='\n');

	n=0 , num=0;
	while(1){
		for(i=0;i<64;i++){
			inputc[i]=input[i]=FileRead_getc(fp);//1文字取得する
			if(inputc[i]=='/'){//スラッシュがあれば
				while(FileRead_getc(fp)!='\n');//改行までループ
				i=-1;//カウンタを最初に戻して
				continue;
			}
			if(input[i]==',' || input[i]=='\n'){//カンマか改行なら
				inputc[i]='\0';//そこまでを文字列とし
				break;
			}
			if(input[i]==EOF){//ファイルの終わりなら
				goto EXFILE;//終了
			}
		}
		switch(num){
						case 0: enemy_order[n].cnt      =atoi(inputc);break;
						case 1: enemy_order[n].pattern  =atoi(inputc);break;
						case 2: enemy_order[n].knd      =atoi(inputc);break;
						case 3: enemy_order[n].x        =atof(inputc);break;
						case 4: enemy_order[n].y        =atof(inputc);break;
						case 5: enemy_order[n].sp       =atof(inputc);break;
						case 6: enemy_order[n].bltime   =atoi(inputc);break;
						case 7: enemy_order[n].blknd    =atoi(inputc);break;
						case 8: enemy_order[n].col      =atoi(inputc);break;
						case 9: enemy_order[n].hp       =atoi(inputc);break;
						case 10:enemy_order[n].blknd2   =atoi(inputc);break;
						case 11:enemy_order[n].wait     =atoi(inputc);break;
						case 12:enemy_order[n].item_n[0]=atoi(inputc);break;
						case 13:enemy_order[n].item_n[1]=atoi(inputc);break;
						case 14:enemy_order[n].item_n[2]=atoi(inputc);break;
						case 15:enemy_order[n].item_n[3]=atoi(inputc);break;
						case 16:enemy_order[n].item_n[4]=atoi(inputc);break;
						case 17:enemy_order[n].item_n[5]=atoi(inputc);break;
		}
		num++;
		if(num==18){
			num=0;
			n++;
		}
	}
EXFILE:
	FileRead_close(fp);
}




enemy_order_t *get_next_enemy_order(int cnt){
	int i;
	for(i=0;i<ENEMY_ORDER_MAX;i++){
		if(cnt==enemy_order[i].cnt){
			return &enemy_order[i];
		}
	}
	return NULL;
}






void init_enemy_buffer(enemy_t *enemy,enemy_order_t *enemy_order){
	int i;
	enemy->flag   =1;//フラグ
	enemy->cnt    =0;//カウンタ
	enemy->pattern=enemy_order->pattern;//移動パターン
	enemy->back_col=GetRand(4);
	enemy->knd    =enemy_order->knd;//敵の種類
	enemy->x      =enemy_order->x;//座標
	enemy->y      =enemy_order->y;
	enemy->sp     =enemy_order->sp;//スピード
	enemy->bltime =enemy_order->bltime;//弾の発射時間
	enemy->blknd  =enemy_order->blknd;//弾幕の種類
	enemy->blknd2 =enemy_order->blknd2;//弾の種類
	enemy->col    =enemy_order->col;//色
	enemy->wait   =enemy_order->wait;//停滞時間
	enemy->hp     =enemy_order->hp;//体力
	enemy->hp_max =enemy->hp;//体力最大値
	enemy->vx     =0;//水平成分の速度
	enemy->vy     =0;//鉛直成分の速度
	enemy->ang    =0;//角度
	for(i=0; i<6; i++){
		enemy->item_n[i]=enemy_order->item_n[i];//落とすアイテム
	}

}


void enemy_enter() { //敵の行動を登録・制御する関数
	enemy_order_t *enemy_order_ptr;
	//敵キャラの出現オーダーからタイミングの一致するものを選択
	// 以下を選択されたオーダーごとに繰り返す
	while(enemy_order_ptr=get_next_enemy_order(global_stage_cnt)){
		enemy_t *enemy_ptr;
		//敵キャラ用のメモリ領域を新規に取得する
		enemy_ptr=get_enemy_buffer();
		if(enemy_ptr==NULL){
			continue;
		}
		//前段で取得した敵キャラ用のメモリ領域を初期化する
		init_enemy_buffer(enemy_ptr,enemy_order_ptr);
		return;
	}
}









void EnemyMgr_Init(){
	load_enemy_order();
	Enemy_Init();
	
}


void EnemyMgr_Update(){
	enemy_enter();
	Enemy_Update();

}


void EnemyMgr_Draw(){
	Enemy_Draw();


}



void EnemyMgr_Final(){
}



コード:

#ifndef DEF_ENEMY_H
#define DEF_ENEMY_H


//enemy.h


//空いている敵のポインタを返す
enemy_t *get_enemy_buffer();


//初期化、ロード
void Enemy_Init();
//自機の計算
void Enemy_Update();
//描画
void Enemy_Draw();
//終了処理
void Enemy_Final();


#endif

コード:

#include "DxLib.h"
#include "about_enemy.h"
#include "define.h"
#include "math.h"

//enemy.cpp
static enemy_t enemy[ENEMY_MAX];//敵の実態
static int m_img;//敵の画像ハンドル



enemy_t *get_enemy_buffer(){
	int i;
	for(i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==0){
			return &enemy[i];
		}
	}
	return NULL;
}




//敵の行動制御
void enemy_act(enemy_t *enemy){
	if(enemy->flag==1){//その敵のフラグがオンになってたら
		if(0<=enemy->pattern && enemy->pattern<ENEMY_PATTERN_MAX){


			//とりあえずの移動制御パターン
			int t=enemy->cnt;
			if(t==0){
				enemy->vx=3;
				enemy->vy=2;//下がってくる
			}
			if(t==60){
				enemy->vy=0;//止まる
				enemy->vx=0;
			}
			if(t==60+enemy->wait)//登録された時間だけ停滞して
				enemy->vy=-2;//上がっていく



			enemy->x+=cos(enemy->ang)*enemy->sp;
			enemy->y+=sin(enemy->ang)*enemy->sp;
			enemy->x+=enemy->vx;
			enemy->y+=enemy->vy;
			enemy->cnt++;
			//敵が画面から外れたら消す
			if(enemy->x<-50 || F_M_X+20<enemy->x || enemy->y<-50 || F_M_Y+20<enemy->y)
				enemy->flag=0;
		}
		else
			printfDx("enemy[i].patternの%d値が不正です。",enemy->pattern);

	}
}




void Enemy_Init(){
	memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);
	m_img=LoadGraph("キャラクター/雑魚1.png");
	for(int i=0;i<ENEMY_MAX;i++){
		enemy[i].img_enemy[0]=m_img;
	}
}


void Enemy_Update(){
	for(int i=0;i<ENEMY_MAX;i++){
		enemy_act(&enemy[i]);
	}
}


void Enemy_Draw(){
	for(int i=0;i<ENEMY_MAX;i++){
		if(enemy[i].flag==1){
			DrawGraph(enemy[i].x+F_X,enemy[i].y+F_Y,enemy[i].img_enemy[0],TRUE);
		}
	}
}


void Enemy_Final(){
}

コード:

#ifndef DEF_ABOUT_ENEMY_H
#define DEF_ABOUT_ENEMY_H


//about_enemy.h

//敵に関する構造体
typedef struct{
	//フラグ、カウンタ、移動パターン、向き、敵の種類、HP最大値、落とすアイテム、画像、背景色
	int flag,cnt,pattern,knd,hp,hp_max,item_n[6],img,back_col;
	//座標、速度x成分、速度y成分、スピード、角度
	double x,y,vx,vy,sp,ang;
	//弾幕開始時間、弾幕の種類、弾の種類、色、状態、待機時間、停滞時間
	int bltime,blknd,blknd2,col,state,wtime,wait;
	//画像ハンドル
	int img_enemy[3];
}enemy_t;





#endif
とりあえずすこしずつ隠蔽も進めようと思い、今までは、enemy_order_tはabout_enemy.hに定義していたのですが、それをenemy_mgr.cppに移動しました。
また、enemy_tも前方宣言し、enemy_mgr.cppに対し、enemy_tや、enemy_t enemy[ENEMY_MAX]を完全に隠蔽しようとしたのですが、init_enemy_bufferがenemy_tのメンバにアクセスしているため
、それはまだできないのですね。これも、enemy_enter周りを直し、「バッファが表に出ない」ようにするべきということでしょうか・・・。

アバター
Tatu
記事: 445
登録日時: 14年前
住所: 北海道

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#89

投稿記事 by Tatu » 10年前

SDDさんが
「get_next_enemy_order関数は、enemy.cppが相応しいでしょうか。」(No:79)、
「get_next_enemy_order関数やload_enemy_order関数が含まれるモジュール」(No:87)
についてどう考えているのか気になりました。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#90

投稿記事 by SDD » 10年前

Tatuさん
前者に関しては、get_next_enemy_orderにはenemy.cppはふさわしくないと思っています。
後者に関しては、よくわからないのですが、ISLeさんがその書き込みをされたときにも思ったことなのですが、新しくモジュールを作るべきということなのでしょうか・・・。
get_next_enemy_orderやload_enemy_orderには、enemymgr.cppさえもふさわしくないということでしょうか。

アバター
Tatu
記事: 445
登録日時: 14年前
住所: 北海道

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#91

投稿記事 by Tatu » 10年前

モジュールを作った場合のコードを書いて今までのコードと比較し、
どちらがよいのかを考えてみるといいのではないでしょうか。

No:75に
「どのソースファイルに属するのが良いかは、実際にやってみてください。
いまはまだ構造化の途中なので試行錯誤を繰り返すことになるでしょうけど、いずれ腑に落ちる瞬間が来るはずです。」
と書かれていることですし。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#92

投稿記事 by SDD » 10年前

Tatuさん
そうですね・・・。
ただ、あまり、モジュールを増やして、あの関数はそのモジュール、この関数はあのモジュールという風にすると、
色んなモジュールでenemyやenemy_orderにアクセスしなければいけなくなりそうで、隠蔽がむずかしくなりそうです・・・。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#93

投稿記事 by SDD » 10年前

ISLeさん
すみません、まだ、当面の構造化や隠蔽が完了していないのですが、どうしても気になり、分からないことがあって、それも合わせて質問させていただきます。
このトピックの本題から外れる質問ではないため、新しいトピックは作りませんでした。



龍神録の例なのですが、shotH.cppというモジュールの中に、

コード:


void shot_bullet_H000(int n){
        int k;
        if(shot[n].cnt==0){
                if(shot[n].flag!=2 && (k=shot_search(n))!=-1){
                        shot[n].bullet[k].knd   =enemy[shot[n].num].blknd2;
                        shot[n].bullet[k].angle =shotatan2(n);
                        shot[n].bullet[k].flag  =1;
                        shot[n].bullet[k].x     =enemy[shot[n].num].x;
                        shot[n].bullet[k].y     =enemy[shot[n].num].y;
                        shot[n].bullet[k].col   =enemy[shot[n].num].col;
                        shot[n].bullet[k].cnt   =0;
                        shot[n].bullet[k].spd   =3;
                        se_flag[0]=1;
                }
        }
}
このようにして、敵の弾幕パターンが書かれるのですが、
このモジュール内では、shotという構造体変数とenemyという構造体変数にアクセスしているため、
隠蔽がされていないのですが、今回のようなやり方で進めているとき、どのように隠蔽できるのでしょうか。
構成から変えるべきなのでしょうか・・・。

このトピックでの本題はこういうところの隠蔽ができるようになることだったのですが、
思っていた以上に私が手間取ってしまっていて、なかなか進めていないため、言い出すタイミングがありませんでした・・・。




これまで通り、
・enemy関連を通して構造化を進める
・enter_enemy周辺の関数を改造して、オーダーをしっかり管理する。
というのと、
・上記のような部分を解決できるようにする。


今回、このトピックでは、これらの三つの問題を解決したいと思っています。

現状のコードはとりあえず、NO.88に載せましたので、見ていただけたらと思います

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#94

投稿記事 by ISLe() » 10年前

No.88のコードは概ね良いと思います。
EnemyMgr_*でEnemy_*にそのまま処理を委譲しているのが構造化としてはおかしなふうに見えますけど。
EnemyMgr/Enemyはプレフィックスなので仕方ないといえば仕方ないですが。
ステートメントとして見れば奇妙で、読みにくいコードになってます。

Tatuさんの指摘はもっともなので、実際にやってみて判断してください。



shot_bullet_H000関数については、enemy_enter関数と同様に構造化できます。
enemy[shot[n].num]という実体参照をenter_t*に変えるためにどうすれば良いか既に知っているはずです。
同様にshot[n]はshot_t*で参照するように、shot[n].bullet[k]はbullet_t*で参照するように置き換えてみてください。

先にプログラム全体でshot[n].numに関する部分を別の方法に置き換えてしまえば、この関数もenter_t*に関して楽になるはずです。
どちらを先に片付けるかはお好みで。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#95

投稿記事 by SDD » 10年前

ISLeさん
毎度ありがとうございます。
わかりました。色々動かしてみようと思います。


後者のことなのですが、また特定の構造体の型のポインタをshotH.cppで使うことになると思うのですが、
その型のポインタを使うためには、結局その構造体が定義されているモジュールをインクルードしなければ
いけないのでしょうか。そうするとうまい隠蔽の仕方がわからないです・・・。的外れでしたらすみません。

sq

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#96

投稿記事 by sq » 10年前

柴田芳樹さんの書かれたAPI設計の基礎のPDFです
参考になるでしょうか
http://www012.upp.so-net.ne.jp/eshibata ... basics.pdf

ISLe()

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#97

投稿記事 by ISLe() » 10年前

定義を必要とするなら、インクルードするのが当たり前です。

考えなければいけないのは、それをどこまで許すか、ということです。

shot_t、bullet_t、enemy_tを比べたら、enemy_tを書きかえる頻度がいちばん少なそうです。
実際にそうであればshotH.cppにenemy_tを公開するほうが保守性は高いと言えます。
その辺は状況次第であって正解はないのです。


インクルードしないように、という方向に意識が向きすぎていると思います。

もっとガッチリ隠蔽化を進めたいなら、細かくモジュールを分割しなければいけません。
でもそれはやりたくないとおっしゃっているので先に進まないのではないでしょうか。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#98

投稿記事 by SDD » 10年前

ISLeさん
そうですね・・・。たしかに神経質になりすぎてあまり進めていません・・・。
やはり、学んだことをすこしずつ活かしながらやっていきます。

オーダーのハングアップの対策を実装してみたのですが、いまいち自信がありません。
応急処置として、enter_enemy()の最後につけていたreturn文も取り去ってもハングアップしませんし、
同じオーダーに敵を出現させることもできるようにはなったのですが・・。

コード:


//enemymgr.cpp



enemy_order_t *get_next_enemy_order(int cnt,int order_num){
	int i;
	int n;//order_numとの比較
	for(i=0;i<ENEMY_ORDER_MAX;i++){
		if(cnt==enemy_order[i].cnt){

			//そのときの番号を記録
			n=i;
			if(order_num==n){
				return &enemy_order[i];
			}
		}
	}
	return NULL;
}


void enemy_enter() { //敵の行動を登録・制御する関数


	static int order_num=0;//オーダーを記録するカウント


	enemy_order_t *enemy_order_ptr;

	//敵キャラの出現オーダーからタイミングの一致するものを選択
	// 以下を選択されたオーダーごとに繰り返す
	while(enemy_order_ptr=get_next_enemy_order(global_stage_cnt,order_num)){
		enemy_t *enemy_ptr;
		//敵キャラ用のメモリ領域を新規に取得する
		enemy_ptr=get_enemy_buffer();
		if(enemy_ptr==NULL){
			continue;
		}
		//前段で取得した敵キャラ用のメモリ領域を初期化する
		init_enemy_buffer(enemy_ptr,enemy_order_ptr);

		order_num++;
	}
}





SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#99

投稿記事 by SDD » 10年前

sqさん
参考の資料、ありがとうございます。
読ませていただきました。ちょうど今自分が悩んでいるあたりのことが書かれていました。
今回のことに照らし合わせて完璧にすることは難しいですが、とても勉強になりました。

SDD

Re: 変数のカプセル化を守りながらのデータの受け渡しについて

#100

投稿記事 by SDD » 10年前

すみません。投稿した後に気付いたのですが、nという変数は必要ないですね。

閉鎖

“C言語何でも質問掲示板” へ戻る