LoginSignup
14
19

More than 3 years have passed since last update.

【C++】ゲームプログラミングC++を読書しながらゲームをつくる#1

Last updated at Posted at 2019-06-16

はじめに

UnrealEngine4などでゲームをつくっていたが、C++でも作りたいと思いローベルのC++入門講座を約400ページほど読み進めて(クラスの基本程度)、読み終わったらすすめようと買っていたゲームプログラミングC++を並行して読むことにした。
読み進めながらまとめを書いていきます。
※コードを解説するものではありません。

Githubに作成したコードを公開しています:
https://github.com/murati111/PracticeGameProgramCpp
使用したIDEはVisualStudio2017→VisualStudio2019(リリース直後に移りました)

Chapter1

Chapter1では「Pong」の変種をSimple DirectMedia Layer(SDL)ライブラリを使いながら作りながら、ゲームプログラミングの基礎概念を学ぶ。

学習の進め方

自分で一から作成するものと思いきや、もう完成済みのコードを解説してくれる形だった・・・
ちなみにこんな感じのゲームです。
ブログ用1.gif
さすがに読んでいるだけだとつまらないので、コードを複製して解説している関数の内容を削除して自分で書いていくことにしました。

1.4 ゲームループとゲームクラス

ゲームループ:ゲームプログラム全体の流れを制御する。
ゲームループの1回の繰り返しを1フレームという。
ゲームが60fps(frame per seconds)というのはゲームループが毎秒60回繰り返していることを意味する。

1.4.1 フレームの中身

高いレベルから見ると主に3つのステップを実行する。
1.入力があれば処理
2.ゲームワールドを更新
3.出力するものを生成

パックマンの例はわかりやすい。
1でジョイスティックの入力を読み込む
2で入力に基づいてパックマンを更新、ゴースト、ドット、ユーザーインターフェースを更新。
3でオーディオと映像が出力される。

1.4.2 ゲームクラスの骨組み

Gameクラスを作成し、ゲームループの実行、ゲームの初期化と終了を行うコードを記述する。

Gameクラス


class Game
{
public:
    Game();
    bool Initialize();
    void RunLoop();
    void Shutdown();
private:
    void ProcessInput();
    void UpdateGame();
    void GenerateOutput();

    // Window created by SDL
    SDL_Window* mWindow;
    // Renderer for 2D drawing
    SDL_Renderer* mRenderer;
    // Number of ticks since start of game
    Uint32 mTicksCount;
    // Game should continue to run
    bool mIsRunning;

    // Pong specific
    // Direction of paddle
    int mPaddleDir;
    // Position of paddle
    Vector2 mPaddlePos;
    // Position of ball
    Vector2 mBallPos;
    // Velocity of ball
    Vector2 mBallVel;
};

Game::Initialize

SDL_Init関数
SDL_CreateWindow関数を呼び出し初期化する。
初期化が成功すればtrueを返す。

初期化できる注目すべきサブシステムのフラグ:
SDL_INIT_AUDIO:オーディオデバイスの管理、再生、録画
SDL_INIT_VIDEO:ビデオサブシステム、ウィンドウの作成、OpenGLとのインターフェイス、2Dグラフィックス処理
SDL_INIT_HAPTIC:フォースフィードバック(振動など)のサブシステム
SDL_INIT_GAMECONTROLLER:コントローラ入力デバイスをサポートするためのサブシステム

Game:Shutdown

Initializeの逆を行う。
SDL_DestroyWindowを使ってWindowを破棄し、SDL_Quitで終わらせる。

Game:RunLoop

ゲームを繰り返し実行するが、mIsRunningがfalseになったら繰り返しをやめる。

void Game::RunLoop()
{
    while (mIsRunning)
    {
        ProcessInput();
        UpdateGame();
        GenerateOutput();
    }
}

1.4.4 入力処理

SDLは、OSから受け取ったイベントを内部のキューで管理する。
フレームごとにイベントがないかキューを調べる。
ProcessInputの中でイベントループ処理をする。
SDL_PollEvent関数はキューにイベントがあればtrueを返す。

void Game::ProcessInput()
{
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
       case SDL_QUIT:
        mIsRunnning=false;
        break;
    }
}

これで、ゲーム中に[×]ボタンを押すと停止する。

SDL_GetKeyborardStateで「キーボードの現在の状態が格納された配列」へのポインタを返す。

