関数化と変数の有効範囲

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

関数化と変数の有効範囲

#1

投稿記事 by りむ » 18年前

こんばんは。りむです。
着々と進んでいます。アクションの部分は、坂・ジャンプで登る時に下からすり抜ける足場…等、地形関係の部分は、まだ手をつけていませんが。。。

→or←…左右移動(二度押しでダッシュ)
↑…ジャンプ(ジャンプ量は調節可)
Z…防御
Z&(向いてる方向のキー)…プッシュガード(敵を押せる)
Z&(背中の方向のキー)…バックステップ(素早く後ろへ小さくジャンプ)
Z&↓…エフェクトつき防御(中身はまだ無し)

C…中段攻撃
C&↓…下段攻撃
C&←or→…上段攻撃
(ジャンプ中に)C…空中攻撃

こんなかんじになっています。細かい仕様をあげて言ったらきりがありませんけれど…。

まあ、こんなかんじで、次は敵を出現させる段階まできました。
現段階では、自由に移動するのみでAIも全くない状態ですが、
プレイヤーが重なろうとすると重なり防止のため押し戻されたりそれ以上進めなくなったりと存在感を出してあったり、
攻撃するとちゃんと当たったらダメージ受けて、仰け反ります。(HP設定とかはまだですが)
とりあえず、「敵がいる」状態にまではしました。

ここからが問題なのです。
敵というのは、同じマップに敵が何匹もいたり、同じ敵が2匹3匹といる場合が多いです。
その場合に、逐一敵の処理や情報を書いていたのではソースが超大ボリュームになるだけでなく、
把握も大変な上に同じ敵を出すとなるとまた同じソースをコピー添付しないといけなくなってしまいます。

なので、大した考えもなしにですが、
まず敵の情報に関するものをほとんど集めて構造体として、さらにその構造体を配列で定義してみました。

typedef struct enemy_data{
int enemy_id;
double x;
int y;
double vx;
double vy;
int muki;
int enemy_hp;
....
}enemy_data;

enemy_data en[100];


のような感じです。
各要素を使う時はen[0].vx*=0.9;のようにして、敵100体までを配列で扱えるようにしてみたのですが、
この場合もやはり全ての敵に関して処理をコピーあるいは作成していく必要があり、
さらにその時に同じ種類の出現位置以外は全くデータが同じの敵を作る場合に、
ソースが重複する部分が大量に出てきてしまい、とても勿体無くなってしまいます。

最低、敵の種類分だけのソースは用意して、同じ種類の敵が複数出た場合は、同じ処理を再利用できるようにしたいのですが…
こんな場合、どうしたらうまく処理することが出来るでしょうか…?


また、私の場合ほとんど自作関数を作ることをしない性質でして、
WinMain関数の中に処理をずらずらずらずらと書き記していってます。
今のところ、使っているのはマップとの衝突判定、敵[0]のマップとの衝突判定、プレイヤーの攻撃と敵との当たり判定(バウンディングボックスとか言うもの)をするものぐらいです。

自作関数での変数や処理の扱いがよくわからないせいですが…。
・自作関数では引数に指定した変数しか、WinMain関数の中の変数は扱えない?
・自作関数で行った処理で、どこまでが反映されるのかわからない。
 (自作関数で他の関数を呼び出したり、画面表示等の処理をさせたり…の実行可能範囲がわからない)
・自作関数で得た結果をWinMain関数に反映したい時、複数の結果を戻したい
・そもそもどれぐらいの処理を関数にすると良いのか

…など、今までプログラミングで習ってはいても、いまいち手がおぼつかないといいますか、手が出ないと言いますか…どうしても積極的に使おう、と思うことが出来ません。ちなみに、今は

変数宣言→マップ・画像や音楽のメモリ読み込み→(ここからループ開始)→プレイヤー移動処理→着地や慣性等の処理→防御関係の処理→攻撃関係の処理→ジャンプの処理→敵の処理(移動処理からダメージ処理まで多数)→画像の表示等→(デバッグ用の変数表示処理)→(ループ最初へ戻る)

