ステージの作り方。

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

ステージの作り方。

#1

投稿記事 by クロカモ » 17年前

 どうもお初にお目にかかります。
 しばらく前からここにあるシューティングゲームの館を拝見して自分なりにプログラムを組んでいるクロカモという者なのですが管理人さんもしくはシューティングゲームの作成に詳しい方に質問があります。

 それというのも自分なりに少しずつシューティングゲームを作って来たのですが大きな壁にぶちあたってしまいました。

 ステージの作り方です。
 管理人さんのサンプルソースだと敵の出現パターンや出現のタイミングはcase文による記述で書かれていたのですがあの方法で四聖龍神録(体験版)も作ったのでしょか?それとも他になにかしらの方法があるのでしょうか?是非教えてください。

 最近管理人さんは試験やらなにやらで大変多忙とは存じますがなにとぞよろしくお願いします。
 また、他の皆さんも『こうやったら良いのではないか?』といったような事がありましたらMAILの方でもいいのでどうか教えてください。
 よろしくお願いします。

管理人

Re:ステージの作り方。

#2

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

回答はメールの方がいいのでしょうか??
一応両方にお書きしますが、よければ掲示板の方にご返答下さい。

>管理人さんのサンプルソースだと敵の出現パターンや出現のタイミングはcase文による記述で書かれていたのですがあの方法で四聖龍神録(体験版)も作ったのでしょか?

