はかせのラボ

私の頭の中を書いていく雑記ブログです

DirectX コンボ実装

あいさつ

どうも、はかせです。
今回はコンボ実装と細かな修正追加です。

コンボ

弾幕シューティングには大体連続で敵を倒すとスコアにボーナスがつくものが多いと思います。
そんなわけで実装したいと思います。

実装

今回は一定時間内に敵を撃破するとコンボが加算され、
途切れた際にコンボ中に倒した敵のスコアがそのまま上乗せされるという
よくある感じのものを作ります。

コンボのトリガーですが、
スコア加算をトリガーとしています。
(今現状では敵を倒す以外にスコア変動がないため)

void AddScore(double score)
{ 
	if(mIsCombo)
	{
		mComboScore += score;
		mComboCountFrame = 0;
	}
	else mIsCombo = true;
	*mScoreRP + score; 
}

スコア加算メソッドの中でコンボ中か判定し
コンボ中であればコンボボーナスにスコアを加算し経過フレームをリセット、
コンボ中でなければコンボフラグを立てます。

コンボ中の処理ですが、
①経過フレームをカウント
②経過フレーム数が一定値いないか判定
 (コンボ継続判定)
③コンボ終了時にボーナススコアをスコアに加算

という処理をしています。

void PlayScene::ComboAction()
{
	//コンボ中でなければ処理をしない
	if (!mIsCombo) return;
	//経過フレームカウント
	mComboCountFrame++;
	//一定フレーム以上経過したら
	if(mComboCountFrame > COMBO_LIMIT_FRAME)
	{
		mIsCombo = false;
		*mScoreRP + mComboScore;
		mComboCountFrame = 0;
	}
}

アイテム移動

今までアイテムは出現した位置に固定されていました。
固定されているアイテムもあると思いますが、
動くアイテムもあると思います。

なのでアイテムを動かすコンポーネントを作りました。
やってることはほとんど弾と同じで
移動するベクトルが下方向に固定されています。

void ItemMover::Initialize(DXGameObject * gameObject)
{
	mGameObject = gameObject;
	mDXCamera = mGameObject->GetDXCamera();
	mId = mGameObject->GetID();
	mTag = mGameObject->GetTag();
	mMoveSpeed = 0.02f;
}

void ItemMover::Update()
{
	auto transform = mGameObject->GetTransform();
	transform->Position.y -= mMoveSpeed;
	//スクリーン座標取得
	auto screenPos = mDXCamera->WorldToScreenPoint(transform);
	//返ってきた行列の中から座標を取得
	auto screenPosX = screenPos.m128_f32[0];
	auto screenPosY = screenPos.m128_f32[1];
	//画面中央はcHeight / 2
	//画面外に出たら
	if (screenPosY < -cHeight / 2)
	{
		mGameObject->SetEnable(false);
	}
	if (screenPosY > cHeight + cHeight / 2)
	{
		mGameObject->SetEnable(false);
	}
	if (screenPosX < -cWidth / 2)
	{
		mGameObject->SetEnable(false);
	}
	if (screenPosX > cWidth + cWidth / 2)
	{
		mGameObject->SetEnable(false);
	}
}

実行結果

アイテム移動とコンボです。
f:id:hakase0274:20190115233710g:plain

デストラクタが呼ばれない問題

前回のあとがきの後でデストラクタが呼ばれない原因が
わからないという話をしました。

結果としては
呼ばれていたがVisualStudioのブレークポイントで止まらなかった
ということでした。

これはVisualStudioに昔からあるバグらしくデバッグ情報の形式を変更することで解決できました。
f:id:hakase0274:20190115232301p:plain

変更して実行したら無事止まってデストラクタが呼ばれていること、
ちゃんとメモリが解放されていることが確認できました。

あとがき

今回はコンボ実装+αでした。
徐々に良くなってはいるんですが
やっぱりエフェクトとか欲しくなりますね。

パーティクルとかポストプロセスとか入るだけで
ゲームのクオリティってぐっと上がりますからね。

それでは今回はこの辺でノシ

今回作ったものはgithubに上げました。
github.com

DirectX バグ取り ~キャッシュミス~

あいさつ

どうも、はかせです。
今回は前回解決できていなかったメモリ増加バグの解決編です。

前回までのおさらい

前回犯人はローカル変数が解放されていないことだと判断し
捜査をしていました。

ですが、それは真犯人が仕組んだ巧妙な罠だったのです。

罠の概要

まずはコードを見てみます。

std::wstringstream hp;//←前回犯人だと思っていた変数
hp.precision(6);
hp << value;
auto hpt = hp.str();
auto hpptr = hpt.c_str();
mHPText->UpdateText(hpptr);
hp.clear();

前回は確保したstd::wstringstreamがスコープ抜けたときに解放されていない
という判断をしました。

それはこの一連の処理をコメントアウトしたらメモリ増加が止まったからです。
動的にメモリ確保しているのはstd::wstringstreamだけだと思っていました。
なのでstd::wstirngstreamstd::to_wstringに変えて実行してみました。
(ToString的なものを発見できました)