のような一連の流れになっています。コメントアウトやデバッグ用数値表示機能を入れると、今では1800行ぐらいです。インテンドや改行は教科書みたいに整えてあります。
…なので、と言うか…、自作関数に関して説明していただけるか、もしくはわかりやすい解説ページなどの紹介をお願いしたいのです。
よろしくおねがいします><

バグ

無題

#2

投稿記事 by バグ » 18年前

同じ処理を繰り返すならば、関数化すべきです。
C++ならば、敵に関する情報全てをクラス化してしまうのもいいかと思いますよ。
構造体を引数で関数に渡す事は可能です。また、関数内でその構造体の中身を書き替えたい場合にはポインタを利用する事で可能になります。
今回の場合でしたら、メンバ変数に1つ変数(仮にMove_Eとします)を追加してもらって、敵の動きの種類を決定するルーチンのところで…

switch( Move_E )
{
case 1:
MoveEnemy1();
break;
case 2:
MoveEnemy2();
break;
}

みたいな感じにしてはいかがでしょうか?

管理人

Re:無題

#3

投稿記事 by 管理人 » 18年前

私が初めてゲームを作ったときも、どこまで関数化すればよいのかわからず、またダラダラと直線型プログラムを書いていました。
一直線な処理しかしないプログラムなので、一度に敵はいったいしか動きません。
関数化していないので、同じような処理でもまた新たにコピーしてソースをかかなければならないので、
ダラダラながくなりコードは1万行以上になりました。

何度か苦労しているうちに、もっとスマートにかけないのか?もっと処理に汎用性を持たせられないのか?
などと考えるようになりました。

いきついた答えは「とにかく細かく関数化した方がいい」ということです。
プログラミング手法にとにかく細かく関数化するという手法がありますが、たった1行しかない処理でも関数化したりします。
現に私も2~3行しかないものでも関数化したりします。
何でも関数化することで、汎用性が広がるんですよね。

例えば
void 入力を受け付ける関数(){


}

void 自分の動きを制御する関数(){


}

void 敵1の動きを制御をする関数(){


}

void 敵2の動きを制御する関数(){


}

void 描画する関数(){


}


WinMain(){

    while(1){
        入力を受け付ける関数();
        自分の動きを制御する関数();
        敵1の動きを制御する関数();
        敵2の動きを制御する関数();
        描画する関数();
    }

}
このように書いてみましょう。全てWinMainにダラダラかくより、WinMainを見ただけで、
どのような処理が行われているか把握しやすいですね。

自分の動きを制御する関数();
敵1の動きを制御する関数();
敵2の動きを制御する関数();

という似たような関数が3つありますね。
敵が1種類、2種類のうちはいいですが、30種類も40種類もあれば、このようにかけなくなってきます。
そんな時、関数ポインタを用いればとてもスムーズにかけます。
関数を呼び出すための配列を用意して、

[0]に自分の動きを制御する関数のポインタ
[1]に敵1の動きを制御する関数のポインタ
[2]に敵2の動きを制御する関数のポインタ

を持たせれば、これら全てを実行するときにfor文でたった1行で実行できますね。

関数ポインタについても、ゲームプログラミングの館で解説していますから、
参考にしてください。

http://dixq.net/g/index2.html#1

では、実際にコードを次に書いてみます。




・・・自作関数をあまり使用しないなら、
関数間の値受け渡しや、アドレス渡しをしても、意味がよくわかりませんか?

それならそちらからまず説明したほうがいいかと思いまして

管理人

Re:無題

#4

投稿記事 by 管理人 » 18年前

****自作関数について****

まず、①と②は同じ処理結果を示します。
①

#include <stdio.h>

int main(){
	int a;
	a=10;
	printf("%d",a);
	return 0;
}
②

#include <stdio.h>

void graph(int a){
	printf("%d",a);
	return ;
}

int main(){
	int a;
	a=10;
	graph(a);
	return 0;
}
main内でaを引数に持たせて処理をgraph関数に渡します。
graph関数では、渡されたaを自分の関数内で宣言したint aにコピーします。
そして、表示して処理をmainに返しています。
③

#include <stdio.h>

void graph(int a){
	printf("%d",a);
	a=20;
	return ;
}

int main(){
	int a;
	a=10;
	graph(a);
	printf("%d",a);
	return 0;
}

