参考書の初めに作るゲームであるpongGameをつくります。日本ではポンゲームとよばれ、画面上で遊ぶ卓球ゲームのことです。
Visual Studioで新しいプロジェクトを作ります。c++の空のオブジェクト。
前回と同じようにソリューション名のプロジェクト名(Project1)を右クリック、追加->新しい項目->C++ファイルからGame.cppとmain.cppを作成します。また、追加->新しい項目->ヘッダーファイルを選択し、Game.hを作成します。
前回同様にSDLが使用できるように設定していきます。githubからすべてをコピぺする前に、main.cppに前回のHello.cppをコピペして動くか試したほうが無難です。
main.cpp
#include "Game.h"
int main(int argc, char** argv) {
Game game;
bool success = game.Initialize();
if (success) {
game.RunLoop();
}
game.Shutdown();
return 0;
}
プログラムを動かすとき、はじめにこのプログラムが起動します。Gameクラスオブジェクトが初期化され、ループされます。ループ状態を抜けると、シャットダウンして終了します。
Game.h
#pragma once
#include <cstdio>
#include <cstdlib>
#include <vector>
#include "SDL.h"
struct Vector2 {
float x;
float y;
};
struct Ball {
Vector2 pos;
Vector2 vel;
};
class Game {
public:
Game();
//ゲームを初期化する
bool Initialize();
//ゲームオーバーまでゲームループを実行する
void RunLoop();
//ゲームをシャットダウンする
void Shutdown();
private:
//ゲームループのためのヘルパー関数群
void ProcessInput();
void UpdateGame();
void GenerateOutput();
//SDLが作るウィンドウ
SDL_Window* mWindow;
//2D描画のためのレンダリング
SDL_Renderer* mRenderer;
// Number of ticks since start of game
Uint32 mTicksCount;
//ゲームの続行を支持する
bool mIsRunning;
//Pong の詳細
//パドルの方向
std::vector<int> mPaddlesDir;
//パドルの位置
std::vector<Vector2> mPaddlesPos;
//ボール
std::vector<Ball> mBalls;
};
ゲームのための様々なフィールドと関数を持っています。ヘッダーファイルなので具体的なプログラムはGame.cppに書き込まれます。
Game.cpp
初期化・入力処理・ゲームループ・描画処理をするための具体的なプログラムが書き込まれます。
初期化
bool Game::Initialize() {
...
// SDLレンダラーを作成
mRenderer = SDL_CreateRenderer(
mWindow,
-1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
);
...
}
初期化では、Game.hのフィールドを初期化しています。SDLで描画に必要なレンダラーを生成し、このオブジェクトを参照して画面に変化を起こしています。
入力処理
void Game::ProcessInput() {
SDL_Event event;
// キューにイベントがあれば繰り返す
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
mIsRunning = false;
break;
default:
break;
}
}
//その他ボタン処理
...
}
入力処理は、OSからのイベントを処理したり、ボタン入力を処理します。SDLではボタンを押す等のイベント(SDL_QUOT)をキューに入れるのでSDL_PollEvent(&SDL_Event)でとりだして処理します。
ゲームループ
main.cppでGame.Initialize()を終えてると、Game.RunLoop()がはじまります。
void Game::RunLoop() {
while (mIsRunning) {
ProcessInput();
UpdateGame();
GenerateOutput();
}
}
void Game::UpdateGame() {
//前のフレームから16msが経過するまで待つ
while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16));
// deltatimeは前のフレームとの時刻の差を秒に変換した値
float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f;
//deltatimeを最大値で制限する
if (deltaTime > 0.05f) {
deltaTime = 0.05f;
}
・・・
}
RunLoopでは入力とそれに基づいたゲーム情報の更新、出力を行います。また、更新では、プログラム全体のfpsをきめる、待機が存在します。16msの待機で1秒間に60回処理を行えるので60fpsになります。待機は前後フレームの秒数を参考にしており、これをデルタタイムと呼びます。
描画処理
void Game::GenerateOutput() {
// 描画色を青色に設定
SDL_SetRenderDrawColor(
mRenderer,
0, 0, 255, 255 // (R,G,B,A)
);
// バックバッファを青色で塗りつぶす
SDL_RenderClear(mRenderer);
// 描画色を白色に設定
SDL_SetRenderDrawColor(mRenderer, 255, 255, 255, 255);
// 壁を作成
SDL_Rect wall{
0, // Top left x
0, // Top left y
1024, // width
thickness // height
};
SDL_RenderFillRect(mRenderer, &wall);
...
// バックバッファとフロントバッファを入れ替え
SDL_RenderPresent(mRenderer);
}
バッファはコンピュータ内の画像情報です。画面に映すフロントバッファと、次のフレーム情報を書き込むバックバッファの2つを利用し、描画更新の最後にバッファを交換します。これはダブルバッファと呼ばれるゲームプログラムの基本です。
起動
ローカルデバッカーをクリック、もしくはデバック->デバックの開始するとゲームが起動します。
左のパドルはWとSキーで操作し、右のパドルはIとKキーで操作します。ボールを左もしくは右の画面端の壁にぶつかると自動で終了します。
改良版
これをもとに少し改良したバージョンをつくりました。
コメント