SOFTドット絵エディタ「EDGE2」、MIDI音楽編集ソフト「Domino」、楽曲などを配布している個人サイトです。ゲーム制作基礎編サンプルコードのダウンロードでは、まずサンプルコードをダウンロードし、解凍してください。C++でプロジェクトファイルを開いてください(「game_09.dsw」をダブルクリックすれば開けます)。圧縮ファイルに含まれる「game_09.exe」をダブルクリックし、実行してみてください。?カーソルキーで自機が移動し、スペースキーを押すと、弾が発射されると思います。いうわけで今回は、弾処理について解説したいと思います。構造化プログラミング基礎ここのプログラミング講座を見ている人は、ほとんどが初心者だと思います。初心者にプログラムを組ませてみると、高い確率で、WinMain(またはmain)関数内にひたすら書きまくるようです。たしかに、それでも動くには動くのですが、プログラムが複雑になってくると、どうしても管理がしにくくなってきますよね?そこで、ある程度固まった処理、例えば「キー入力でキャラを動かす」だとか「キャラクターを表示する」というような処理を区切って記述してやります。する事で、プログラムを管理しやすくなったり、バグを発見しやすくなります。ようするに役割ごとにサブルーチン化するって事です。弾の発射から消えて無くなるまでの大まかな流れは以下の通りです。弾用の変数(構造体等)を複数、用意するキー入力があったら、弾データを作成する弾を移動させる弾が画面外に出たら、弾データを消す弾表示2へ重要なポイントは、弾のデータを作ったら、その後、弾のデータを移動させたり削除したりする事でしょうかねぇ。実際にプログラムを組むでは、判りやすい(たぶん)ように、順番に説明していきたいと思います。構造化プログラムという事で、必要な変数等をグローバル変数として宣言してやります(ソースの出来るだけ上に書く。angle;//進む角度}bullet[BMAX];こうする事で、どこでも(どの自作関数内でも)これらの変数を参照する事が出来るようになります。今まで、プレイヤーの座標はWinMain関数内でcx,cyと宣言し、それらを使用していましたが、今回は構造体(_player)として宣言します。_bullet構造体は、弾の処理に使用します。今回使う変数はとりあえず、使用・未使用を表すフラグ(enable)と座標(x,y)と、進む方向(angle)だけです。最初に実行するWinMain関数を見てみましょう。nCmdShow){//ウィンドウ作成if(!InitWindow(hInstance,hPrevInstance,nCmdShow))Quit("ウィンドウ作成に失敗しました。");//DirectDraw開始if(!StartDirectDraw(hw))Quit("DirectDrawの初期化に失敗しました。");//ゲーム初期化InitGame();//ゲームループへGameLoop();return(FALSE);}いやぁ、今まではここ(↑)にひたすら書いていたので、今回のプログラムは結構すっきり見えますね。InitWindow()はウィンドウを作成する処理を書いてあるだけの自作関数なので解説省略。DirectDrawを初期化したらInitGame();を呼び出していますね?これはゲームに必要な処理を先にしておく自作関数です。i=0;i<BMAX;i++)bullet[i].enable=0;//全て未使用データとする//サイン、コサインテーブル作成for(i=0;i<360;i++){fsin[i]=(float)sin(i*3.1415926535/180);fcos[i]=(float)cos(i*3.1415926535/180);}}まず、作業用サーフェイス(lpWork)にキャラの画像を読み込んでおきます(左の絵)。_bullet構造体のメンバ変数「enable」を0に設定しておきます(全て)。enableが0だったら、その構造体は使用していないという事にします。ここの処理が終わると、処理はWinMainの方へ戻って、次はGameLoop();を呼び出していますよね?この関数はゲームのループ用の自作関数です。コードは次のようになっています。GameLoop(void){while(1){WaitSet();//現在の時間(単位:ミリ秒)取得MovePlayer();//自機移動Shoot();//弾発射判定MoveBullet();//弾移動Show();//画像表示Wait(1000/60);//メッセージループへ}}whileの中の最初と最後のWaitSet()、Wait()については既にやっているので解説省略。MovePlayer()関数を呼び出していますよね?こいつはキー入力により自機を動かす処理をする自作関数です。mv=4.0f;//移動量if(angle!=-1){player.x+=fcos[angle]*mv;player.y-=fsin[angle]*mv;}}この関数内でやっていることは第5回で説明したので、みりゃ判りますね?ここの処理が終わったら、GameLoopの方へ戻って、次にShoot()関数が呼ばれています。Shoot()関数では、スペースキーが押されているか判定し、押されていれば弾を作成します。ここでは、シューティングゲームで使うような、押しっぱなしでも連射できる処理をやります。{//キーが押されていない場合if(player.renda){player.renda++;player.renda%=10;}}}ここでは、ちょっとした工夫が必要ですかね。押している間、毎回弾が作成されてしまいますよね?シューティングゲームのように、一定間隔で弾を撃ちたい場合は、一つ変数を用意し(今回はplayer.renda)、スペースキーが押されている間はこの変数に1を足し、10になったら0に戻してやります。この変数が0の時だけ弾を作成すれば、処理的には10ループに1回弾を発射する事になります。ここで極端な例を挙げてみます。この弾を出す間隔が10では無く、1000だとしましょう。普通、ゲームで発射キーを押しっぱなしにする場合、発射キーを押した瞬間に弾が発射され、一定間隔で弾がでますよね?で、もし変数「player.renda」が仮に500で止まっていたらどうでしょう。発射キーを押しても、player.rendaは0でないため、しばらく発射されませんね?これではマズイので、発射キーが押されていない時、player.rendaが0で無い場合には、player.rendaに1を足し、ある数を越えたら0にする、といった処理を付け加えてやります。すれば、発射キーを離している間でもplayer.rendaの値が変化するので、発射キーを離し、しばらくして発射キーを押した瞬間に、弾を発射させる事が可能となります。ついでですが、発射キーが押されていない時にplayer.rendaを0にすると、いつでも発射キーを押した瞬間に弾が出せます(実際にやってみてください)。)自作関数を呼び出しています。いった処理をしているのか見てみましょう。no=-1;//作成する弾のデータ番号//構造体の中で、空いている(未使用)データを探すfor(i=0;i<BMAX;i++)if(bullet[i].enable==0){no=i;break;}//空いているデータが見つからなかったら作成せずに戻るif(no==-1)return;//データ詰めるbullet[no].x=x;bullet[no].y=y;bullet[no].angle=angle;bullet[no].enable=1;//使用中にする}さて、初めの方で呼び出したInitGame()内では_bullet構造体の全てのenableを0にし、未使用データとしましたね?ですから、ここ(CreateBullet)では、構造体の配列の0番からBMAX-1(今回は99)までを順に参照し、enableが0(つまり未使用となっているデータ)を探します。そんでもって、空いているデータが見つかったら、その番号の構造体に情報を詰め込んでやります。最後にenableを1とし、no番の構造体を使用データという事にしていますよね?ですから、次にCreateBullet関数内で空いているデータを順に探した時も、no番のenableは0では無いので上から新しくデータが詰められる事はありません。Shoot()関数へ戻り、GameLoop()の方へ戻り、次にMoveBullet()を呼び出しています。mv=6.0f;for(i=0;i<BMAX;i++){if(bullet[i].enable==0)//未使用データだったら次の処理へcontinue;//移動bullet[i].x+=fcos[bullet[i].angle]*mv;bullet[i].y-=fsin[bullet[i].angle]*mv;//画面外(今回は画面上)に出たら未使用データにするif(bullet[i].y<-32)bullet[i].enable=0;}}ここでは、構造体_bulletの配列を0から順番に参照し、enableが0でないデータ(つまり使用しているデータ)だけを処理します。角度による移動自体は第4回で説明したので省略。画面外に出たらenableを0にし、未使用データとしてやります。GameLoop()に戻り、次にShow()が呼び出されます。これは、ディスプレイに一連の表示をする自作関数です。コードを見てみましょう。弾は使用しているものだけ表示してやります。これで説明は終わりです(^^;今回のプログラムの流れを図で表すと、こんな感じになるのかも矢印は関数の呼び出しです。おまけ今回は弾のデータ構造に構造体の配列を使用しましたが、この場合、配列中に使用しているデータと使用していないデータが両方含まれていたり、弾数に制限があったりして少し不便ですよね?そんなときはリスト構造を使用すると結構スッキリ記述できます。Soldierは双方向リスト構造でやってました。blockworks.このサイトについてサイトマップRSSフィード。