実行結果
10
graph関数内でaの値を変更しても、main関数内でのaは変化しません。
何故ならgraph関数へコピーしたaはコピーしたものであって、それそのものではありませn。
コピー先のデータをいくらいじっても、コピー元のデータは変化しないのです。
その証拠に、graph関数内のaとmain関数内のaではアドレスが違いいます。
④

#include <stdio.h>

void graph(int b){
	printf("%d",b);
	return ;
}

int main(){
	int a;
	a=10;
	graph(a);
	return 0;
}

実行結果
10
引数にする変数名と受け取る変数名は異なっていてもかまいません。
何故ならgraph関数で受け取るbはコピー先のデータであって、それそのものではないから
コピー先のデータ変数名はなんでもいいのです。

⑤

#include <stdio.h>

int graph(){
	return 20;
}

int main(){
	int a;
	a=10;
	a=graph();
	printf("%d",a);
	return 0;

}

実行結果
20

関数はreturn の値によって値を返すことが出来ます。
graph関数の最後にreturnした値は、main関数内で受け取ることが出来ます。

関数が値を返さない場合はvoid型、returnも値を書きません。
void graph(){
	return ;
}
関数が値を返す場合は、その返す値の型を関数名の前に書き、returnに返す値を書きます。
int graph(){
	return 10;
}
double graph(){
	return 10.0;
}
では、次に、以前graph関数内でのbはコピー先のデータであって、本体ではないから変更しても
本体に影響しないといいました。
変数に変化をもたらしたいときは、以下のようにします。
⑥

#include <stdio.h>

void graph(int *b){
	printf("%d\n",*b);
	*b=20;
	return ;
}

int main(){
	int a;
	a=10;
	graph(&a);
	printf("%d\n",a);
	return 0;
}

実行結果
10
20
graph関数内のbとmain関数内のaではアドレスが違うから値を変化させても、影響しないといいました。
それならアドレスを同じにして、示している先のデータの格納場所を同じにしてやれば、
値を変更して影響させることが出来ます。

では、アドレスを渡してみましょう。
graph(&a);