はい、全く違います・・・。
というか、全体のソースコード丸ごと違います(汗

というのも、作った当時、あのシューティングの館は、ここまで多くの人に見ていただけると思っておらず、
また、小さなミニゲームを想定していたのと、知り合いが「効率いいプログラムより、見慣れた書き方で書いてほしい」と言っていたのを受けて、
最適な書き方より、とにかく難しくないコードで書こうとして出来たものです。
なので、本来の効率的なかき方とは全然違うのです。

そのせいで、色々な人から質問が来てしまい、私のせいで、いろんな人を混乱させてしまったようです・・;
シューティングの館は近々削除予定です。
龍神録が完成したらシューティングの館をもっときちんとしたコードに書き直す予定です。

敵は数百~数千も出てくるので、case文で書いているととんでもないことになりますし、拡張性も汎用性もありませんし、
修正も困難ですよね。
私は、一般的にどのようにするかは存じませんし、シューティングプログラムの本などを読んだこともありません。
私が我流でやっている方法ですが、エクセルでデータを書いています。
例えば私が作っているゲームのステージ2では、添付ファイルのようになっています。
ファイルを添付したので、よければご覧下さい。
(添付は下の投稿にやり直しました。)

私は、敵1体にこれだけ情報を持たせています。
カウンタ 移動パターン 敵の種類 x座標 y座標 スピード 発射時間 弾幕種類 弾の色 体力 アイテム1 2 3 4 5 6 弾種類 待機時間
csvのエクセルデータはカンマで区切られているため、簡単にデータがとり出せます。
一番上の行をノンスクロールにして、xlsデータファイルから読み込めば一番いいのでしょうが、読み込み方がよくわからなかったので・・cvsにしました。
すらっすのある行は読み飛ばします。


ところで、ステージの作り方ですが、
基本的に全て多次元配列で作っています。
例えば、関数ポインタなどで、
49番の弾幕
void bullet49();
を呼び出すとき、bulletという関数ポインタを使って呼び出すなら

・配列に関数のアドレスが最初から順番に入っていること
・boss.bullet_kndはボスの弾幕の番号が入っていること
を想定


bullet[boss.bullet_knd]();

このようにかけますよね。しかしこれでは、どの面の弾幕かわからない・・。
そこで、ステージはもう一つ上の配列に入れて、

bullet[stage][boss.bullet_knd]();

とすればいいです。しかし、これでは難易度の区別が出来ない・・そこで

bullet[leve[/url][stage][boss.bullet_knd]();

このようにしてやれば、どうでしょうか。
ゲームのレベルが4種類、ステージが6種類、弾幕の種類が20種類あるなら

void (*boss[4][6][20])(void) ;

で用意してやればいいはずです。


方法は沢山あるでしょうし、別にこれでなくてもいいはずです。
ゲームのステージごとに、データをとってきてもいいでしょうし、
レベルごとにとってきてもいいでしょう。
そうすれば一次元配列でいいでしょうし、
データは全て外部にしておいた方が汎用性が高いでしょう。
プログラムはもっぱらシステムだけ。
どのような振る舞いをするかは全て外部データに依存する。としたほうが綺麗かもしれません。

色々書きましたが、ご参考程度に。

やそ

Re:ステージの作り方。

#3

投稿記事 by やそ » 17年前

管理人さん、お疲れ様です。

>最適な書き方より、とにかく難しくないコードで書こうとして出来たものです。
>なので、本来の効率的なかき方とは全然違うのです。

>そのせいで、色々な人から質問が来てしまい、私のせいで、いろんな人を混乱させてしまったようです・・;
>シューティングの館は近々削除予定です。

とありますが、コレはコレで十分役立っています。
リニューアルした後も、旧館として残して欲しいです^^

あまり効率に傾きすぎると、(私を含めた)初心者はロジックを追うのが大変なのですよ((((((^_^;)

管理人

Re:ステージの作り方。

#4

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

う~ん、掲示板が下手に高機能なせいで、csvファイルがエクセルデータだと認識されて勝手にxlsファイルに変更されてしまいます;
.txtファイルにしてアップロードしましたので、ダウンロードした後で、拡張子を.csvに変更してお開き下さい。

>とありますが、コレはコレで十分役立っています。

ありがとうございます。そういっていただけると幸いです。
ただ・・本腰いれて製作されていらっしゃる方から、「これ以上拡張するのが困難になってきた。どうしたらいのでしょう」という相談が
メールで多々寄せられるのでここまで作っていらっしゃるのに、今更「全面的に変えてください」とも言いづらく
非常にもったいないので・・。

>リニューアルした後も、旧館として残して欲しいです^^

そうですね。
そういえばこんなショウモナイ館もあったな・・といういい思い出になるかも・・。

現在のFLASH映像は非常にカクカクしているので、全部とり直そうと思っています。
しかし、いまだにflvファイルの自動再生の仕方がわからないので、ActionScriptを勉強しなければ・・と思っています。
将来的にはRPGの館、サウンドノベルの館、格闘の館、パズルの館・・・など等一通りのゲームの館で埋め尽くせたらなと思っていますが、はたしていつになることやら^^;

一つの館をどこまで作るかなんですよねぇ・・。ミニゲームみたいなものならサクサク作っていけるでしょうが、
龍神録が出来るまで・・みたいな館だと、あのペースでは「第200章」とかまで必要になるかもしれませんし、そうすると中々・・。

クロカモ

Re:ステージの作り方。

#5

投稿記事 by クロカモ » 17年前

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

 そして、自分の返答が遅くて申し訳ないです。

 なるほど、やっぱり四聖龍神録(体験版)のプログラムとシューティング館のソースコードは根本的に違いましたか。

 えっとステージの作り方は管理人さんの場合csvデータを読み込んでそこに書いてある記述どおりに敵を表示させているのですか…なるほど確かにこれならcase文での記述と違って敵を新しく追加表示させる時に楽ですね。

 いや、やっぱり自分一人で延々と悩んでたのですが一発で解決とまではまだ行ってませんが先が見えてきました。

 csvデータファイルの読み込みに必要な関数はやっぱりDXライブラリの中に入っている『FileRead_open』関数を使うのですかね?

 読み込んだファイルをどう処理して敵の持っている情報と合致させるかっていうのが自分には良くわかりません。

 質問ばっかりで申し訳ないのですがそこを教えていただけると幸いです。

 あと、管理人さんの場合xlsファイルでそれを書いているとの事でしたがtxtファイルで書くよりxlsファイルで書いておいた方が便利なのですかね?
 自分には一々区分けされていないtxtファイルの方が文章全体を見渡せて分かりやすいと思ってしまうのですが。

 なんだか生意気な事言っているように思われた方本当に申し訳ないです。

>そのせいで、色々な人から質問が来てしまい、私のせいで、いろんな人を混乱させてしまったようです・・;
>シューティングの館は近々削除予定です。

 他の方の事までは分かりませんが混乱してるのは自分の勉強不足です。
 自分もシューティングの館は存続して頂いた方が嬉しいです自分で言うのもなんですがこのシューティングの館を拝見させて頂いてかC++の理解力が上がりましたから初心者にはむしろ最適なLVかと。
 未完成の方が逆に質問意欲や自分で考える力もつくかもしれませんし。

>龍神録が完成したらシューティングの館をもっときちんとしたコードに書き直す予定です。

 首を長くして待ってます!と言いますか切望してますw
 
>龍神録が出来るまで・・みたいな館だと、あのペースでは「第200章」とかまで必要になるかもしれませんし、そうすると中々・・。

 200章ですか流石に長いですね自分勝手な意見で申し訳ないのですが竜神録のソースコードを是非とも拝見してみたいです。
 知的好奇心が凄いそそられます。多分今の自分の理解力だとコード全体の半分も理解できないとは思いますがそれはそれで一日中でもソースコードとにらめっこするとっても楽しい時間になりそうですしw

 これはクロカモ自信の解釈なのですが管理人さんが書いた弾幕のソースコードって凄く綺麗なんですよねあれは見てるだけで楽しいっす計算方法もクロカモが考えてもなかなか思いつかないようなものが盛り沢山ですし。

 竜神録のステージ1のボスが使用するスペルカード付きの弾幕なんてどういったコードなのか考えながら避けて何度死んでしまった事かw

 管理人さんのプロフィールで紹介されていた東方ですが凄いですね…東方をお作りになったZUNさんはやはり天才なのですかね…自分とは住んでる世界がまったく違うんじゃないかと思ってしまうほどです。

 長々とすみません…なんだか『質問』なのか『雑談』なのか分からなくなってしまいましたがこれからもよろしくお願いします。

管理人

Re:ステージの作り方。

#6

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

今ネット料金未払いで解約されてしまったので、家からネットに繋がらず、お返事遅くなってすみません(_ _|||)
これだけネットを使ってると1ヶ月ネットが使えないだけでホントに不便で仕方ないです・・・。

txtファイルでもいいんですが、エクセルだと、パターンのあるデータが作りやすいと思ったんですよ。
例えば、シーン1で、
10
20
30
40
50
60
70
80
100
なんて、等間隔で出現するとき、データ作るときに、いちいち上記のように手打ちしないといけないじゃないですか。
でも、エクセルなら、
10
20
だけ書いてピーーと引っ張ったら100まで自動で作れますよね。
しかも、
「あ、シーン3とシーン4の間に他のシーンを入れないといけなかった」とかってとき、
txtファイルだと全部の出現カウンタの時間をずらさないといけませんけど、エクセルデータなら、「前のカウンタ+X」としておけば、全自動で計算しなおしてくれるので、かなり便利です。
上に書いた10個位なら「手打ちで直せばいいじゃん」と思うかもしれませんが、それが数百、数千という数になってくると、結構ありがたくなるんですよね。
そして、xlsデータはスクロール固定行って作れるので、
「あれ、この行に書いてるデータ何のデータだっけ?」と思っても、列の上にデータの種類が表示されるので、わかりやすいんです。
・・とまぁ色々理由があってエクセルデータを使っています。
世間でどういう方法が主流なのかわかりませんが、とりあえず私はこうする・・というだけですので、クロカモさんの好きな方法でやっていただいたらいいと思いますよ。

取り込む方法は簡単です。
fopen関数でファイルを開き、fpのデータからカンマで区切られている文字列データを取ってきます。
文字列データをatoiで数値データに変換して、敵出現データに格納する。
それだけです。

後は現在のカウンタが出現カウンタと同じになった時、格納した敵の種類を出現させればいいということです。

ちなみに、うちのサイトには全くC++の書き方がしてありません。純粋なCです。
(宣言を途中でやったり、C++のコンパイラじゃないと通らない記述はありますが)

>竜神録のステージ1のボスが使用するスペルカード付きの弾幕なんてどういったコードなのか考えながら避けて何度死んでしまった事かw

結構複雑なコードを想像されていらっしゃるのかもしれませんが、
ここまで沢山作らないといけなくなると「プログラム的作成ツールモドキ」みたいなものを作ってからじゃないと中々管理しにくいので、それで作っていますからコード自体は簡単です。
(私の場合は作成モジュールとでもいいますか・・)
本格RPGとかを作るときだって、プログラマー達はきっとマップ作成ツールやいろんなツールを事前に作ってから作成を開始すると思います(知りませんが(汗)

小さなゲームを作るときは、そんなツールを作る方が面倒ですが、少し大きくなってくると作成ツールモドキを作ってから作る方が簡単になりますので。

龍神録の場合、そんな立派なものじゃなく、単に変数に値を代入したらその通り動いてくれるだけです。
変数に値を代入するだけで、その通りの動きをしてくれるようにプログラムを作っておけば、弾幕を作るとき、変数に値を代入するだけでよくなります。
・・・と文字で言ってもわかりにくいので、プログラムの一部をお見せするとこんな感じです。

261番目のボスの弾幕が、角度等間隔で30種類の方向に飛んでいく弾幕だったとします。
400カウントに1回発射します。
vxとvyはspdとangleから自動で計算されます。
#define TM261 400
void BossShotBulletNormal261(int n){
  int i,t=boss_shot[n].cnt%TM261;
  double base_angle=bossatan2();//敵と自機の角度を計算して返す関数
  if(t==0){
    for(i=0;i<30;i++){
      if((k=serch(n))!=-1){
        boss_shot[n].bullet[k].col   = 6;                   //弾の色
        boss_shot[n].bullet[k].x     = enemy[shot[n].num].x;//初期弾x座標
        boss_shot[n].bullet[k].y     = enemy[shot[n].num].y;//初期弾y座標
        boss_shot[n].bullet[k].angle = base_angle+PI2/30*i; //発射角度
        boss_shot[n].bullet[k].knd   = 1;                   //弾の種類
        boss_shot[n].bullet[k].flag  = 1;                   //弾の表示フラグ
        boss_shot[n].bullet[k].cnt   = 0;                   //カウンタ
        boss_shot[n].bullet[k].graze = 1;                   //グレイズをいくつまでカウントするか
        boss_shot[n].bullet[k].spd   = 3;                   //弾の発射スピード
        boss_shot[n].bullet[k].pattern= 0;                  //弾の起動パターン
        boss_shot[n].bullet[k].state = 0;                   //弾のステータス
        boss_shot[n].bullet[k].vx = 0;                      //弾の速度x成分
        boss_shot[n].bullet[k].vy = 0;                      //弾の速度y成分
        boss_shot[n].bullet[k].ax = 0;                      //弾の加速度x成分
        boss_shot[n].bullet[k].ay = 0;                      //弾の加速度y成分
      }
    }
    sound_se_flag[17]=1;                                    //17番の音を鳴らすフラグを立てる
  }
}
 
このように弾幕データをいれるだけでその弾幕になります。
またまた果たしてこんなコードでいいのかどうか疑問です…、あくまで我流だと言うことをお忘れなく・・。
私の書くコードは無駄に長いので読んでると長期休暇すらなくなってしまうでしょう^^;
今の時点で・・6万行位かな?と思います。(ファイル分割してるので行数はわかりませんが)

>自分とは住んでる世界がまったく違うんじゃないかと思ってしまうほどです

多分半年後にもう一度ご覧になったら何でそんなことを言っていたんだろうとお思いになると思いますよ^^;
というのも、私がC言語を勉強しだしてまだ2年弱なので。まだまだ全くの初心者です。
まず私がこのような質問サイトを作れるような人間じゃないのですが、Cについて、雑談交じりに気軽に話せる掲示板無いな~と思って作った何とも適当なサイトです(_ _|||)
元々意見交流や雑談目的で作った掲示板ですので、雑談でもお構いなくどうぞ~♪

クロカモ

Re:ステージの作り方。

#7

投稿記事 by クロカモ » 17年前

 ネット環境に一回どっぷりと浸かってしまったらたとえ1ヶ月でもそこから離れるのは凄い面倒といいますか苦痛といいますかなんとも言葉に出来ないような状況ですね。

 自分も今ネット出来なくなってしまったら苦労しそうです。

 なるほど、エクセルには計算機能が付属してますしそれを利用しない手はないですよねぇ。

>fopen関数でファイルを開き、fpのデータからカンマで区切られている文字列データを取ってきます。
>文字列データをatoiで数値データに変換して、敵出現データに格納する。
>後は現在のカウンタが出現カウンタと同じになった時、格納した敵の種類を出現させればいいということです。

 むむむ…難しい一番最初斜め読みで文章を読んで思わず頭の中に浮かんだ言葉が『そーなのかー』でした。
 ルーミアと一緒に自分の思考まで真っ黒になりそうです。
 いまいち理解できない自分がいます…色々試してみようと思いますがこの部分の説明をもっと詳しくしていただけると幸いです。
 具体的なソースもあれば是非。すみませんなんか図々しくなってしまって。

>ここまで沢山作らないといけなくなると「プログラム的作成ツールモドキ」みたいなものを作ってからじゃな>いと中々管理しにくいので、それで作っていますからコード自体は簡単です。
>(私の場合は作成モジュールとでもいいますか・・)

 ゲームを一つ作るのにそのゲームの一要素を作るのを助けるツールをまた別に作るなんて…まさにプロの仕事ですね。
 さらには、モジュールまで作ってしまうなんて…まったくもって凄すぎです。

>261番目のボスの弾幕が、角度等間隔で30種類の方向に飛んでいく弾幕だったとします。
>400カウントに1回発射します。

 さて、どれくらいの密度の弾幕なのか具体的な数字を知って弾幕を避けるとこれまた具体的な数字で自分の実力が分かりそうですねw
 角度等間隔で25種類500カウントづつでの弾幕なら80%以上の確立で避けられるとかw

>今の時点で・・6万行位かな?と思います。(ファイル分割してるので行数はわかりませんが)

 ( ゜д゜)ポカーン…すみません馬鹿にしてんじゃないんです驚きすぎて言葉が…そかぁ、やっぱりあれぐらい高いクオリティのシューティングだと平気でそれぐらいいくんですね。

>多分半年後にもう一度ご覧になったら何でそんなことを言っていたんだろうとお思いになると思いますよ^^;
>というのも、私がC言語を勉強しだしてまだ2年弱なので。まだまだ全くの初心者です。

 半年後には本当に言えるようになるといいなぁ…そして2年弱でここまで凄いLVに到達した管理人さんの凄さにまたまた脱帽。

>元々意見交流や雑談目的で作った掲示板ですので、雑談でもお構いなくどうぞ~♪

 うぃ、雑談、質問、意見交流など色々と使わせていただきます。

管理人

Re:ステージの作り方。

#8

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

現在携帯からなので、コードが書けませんが
fopenで開き
fgetcで一文字ずつ配列に格納します。カンマか改行ならそこに終端記号を書いて終了し、
atoiで数値に変換して代入します。代入しおわったらまた同じ事をEOFになるまで繰り返したらいいだけです。

tk-xleader

Re:ステージの作り方。

#9

投稿記事 by tk-xleader » 17年前

もう2日はパソコンしてない状態です。やっと入試終わったー

で、本題ですが、CSVの読み込みに使える関数として、strtokがあります。どうせならfgetcでちまちまやるよりもfgetsで読み込んでしまいましょう。
#include<stdio.h>
#include<string.h>

int main(void){
	FILE *file;
	int data;
	char string[1000],*p;
	file=fopen("data.csv","r");
	if(file==NULL)return -1;
	while(fgets(string,sizeof(string),file)!=NULL){
		while((p=strtok(string,",\n"))!=NULL){
			data=atoi(p);
			printf("%d%%",data);
		}
		putc('\n');
	}
	fclose(file);
	return 0;
}
CSVの内容を%で区切って表示するプログラムです。でも意味なさそうです。

管理人

Re:ステージの作り方。

#10

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

↑.xlsファイルを.csvファイルに拡張子をかえて、
プロジェクトのあるフォルダに保存して実行してください。

こちらにサンプルを書いておきます。
csvファイルから情報を読み込み、ベクターに登録し、
敵をその情報に従って動かすプログラムのサンプルです。

若干15分で作ったようなプログラムなので、間違ってる可能性ありますが・・。


#include "DxLib.h"
#include <vector>
#include <math.h>
using namespace std;

#define PI 3.1415926

struct order_t{
	int count;
	double x,y,spd;
};
vector< order_t > vOrder;

struct enemy_t{
	int flag;
	double x,y,spd,angle;
};
vector< enemy_t > vEnemy;

int count;
char Key[256];

//出現情報をロード
#define STR_MAX 64
int order_load(){
	FILE *fp;
	int str,flag,i,t;
	char st[STR_MAX];
	struct order_t order;

	if((fp = fopen("dat.csv", "r")) == NULL )//ファイルを開く
		return -1;

	flag = t = 0;
	
	while(flag==0){
		for(i=0;i<STR_MAX-1;i++){
			str=fgetc(fp);
			if(str==EOF){//ファイルの終端EOF(-1)なら
				flag=1;//終了フラグ
				t=-1;//格納しない
				break;
			}
			else{
				st=str;
				if(st==',' || st=='\n'){
					st='\0';
					break;
				}
			}
		}
		switch(t){//ファイルに書いてある順番に格納
			case 0:
				order.count=atoi(st);//int変換
				break;
			case 1:
				order.x=atoi(st);
				break;
			case 2:
				order.y=atoi(st);
				break;
			case 3:
				order.spd=atof(st);//double変換
				break;
			default:
				break;
		}
		t++;
		if(t>3){//1行分読み込み終わったら
			vOrder.push_back(order);//ベクターの最後に追加
			t=0;
		}
	}
	fclose(fp);
	return 0;
}

//敵を出現させる
void enter_enemy(){
	unsigned int i;
	struct enemy_t Enemy;
	for(i=0;i<vOrder.size();i++){
		if(vOrder.count==count){
			Enemy.flag	=1;
			Enemy.x		=vOrder.x;
			Enemy.y		=vOrder.y;
			Enemy.spd	=vOrder.spd;
			Enemy.angle	=PI/2;
			vEnemy.push_back(Enemy);
		}
	}
}

//敵の座標を計算
void calc_enemy_point(){
	unsigned int i;
	for(i=0;i<vEnemy.size();i++){
		if(vEnemy.flag==1){//表示中の敵がいたら
			vEnemy.x+=cos(vEnemy[i].angle)*vEnemy[i].spd;
			vEnemy[i].y+=sin(vEnemy[i].angle)*vEnemy[i].spd;
			if(vEnemy[i].y>480)//画面外になったら
				vEnemy[i].flag=0;
		}
	}
}

//敵を描画
void graph_enemy(){
	unsigned int i;
	for(i=0;i<vEnemy.size();i++){
		if(vEnemy[i].flag==1){//表示中の敵がいたら
			DrawCircle( (int)vEnemy[i].x, (int)vEnemy[i].y, 10, 0xffffff, TRUE );//白い円で描画
		}
	}
}

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 );

	order_load();//敵の出現情報をロード

	while(!ProcessMessage() && !ClearDrawScreen() && 
		!GetHitKeyStateAll( Key ) && !Key[KEY_INPUT_ESCAPE]){
		enter_enemy();     //敵を登録
		calc_enemy_point();//敵の座標を計算
		graph_enemy();     //敵を描画

		ScreenFlip();
		count++;
	}

	DxLib_End();
	return 0;
}

閉鎖

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