細かい話になりそうですから、画面に表示する情報を増やしましょう。
「Shot.cpp」を開き、「Draw関数」に座標を表示するコードを追加します。
void Shot::Draw(Engine *pEngine)
{
if (m_bExist) {
SetRect(&m_sour, 0, 0, WIDTH, HEIGHT);
SetRect(&m_dest, m_x, m_y, m_x + WIDTH, m_y + HEIGHT);
pEngine->Blt(&m_dest, TEXTURE_SHOT, &m_sour);
}
pEngine->DrawPrintf(0, 20, FONT_GOTHIC, D3DCOLOR_XRGB(0, 255, 0), "X = %d : Y = %d", m_x, m_y);
}
|
弾の発射中だけでなく、常に座標を表示しています。
実行した瞬間の座標は「不定値」が入っているため↓のようになります。
発射中は弾の座標が変わっていくのが見えます。
敵に当たると、当たった時の座標が残ります。
敵の座標も表示しておきましょう。
「Enemy.cpp」を開き、「Draw関数」に座標を表示するコードを追加します。
void Enemy::Draw(Engine *pEngine)
{
if (m_bExist) {
SetRect(&m_sour,
m_kind * WIDTH * 2 + m_animation * WIDTH,
m_direction * HEIGHT,
m_kind * WIDTH * 2 + m_animation * WIDTH + WIDTH,
m_direction * HEIGHT + HEIGHT);
SetRect(&m_dest,
m_x,
m_y,
m_x + WIDTH,
m_y + HEIGHT);
pEngine->Blt(&m_dest, TEXTURE_ENEMY, &m_sour);
}
pEngine->DrawPrintf(0, 40, FONT_GOTHIC, D3DCOLOR_XRGB(255, 255, 0), "X = %d : Y = %d", m_x, m_y);
}
|
こちらも敵の存在に関係なく座標を表示します。
弾を当てて、当たった瞬間の状態を確認します。
最初の位置から動かず、弾を発射した時の結果です。
プレイヤーから弾が発射された瞬間の弾の座標は、プレイヤーの中心から発射させるため調整値が加算されます。
プレイヤーのX座標 + (プレイヤーの幅 − 弾の幅) / 2 = 弾のX座標
数値にすると、
32 + (32 − 16) / 2 = 40
Y座標も、
プレイヤーのY座標 + (プレイヤーの高さ − 弾の高さ) / 2 = 弾のY座標
となり、数値にすると、
256 + (32 − 16) / 2 = 264
となります。
弾の発射位置は、Xが40、Yが264でした。
弾の移動スピードは定数で「10」が設定されていますから、X座標が「10」ずつ加算され「560」の時に敵に当たったと言う事です。
この時の状況を図で表してみると↓のようになります。
※「弾」のスピードを「1」にするとはっきり見えるかも知れません。
計算上では2つの「円」が接しましたので、衝突していると判断されます。
しかし、図をみて分かるように「弾」と「敵」が当たっているようには見えません・・・
もう少し詳しく説明すると、実際にはもう1つ手前の時点で「弾」は消えています。
上の図が衝突する直前の状況を表した画像です。
プログラムの実行順で説明すると「SceneGame.cpp」の「Draw関数」が呼び出された直後の画像となります。
この次は「Update関数」が実行されるのですが、プログラムの実行順をよく見てみましょう。
void SceneGame::Update() { m_player.Update(m_pEngine, m_shot); m_shot.Update(); //・・・@ m_enemy.Update(); if (m_shot.CollideCircle(m_enemy)) { //・・・A m_enemy.Delete(); //・・・B m_shot.Delete(); //・・・C } } |
まず@の箇所が実行され、「弾」のX座標が「560」になります。
Aで衝突判定を行い、「弾」と「敵」の衝突が検出されます。
BCで「敵」と「弾」が消えます。
この後「Draw関数」が実行され、下の画面が表示されます。
前の画像と比べてみてください。
「敵」に衝突する随分前で「弾」は消えてしまっています。
「弾」が当たった画像が描画される前に、内部の座標を使って衝突判定をしてしまっている事が原因でこうなります。
対処法の1つとして、プログラムの実行順を変えてみましょう。
「SceneGame.cpp」の「Update関数」を開き、先に衝突判定を行うよう変更します。
void SceneGame::Update()
{
if (m_shot.CollideCircle(m_enemy)) {
m_enemy.Delete();
m_shot.Delete();
}
m_player.Update(m_pEngine, m_shot);
m_shot.Update();
m_enemy.Update();
}
|
こうする事で、直前の画像は下の図のようになります。
結局当たったようには見えませんね・・・
そもそも上の図の状態で衝突していると判定しているのは、判定する「円」が大きい事も原因の1つです。
今回はオブジェクトの半径を使っていますので、こうなっていますが、衝突判定用の半径を別途持っておくことも考えられます。
ただ、今回はプログラムを大きく変えたくないので対処療法的な方法でやってみます。
※対処療法的な方法ですから、あまりお勧めはしません。
「Base.cpp」の「CollideCircle関数」を開いてください。
判定に使う「半径の合計」を短くするようプログラムを変更します。
bool Base::CollideCircle(const Base &refTarget)
{
if (!m_bExist) {
return false;
}
if (!refTarget.m_bExist) {
return false;
}
int myRadius = WIDTH / 2;
int targetRadius = refTarget.WIDTH / 2;
int myCenterX = m_x + myRadius;
int myCenterY = m_y + myRadius;
int targetCenterX = refTarget.m_x + targetRadius;
int targetCenterY = refTarget.m_y + targetRadius;
int x = myCenterX - targetCenterX;
int y = myCenterY - targetCenterY;
double distance = sqrt(x * x + y * y);
int totalRadius = myRadius + targetRadius;
totalRadius = static_cast<int>(totalRadius * 0.8);
if (distance <= totalRadius) {
return true;
}
return false;
}
|
無理やり「半径の合計」を0.8倍した長さに変更しました。
実行した結果を見てみましょう。
衝突した瞬間の「弾」のX座標が「570」になりました。
衝突した瞬間を画像として表示してみると下の図のようになります。
当たっているように見えますね。
今回は対処療法的な方法(付け焼き刃とも言います)で対応しましたが、本来であれば衝突判定用の半径を別途持っておく方が良いと思います。
最初の段階でゲームの全体像をはっきりさせ、どのようなデータが必要なのか、その中で「基底クラス」で宣言するべきものはどれか、など本来はクラスの設計をよく考えてから作り始めるべきです。
この講座は「積み上げ方式」で説明しているため、全体像が不明瞭な状態から色々な機能を追加しているため非常に場当たり的なプログラムになっています。
学習する過程はこれでも良いと考えていますが、実際にゲームを作る際には、最初にしっかり設計してから作るようにしてください。
次回は「円」の衝突判定の中身を変えてみます。