17. 敵に弾を発射させる。(概念説明)
敵がうってくる弾の情報は2つの構造体を組み合わせて作ります。
1つの敵がうってくる最大の弾の数は200個とし、この数を定義でENEMY_TOTAL_SHOT_NUMとします。
名前はどうでもいいのですが、1つの敵がうってくる弾の数は200個。
これが全部で最大100体敵がいるわけですから、200x100個の情報を確保する必要があります。
普段あまり使わないかもしれませんが、構造体の中に構造体を入れることができます。
弾単体が持つべき情報は、x、y座標と発射しているかのフラグだけです。
この事を構造体で表現すると、
typedef struct{
int flag;
double x,y;
} ENEMY_SHOTS_t;
一つの弾が持つ情報はこうかけます。
この弾100個1組が持つ情報は、またその上にあります。
弾が持つべき構造体の情報は以下これだけあります。
・弾が発射されたかどうかを示すflag
・弾がどんなパターンの動きをするか示すpattern
・弾が発射されて何カウントたったかカウントするcount
・弾が何の画像であるか示すimg
・弾が発射した瞬間の初期座標を示す mem_ex , mem_ey ※1
・弾が発射した瞬間のプレイヤーの座標を示すmem_px , mem_py ※2
・敵が弾を発射した時の位置と、プレイヤーの位置から計算した弾の飛んでくる角度ラジアンを示すAngle[ENEMY_TOTAL_SHOT_NUM];//[200]
※3
・そして、先ほど構造体で示した、200個のココの弾情報EnemyShots[ENEMY_TOTAL_SHOT_NUM];
//[200] ※4
沢山ありますが、全部で弾が持つべき情報はこれだけあります。
※1:発射した瞬間の初期座標は、敵が発射するわけですから、その時の敵の座標になります。
※2:プレイヤーがいる場所へショットをうつパターンの時に必要になります。
※3:時間差で弾を発射するときもあるので、角度を格納する変数は弾の数だけ用意する。
※4:構造体の中に、構造体をここで宣言しています。
これらの事を実際プログラムで宣言するとこうなります。
//個々の弾が持つ情報 typedef struct{ int flag; double x,y; } ENEMY_SHOTS_t; //1組の弾が持つ情報 typedef struct{ int flag,pattern,counter,img; double mem_ex,mem_ey,mem_px,mem_py,Angle[ENEMY_TOTAL_SHOT_NUM];//[200]; ENEMY_SHOTS_t EnemyShots[ENEMY_TOTAL_SHOT_NUM];//[200]; } ENEMY_SHOT_t; ENEMY_SHOT_t EnemyShot[ENEMY_TOTAL_NUM];//[100];
//で書いてある括弧の数字は定義された値が何であるかわかりやすく書いたものです。
それぞれの構造体は名前の最後にsがついているかいないかの違いなので注意してみてください。
ですから、例えば3組目のショットが持つ画像情報は
EnemyShot[2].img
であり、3組目のショットの5個目の弾単体が持つx座標情報は
EnemyShot[2].EnemyShots[4].x
となるわけです。少し複雑ですが、落ち着いてみれば何てことありません。
途中で構造体の仕組みが判らなくなった時はもう一度ここを読み返してください。
先ほど、敵にパターン2の動きをさせましたね。下に降りてきて、少し滞在して、上に戻っていくパターンです。
あの、少し滞在している時に弾を発射させてみましょう。
パターン2の関数はさきほどこう書きましたね。
void EnemyPattern2(int i){ int j; if(enemy[i].counter<50) enemy[i].y+=4.0f; if(enemy[i].counter>100) enemy[i].y-=4.0f; }
エネミーカウンターが50〜100の時止まっているということですね。
じゃ中間の75の時、発射させてみましょう。
発射させるのは簡単です。発射フラグを立てるだけです。
つまりenemy[i].flag=1;をするのです。
ただ、発射に際して、先ほど構造体で宣言しただらだらした情報を色々初期化する必要があります。
一つ一つみていってください。
void EnemyPattern2(int i){ int j; if(enemy[i].counter<50) enemy[i].y+=4.0f; if(enemy[i].counter==75){//エネミーカウンターが75の時 for(j=0;j < ENEMY_TOTAL_NUM ;j++)//発射されていない弾を探す。 if(EnemyShot[j].flag==0)//あったらそこでiを保持してブレイク break; EnemyShot[j].flag=1;//発射フラグを立てる EnemyShot[j].mem_ex=enemy[i].x;//弾の初期x座標を敵の座標から設定する EnemyShot[j].mem_ey=enemy[i].y;//同様y EnemyShot[j].mem_px=Player.x;//うつ瞬間のプレイヤーの座標を記憶 EnemyShot[j].mem_py=Player.y;//同様y EnemyShot[j].counter=0;//カウンターを初期化 EnemyShot[j].pattern=1;//ショットパターン1とする※1 EnemyShot[j].img=0;//画像タイプは0とする※2 } if(enemy[i].counter>100) enemy[i].y-=4.0f; }
※1:ショットパターンの説明はまだしていません。次に説明します。
※2:画像タイプ0の説明はまだしていません。次に説明します。
ショットパターンを1とすると先ほど言いましたね。
敵の移動パターンも1〜4まで設定しましたが、弾のショットパターンも同様に決めていきます。
enemy.cppと同じようにenemy_shot.cppも作っていきます。
ソースファイルの追加から「enemy_shot.cpp」を作成してください。
そこにショットパターン1であるEnemyShotPattern1関数を作ります。
まず、ショットパターン1は、現在敵がいる場所からプレイヤーがいる場所にむかって1発ショットをうつだけのパターンとします。
この関数が弾にとって初めて呼び出されたとき、つまりエネミーショットカウンターが0の時、
初期化処理をします。先ほどは1組の弾の情報の初期化をしましたが、今度はその組の中の1つの弾単体の初期化をします。
弾単体はEnemyShot[i]構造体にあるEnemyShots[j]構造体でしたね。
ですから1つ目の弾の発射フラグをたてるには
EnemyShot[i].EnemyShots[0].flag=1;
と書きます。
弾の初期座標は、先ほど格納したmem_ex,mem_eyの値ですから
EnemyShot[i].EnemyShots[0].x=EnemyShot[i].mem_ex;
EnemyShot[i].EnemyShots[0].y=EnemyShot[i].mem_ey;
となります。
ここで数学・・いや算数のお時間なんですが
敵のいるx座標からプレイヤーのいるx座標を引くと、敵とプレイヤーとの水平方向つまりx方向の距離が出ますね。
敵のいるy座標からプレイヤーのいるy座標を引くと、敵とプレイヤーとの鉛直方向つまりy方向の距離が出ますね。
atan2関数というのはmath.hにあるとても便利な関数で、yの長さとxの長さを渡すと、その直角三角形がなす角度ラジアンが返って来ます。
この帰ってきたラジアンを1つ目の弾の角度の格納場所であるEnemyShot[i].Angle[0]に代入します。これを式で書くと
EnemyShot[i].Angle[0] = atan2(EnemyShot[i].mem_py-EnemyShot[i].mem_ey,EnemyShot[i].mem_px-EnemyShot[i].mem_ex);
こうなります。
ラジアンがわかったので、簡単に一定の割合で、x、yが増加できるようになりました。
ラジアンがわかっていれば、
斜辺 * sin(ラジアン) でYの距離が出て、 斜辺 * cos(ラジアン) でXの距離が出ます。
斜辺の長さを自由に変化させることで、好きなスピードで、弾が発射できます。
この斜辺の長さが弾のスピードとなるので、パターン1の弾の速さは定義で
#define PATTERN1SPEED 4
としましょう。スピードが4ということは、敵との距離が40の時、10カウントでぶつかるということになります。
これらをまとめてプログラムで書くとこちらになります。
void EnemyShotPattern1(int i){ if(EnemyShot[i].counter==0){ EnemyShot[i].EnemyShots[0].flag=1; EnemyShot[i].EnemyShots[0].x=EnemyShot[i].mem_ex; EnemyShot[i].EnemyShots[0].y=EnemyShot[i].mem_ey; EnemyShot[i].Angle[0] = atan2(EnemyShot[i].mem_py-EnemyShot[i].mem_ey,EnemyShot[i].mem_px-EnemyShot[i].mem_ex); PlaySoundMem(sound_enemy_shot[0],DX_PLAYTYPE_BACK); } EnemyShot[i].EnemyShots[0].x+=PATTERN1SPEED*cos(EnemyShot[i].Angle[0]); EnemyShot[i].EnemyShots[0].y+=PATTERN1SPEED*sin(EnemyShot[i].Angle[0]); }
長くなったので、この章では、サンプルを紹介しません、次の章で、実際にプログラムをコピーして動かしてみましょう。
DXライブラリ著作権表示
DX Library Copyright (C) 2001-2006 Takumi
Yamada.