52 章
今章は51章の続きです。前回作ったプロトタイプ版を完成版にさせてあげましょう。
51章のプログラムは作った弾の位置を一時的に覚えて、描画するだけでした。
今回は
1. Ctrl + Zキーで操作が元に戻せるように (windows標準のCtrl+Zと同じ役)
2. Ctrl + Yキーで元に戻した操作を逆に戻せるように (windows標準のCtrl+Yと同じ役)
3. Sキーを押しながら左右キーで弾のスペースが変更出来るように ※1
4. 左右キーで弾の角度が変更できるように
5. C(color)キーで弾の色が選べるように
6. K(kind)キーで弾の種類を選べるように
7. スペースキーで表示切替が出来るように
※1: 弾はドラッグして赤い線を引っ張り、クリックすると、自動的にそこに一定の間隔で直線的に置かれていきますね。この間隔の事です。
この7つの機能と、作ったデータをファイルに書き出す機能を追加させてやりましょう。
今回もまず、完成版の動作を見て下さい。
どんな感じになるかイメージがつかめたでしょうか。
では具体的なプログラムコードを見ていきましょう。
今回は弾の画像を使うので、弾の画像を読み込んでやります。これは以前と変わりませんね。
--- load.cpp に以下を追加 --- LoadDivGraph( "../dat/img/bullet/b0.png" , 5 , 5 , 1 , 76 , 76 , ImgBullet[0] ) ; LoadDivGraph( "../dat/img/bullet/b1.png" , 6 , 6 , 1 , 22 , 22 , ImgBullet[1] ) ; LoadDivGraph( "../dat/img/bullet/b2.png" , 10 , 10 , 1 , 5 , 120 , ImgBullet[2] ) ; LoadDivGraph( "../dat/img/bullet/b3.png" , 5 , 5 , 1 , 19 , 34 , ImgBullet[3] ) ; LoadDivGraph( "../dat/img/bullet/b4.png" , 10 , 10 , 1 , 38 , 38 , ImgBullet[4] ) ; LoadDivGraph( "../dat/img/bullet/b5.png" , 3 , 3 , 1 , 14 , 16 , ImgBullet[5] ) ; LoadDivGraph( "../dat/img/bullet/b6.png" , 3 , 3 , 1 , 14 , 18 , ImgBullet[6] ) ; LoadDivGraph( "../dat/img/bullet/b7.png" , 10 , 10 , 1 , 16 , 16 , ImgBullet[7] ) ; LoadDivGraph( "../dat/img/bullet/b8.png" , 10 , 10 , 1 , 12 , 18 , ImgBullet[8] ) ; LoadDivGraph( "../dat/img/bullet/b9.png" , 3 , 3 , 1 , 13 , 19 , ImgBullet[9] ) ; LoadDivGraph( "../dat/img/bullet/b10.png", 8 , 8 , 1 , 8 , 8 , ImgBullet[10]) ; LoadDivGraph( "../dat/img/bullet/b11.png", 8 , 8 , 1 , 35 , 32 , ImgBullet[11]) ; LoadDivGraph( "../dat/img/bullet/b12.png", 10 , 10 , 1 , 12 , 12 , ImgBullet[12]) ; LoadDivGraph( "../dat/img/bullet/b13.png", 10 , 10 , 1 , 22 , 22 , ImgBullet[13]) ;
今回実際変更を加える関数は上記を除くと CalcOperate()、Show() の2つだけです。 他の関数は前の章から変更を加えませんので、この関数だけ見て下さい。 キーボード入力によって、操作を変更するわけですが、変更する操作に関わる変数はこちらの構造体になりますので確認しておいて下さい。 //操作設定系情報 typedef struct{ int State;//状態 int Knd;//種類 int Col;//色 int Space;//スペース float Angle;//角度 int flag;//表示・非表示フラグ fPt_t fPt1;//1回目クリックの場所 fPt_t fPt2;//2回目クリックの場所 }Operate_t; ではこの変数を 1. Ctrl + Zキーで操作が元に戻せるように (windows標準のCtrl+Zと同じ役) 2. Ctrl + Yキーで元に戻した操作を逆に戻せるように (windows標準のCtrl+Yと同じ役) 3. Sキーを押しながら左右キーで弾のスペースが変更出来るように ※1 4. 左右キーで弾の角度が変更できるように 5. C(color)キーで弾の色が選べるように 6. K(kind)キーで弾の種類を選べるように 7. スペースキーで表示切替が出来るように の順で変更する機能を実装してみましょう。 今から実装するColcOperate関数の中身をみていきます。 void CalcOperate(){ //各弾の色数 int Col[14]={5,6,10,5,10,3,3,10,10,3,8,8,10,10}; //左コントロールキーが押されている時 1を実装します。 BlPoint.Numとは現在表示している弾の数です。5つ弾を置いたとしたらこの数は5になります。 ですからこの数を4にすれば、表面的に現在4つまで登録していることになるわけです。 加えて0.5秒以上押しっぱなしの時はダララララと連続で操作出来るようにします。 if( Key[KEY_INPUT_LCONTROL]>0 ){ //Zキーが押されたら if( Key[KEY_INPUT_Z]==1 || Key[KEY_INPUT_Z]>30){ //操作を一つ前に戻す if( BlPoint.Num>0 ){ BlPoint.Num--; } } 2を実装します。 これは先ほどZキーで戻した操作を更に逆に戻す機能です。 Bl.Point.Numを減らすことで行った手続きを元に戻しました。 これを逆に戻すなら増やせばいいですね。 ただ、先ほどの手順で4に戻したとすると、登録を行っていない6まで逆に戻してしまってはいけません。 そこで、登録してある番号まで逆に戻します。 x座標もy座標も0であることは、その弾はまだ登録されていないことを示すので、この条件にあてはまらない間数を増やしましょう //Yキーが押されたら if( Key[KEY_INPUT_Y]==1 || Key[KEY_INPUT_Y]>30 ){ //以前セットしたところまでY押した数だけ戻す if( BlPoint.Num<PMAX-1 && !(BlPoint.Bl[BlPoint.Num].x==0 && BlPoint.Bl[BlPoint.Num].y==0) ){ BlPoint.Num++; } } } 3を実装します。 弾のスペースはOperate.Spaceで調整します。まぁここは簡単ですね。 //Sキーが押されているとき if( Key[KEY_INPUT_S]>0 ){ //左キー押されたら if( Key[KEY_INPUT_LEFT]==1 || Key[KEY_INPUT_LEFT]>30){ //スペース減らす if( Operate.Space>2 ){ Operate.Space--; } } //右キー if( Key[KEY_INPUT_RIGHT]==1 || Key[KEY_INPUT_RIGHT]>30){ //スペース増やす if( Operate.Space<300 ){ Operate.Space++; } } } 4を実装します。 何も押さずに左右キーを押すことで、弾の角度を変えています。 まぁここも難しくないですね。 else{ if( Key[KEY_INPUT_LEFT]>0 )//左キーで Operate.Angle-=PI2/360;//左回り if( Key[KEY_INPUT_RIGHT]>0 )//右キーで Operate.Angle+=PI2/360;//右回り } 5を実装します。 Cキーを押すごとに弾の色を変更しています。 ここで、弾によって色の最大数は違うことに注意して下さい。 ちょっと「dat/img/bullet/」を見て下さい。 0番の弾であるb0.pngの弾は5色ありますが、 1番の弾であるb1.pngの弾は6色あります。 なので、例えば0番の弾で色6を示してはいけませんね。 ですから、色の最大数を格納した配列Colをこの関数の始まりに用意しました。 この数によって制御させてやりましょう。 if( Key[KEY_INPUT_C]==1 )//色 Operate.Col = (Operate.Col+1)%Col[Operate.Knd]; まぁ残りはみたまんまということで。 スペースボタンを押すと表示フラグが切入交互に変わります。 Kキーで種類を変更します。変更した時は自動的に色は0に戻ります。 if( Key[KEY_INPUT_SPACE]==1 )//表示フラグ Operate.flag*=-1; if( Key[KEY_INPUT_K]==1 ){//種類 Operate.Knd = (++Operate.Knd)%14; Operate.Col = 0; } } 後はデータの中身を見せるShow関数を作ってあげればOKです。 前半やってることは前章の中身と変わりません。 Operateの変数の中身を表示させる部分を追加しただけです。 //データを見せる void Show(){ int i; //背景を描画 DrawGraph(0,0,ImgBack,FALSE); //弾を描画 for(i=0; i<BlPoint.Num; i++){ DrawRotaGraphF( BlPoint.Bl[ i ].x, BlPoint.Bl[ i ].y,1.0,BlPoint.Bl[i].Angle, ImgBullet[BlPoint.Bl[i].Knd][BlPoint.Bl[i].Col], TRUE ); } //ピーと引っ張る線を描画 if( Operate.State==1 ){ DrawLine( (int)Operate.fPt1.x, (int)Operate.fPt1.y, Mouse.x, Mouse.y, Red ); } //マウスポインタ部に弾を表示する DrawRotaGraph( Mouse.x, Mouse.y, 1.0, Operate.Angle, ImgBullet[Operate.Knd][Operate.Col], TRUE ); //弾の上に、今設定されているスペースがどれ位か表示する DrawLine(Mouse.x,Mouse.y,Mouse.x+Operate.Space,Mouse.y,Blue); //表示フラグがオンなら現在の操作設定内容を表示 if(Operate.flag==1){ SetDrawBlendMode( DX_BLENDMODE_ALPHA , 128 ) ; DrawBox(0,0,230,120,0,TRUE); SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0 ) ; DrawFormatString(0, 0,White,"座標[%3d,%3d]",Mouse.x,Mouse.y); DrawFormatString(0, 20,White,"種類 [%2d] : Kキー",Operate.Knd); DrawFormatString(0, 40,White,"色 [%2d] : Cキー",Operate.Col); DrawFormatString(0, 60,White,"角度[%5.1f°] : ←→キー",Operate.Angle/PI2*360.0f); DrawFormatString(0, 80,White,"空白 [%3d] : S+←→キー",Operate.Space); DrawFormatString(0,100,White,"スペースキーで非表示"); } } おっと、せっかく作ったデータをファイルに書き出す関数を作るのを忘れていました。 //データを出力 void Output(){ int i; FILE *fp; //座標データを-1〜1に変換する for(i=0; i<BlPoint.Num; i++){ BlPoint.Bl[i].x -= WINDOW_SIZE_X/2; BlPoint.Bl[i].x /= WINDOW_SIZE_X/2; BlPoint.Bl[i].y -= WINDOW_SIZE_Y/2; BlPoint.Bl[i].y /= WINDOW_SIZE_Y/2; } fp = fopen( "Output.dat" , "wb" ); if( fp == NULL ) return; fwrite( &BlPoint, sizeof(BlPoint), 1, fp ); fclose(fp); } ファイル入出力はfopen関数で出来ますね。 特に特別な関数ではなく、Cの標準関数です。 使い方が解らない人は「fopen」でググって見て下さい。 構造体をいっぺんにファイルに書き出します。 読み込むときはfreadでいっぺんに読み込むことが出来ます。 読み込みについては次の章で説明します。 ここでちょっと注目です。 データ書き込む前に何やらfor文で処理していますね。ここがポイントです。 今、座標データ(x,y)はそれぞれ0〜640(WINDOW_SIZE_X, WINDOW_SIZE_Y)の値で表現されていますね。(厳密には639) 上のプログラムでは(0,0)〜(640,640)までありうるわけです。 でも画像の中心って(320,320)なんですよね。ここが原点(0,0)であって欲しいものです。 何故そのほうがいいのかは次の章で実感すると思います。 また、中心からどれ位離れているか、-1〜1の範囲で座標を表現した方が弾の速さの設定とかしやすいんで、 (0,0)〜(640,640)のデータを (-1,-1)〜(1,1)に変換してやります。 その処理がしてあるんですね。
では最後に全体をキレイに見渡してみましょう。
--- main.cpp --- に以下の部分を追加 #define WINDOW_SIZE_X 640 #define WINDOW_SIZE_Y 640 --- (中略) --- void CalcOperate(){ //各弾の色数 int Col[14]={5,6,10,5,10,3,3,10,10,3,8,8,10,10}; //左コントロールキーが押されている時 if( Key[KEY_INPUT_LCONTROL]>0 ){ //Zキーが押されたら if( Key[KEY_INPUT_Z]==1 || Key[KEY_INPUT_Z]>30){ //操作を一つ前に戻す if( BlPoint.Num>0 ){ BlPoint.Num--; } } //Yキーが押されたら if( Key[KEY_INPUT_Y]==1 || Key[KEY_INPUT_Y]>30 ){ //以前セットしたところまでY押した数だけ戻す if( BlPoint.Num<PMAX-1 && !(BlPoint.Bl[BlPoint.Num].x==0 && BlPoint.Bl[BlPoint.Num].y==0) ){ BlPoint.Num++; } } } //Sキーが押されているとき if( Key[KEY_INPUT_S]>0 ){ //左キー押されたら if( Key[KEY_INPUT_LEFT]==1 || Key[KEY_INPUT_LEFT]>30){ //スペース減らす if( Operate.Space>2 ){ Operate.Space--; } } //右キー if( Key[KEY_INPUT_RIGHT]==1 || Key[KEY_INPUT_RIGHT]>30){ //スペース増やす if( Operate.Space<300 ){ Operate.Space++; } } } else{ if( Key[KEY_INPUT_LEFT]>0 )//左キーで Operate.Angle-=PI2/360;//左回り if( Key[KEY_INPUT_RIGHT]>0 )//右キーで Operate.Angle+=PI2/360;//右回り } if( Key[KEY_INPUT_C]==1 )//色 Operate.Col = (++Operate.Col)%Col[Operate.Knd]; if( Key[KEY_INPUT_SPACE]==1 )//表示フラグ Operate.flag*=-1; if( Key[KEY_INPUT_K]==1 ){//種類 Operate.Knd = (++Operate.Knd)%14; Operate.Col = 0; } } //データを見せる void Show(){ int i; //背景を描画 DrawGraph(0,0,ImgBack,FALSE); //弾を描画 for(i=0; i<BlPoint.Num; i++){ DrawRotaGraphF( BlPoint.Bl[ i ].x, BlPoint.Bl[ i ].y,1.0,BlPoint.Bl[i].Angle, ImgBullet[BlPoint.Bl[i].Knd][BlPoint.Bl[i].Col], TRUE ); } //ピーと引っ張る線を描画 if( Operate.State==1 ){ DrawLine( (int)Operate.fPt1.x, (int)Operate.fPt1.y, Mouse.x, Mouse.y, Red ); } //マウスポインタ部に弾を表示する DrawRotaGraph( Mouse.x, Mouse.y, 1.0, Operate.Angle, ImgBullet[Operate.Knd][Operate.Col], TRUE ); //弾の上に、今設定されているスペースがどれ位か表示する DrawLine(Mouse.x,Mouse.y,Mouse.x+Operate.Space,Mouse.y,Blue); //表示フラグがオンなら現在の操作設定内容を表示 if(Operate.flag==1){ SetDrawBlendMode( DX_BLENDMODE_ALPHA , 128 ) ; DrawBox(0,0,230,120,0,TRUE); SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0 ) ; DrawFormatString(0, 0,White,"座標[%3d,%3d]",Mouse.x,Mouse.y); DrawFormatString(0, 20,White,"種類 [%2d] : Kキー",Operate.Knd); DrawFormatString(0, 40,White,"色 [%2d] : Cキー",Operate.Col); DrawFormatString(0, 60,White,"角度[%5.1f°] : ←→キー",Operate.Angle/PI2*360.0f); DrawFormatString(0, 80,White,"空白 [%3d] : S+←→キー",Operate.Space); DrawFormatString(0,100,White,"スペースキーで非表示"); } } //データを出力 void Output(){ int i; FILE *fp; fp = fopen( "Output.dat" , "wb" ); for(i=0; i<BlPoint.Num; i++){//座標データを-1〜1に変換する BlPoint.Bl[i].x -= WINDOW_SIZE_X/2; BlPoint.Bl[i].x /= WINDOW_SIZE_X/2; BlPoint.Bl[i].y -= WINDOW_SIZE_Y/2; BlPoint.Bl[i].y /= WINDOW_SIZE_Y/2; } if( fp == NULL ) return; fwrite( &BlPoint, sizeof(BlPoint), 1, fp ); fclose(fp); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode(TRUE);//ウィンドウモード SetGraphMode(WINDOW_SIZE_X,WINDOW_SIZE_Y,32);//画面サイズ変更 SetWindowSizeChangeEnableFlag(TRUE);//画面の大きさ調整可能 if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化 SetMouseDispFlag( TRUE ) ;//マウス表示オン ini();//初期化 load();//ロード while(ProcessMessage()==0 && ClearDrawScreen()==0 && GetHitKeyStateAll_2(Key)==0 && Key[KEY_INPUT_ESCAPE]==0){ //↑メッセージ処理 ↑画面をクリア ↑入力状態を保存 ↑ESCが押されていない GetHitMouseStateAll_2(&Mouse); CalcMouse();//マウス部の計算 CalcOperate();//操作系の計算 Show();//見せる ScreenFlip(); } Output();//データを出力 DxLib_End(); return 0; }
これで弾で絵や字を書き、書き終わったらEscボタンを押すことで、弾の座標情報Output.datが出来上がります。
次の章でこのデータを使って弾幕を作ってみましょう。
- Remical Soft -