前回(d:id:hecomi:20100727),画像の2値化を行ないました.次にこれを継承して,画像を弾幕の素へと変換するクラスを作りたいと思います.前回同様,中身は「龍神録プログラミングの館(http://dixq.net/rp/)」の第55章(http://dixq.net/rp/55.html)のコードを参考にしています.というかそのままです.
前回から多少変更がありましたので,画像を2値化するクラス新しいCLoadBmpは以下に置いておきます.
LoadBmp.cpp
LoadBmp.h
BMPを読み込んで弾幕(の素)を作るクラス
ImgToBulletCurtain.h
#include <vector> #include "LoadBmp.h" #define BULLET_NUM_MAX (3000) /*! @brief 弾の座標 */ typedef struct { float X, Y; } _BULLET_POS; /*! @brief 書き出し用データ */ typedef struct { int Num; _BULLET_POS Bullet[BULLET_NUM_MAX]; } _IMG_BULLET_OUTPUT_DATA; class CImgToBulletCurtain : public CLoadBmp{ private: /* @brief 描画モード切り替え 0: 画像表示 1: 背景-黒 2: 背景-白 */ int State; //!< 描画モード double Len; //!< 弾と弾の間隔 int Img[10]; //!< 弾のイメージハンドル int ImgNum; //!< 弾の色 /*! @brief 置いてある弾の情報 */ _BULLET_POS Bullet[BULLET_NUM_MAX]; int Num; public: /*! @brief コンストラクタ @param[in] fileName 読み込むBMPファイル */ CImgToBulletCurtain(const char* fileName); /*! @brief 弾を配置する */ void CalcBulletPosition(); /*! @brief 描画の状態を変更する */ void ChangeState(); /*! @brief 弾の色を変更する @param[in] n 現在の値から変更する値 ImgNum += n; */ void ChangeBulletColor(int n); /*! @brief 弾の描画間隔を変更する @param[in] dl 変更する値 Len += dl; */ void ChangeLen(double dl); /*! @brief 画面に現在の状態に応じた画面を描画 */ void Draw(); /*! @brief 座標データを出力する(バイナリ) */ void Output(const char* fileName); };
Dixqさんのコードを参考に,クラスを設計したらこんな感じになりました.
ImgToBulletCurtain.cpp
#include "ImgToBulletCurtain.h" #include <fstream> #include <DxLib.h> #define MIN_BULLET_MARGIN (1) #define DEFAULT_BULLET_MARGIN (5) using namespace std; CImgToBulletCurtain::CImgToBulletCurtain(const char* fileName) : CLoadBmp(fileName), Len(DEFAULT_BULLET_MARGIN), State(0), ImgNum(0), Num(0) { Binarization(); CalcBulletPosition(); } void CImgToBulletCurtain::CalcBulletPosition() { // 弾の数を元に戻す Num = 0; // 全てのピクセルを調べる for (int x=0; x<Width; x++) { for (int y=0; y<Height; y++) { if (Num >= BULLET_NUM_MAX ) { break; } // 色付ピクセルの場合 if (BitTable(x,y)) { // 弾の間隔が全てLenより大きいか bool flag = true; // 弾の間隔を全て調べる for (int i=0; i<Num; i++) { double dx, dy; dx = x - Bullet[i].X; dy = y - Bullet[i].Y; if (dx*dx + dy*dy < Len*Len) { flag = false; break; } } // 弾の間隔が全てLenより大きかった場合 if (flag) { Bullet[Num].X = static_cast<float>(x); Bullet[Num].Y = static_cast<float>(y); Num++; } } } } } void CImgToBulletCurtain::ChangeState() { State = (State+1)%3; } void CImgToBulletCurtain::ChangeLen(double dl) { Len += dl; if (Len <= MIN_BULLET_MARGIN) { Len = MIN_BULLET_MARGIN; } CalcBulletPosition(); } void CImgToBulletCurtain::ChangeBulletColor(int n) { ImgNum = ( ImgNum + n ) % ( sizeof(Img)/sizeof(int) ); } void CImgToBulletCurtain::Draw() { // 最初だけ画像を読み込む static bool ifFirst = false; if (!ifFirst) { LoadDivGraph("bullet/small.png", 10, 10, 1, 16, 16, Img); ifFirst = true; } // 色を取得 static int Black = GetColor(0,0,0), White = GetColor(255,255,255); int bgColor = White, fontColor = Black; // 背景色切り替え if (State == 1) { bgColor = Black; fontColor = White; } // 背景描画 if (State == 0) { CLoadBmp::Draw(); } else { DrawBox(0, 0, Width, Height, bgColor, TRUE); } // 弾描画 if (State <= 2) { for (int i=0; i<Num; i++) { DrawRotaGraphF(Bullet[i].X, Bullet[i].Y, 1.0, 0, Img[ImgNum], TRUE); } } // 現在の状態描画 DrawFormatString(0, 0, fontColor, "間隔 = %.1f", Len); DrawFormatString(0, 30, fontColor, "弾数 = %u", Num); } void CImgToBulletCurtain::Output(const char *fileName) { // 出力用構造体に格納 _IMG_BULLET_OUTPUT_DATA outputData; outputData.Num = Num; memcpy(&(outputData.Bullet), &Bullet, sizeof(Bullet)); // 座標を-1〜1に変換 for (int i=0; i<Num; i++) { outputData.Bullet[i].X -= Width/2; outputData.Bullet[i].Y -= Height/2; outputData.Bullet[i].X /= Width/2; outputData.Bullet[i].Y /= Height/2; } // ファイル出力 ofstream ofs(fileName, ios::out | ios::binary); if (ofs.fail()) { printfDx("file open error: %s\n", fileName); return; } ofs.write(reinterpret_cast<char*>(&outputData), sizeof(outputData)); }
1ピクセルずつ調べて,2値化後の黒い点にぶち当たったら,過去に置かれた弾との距離をすべて調べて,距離が変数Lenより離れていればそこを新しい弾の位置として登録していく,という操作を繰り返しています.うまく考えられてますね.
実際に実行してみた
次のコードをmain.cppとして実行しました.例のごとくKeyMouse.hは51章のものを用いています.
#include <DxLib.h> #include "ImgToBulletCurtain.h" #include "KeyMouse.h" #define DEFAULT_WINDOW_WIDTH (100) #define DEFAULT_WINDOW_HEIGHT (100) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { // BMPの読み込み CImgToBulletCurtain bmp("bmp/hecomi.bmp"); // スクリーンサイズ変更 SetGraphMode(bmp.GetWidth(), bmp.GetHeight(), 16); // Windowサイズ変更許可 SetWindowSizeChangeEnableFlag(TRUE); // 初期化&描画スクリーンセット if (DxLib_Init() == 1 || SetDrawScreen(DX_SCREEN_BACK) != 0) { return -1; } // ループ bool Flag = true; int Key[256]; while ( ProcessMessage() == 0 && ClearDrawScreen() == 0 && GetHitKeyStateAll_2(Key) == 0 && Key[KEY_INPUT_ESCAPE] == 0 ) { // スペースを押すと描画モード変更 if (Key[KEY_INPUT_SPACE] == 1) bmp.ChangeState(); // 左右キーで長さ変更 if (Key[KEY_INPUT_RIGHT] == 1 || Key[KEY_INPUT_RIGHT] > 20) bmp.ChangeLen(0.2); if (Key[KEY_INPUT_LEFT] == 1 || Key[KEY_INPUT_LEFT] > 20) bmp.ChangeLen(-0.2); // 上下キーで色変更 if (Key[KEY_INPUT_UP] == 1 || Key[KEY_INPUT_UP] > 20) bmp.ChangeBulletColor(1); if (Key[KEY_INPUT_DOWN] == 1 || Key[KEY_INPUT_DOWN] > 20) bmp.ChangeBulletColor(-1); // 出力 if (Key[KEY_INPUT_O] == 1) { bmp.Output("output.dat"); printfDx("Output was done.\n"); } // 描画 bmp.Draw(); // 反映 ScreenFlip(); } DxLib_End(); return 0; }
キーを押すと,クラスのメンバ変数が変更されるようになってます.それに従ってDrawします.「o」キーを押すとバイナリで構造体を出力します.
実行結果
上手く動作して,出力もできました.次のエントリでは,実際に弾幕にした結果を紹介したいと思います.