>> シューティングゲーム作成入門トップに戻る
今回は敵の消滅エフェクトを作成します。
画像はこちらを使います。
敵が爆発する感じを出すために、上記の画像を角度をつけて拡大させながら、
徐々に薄くしていくように描画させます。
新しく、EFFECT_EDEADクラスを作成します。
座標やグラフィックハンドル、エフェクト画像の角度、拡大率、
透明度やカウント等の変数を宣言しました。
グラフィックハンドルはインスタンス作るたびに画像を読み込むのは
無駄にリソースを使うことになるので、静的変数にし、
各インスタンスで共有することにします。
次にコンストラクタです。
グラフィックハンドルについては静的変数なので、最初は0に初期化されます。
画像を読み込んだら0ではなくなるので、上記のif文のようにすることで、
初回だけ画像を読み込むことができます。
先程の三つの画像を先に全部読み込んでおきます。
その他は乱数を初期化したり、値を初期化しているだけです。
次にMove関数です。
flagという変数は現在エフェクトを描画中かどうかを示すフラグです。
これが立っているときだけ処理を行います。
まずカウントが0の時、つまり初回だけ角度と、どの画像を使うかを示す添字をランダムで設定してます。
rand()関数によってランダムに生成された値を624で割ると余りはそれ以下の値を返すので、0~623の値を返します。
それを100で割っているので、0~6.23までの値が取得できます。
これで円一周分(360度)以内で、ランダムなラジアンの値を取得できます。
indexは3の余りを取得してるので、0~2までのランダムな値を取得できます。
その下のrateは拡大率です。
徐々に大きくしていく必要があるので、
初期値0.5にcount×0.05を足してます。
カウントは30カウントまで増やすようにするので、最大値は約2.0、つまり元画像の2倍ぐらいになります。
alphaは255の時が不透明なので、
255カラ255/30×countの値を引くことで、カウントが増えていくごとに、
アルファ値が減少していき、30カウントで約0のアルファ値が取得できます。
最後のif文はカウントが30になったら、カウントを0に戻し、
フラグをfalseに戻します。
次にDraw関数とAll関数です。
単純にアルファブレンドモードにして、Move関数で取得された値を指定して描画しているだけです。
All関数では単純にMove関数とDraw関数を呼び出しているだけです。
このAll関数はCONTROLクラス内で呼び出しますが、
エフェクトの初期位置の座標やフラグをセットするための関数が必要になります。
SetFlag関数とGetFlag関数です。
SetFlag関数は引数のx,y座標をそれぞれ代入し、flagをtrueにします。
GetFlag関数は変数flagを返すだけです。
次にCONTROLクラスのヘッダーファイルとコンストラクタです。
EFFECT_EDEADクラスのポインタを宣言し、
コンストラクタの最初の方で、EFFECT_EDEADNUMの数だけインスタンスを生成しています。
これはdefine.hで20と定義しています。
画面内に現われるエフェクトは多くても20個ぐらいかなということでこの値にしてます。
次にエフェクトのフラグをセットするために、SetFlag関数を呼び出すのですが、
それ専用の関数もCONTROLクラス内に作ってしまいましょう。
EnemyDeadEffect関数です。
引数には敵のx,y座標を渡します。
forループでエフェクトのインスタンス数分のループを行い、
フラグが立っていないやつがあれば、SetFlag関数を実行しています。
あとはこれをどこで呼び出すかですが、
当然敵が消滅したときに呼び出すので、操作キャラの弾と敵との当たり判定部分で呼び出します。
上側の操作キャラの弾と敵との当たり判定部分コードの、
一番下の処理で呼び出しています。
これでエフェクトのセットができるようになったので、
あとはAll関数で、このエフェクトクラスのAll関数を呼び出すだけです。
上記コードの「敵消滅エフェクト」と書かれた部分のコードがそうです。
エフェクトのインスタンスの数だけループさせ、フラグが立っているもののみAll関数を実行してます。
デストラクタのコードは省略しますが、newで動的確保しているのでdeleteするのを忘れないでください。
敵消滅エフェクトを追加することで、以下の動画のようになります。
今回の説明は以上です。
次回はプレイヤー消滅エフェクトを追加しましょう。
>> 【プレイヤー消滅エフェクトを作成しよう】に進む
>> シューティングゲーム作成入門トップに戻る
敵の消滅エフェクトを作成しよう
画像はこちらを使います。
敵が爆発する感じを出すために、上記の画像を角度をつけて拡大させながら、
徐々に薄くしていくように描画させます。
新しく、EFFECT_EDEADクラスを作成します。
class EFFECT_EDEAD{ private: //座標 double x,y; //グラフィックハンドル static int gh[3]; //エフェクト画像の角度 double rad; //拡大率 double rate; //透明度 int alpha; //どの画像を使うかの添字 int index; //カウント int count; //実行中かどうかのフラグ bool flag; private: void Move(); void Draw(); public: EFFECT_EDEAD(); bool GetFlag(); void SetFlag(double x,double y); void All(); };
座標やグラフィックハンドル、エフェクト画像の角度、拡大率、
透明度やカウント等の変数を宣言しました。
グラフィックハンドルはインスタンス作るたびに画像を読み込むのは
無駄にリソースを使うことになるので、静的変数にし、
各インスタンスで共有することにします。
次にコンストラクタです。
EFFECT_EDEAD::EFFECT_EDEAD() { x=y=0; //画像読み込み。初回だけ if(gh[0]==0){ gh[0]=LoadGraph("effect1.png"); gh[1]=LoadGraph("effect2.png"); gh[2]=LoadGraph("effect3.png"); } //乱数を初期化 srand((unsigned int)time(NULL)); rad=0; rate=1; alpha = 255; count=0; flag=false; index=0; }
グラフィックハンドルについては静的変数なので、最初は0に初期化されます。
画像を読み込んだら0ではなくなるので、上記のif文のようにすることで、
初回だけ画像を読み込むことができます。
先程の三つの画像を先に全部読み込んでおきます。
その他は乱数を初期化したり、値を初期化しているだけです。
次にMove関数です。
void EFFECT_EDEAD::Move() { if(flag){ //初回だけ角度と添字セット if(count==0){ rad=rand()%624/100; index=rand()%3; } rate=0.5+count*0.05; alpha=255-255/30*count; ++count; if(count==30){ count=0; flag=false; } } }
flagという変数は現在エフェクトを描画中かどうかを示すフラグです。
これが立っているときだけ処理を行います。
まずカウントが0の時、つまり初回だけ角度と、どの画像を使うかを示す添字をランダムで設定してます。
rand()関数によってランダムに生成された値を624で割ると余りはそれ以下の値を返すので、0~623の値を返します。
それを100で割っているので、0~6.23までの値が取得できます。
これで円一周分(360度)以内で、ランダムなラジアンの値を取得できます。
indexは3の余りを取得してるので、0~2までのランダムな値を取得できます。
その下のrateは拡大率です。
徐々に大きくしていく必要があるので、
初期値0.5にcount×0.05を足してます。
カウントは30カウントまで増やすようにするので、最大値は約2.0、つまり元画像の2倍ぐらいになります。
alphaは255の時が不透明なので、
255カラ255/30×countの値を引くことで、カウントが増えていくごとに、
アルファ値が減少していき、30カウントで約0のアルファ値が取得できます。
最後のif文はカウントが30になったら、カウントを0に戻し、
フラグをfalseに戻します。
次にDraw関数とAll関数です。
void EFFECT_EDEAD::Draw() { SetDrawBlendMode(DX_BLENDMODE_ALPHA,alpha); DrawRotaGraph(x,y,rate,rad,gh[index],TRUE); SetDrawBlendMode(DX_BLENDMODE_NOBLEND,0); } void EFFECT_EDEAD::All() { Move(); Draw(); }
単純にアルファブレンドモードにして、Move関数で取得された値を指定して描画しているだけです。
All関数では単純にMove関数とDraw関数を呼び出しているだけです。
このAll関数はCONTROLクラス内で呼び出しますが、
エフェクトの初期位置の座標やフラグをセットするための関数が必要になります。
void EFFECT_EDEAD::SetFlag(double x,double y) { this->x=x; this->y=y; flag=true; } bool EFFECT_EDEAD::GetFlag() { return flag; }
SetFlag関数とGetFlag関数です。
SetFlag関数は引数のx,y座標をそれぞれ代入し、flagをtrueにします。
GetFlag関数は変数flagを返すだけです。
次にCONTROLクラスのヘッダーファイルとコンストラクタです。
#include "player.h" #include "back.h" #include "enemy.h" #include "effect_edead.h" class CONTROL{ //プレイヤークラス PLAYER *player; //背景クラス BACK *back; //敵クラス ENEMY *enemy[ENEMY_NUM]; //敵消滅エフェクトクラス EFFECT_EDEAD *effect_edead[EFFECT_EDEADNUM]; //サウンドハンドル int s_eshot; int s_pshot; int s_edead; int s_pdead; //サウンドを鳴らすかどうかのフラグ bool eshot_flag; bool pshot_flag; bool edead_flag; bool pdead_flag; private: CONTROL(); ~CONTROL(); void SoundAll(); void CollisionAll(); bool CircleCollision(double c1,double c2,double cx1,double cx2,double cy1,double cy2); void EnemyDeadEffect(double x,double y); public: void All(); void GetPlayerPosition(double *x,double *y); void GetEnemyPosition(int index,double *x,double *y); static CONTROL& GetInstance(){ static CONTROL control; return control; } }; CONTROL::CONTROL() { player = new PLAYER; back = new BACK; //エフェクトクラスのインスタンス生成 for(int i=0;i<EFFECT_EDEADNUM;++i){ effect_edead[i]= new EFFECT_EDEAD; } FILE *fp; ENEMYDATA data[ENEMY_NUM]; char buf[100]; int c; int col=1; int row=0; memset(buf,0,sizeof(buf)); fp = fopen("enemydata.csv","r"); //ヘッダ読み飛ばし while(fgetc(fp)!='\n'); while(1){ while(1){ c=fgetc(fp); //末尾ならループを抜ける。 if(c==EOF) goto out; //カンマか改行でなければ、文字としてつなげる if(c!=',' && c!='\n') strcat(buf,(const char*)&c); //カンマか改行ならループ抜ける。 else break; } //ここに来たということは、1セル分の文字列が出来上がったということ switch(col){ //1列目は敵種類を表す。atoi関数で数値として代入。 case 1: data[row].type=atoi(buf); break; //2列目は弾種類。以降省略。 case 2: data[row].stype=atoi(buf); break; case 3: data[row].m_pattern=atoi(buf); break; case 4: data[row].s_pattern=atoi(buf); break; case 5: data[row].in_time=atoi(buf); break; case 6: data[row].stop_time=atoi(buf); break; case 7: data[row].shot_time=atoi(buf); break; case 8: data[row].out_time=atoi(buf); break; case 9: data[row].x=atoi(buf); break; case 10: data[row].y=atoi(buf); break; case 11: data[row].speed=atoi(buf); break; case 12: data[row].hp=atoi(buf); break; case 13: data[row].item=atoi(buf); break; } //バッファを初期化 memset(buf,0,sizeof(buf)); //列数を足す ++col; //もし読み込んだ文字が改行だったら列数を初期化して行数を増やす if(c=='\n'){ col=1; ++row; } } out: //敵クラス生成 for(int i=0;i<ENEMY_NUM;++i){ enemy[i]=new ENEMY(data[i].type,data[i].stype,data[i].m_pattern,data[i].s_pattern,data[i].in_time,data[i].stop_time,data[i].shot_time, data[i].out_time,data[i].x,data[i].y,data[i].speed,data[i].hp,data[i].item); } //サウンドファイル読み込み s_eshot=LoadSoundMem("enemyshot.wav"); s_pshot=LoadSoundMem("playershot.wav"); s_edead=LoadSoundMem("enemydown.wav"); s_pdead=LoadSoundMem("playerdown.wav"); eshot_flag=false; pshot_flag=false; edead_flag=false; pdead_flag=false; }
EFFECT_EDEADクラスのポインタを宣言し、
コンストラクタの最初の方で、EFFECT_EDEADNUMの数だけインスタンスを生成しています。
これはdefine.hで20と定義しています。
画面内に現われるエフェクトは多くても20個ぐらいかなということでこの値にしてます。
次にエフェクトのフラグをセットするために、SetFlag関数を呼び出すのですが、
それ専用の関数もCONTROLクラス内に作ってしまいましょう。
void CONTROL::EnemyDeadEffect(double x,double y) { //エフェクトセット for(int z=0;z<EFFECT_EDEADNUM;++z){ if(!effect_edead[z]->GetFlag()){ effect_edead[z]->SetFlag(x,y); break; } } }
EnemyDeadEffect関数です。
引数には敵のx,y座標を渡します。
forループでエフェクトのインスタンス数分のループを行い、
フラグが立っていないやつがあれば、SetFlag関数を実行しています。
あとはこれをどこで呼び出すかですが、
当然敵が消滅したときに呼び出すので、操作キャラの弾と敵との当たり判定部分で呼び出します。
void CONTROL::CollisionAll() { double px,py,ex,ey; bool tempflag=false; //操作キャラの弾と敵との当たり判定 for(int i=0;i<PSHOT_NUM;++i){ if(player->GetShotPosition(i,&px,&py)){ for(int s=0;s<ENEMY_NUM;++s){ //敵クラスのポインタがNULLじゃない、かつdeadflagがfalse(死んでない&帰還してない) if(enemy[s]!=NULL && !enemy[s]->GetDeadFlag()){ enemy[s]->GetPosition(&ex,&ey); //当たり判定 if(CircleCollision(PSHOT_COLLISION,ENEMY1_COLLISION,px,ex,py,ey)){ //当たっていれば、deadflagを立てる enemy[s]->SetDeadFlag(); //当たった弾のフラグを戻す player->SetShotFlag(i,false); //敵消滅音フラグセット edead_flag=true; //敵消滅エフェクトセット EnemyDeadEffect(ex,ey); } } } } } //敵の弾と操作キャラとの当たり判定 //プレイヤーが生きてれば if(!player->GetDamageFlag()){ player->GetPosition(&px,&py); for(int i=0;i<ENEMY_NUM;++i){ if(enemy[i]!=NULL){ for(int s=0;s<ENEMY_SNUM;++s){ //弾フラグが立っていればtrueを返す if(enemy[i]->GetShotPosition(s,&ex,&ey)){ //弾によって当たり判定が違うのでswitch文で分岐 switch(enemy[i]->GetShotType()){ case 0: //当たってれば if(CircleCollision(PLAYER_COLLISION,ESHOT0_COLLISION,px,ex,py,ey)){ tempflag=true; } break; case 1: if(CircleCollision(PLAYER_COLLISION,ESHOT1_COLLISION,px,ex,py,ey)){ tempflag=true; } break; case 2: if(CircleCollision(PLAYER_COLLISION,ESHOT2_COLLISION,px,ex,py,ey)){ tempflag=true; } break; } if(tempflag){ //操作キャラのdamageflagを立てる player->SetDamageFlag(); //弾を消す enemy[i]->SetShotFlag(s,false); //プレイヤー消滅音フラグを立てる pdead_flag=true; //一時フラグを戻す tempflag=false; } } } } } } }
上側の操作キャラの弾と敵との当たり判定部分コードの、
一番下の処理で呼び出しています。
これでエフェクトのセットができるようになったので、
あとはAll関数で、このエフェクトクラスのAll関数を呼び出すだけです。
void CONTROL::All() { //サウンドフラグを初期化 eshot_flag=pshot_flag=edead_flag=pdead_flag=false; //描画領域を指定 SetDrawArea(MARGIN,MARGIN,MARGIN+380,MARGIN+460); back->All(); player->All(); //プレイヤーショットサウンドフラグチェック if(player->GetShotSound()){ pshot_flag=true; } for(int i=0;i<ENEMY_NUM;++i){ if(enemy[i]!=NULL){ //敵ショットサウンドフラグチェック if(enemy[i]->GetShotSound()){ eshot_flag=true; } if(enemy[i]->All()){ delete enemy[i]; enemy[i]=NULL; } } } CollisionAll(); //敵消滅エフェクト for(int i=0;i<EFFECT_EDEADNUM;++i){ if(effect_edead[i]->GetFlag()){ effect_edead[i]->All(); } } SoundAll(); ++g_count; }
上記コードの「敵消滅エフェクト」と書かれた部分のコードがそうです。
エフェクトのインスタンスの数だけループさせ、フラグが立っているもののみAll関数を実行してます。
デストラクタのコードは省略しますが、newで動的確保しているのでdeleteするのを忘れないでください。
敵消滅エフェクトを追加することで、以下の動画のようになります。
今回の説明は以上です。
次回はプレイヤー消滅エフェクトを追加しましょう。
>> 【プレイヤー消滅エフェクトを作成しよう】に進む
>> シューティングゲーム作成入門トップに戻る