このように書けばaのアドレスが渡せますね。
void graph(int *b){
このように渡されたアドレスはポインタで受け取ります。
ポインタで受け取った*bはa本体と同じデータ先を示します。
ですから
*b=20;
などとしれやればaの値も変わります。
⑦

#include <stdio.h>

void graph(int b[ ]){
	printf("%d %d\n",b[0],b[1]);
	b[1]=30;
	return ;
}

int main(){
	int a[2];
	a[0]=10;	a[1]=20;
	graph(a);
	printf("%d %d\n",a[0],a[1]);
	return 0;
}

実行結果
10 20
10 30
配列を渡してやるときは、&や*をつけなくてもいいです。
何故なら、a[/url]はaと書くとそれ自体がアドレスを示すためです。
配列を使う場合は普段と変わりなく使用するだけで、値は変更できます。

これは構造体であっても、利用できます。
⑧

#include <stdio.h>

typedef struct{
	int x,y;
}enemy_t;

void graph(enemy_t enemy){
	enemy.x=200;
	enemy.y=150;
	return ;
}

int main(){
	enemy_t enemy;
	enemy.x=100;
	enemy.y=50;
	graph(enemy);
	printf("%d %d\n",enemy.x,enemy.y);
	return 0;
}

実行結果
100 50
以前と同じように、コピー先で値を変更しても、main内で変数の中身は変わりません。
また、enemy_t型の型はmain内で宣言してはいけません、両方の関数がこの型の種類を知っている必要があるので、
#include
の直下に書きましょう。
⑨

#include <stdio.h>

typedef struct{
	int x,y;
}enemy_t;

void graph(enemy_t *enemy){
	enemy->x=200;
	enemy->y=150;
	return ;
}

int main(){
	enemy_t enemy;
	enemy.x=100;
	enemy.y=50;
	graph(&enemy);
	printf("%d %d\n",enemy.x,enemy.y);
	return 0;
}

実行結果
200 150
構造体のコピー先では、アロー演算子「->」を使って参照します。
以前と同様にコピー先とコピー元の示しているアドレスを同じにしたので変更が可能になりました。

管理人

Re:無題

#5

投稿記事 by 管理人 » 18年前

****変数の有効範囲について****

変数の有効範囲についてですが、基本的に「括弧から括弧まで」です。
基本的にプログラムは全て括弧で始まり括弧で終わります。

特に意味のない括弧を途中で書いてもかまいません。
括弧は好きなところにかけます。
#include <stdio.h>

int a;				a
				a
void graph(int b){		ab
	b=1;			ab
	{			ab
		int c;		abc
		c=10;		abc
				abc
	}			abc
				ab
	b=2;			ab
	return ;		ab
}				a
				a
int main(){			a 
	int d;			a  d
	d=1;			a  d
	graph(d);		a  d
	return 0;		a  d
}				a
変数の有効範囲はプログラムの右部分に書いておきました。

aのように全ての領域で使える変数をグローバル変数
b,c,dのように一部の関数内でしか使用できない変数をローカル変数といいます。

管理人

Re:無題

#6

投稿記事 by 管理人 » 18年前

え~とどうしましょう。
よりよいゲームプログラムの書き方・・という事でアドバイスをお書きしていたつもりだったんですが、
自作関数の説明と、変数の有効範囲の説明だけでこんなに長くなってしまいました^^;

あまり長くなると読むほうも億劫になるでしょうから、とりあえず、この辺でやめておきますね。
後他に聞きたいことがあればお聞きください☆

なお、「説明してくれ」という質問だと、回答に限界がありますので、
一応googleなどで検索し、解説サイトを読んで学んでくださいね。
これだけ詳しく書いても、まだ全部解説できたわけではありません。
関数に関しては色んな性質がありますから、その全てについてここでタイピングした文字によって解説する事は出来ません。
googleは賢いもので、人気のあるサイト、みんながわかりやすくてイイと思っているサイト順に表示されます。
ですから、たいていgoogleで検索すればわかりやすいサイトが上の方に出てきます。
上から20件~30件も調べてばだいたい把握できるでしょう。

わからない関数名があれば、その関数名を、
自作関数について解らなければ「C言語 入門」とでも
関数の値受け渡しについてわからないければ「関数の値受け渡し」とでも
検索すれば出てきます。

「どのようなキーワードで入力すれば自分の思うサイトが見つけられるか」

を検索していくうちに身に付けるのもプログラミングのスキルの一つだと思うので、色々頑張ってください☆

Justy

Re:無題

#7

投稿記事 by Justy » 18年前

WinMain関数の中に処理をずらずらずらずらと書き記していってます それは・・・マズイですね。
 1関数が 1800行にもなると、そろそろ変数が大量になってたり、
同じような処理が複数あって管理しきれなくなったりして
破綻の兆しがあちこちに見えかけているかと思います。

 まずはゲーム制作の手を止めてでも、関数を使った小さなテストプログラムを
いくつか書いて動作をテストしながら関数についてしっかり学ぶことをお勧めします。

 
そもそもどれぐらいの処理を関数にすると良いのか
 http://www.play21.jp/board/formz.cgi?ac ... q&rln=3784

りむ

Re:無題

#8

投稿記事 by りむ » 18年前

出かけていて返事が遅くなりました><;
回答ありがとうございます。

>管理人さん
⑥以外は、全て調べて理解はしていたのですが、⑥は今回説明していただいたことで理解できました^^
変数の有効範囲も、今まで多少あいまいだったのが、きちんと理解できました。ご丁寧にありがとうございます。

ですが、説明の内容は、知りたい内容ぜんぶってわけではなかったみたいで…
私の説明がわかりにくかったですかね…。
私が一番知りたかったのは、
自作関数内でDXライブラリのリファレンスに載っている関数や、元からあるprintfやfopen等の処理を実行した際に、その関数の影響が自作関数の外…メイン関数やそれ以外の関数にも適用されるのか…みたいな感じです。
例として、メインで自作関数を呼んで、その中でグラフィックをメモリに読んでそのハンドルを指定したら、メイン関数でもそれが使えるようになるのかどうか…
もしくは、画像の表示という処理が自作関数で行われた場合、それがメイン関数に戻っても表示されたままなのかどうなのか… 
…のような感じみたいなものです。(なんだか自分でもよくわかんなくなってきましたが…)
…つまり、関数化する際にマズイ処理があるとイヤだなと思っての質問です><;
(それゆえに私は関数を作りたがらないようで…)
まあ、変数の有効範囲の説明のおかげで半分近くはどうなるかわかりましたが^^;

まあ、それも確かに、Justyさんが言われた、
>数を使った小さなテストプログラムをいくつか書いて動作をテストしながら
のように、使う際に一回一回調べていけばいいのですけどね…><;


>一応googleなどで検索し、解説サイトを読んで学んでくださいね。
…すみません。ですが、実は私、わからないことがあったら、仰るとおりすぐにGoogle先生のお世話になっているのです。
もちろん今回の質問も調べに調べてみたのですが、
苦しんで覚えるC言語と言うサイト等を見つけては読んでいきましたが、必要な情報、説明が無い、と言う状態になったのです。(理解不足もあるかもしれませんが…)
こちらの掲示板で質問させていただく場合は、必ずそういった自力では理解や解決できない、と言う時に、最終手段…と言ったら大げさですが、そのように思った時に、させていただいてます。
その説明を最初にしておくべきでしたね。すみませんでした。

むつ

Re:無題

#9

投稿記事 by むつ » 18年前

 スコープ(有効範囲)は理解しているけど、
どのようにメモリ上に展開されているか視覚的に分からないと不安という事でしょうか。
 デバッガを利用してみてはいかがでしょうか。

参考:http://altair.toc.cs.hiroshima-cu.ac.jp ... e/progex3/

管理人

Re:無題

#10

投稿記事 by 管理人 » 18年前

なんとなくおっしゃっている意味がわかりました。

では、「これなら大丈夫かな?」「これなら影響を及ぼすのかな?」など様々な思いつくパターンを全て実行してみたらすっきりするんじゃないかと思います。

私は何が疑問があったら全てのパターンについて実行してみないと気がスミマセン。
ですので普段作っているプロジェクト以外に「テスト」という名の上記のような実行をするためだけのプロジェクトも作っています。

例えば上で説明した関数間の値受け渡しだとすると、

・変数単体ではどうなるのか
・配列ではどうなるのか
・配列要素だけではどうなるのか
・構造体ではどうなるのか
・構造体の一部だとどうなるのか

など、1通りだけわかったのでは理解出来ないような事がありますね。
そういう事は考えられる全てのパターンについてソースを書き、実行します。

プログラムは自分であれこれやって実際に見て身に付けるものですから、これが一番早いです。
分割コンパイルに関しても、私は当初分割コンパイルの方法を知りませんで、ネットで探しても特に丁寧に説明してあるところが見つからなかったので色々試しまくりました。
externという書き方を知らなかったのでとても苦労しましたが、自分で見つけた方法はやはり定着が早いです。
人に聞いた方法だとすぐ忘れます。

テストプロジェクトは「解らない事が特に無い」時にも良く使います。
例えば画像を回転描画させる時、500個位のグラデーションをある時間である円周率の変化をさせながら自分の思う通りに画像が演出できるか試したとします。
実行結果も自分の思うとおりになったとします。
しかし500個もの画像を表示していますから円周率の変化率を変えたり変化速度を変更したりすると
違った演出が発見できるかもしれません。
そんな時は、特に解らない事があるわけでなくとも、円周率の変化率を変えたり、変化速度を変更したりあらゆるパターンについていじくり倒します。
そこで新たな発見があるかもしれませんからね。

やはりここで、人に言われて作った演出方法より自分で考え出した演出方法の方がよく覚えていますし、そこからの応用もでき拡張性も高いです。

でも、「色々実行しろといっても、どう実行したらいいかわからない」という場合でしたら、私が推測する疑問をお持ちになるであろうコードを書きますので、実行してみてください。
りむさんが疑問をお持ちになる全てのパターンについて私が上げる事は無理ですし、わかっているのに私が挙げるかもしれませんからやはりご自分で色々コードを書いてみるのが一番です。
以下のサンプルを実行したら自分で色々いじくってみてください。

そして「いじくる用のプロジェクト」もう用意しておいたほうがいいですよ。

サイモン

Re:無題

#11

投稿記事 by サイモン » 18年前

質問者は、「自分で調べたらわかるけど、調べるのがめんどくさいから教えてくれ」と言っているように聞こえるのは気のせい?

管理人

Re:無題

#12

投稿記事 by 管理人 » 18年前

ではまず、コンパイルする環境を整えてください。
今使用しているであろうプロジェクトは使えませんから「いじくる用」とでもフォルダに名前をつくって
新たにDXライブラリの実行環境を作ってください。

ゲームプログラミングの館で使用している画像をサンプルで使用します。

では、始めます。

他でロードした画像を他で描画した時どのように影響が出るか?について。
また、その変数の有効範囲についての確認の意味もこめて以下のサンプルを書きました。

変数はグローバル変数を使用しているのでどこの関数でも使えます。
エンターを入力しているときだけ他の画像も表示されます。

これを見ても分かるとおり、関数を使用することで何か特別な場所を呼び起こしたわけではなく、結局メイン関数に処理を書いているのとかわらないことがわかります。

つまり今までメイン関数で処理を書いていた





という流れを






という順番に書き換えただけであり、処理の進む流れはまったく同じです。
#include "DxLib.h"

int image[24];
char Key[256];

void graph(){
	if(Key[KEY_INPUT_RETURN]==1)//エンター入力中なら
		DrawGraph(0,0,image[6],FALSE);
	return;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
    SetDrawScreen( DX_SCREEN_BACK );
    LoadDivGraph( "char.png" , 24 , 6 , 4 , 24 , 32 , image ) ;

    while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){

        DrawGraph(200,0,image[9],FALSE);

        graph();

        ScreenFlip();
    }
    DxLib_End();
    return 0;
}

