12. 敵の概念と表示。
次に敵を出現させてみましょう。
まず最初に敵を表示するために、画像を用意します。敵の画像のハンドルをいれる変数名をimg_enemy[5];で宣言しましょう。
5種類の絵を動かしてモーションを作るからです。
実際に追加する手続きは後でしめします。
ちょっと概念的なお話をします。
〜「パターン」のお話。〜
ちょっとあなたが経験したシューティングゲームを思い出してください。
一昔前のシューティングゲームなら、ただ単に直線で敵が降りてくるだけだったと思いますが、これじゃ面白くないですね。
その時々によって斜めにおりてくる敵がいたり、円運動をしながら降りてくる敵がいたり、
関数の波形のような軌跡を描いて飛んできる敵がいたり。
色んなモーションをする敵が現れたほうが面白いと思います。
つまり敵1匹1匹に敵が移動する個別の「軌跡」を設ける必要があります。
これを敵が移動するパターンを「pattern」という変数を用いて、表現します。
パターン1の軌跡は縦に直線、パターン2の軌跡はy=xの斜め直線。
そんなパターンをあらかじめ設定しておき、
ゲームを開始して2秒後に飛んでくる敵は「パターン2」(pattern=2)で飛んでくる。
ゲームを開始して4秒後に飛んでくる敵は「パターン1」(pattern=1)で飛んでくる。
こんな風に指示を出せばその敵は設定されたパターンの軌跡の上を飛ぶことが出来ます。
つまり、敵の構造体に「pattern」を追加する必要があるという事を覚えておいてください。
〜「カウンター」のお話〜
先ほど
ゲームを開始して2秒後に飛んでくる敵は「パターン2」(pattern=2)で飛んでくる。
といいましたが、敵が出現して何秒たったかカウントする変数がほしいですね。
だって、飛んできてその場で滞在する敵の移動パターンがあったとき、敵を倒さなければ、
永久にそこに滞在してしまうようなゲームになってしまいます。
敵がドンドンあらわれると、画面が敵でうめつくされてしまう・・。
ですから、一定時間倒されなかったら自分で画面外に退避しなければなりません。
出現してから何秒たったかを、測定するために、counterを設けます。
1周の処理が17ミリ秒ですから、60カウントで1秒です。このように(だいたいの)秒数を測定することが出来ます。
敵の構造体に「counter」を追加する必要があるということを覚えておいてください。
〜「フラグ」のお話〜
先ほどから発射する弾のお話をしてまいりましたが、flagを用いてその配列要素の弾が発射されているのかどうか
判定すると、楽でしたね。敵にもこのフラグを持たせ、その配列要素の敵が今出現しているのかどうかを判定させましょう。
0なら出現していない。1なら出現しているということです。
敵の構造体に「flag」を追加する必要があることを覚えておいてください。
〜「体力」のお話〜
シューティングをするとき、敵の体力はみな同じじゃないですね。ちっこい敵はすぐ倒せるけど、ゴツイ敵はなかなか倒せません。
つまり敵にも個別に体力(hp)を設定する必要があります。
敵の構造体に「hp」を追加する必要があることを覚えておいてください。
〜「サイズ」のお話〜
敵は「大きければ強い」というイメージがありますね。同じ画像を用いても、大きくすれば強く見えます。
ですから、敵に「サイズ」を持たせましょう。これには元の画像を拡大率何倍にするか設定する変数を用意します。
元の画像より拡大してしまったら荒くなってしまうので、0.0〜1.0までの数値を格納できるdouble
size;を用意します。
敵の構造体に「size」を追加する必要があることを覚えておいてください。
以上の事をふまえて、敵の情報をもった構造体をつくってみましょう。
上記の事に、敵の座標であるxとyを追加するとこのようになります。
typedef struct{
double x,y,size;
int flag,counter,hp,pattern;
} BODY_enemy_t;
この型で敵を100体作ります。
BODY_enemy_t enemy[100];
これで、最大で100匹まで一度に画面に敵を表示できるようになりました。(そんなに表示する機会はないでしょうが)
次に、敵の動きを計算するための、EnemyCalc関数を作ります。
関数の中身はこのようになっています。
void EnemyCalc(){ int i; for(i=0;i<100;i++) if(enemy[i].flag==0) break; switch(counter){ case 100: case 150: case 200: case 250: enemy[i].flag=1; //出現フラグを立てる enemy[i].counter=0;//出現して何カウント目か測るカウンター初期化 enemy[i].pattern=1; //どういう軌道を描くか enemy[i].size=0.5f;//敵の大きさ enemy[i].x=100.0; //xの初期座標 enemy[i].y=-10.0; //yの初期座標 break; default: break; } }
最初のfor文で、まだ表示していない敵を探します。まだ表示していない敵が見つかったらそのiを保持してブレイクします。
先ほど
「ゲームを開始して2秒後に飛んでくる敵は「パターン2」(pattern=2)で飛んでくる。なんて感じにしたらいいんじゃないか」
と言いました。ゲームが始まってからの時間はcounterがカウントしています。counterが1あたり17ミリ秒ですから、60カウントで1秒です。
カウンターが100になった時(だいたい1.7秒)、150になった時、200になった時、250になった時、敵を出現させたければ
このようなスイッチ文を書けばいいのです。
なにやら長々と敵の出現に際して初期化処理が行われていますね。
enemy[i].flag=1; //出現フラグを立てる
ここで、敵が出現中であることを示すフラグをたてます。
enemy[i].counter=0;//出現して何カウント目か測るカウンター初期化
敵が出現してからの時間をカウントするカウンターを初期化します。
enemy[i].pattern=1; //どういう軌道を描くか
先ほどからいっている、敵の動くパターンを示しています。
今はまだ、何の数字が何のパターンに対応するかは説明していません。
とりあえず、パターン1で動くように設定しておきましょう。
enemy[i].size=0.5f;//敵の大きさ
敵の大きさを代入しています。
enemy[i].x=100.0; //xの初期座標
enemy[i].y=-10.0; //yの初期座標
これはそれぞれの座標の初期値ですね。こうすることで、左上から出現するようになります。
次に先ほど話にでた、敵の移動パターンを設定します。
今、敵の移動パターンを1種類だけ作ってみます。このパターンをパターン1(pattern=1)とします。
パターン1は設定されたy座標を時間とともに増やして、直線的に下に下りるだけのモーションとします。
パターン1を処理する関数EnemyPattern1関数を作ります。
void Enemypattern1(int i){
enemy[i].y+=1.5f;
}
どの敵かiで識別番号をうけとって、その敵のy座標を1.5ずつ増やしていくだけの関数です。
ではこれを呼び出す、敵の計算を行うEnemyControl関数を作ってみます。
void EnemyControl(){ for(i=0;i<100;i++) if(enemy[i].flag==1) if(enemy[i].pattern==1) EnemyPattern1(i); }
for文で、敵の総計100体ループ。
もしその敵が出現中であれば
そしてその敵の移動パターンが1であれば、EnemyPattern1を呼び出すというものです。
どの敵であるかを示すiを引数として与えましょう。
次に、描写するEnemyDisp関数を作ってみましょう。
void EnemyDisp(){ for(i=0;i<100;i++) if(enemy[i].flag==1) DrawRotaGraph( (int)enemy[i].x , (int)enemy[i].y , enemy[i].size , 0.0f , img_enemy1[0] , TRUE ) ; }
描写には、拡大率が指定できるDrawRoteGraph関数を用います。
本来回転させるための関数ですが、使いやすいのでこちらを使います。回転率は使用しないので0.0fとしています。
敵の最高100体分ループさせもしその敵が出現中だったら表示しているだけです。
今回は、「敵」という新しい要素が出てきたので新しいenemy.cppというファイルを追加しましょう。
ソースファイルに「enemy.cpp」という名前で新しくファイルを追加してください。そこに以下のように追加してください。
/* enemy.cpp */
/* 素材 ver 1.10以上必要 */
#include "DxLib.h" #include "ExternGV.h" void EnemyPattern1(int i){ enemy[i].y+=1.5f; } void EnemyControl(){ for(int i=0;i<100;i++) if(enemy[i].flag==1) if(enemy[i].pattern==1) EnemyPattern1(i); } void EnemyCalcDisp(){ int i; for(i=0;i<100;i++) if(enemy[i].flag==0) break; switch(counter){ case 100: case 150: case 200: case 250: enemy[i].flag=1; //出現フラグを立てる enemy[i].counter=0;//出現して何カウント目か測るカウンター初期化 enemy[i].pattern=1; //どういう軌道を描くか enemy[i].size=0.5f;//敵の大きさ enemy[i].x=100.0; //xの初期座標 enemy[i].y=-10.0; //yの初期座標 break; default: break; } for(i=0;i<100;i++) if(enemy[i].flag==1) DrawRotaGraph( (int)enemy[i].x , (int)enemy[i].y , enemy[i].size , 0.0f , img_enemy1[0] , TRUE ) ; }
上に変更点は書きましたが、今回他のファイルも変更しましたので、改めて、他のファイルの内容も書いておきます。
GlobalVariable.hとExternGV.hには、新たに定義を追加していますが、これは次の章で説明するんで、とりあえず追加しておいてください。
敵の最大数である100じゃENEMY_TOTAL_NUMという定義名にしておきました。
/* GlobalVariable.h */
/* 定義 */ #define PLAYER_MAX_SHOT1 11 #define PLAYER_MAX_SHOT2 15 #define ENEMY_TOTAL_NUM 100 #define MONSTER1_X_SIZE 140.0 #define MONSTER1_Y_SIZE 128.0 /* グローバル宣言 */ int counter=0; int color_white; char Key[256]; //画像ファイルハンドル int img_background[2]; int img_player[4]; int img_enemy1[5]; //玉 int img_player_shot[2]; //各種変数 int ShotLevel=11; //音声ファイルハンドル int sound_player_shot[2]; //構造体 //プレイヤー typedef struct{ double x,y; int status,counter; } BODY_player_t; BODY_player_t Player; //敵 typedef struct{ double x,y,size; int flag,counter,hp,pattern; } BODY_enemy_t; BODY_enemy_t enemy[ENEMY_TOTAL_NUM];//[100] //ショット typedef struct{ double x,y; int flag; } SHOT_t; SHOT_t PlayerShot[PLAYER_MAX_SHOT1][PLAYER_MAX_SHOT2];//[11][15]
/* ExternGV.h */
/* 定義 */ #define PLAYER_MAX_SHOT1 11 #define PLAYER_MAX_SHOT2 15 #define ENEMY_TOTAL_NUM 100 #define MONSTER1_X_SIZE 140.0 #define MONSTER1_Y_SIZE 128.0 /* extern 宣言 */ extern int counter; extern int color_white; extern char Key[256]; //画像ファイルハンドル extern int img_background[2]; extern int img_player[4]; extern int img_enemy1[5]; //玉 extern int img_player_shot[2]; //各種変数 extern int ShotLevel; //音声ファイルハンドル extern int sound_player_shot[2]; //構造体 //プレイヤー typedef struct{ double x,y; int status,counter; int shot [11][15]; } BODY_player_t; extern BODY_player_t Player; //敵 typedef struct{ double x,y,size; int flag,counter,hp,pattern; } BODY_enemy_t; extern BODY_enemy_t enemy[100]; //ショット typedef struct{ double x,y; int flag; } SHOT_t; extern SHOT_t PlayerShot[PLAYER_MAX_SHOT1][PLAYER_MAX_SHOT2];
/* img_sound_load.cpp */
#include "DxLib.h" #include "ExternGV.h" void img_sound_load(){ //画像ハンドル取得 img_background[0] = LoadGraph("Sh/img/back/background0.png"); img_background[1] = LoadGraph("Sh/img/back/background1.png"); img_player_shot[0] = LoadGraph("Sh/img/shot/player/1.png"); img_player_shot[1] = LoadGraph("Sh/img/shot/player/2.png"); img_enemy1[0] = LoadGraph("Sh/img/mons/mons1_0.png"); img_enemy1[1] = LoadGraph("Sh/img/mons/mons1_1.png"); img_enemy1[2] = LoadGraph("Sh/img/mons/mons1_2.png"); img_enemy1[3] = LoadGraph("Sh/img/mons/mons1_3.png"); img_enemy1[4] = LoadGraph("Sh/img/mons/mons1_4.png"); LoadDivGraph( "Sh/img/char/player.png" , 4 , 4 , 1 , 32 , 48 , img_player ) ; //音楽ハンドル取得 sound_player_shot[0]= LoadSoundMem("Sh/sound/Player/shot.ogg"); }
初期化を行うinitial.cpp内で、敵の出現フラグを全部0にしておきます。
/* initial.cpp */
#include "DxLib.h" #include "ExternGV.h" void initialization(){ int i; Player.x=200.0; Player.y=400.0; Player.counter=0; Player.status=0; for(i=0;i<PLAYER_MAX_SHOT1;i++) for(int j=0;j<PLAYER_MAX_SHOT2;j++) PlayerShot[i][j].flag=0; for(i=0;i<ENEMY_TOTAL_NUM;i++) enemy[i].flag=0; } void SetColor(){ color_white = GetColor(255,255,255); //白色ハンドルを取得 }
/* main.cpp */
#include "DxLib.h" #include "GlobalVariable.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ extern void img_sound_load(); extern void initialization(); extern void SetColor(); extern void Background(); extern void PlayerShotCalc(); extern void PlayerShotDisp(); extern void PlayerControl(); extern void EnemyControl(); extern void EnemyCalcDisp(); extern void Background2(); extern void FpsTimeFanction(); int RefreshTime=0; ChangeWindowMode( TRUE ) ; if( DxLib_Init() == -1 ) return -1; SetDrawScreen( DX_SCREEN_BACK ) ; //裏画面を使用する。 img_sound_load(); initialization(); SetColor(); while(ProcessMessage() == 0 && GetHitKeyStateAll(Key) == 0){ RefreshTime = GetNowCount(); //今の時間を取得 ClearDrawScreen(); //裏画面のデータを全て削除 Background(); PlayerShotCalc(); PlayerShotDisp(); PlayerControl(); EnemyControl(); EnemyCalcDisp(); Background2(); FpsTimeFanction(); ScreenFlip() ; //裏画面データを表画面へ反映 counter++; if(Key[KEY_INPUT_ESCAPE]==1) break; //Escapeが押されたら終了 while(GetNowCount() - RefreshTime < 17);//1周の処理が17ミリ秒になるまで待つ } DxLib_End() ; return 0 ; }
実行画面
DXライブラリ著作権表示
DX Library Copyright (C) 2001-2006 Takumi
Yamada.