これでローカル変数確保がなくなりメモリ増加も止まるだろうとそう思っていました。
ですが結果はむしろ逆で、メモリ増加はより増えました。

真犯人

std::wstringstream hp;//←前回犯人だと思っていた変数
hp.precision(6);
hp << value;
auto hpt = hp.str();
auto hpptr = hpt.c_str();
mHPText->UpdateText(hpptr);//←真犯人
hp.clear();

真犯人はキャッシュミスでした。
キャッシュはテクスチャ描画の時に使用しています。
今回のキャッシュミスは数値の描画の時に発生していました。

キャッシュの概要

テキスト描画では描画するときに既に文字が作成済みだった場合は
キャッシュから文字のテクスチャを持ってきて描画、
なければ作成してキャッシュします。

キャッシュに関してはこちらをどうぞ。
こちらの記事では画像のテクスチャのキャッシュを行っていますが
文字のテクスチャも所詮同じテクスチャなので同様の機構でキャッシュしていました。

キャッシュのキーとなる値は画像の場合はその画像のパスを、
文字の場合は1文字ずつ切り分けてその文字で設定しキャッシュしていました。
(GAMEなら"G","A","M","E"というように分けてキャッシュを行っていました)
実際ちゃんとその値でキャッシュはされていましたし、
表示もされていました。

ただキャッシュ検索が問題でした。
私は1文字ずつ切り分ける時よくあるchar型のポインタを++して実体を取り出すやり方で切り分けをしました。

ですが、キャッシュ検索するときには
実態を取り出す前のポインタの状態で比較をしていました。
なので"GAME"で実体は”G"であっても”GAME”で比較されてしまい、キャッシュミスを引き起こしていました。

//textは"G"を示しているがポインタ比較のため"GAME"で比較されてしまった
if (text == tex.get()->fileName)
{
	pReturn = tex.get();
	break;
}

真犯人逮捕

今度こそ真犯人をしっかりみつけました。
罪状もばっちりです。
あとは逮捕するのみ。
ということで逮捕します。

問題なのはキャッシュ検索の際に実体ではなく
ポインタを使っていたことです。
なので実体で比較するように直します。
ただstd::wstringwchar_tは直接比較できないようなので
std::wstringに直してみます。
wchr_tstd::wstringへの変換は直代入でオッケーみたいです。

TEXTURE_DATA * DXRenderDataPool::FindTextureData(wchar_t text)
{
        //実体のポインタなので特定の1文字を示してくれる
	std::wstring str = &text;
	TEXTURE_DATA* pReturn = nullptr;
	//テクスチャ情報のキャッシュがあるか
	for (auto &tex : mTextureList)
	{
		if (str == tex.get()->fileName)
		{
			pReturn = tex.get();
			break;
		}
	}
	return pReturn;
}

これで実行してみたところ最初はキャッシュが無いため
メモリ増加が起こりましたが、
少しするとあとは何回やってもメモリ増加は発生しなくなりました。

あとがき

今回は前回に続きバグ取りでした。
バグは犯人が見つかればあとは楽なのですが、
犯人捜しがとにかくむずかしいです。
今回のように犯人は捜査をかく乱するための罠を張り巡らしています。
(まぁ罠というか自爆というか・・・orz)

今後は罠を最初から見抜いて真犯人に一気に詰め寄れるように精進します。
それでは今回はこの辺でノシ

今回作ったものはgithubに上げました。
github.com

ところで・・・

前回発生していた問題のメモリ増加バグは解決できました。
ですが、解決の途中でぼやいていたデストラクタ呼ばれない問題は解決できていません。
こっちはネットで調べても手元で色々やってもうんともすんとも言いません。
直接的な問題発生にはならないと思いますが、
解決できていないのはなんか気持ち悪いです。
コードを載せますのでわかる方がいらっしゃいましたら教えてくださいm(__)m

struct BULLET_SETTING_DATA
{
	//位置情報
	TRANSFORM* pTransform;
	TRANSFORM transform;
	//弾に設定するタグ
	Tag tag;
	//x方向にかける力
	float xVectol;
	//y方向にかける力
	float yVectol;
	//読み込む画像のパス
	std::wstring texturePath;
	void ScaleRatio(float ratio)
	{
		scaleXRatio = ratio;
		scaleYRatio = ratio;
		scaleZRatio = ratio;
	}
	//自身のスケールを1とした時の弾の大きさの割合
	float scaleXRatio = 1.0f;
	float scaleYRatio = 1.0f;
	float scaleZRatio = 1.0f;
	bool isXFixed = false;
	bool isYFixed = false;
	BULLET_SETTING_DATA()
	{
		scaleXRatio = 1.0f;
		scaleYRatio = 1.0f;
		scaleZRatio = 1.0f;
	}
	~BULLET_SETTING_DATA()
	{
                //何も書かないとブレークポイントで確かめられなかった
		scaleXRatio = 0.0f;
		pTransform = nullptr;
	}
};