管理人

Re:無題

#13

投稿記事 by 管理人 » 18年前

次は標準関数を含め、違う関数で格納した変数の中身を違う関数で描画しています。

先ほどのサンプルに付け加える形で書いています。
#include "DxLib.h"

int image[24];
char Key[256],str1[128];

void string_copy(){
	char st[128]={"今日はDXライブラリのお勉強をしています。"};
	strcpy(str1,st);
	return;
}

void graph(){
	if(Key[KEY_INPUT_RETURN]==1)//エンター入力中なら
		DrawGraph(0,0,image[6],FALSE);
	DrawFormatString(0,50,GetColor(255,255,255),"%s",str1);
	return;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
    SetDrawScreen( DX_SCREEN_BACK );

	LoadDivGraph( "char.png" , 24 , 6 , 4 , 24 , 32 , image ) ;

    while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){

		string_copy();

		DrawGraph(200,0,image[9],FALSE);

		graph();

		ScreenFlip();
    }
    DxLib_End();
    return 0;
}

管理人

Re:無題

#14

投稿記事 by 管理人 » 18年前

他の関数で画像を読み込んでもメインで描画できるのか?についての疑問を晴らすプログラムです。

先ほどのプログラムに付け加える形でかいています。
#include "DxLib.h"

int image[24];
char Key[256],str1[128];

