【ゲームプログラミングC++】Chapter1課題を解いてみた
記事の目的
最近、ゲームプログラミングC++という書籍を購入しまして、その中にある実装課題を解いていたのですが、書籍の中にもネット上にも模範解答が見当たらないんですよね。
なので、自分なりに実装したモノを記事で紹介することで、記事を見た方からのアドバイスを頂きつつ、今後この書籍で勉強する人が挫折せず続けられる一助となればと思った次第です。
そんなわけで、この記事ではどうやって課題を解いたかを中心に解説します。
書籍を読んでない方向けに、Chapterごとの簡単な説明はするかもしれませんが、書籍本文の内容は解説しませんので、悪しからず。
課題1.1
サンプルコードで実装されているPongに対して、1つしかないパドルを2つに増やし、Pongを2人プレイに対応させる
1.1は基本、既に実装されているパドルのコードを参考にすれば、自力でもそこまで苦戦しないはずです。
Game.h
2つ目のパドル(以下パドル2)に関するクラス変数を定義するだけでOK。
private:
//...省略
// Pong specific
// Direction of paddle
int mPaddleDir;
// Position of paddle
Vector2 mPaddlePos;
+ // Direction of paddle
+ int mPaddleDir2;
+ // Position of paddle
+ Vector2 mPaddlePos2;
Game.cpp
Initalize()
で定義したパドル2を初期化。
元のパドルは左端から10px離れた位置にあるため、パドル2のx値は右端から10px + パドルの幅(thickness)
に指定。
bool Game::Initialize()
{
//.略
mPaddlePos.x = 10.0f;
mPaddlePos.y = 768.0f/2.0f;
+ mPaddlePos2.x = 1024.0f - 10.0f - thickness;
+ mPaddlePos2.y = 768.0f / 2.0f;
return true;
}
ProcessInput()
では、IキーとKキーによる上下移動をサポートさせる。
void Game::ProcessInput()
{
//略
// Update paddle direction based on W/S keys
mPaddleDir = 0;
if (state[SDL_SCANCODE_W])
{
mPaddleDir -= 1;
}
if (state[SDL_SCANCODE_S])
{
mPaddleDir += 1;
}
+ mPaddleDir2 = 0;
+ if (state[SDL_SCANCODE_I])
+ {
+ mPaddleDir2 -= 1;
+ }
+ if (state[SDL_SCANCODE_K])
+ {
+ mPaddleDir2 += 1;
+ }
}
UpdateGame()
には、パドル2の位置を更新する処理と、ホールがパドル2に当たった時のx速度反転処理を追加。パドル2用のdiff2
も定義する。
画面右端に当たったらゲームを終了する処理も追加する。
void Game::UpdateGame()
{
//略
// Update paddle position based on direction
if (mPaddleDir != 0)
{
mPaddlePos.y += mPaddleDir * 300.0f * deltaTime;
// Make sure paddle doesn't move off screen!
if (mPaddlePos.y < (paddleH / 2.0f + thickness))
{
mPaddlePos.y = paddleH / 2.0f + thickness;
}
else if (mPaddlePos.y > (768.0f - paddleH / 2.0f - thickness))
{
mPaddlePos.y = 768.0f - paddleH / 2.0f - thickness;
}
}
+ if (mPaddleDir2 != 0)
+ {
+ mPaddlePos2.y += mPaddleDir2 * 300.0f * deltaTime;
+ // Make sure paddle doesn't move off screen!
+ if (mPaddlePos2.y < (paddleH / 2.0f + thickness))
+ {
+ mPaddlePos2.y = paddleH / 2.0f + thickness;
+ }
+ else if (mPaddlePos2.y > (768.0f - paddleH / 2.0f - thickness))
+ {
+ mPaddlePos2.y = 768.0f - paddleH / 2.0f - thickness;
+ }
+ }
//略
// Bounce if needed
// Did we intersect with the paddle?
float diff = mPaddlePos.y - ball.position.y;
+ float diff2 = mPaddlePos2.y - ball.position.y;
// Take absolute value of difference
diff = (diff > 0.0f) ? diff : -diff;
+ diff2 = (diff2 > 0.0f) ? diff2 : -diff2;
if (
(//左パドル
// Our y-difference is small enough
diff <= paddleH / 2.0f &&
// We are in the correct x-position
ball.position.x <= 25.0f && ball.position.x >= 20.0f && //25は左paddleの右辺, 20は反射の有効範囲?
// The ball is moving to the left
ball.velocity.x < 0.0f)
+ ||
+ (//右パドル
+ // Our y-difference is small enough
+ diff2 <= paddleH / 2.0f &&
+ // We are in the correct x-position
+ ball.position.x <= mPaddlePos2.x + 5 && ball.position.x >= mPaddlePos2.x && // mPaddlePos2.xは右paddleの左辺、mPaddlePos2.x + 5は反射の有効範囲
+ // The ball is moving to the left
+ ball.velocity.x > 0.0f)
+ )
{
ball.velocity.x *= -1.0f;
}
// Did the ball go off the screen? (if so, end game)
else if (ball.position.x <= 0.0f)
{
mIsRunning = false;
}
// Did the ball collide with the right wall?
+ else if (ball.position.x >= (1024.0f - thickness) && ball.position.x > 0.0f)
+ {
+ mIsRunning = false;
+ }
//略
}
(記事にしてて思いましたが、右パドル(パドル2)の反射処理、ballの幅・thicknessを考慮し忘れてる気がしますね)
最後にGenearteOutpt()
にパドル2の描画処理を書けばOK。
右壁の描画処理は消しちゃいましょう。
void Game::GenerateOutput()
{
//略
- // Draw right wall
- wall.x = 1024 - thickness;
- wall.y = 0;
- wall.w = thickness;
- wall.h = 1024;
- SDL_RenderFillRect(mRenderer, &wall);
// Draw paddle
SDL_Rect paddle{
static_cast<int>(mPaddlePos.x),
static_cast<int>(mPaddlePos.y - paddleH / 2),
thickness,
static_cast<int>(paddleH)
};
SDL_RenderFillRect(mRenderer, &paddle);
+ SDL_Rect paddle2 = paddle;
+ paddle2.x = mPaddlePos2.x;
+ paddle2.y = mPaddlePos2.y;
+ SDL_RenderFillRect(mRenderer, &paddle2);
}
以上で課題1.1は完了です。
今後余裕があれば課題ごとの実行動画を載せたいですね。
課題1.2
引き続き実装済みのPongに対して、1つしかないパドルを2つに増やし、Pongを2人プレイに対応させる
更新を楽にするため、ボールはvector
でまとめて管理するようにしました。
(時間に余裕のある人は、パドルもvectorで管理するようにしてみてください)
Game.h
vectorで管理するために、Ball
構造体を作成しています。
+struct Ball
+{
+ Vector2 position;
+ Vector2 velocity;
+};
class Game
{
private:
//略
+ vector<Ball> mBalls;
}
Game.cpp
Initalize()
で、定義した2つのボールをmBalls
に突っ込みます
bool Game::Initialize()
{
//略
+ Ball ball1;
+ ball1.position.x = 1024.0f / 2.0f;
+ ball1.position.y = 768.0f / 2.0f;
+ ball1.velocity.x = -200.0f;
+ ball1.velocity.y = 235.0f;
+ Ball ball2;
+ ball2.position.x = 1024.0f / 2.0f;
+ ball2.position.y = 768.0f / 2.0f;
+ ball2.velocity.x = 130.0f;
+ ball2.velocity.y = -335.0f;
+ mBalls = { ball1, ball2 };
return true;
}
UpdateGame()
では、mBallsからボールを一つずつ取り出して処理するようにする。
(1.1で実装したのと合わせて、関連するコードを全部提示します)
void Game::UpdateGame()
{
//略
// Update ball position based on ball velocity
for (Ball& ball : mBalls)
{
ball.position.x += ball.velocity.x * deltaTime;
ball.position.y += ball.velocity.y * deltaTime;
// Bounce if needed
// Did we intersect with the paddle?
float diff = mPaddlePos.y - ball.position.y;
float diff2 = mPaddlePos2.y - ball.position.y;
// Take absolute value of difference
diff = (diff > 0.0f) ? diff : -diff;
diff2 = (diff2 > 0.0f) ? diff2 : -diff2;
if (
(//左パドル
// Our y-difference is small enough
diff <= paddleH / 2.0f &&
// We are in the correct x-position
ball.position.x <= 25.0f && ball.position.x >= 20.0f &&
// The ball is moving to the left
ball.velocity.x < 0.0f)
||
(//右パドル
// Our y-difference is small enough
diff2 <= paddleH / 2.0f &&
// We are in the correct x-position
ball.position.x <= mPaddlePos2.x + 5 && ball.position.x >= mPaddlePos2.x &&
// The ball is moving to the left
ball.velocity.x > 0.0f)
)
{
ball.velocity.x *= -1.0f;
}
// Did the ball go off the screen? (if so, end game)
else if (ball.position.x <= 0.0f)
{
mIsRunning = false;
}
// Did the ball collide with the right wall?
else if (ball.position.x >= (1024.0f - thickness) && ball.position.x > 0.0f)
{
mIsRunning = false;
}
// Did the ball collide with the top wall?
if (ball.position.y <= thickness && ball.velocity.y < 0.0f)
{
ball.velocity.y *= -1;
}
// Did the ball collide with the bottom wall?
else if (ball.position.y >= (768 - thickness) &&
ball.velocity.y > 0.0f)
{
ball.velocity.y *= -1;
}
}
}
GenerateOutput()
のball描画処理をmBallsのループ処理に変更
void Game::GenerateOutput()
{
// Draw ball
- SDL_Rect ball{
- static_cast<int>(mBallPos.x - thickness/2),
- static_cast<int>(mBallPos.y - thickness/2),
- thickness,
- thickness
- };
- SDL_RenderFillRect(mRenderer, &ball);
+ for (const Ball& const ball : mBalls)
+ {
+ SDL_Rect b{
+ static_cast<int>(ball.position.x - thickness / 2),
+ static_cast<int>(ball.position.y - thickness / 2),
+ thickness,
+ thickness
+ };
+ SDL_RenderFillRect(mRenderer, &b);
+ }
}
以上で課題1.2は完了です。
あとがき
こういった記事を書くのは初めてなので読みにくい点があったら申し訳ないです。
Chapter2も既に実装しているため、なるべく早くアップしたいと思います。
記事の構成はガラッと変わるかもしれないですがご了承を。
余談(エラーハンドリング)
書籍のサンプルプロジェクトクローン後、Chapter1(Pong)をデバッグ起動しようとしたら、エラーで起動できない問題が起きました。
調べたところ、おそらくローカルのSDKとプロジェクトのSDKのズレ?が原因みたいでした。
visual studioのプロジェクト(P)
>ソリューションの再ターゲット
でそのままOKを押せば、ズレが治り、Pongを実行できましたので、類似の問題が起きている方は参考にしていただければ幸いです。
画像はChapter3のもの。また、プロジェクトを新しく開いた時とは表示が微妙に異なります
Discussion