読者です 読者をやめる 読者になる 読者になる

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

BMPファイルから弾幕を生成する(準備)

愉繰郷 C++

前回(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」キーを押すとバイナリで構造体を出力します.

実行結果


上手く動作して,出力もできました.次のエントリでは,実際に弾幕にした結果を紹介したいと思います.