void load_graph(){//画像をロードする関数
	LoadDivGraph( "char.png" , 24 , 6 , 4 , 24 , 32 , image ) ;
	return;
}


void string_copy(){
	char st[128]={"今日はDXライブラリのお勉強をしています。"};
	strcpy(str1,st);
	return;
}

void graph(){
	if(Key[KEY_INPUT_RETURN]==1)//エンター入力中なら
		DrawGraph(0,0,image[6],FALSE);
	DrawFormatString(0,50,GetColor(255,255,255),"%s",str1);
	return;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
    SetDrawScreen( DX_SCREEN_BACK );

	load_graph();
	string_copy();

    while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){

		DrawGraph(200,0,image[9],FALSE);

		graph();

		ScreenFlip();
    }
    DxLib_End();
    return 0;
}

管理人

Re:無題

#15

投稿記事 by 管理人 » 18年前

↑クリックで画像拡大↑

プログラムは長くなっていくと1つのファイルでは収まりきらなくなります。
数千ものコードをスクロールするのは大変だし、どこに何の関数があったかわけがわからなくなります。

ですから「分割コンパイル」してみましょう。

定義文章をまとめたヘッダファイル
define.h

画像のロードを行うファイル
load.cpp

メイン関数のあるファイル
main.cpp

の3種類に先ほどのファイルをわけてみます。
写真にある「SorceFiles」を右クリックして「新しい項目を追加」から新しくファイルを追加します。