const Uint 8* state = SDL_ GetKeyboardState(NULL);

この配列インデックス参照で、特定のキー入力を確認できる
ESCAPEキーの場合はこのようになる

if(state[SDL_SCANCODE_ESCAPE)

1.5 基本的な2Dグラフィックス

ほとんどのディスプレイはラスターグラフィックス(raster graphics)を使っている。
ディスプレイには、ピクセル(pixel)と呼ばれる画素が2次元の格子状に並んでいて、
ここのピクセルは異なる色の光を異なる強さで発行することができる。

1.5.1 カラーバッファ

コンピューターグラフィックスではメモリ内のカラーバッファ(color buffer)という場所に、
画面全体の色情報が置かれている。

カラーバッファがどれだけのメモリを使うかはビット数に依存し、これを色深度(color depth)という。
それぞれに8bitのRGBAの値を格納したければ個々のピクセルのカラーバッファは32bitになる。
解像度が1080pでピクセルごとに32bit使うなら、1920×1080×4byteで、約7.9MBになる。

1.5.2 ダブルバッファ

ゲームは1秒に何十回も更新される。
ゲーム側でカラーバッファに書き込み、ディスプレイが同じカラーバッファを読み出す。
フレームレートとリフレッシュレートは一致しないので、ゲームがバッファへの書き込みをしている最中に、
ディスプレイがカラーバッファから読み出すことが問題となる。
ディアリングという問題。

解決策としてゲーム側でバックバッファという片方のディスプレイに書き込んでいる間、
同時にディスプレイ側で、もう一つのフロントバッファから安全に読み出す。
フレームが完了したら、ゲームとディスプレイが、それぞれのバッファを交換する。
これをダブルバッファを呼ばれる。

1.5.3 基本的な2Dグラフィックスの実装

初期化とシャットダウン

レンダラー(renderer)というグラフィックスを描画するシステムを作成する。

    mRenderer = SDL_CreateRenderer(
        mWindow, // Window to create renderer for
        -1,      // Usually -1
        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
    );

SDL_CreateRendererでも初期化に失敗したらnullptrを返す。
レンダラーを終了するにはGame::ShutdownにSDL_DestroyRendererを呼び出す。

基本的な描画設定

描画の流れは以下の3ステップ。
1.バックバッファを単色でクリアする
2.ゲームのシーン全体を描画する。
3.フロントバッファとバックバッファを交換する。

SDL_SetRenderDrawColorで描画色を指定する。

SDL_SetRenderDrawColor(mRenderer, 0, 0, 255, 255);

次にSDL_RenderClearを呼び出して、バックバッファを描画色でクリアする。

SDL_RenderClear(mRenderer);

最後にSDL_RenderPresentを呼び出して、フロントバッファとバックバッファを交換する。

SDL_RenderPresent(mRenderer);

最終的にこのようになる
image.png

1.5.4 壁とボールとパドルの描画

壁の描画

長方形を描くには、SDL_RenderFillRectを使う。
背景色を変えて、SDL_Rect構造体を使ってサイズを指定する。

SDL_Rect wall{
    0,          // Top left x
    0,          // Top left y
    1024,       // Width
    thickness   // Height
};

最後にSDL_RenderFillRectを呼び出し、長方形を描く。

ボールとパドルの描画

ボールとパドルはUpdateGame段階で動かすので、クラスにした方が合理的だが
それはChapter2で話すらしい。
今回はメンバー変数に両方のオブジェクトの中心位置を保存し、それらの位置を基に長方形を描く。

Vector2構造体の宣言。

struct Vector2
{
    float x;
    float y;
};

Vector2をメンバー変数としてパドルとボールの位置をGameに追加する。
ただし、メンバー変数がパドルとボールの中心座標を表すのに対して、SDL_Rectは左上の座標で定義されている。
なので、それぞれのx座標からは幅の半分、y座標からは高さの半分を差し引けばいい。

SDL_Rect ball{
    static_cast<int>(b.pos.x - thickness / 2),  //static_cast<int>はfloatからint整数に変換する
    static_cast<int>(b.pos.y - thickness / 2),
    thickness,
    thickness
};

以上で描画が完了し、以下のようになった!
image.png

おわりに

次回は1.6ゲームの更新から。
それにしても進まないのでもう少し簡潔に記事にします。
次:https://qiita.com/murati111/items/c07fbe7b0175012f549c

14
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
19