道中には敵が沢山出てきます。
そして敵が持つべき情報は沢山あります。最低限でもこれだけあるでしょう。
--struct.hに以下を追加-- typedef struct{ //カウンタ、移動パターン、敵の種類 int cnt,pattern,knd; //初期座標と移動スピード double x,y,sp; //弾幕開始時間、弾幕の種類、色、体力、弾の種類、停滞時間、アイテム(6種類) int bltime,blknd,col,hp,blknd2,wait,item_n[6]; }enemy_order_t;
これは敵の出現情報に関する構造体です。
いつどこで、どんな敵がどんな弾をうって来て、どれ位滞在して、帰っていくのか・・などの情報を
沢山指定してやる必要があります。 しかし、
112カウント目にx座標がここでy座標がここで、移動パターンはこれで、
187カウント目にはこれがあれでああなって・・・。
なんてデータを手打ちで入力してデータを代入していくのは大変です。
そこで、エクセルを使って敵の出現データを作ってみましょう。
まず、ここからエクセルで作った出現情報データをダウンロードして、
エクセルデータのダウンロード |
datフォルダに「csv」というフォルダを作ってそこに入れて下さい。中身はこんな感じになっています。
もしエクセルを持っていない人はテキストエディタでも開けますのでご覧下さい。
まず一番左は敵が出現する時刻です。60fpsで動作するゲームなので、60=1秒です。
つまりこれは1つ目の敵は、ゲームが始まってから1.6秒位たって出現する事を意味しています。
それから10カウント置き、つまり1/6秒ずれて次々と敵が出現します。
敵の情報はx座標以外全て同じです。エクセルでは同じような変化を持つ値は簡単に作れるのでとても便利です。
さて、DXライブラリでファイルを読み込むときはDXライブラリの関数を使います(アーカイブを使用する為)
もしDXライブラリのファイル読み込み関数の使い方がわからない方は本家で勉強して下さい。
基本的にCの標準関数fopen(←これだけ少し違う)やfgetcなどと同じような使い方が出来ます。
また、csvファイルだからといって構えなくても、このファイルをテキストエディタで開いてもらったらすぐわかると思いますが、
単にセルをコンマで区切っただけのデータなので、コンマで区切って読み込めば普通にテキストデータと同じ
要領で読み込めます。ではその読み込み関数を作ってみます。
以下にエクセルデータを敵の出現情報をいれる変数であるenemy_orderに読み込み、代入する関数を示しますが、
これは別に理解する必要は無く、単にこの変数にデータを代入してくれる関数なんだなと思ってもらったらいいです。
--load.cppに以下を追加-- //敵の出現情報をエクセルから読み込んで格納する関数 void load_story(){ int n,num,i,fp; char fname[32]={"../dat/csv/storyH0.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); }
理解しようとすると日が暮れるので、さっさとここは置いておいて次にいきましょう。
具体的処理に入る前に細かい追加点を示しておきます。
ini.cppでは、現在追加したデータを初期化関数で初期化、
define.hでは、登録出来る最大数を設定、
GV.hでは使用する変数を用意します。
--ini.cppのini関数に以下を追加-- memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX); --define.hに以下を追加-- //敵の出現情報最大数 #define ENEMY_ORDER_MAX 500 --GV.hに以下を追加-- GLOBAL enemy_order_t enemy_order[ENEMY_ORDER_MAX];//敵の出現情報 --function.hに以下を追加-- GLOBAL void load_story(); --main.cpp内のメイン関数のswitch文内にある以下の部分を変更-- case 99://STGを始める前に行う初期化 ini(); load_story(); func_state=100; break;
さて、今回敵の出現情報に関するデータをenemy_orderに入れましたね。
その格納したデータを下に、実際にゲーム内で敵を登録してみましょう。
enemy_enterで敵データの登録を行っています。
enemy_order[n].cntに、いつ出現すべきかというデータが入っているので、
現在のゲームカウンタが一致した時に、敵データの登録を行います。
敵データの登録を行う際、どの番号が空いているかを調べる為 enemy_num_searchという関数を実装しました。
敵データの入れ物であるenemy配列要素は0〜ENEMY_MAX-1までありますが、どの番号を使用すればいいか
この関数が教えてくれます。全部埋まっていたら-1を返すのでその時は登録を行いません。
また、enemy[n].waitは停滞時間だと上にかきましたが、下の赤字のように使います。
すると、敵の停滞時間が制御出来るのです。
なお、敵の移動計算に、angとspの組み合わせによる計算と、vx,vyによる計算が両方存在するのは、
その状況によって、用途が異なるからです。
もちろん今回のenemy_patternで使用しているvy=2はang=3.14/2とsp=2で置き換えられますが、前者の方がわかりやすいですね。
--enemy.cppを変更-- #include "../include/GV.h" //敵の移動パターン0での移動制御 void enemy_pattern0(int i){ int t=enemy[i].cnt; if(t==0) enemy[i].vy=2;//下がってくる if(t==60) enemy[i].vy=0;//止まる if(t==60+enemy[i].wait)//登録された時間だけ停滞して enemy[i].vy=-2;//上がっていく } //空いている敵番号を検索 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_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];//落とすアイテム } } } } //敵の行動制御 void enemy_act(){ int i; for(i=0;i<ENEMY_MAX;i++){ if(enemy[i].flag==1){//その敵のフラグがオンになってたら 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].muki*3+(enemy[i].cnt%18)/6; //敵が画面から外れたら消す if(enemy[i].x<-20 || FIELD_MAX_X+20<enemy[i].x || enemy[i].y<-20 || FIELD_MAX_Y+20<enemy[i].y) enemy[i].flag=0; } } } //敵処理メイン void enemy_main(){ enemy_enter(); enemy_act(); }
実行結果
- Remical Soft -