新しいファイルを作ったらそれぞれのファイルに以下の通り貼り付けてください。

***********define.h**********

#define IMAGE_X 24
#define IMAGE_Y 32

*****************************




**********load.cpp***********

#include "DxLib.h"
#include "define.h"

extern int image[24];

void load_graph(){//画像をロードする関数
	LoadDivGraph( "char.png" , 24 , 6 , 4 , IMAGE_X , IMAGE_Y , image ) ;
	return;
}

*****************************




**********main.cpp***********

#include "DxLib.h"
#include "define.h"

int image[24],ch_x=0;
char Key[256],str1[128];

extern void load_graph();

void string_copy(){
	char st[128]={"今日はDXライブラリのお勉強をしています。"};
	strcpy(str1,st);
	return;
}

void graph(){
	if(Key[KEY_INPUT_RETURN]==1)//エンター入力中なら
		DrawGraph(0,0,image[6],FALSE);
	DrawFormatString(0,50,GetColor(255,255,255),"%s",str1);
	return;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
    SetDrawScreen( DX_SCREEN_BACK );

	load_graph();
	string_copy();

    while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key ) && Key[KEY_INPUT_ESCAPE]==0){

		DrawGraph(IMAGE_X,IMAGE_Y,image[9],FALSE);

		graph();

		ScreenFlip();
    }
    DxLib_End();
    return 0;
}

*******************************
まず、他のファイルで宣言した変数や関数を違うファイルで使用するためには
extern宣言が必要です。
「どこか他の場所で既にこの変数が宣言してありますよ」
という意味です。
extern宣言されると、その変数や関数をみつけてきてくれるので、使用できるようになります。
この使用範囲もカッコからカッコまでです。
グローバル変数の宣言位置にかけば、そのファイル内ではどこでも使えるようになります。

定義関係の項目を書いたdefine.hは、両方のファイルでincludeしましょう。

#include "○○"

は、○○のファイルの中身をファイルのグローバル変数の宣言位置に書いたのと同じ意味になります。
定義は両方のファイルでする必要があるので、両方のファイルで定義しましょう。

なお、既に宣言しているファイル内でextern宣言をするとエラーになってしまうので、
ヘッダファイルの中にはextern宣言をかかないようにしましょう。

define.h
には「定義」と「構造体の形」のみ書きましょう。

りむ

Re:無題

#16

投稿記事 by りむ » 18年前

回答ありがとうございます。

詳しい説明をわざわざありがとうございます。わかりやすくて大変助かります。
あれから私も、実際に同じように小さなテストプログラムを作っては調べてみました。
本来、私のプログラムの書き方もちょこっと書いては試して試して…っていう「自分で試す」やり方のクセに、Justyさんに言われるまで、自分でテストプログラム作って試すってことを思いつかなかった私はどうかしてました><;
void damage_divide_digit(int damage[/url] ,int damage_value[/url], int j){  
	int i;                                        
	for(i=0;i<4;i++){
		damage_value[i+j*4]=damage[j];
	}
	damage_value[j*4]/=1000;
	damage_value[j*4+1]/=100;
	damage_value[j*4+2]/=10;
	for(i=0;i<4;i++){
		damage_value[i+j*4]%=10;
	}
	return;
}

typedef struct enemy_data{
	int damage_value[16];
	int damage[4];
}enemy_data;

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
         enemy_data en[100];
         int damage[4]={0,},damage_value[16]={0,};

         (中略)
         for(i=0;i<4;i++){
         damage_divide_digit(en[0].damage,en[0].damage_value,i);
         damage_divide_digit(damage,damage_value,i);
         }
         return 0;
}
調べた&説明を読んだ結果、↑のように、敵味方どちらでも使える関数を作ることができました。
これから関数化したほうがよさそうな処理は、関数にしてみようと頑張ってみることにします><

閉鎖

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