TechCraft – エンジニアのためのスキルアップメモ

エンジニアのスキルアップを少しでも加速する技術ブログ

ゲームプログラミング入門

C++ゲームプログラミング入門

――ゲームループからデータ構造、WebAssemblyまで

1. ゲームプログラムは「1本のループ」から始まる

ゲームプログラミングをこれから学ぶとき、まず押さえておきたいのは、

ゲームの本質は「ループ」である

ということ。

どんなジャンルのゲームでも、内部ではだいたい次のような処理が、画面の裏側で延々と繰り返されている。

  1. 入力を読む(キーボード / マウス / コントローラ / ネットワーク)
  2. ゲーム世界の状態を更新する(物理 / AI / スクリプト
  3. 画面に描画する(2D / 3D / UI)

この3ステップを 1 秒間に 30 回〜 60 回以上回し続ける。
これが「ゲームループ」と呼ばれるものだ。

C++ で書くと、極端に単純化すれば次のようなイメージになる。

while (isRunning) {
    processInput();  // 1. 入力
    update();        // 2. 状態更新
    render();        // 3. 描画
}

このたった数行のループを、いかに破綻させず、いかに高速に、いかに保守しやすく構築するか
ゲームプログラミングの多くは、この一点に集約されていると言ってもいい。


2. C++でゲームを書く理由と向き合い方

「ゲームと言えば C++」という印象を持っている人も多いと思う。
実際、大手ゲームエンジンUnreal Engine など)や、家庭用ゲーム機向けタイトルの多くは C++ をベースにしている。

理由はいくつかある。

  • ネイティブコードで動き、高速・低レイテンシを出しやすい
  • メモリレイアウトを制御しやすく、CPU/GPU をぎりぎりまで使える
  • 既存のゲームエンジンミドルウェアC++ 前提で設計されている

一方で、C++

  • 言語仕様が広く深い
  • メモリ管理の責任がプログラマ側にある
  • 設計を間違えるとコードベースが一瞬で「沼」になる

という側面もある。
ここで重要になるのが「基本」と「設計」だ。

  • ゲームループ・コンポーネント・イベント駆動といったゲーム特有の基本
  • 抽象化・責務分離・依存関係の整理といったコーディングと設計の基本

この2つを同時に身につけていくと、C++ ゲームプログラミングの学習効率は一気に上がる。


3. ゲームループの設計:フレームと時間の扱い

ゲームループを組むときに最初にぶつかる壁が、「時間の扱い」だ。

フレームレートが一定とは限らない環境で、どうやってゲームの動きを安定させるか。
基本的な考え方は次の2つがある。

3.1 デルタタイム方式(可変フレームレート)

1フレームごとに「前のフレームから何秒経ったか(Δt)」を測り、物理やアニメーションを Δt に応じて進める方式。

double previousTime = now();

while (isRunning) {
    double current = now();
    double dt = current - previousTime;
    previousTime = current;

    processInput();
    update(dt);  // dt秒ぶん進める
    render();
}

メリット: - 環境に関係なく時間ベースで進むので、60fps でも 144fps でもゲームの進み具合が揃う

デメリット: - dt が小さすぎたり大きすぎたりすると、物理計算の精度や安定性が崩れる - 固定ステップ前提のライブラリと組み合わせにくい

3.2 固定タイムステップ方式(Fixed Timestep)

「物理は常に 1/60 秒ごとに進める」と決めてしまい、フレームごとに複数回 update したり、逆にスキップしたりする方式。

const double dtFixed = 1.0 / 60.0;
double accumulator = 0.0;
double previousTime = now();

while (isRunning) {
    double current = now();
    double frameTime = current - previousTime;
    previousTime = current;
    accumulator += frameTime;

    processInput();

    while (accumulator >= dtFixed) {
        update(dtFixed);
        accumulator -= dtFixed;
    }

    render();
}

メリット: - 物理やロジックを「固定間隔」で進められるため、安定しやすい - 携帯機・家庭用ゲーム機の古典的なパターンと相性が良い

デメリット: - コードがやや複雑になる - CPU が弱い環境では update が追いつかず、「スローモーション」になることもある

ゲームによって、あるいはゲームの一部分によって、この2つを使い分けたり、ハイブリッドにしたりする。


4. オブジェクト設計:エンティティとコンポーネント

画面に表示されるものは、プレイヤーも敵も弾もエフェクトも、全部「オブジェクト」だ。
これを C++ でどう表現するかは、ゲームプログラミングの定番テーマのひとつになっている。

4.1 典型的なクラスの分解

例えば、シンプルな 2D シューティングゲームを考えると、こんなクラス分割がありえる。

  • Game:ゲーム全体を管理(ループ・シーン切り替えなど)
  • Actor:プレイヤー・敵・弾などの共通基底
  • SpriteComponent:スプライト描画担当
  • InputComponent:入力に応じて Actor を動かす
  • MoveComponent:速度・加速度の更新を行う
  • CollisionComponent:当たり判定

ざっくり C++ のコードでイメージすると、次のようになる。

class Component {
public:
    virtual ~Component() = default;
    virtual void update(float dt) {}
};

class Actor {
public:
    void update(float dt) {
        for (auto& comp : components) {
            comp->update(dt);
        }
        // Actor自身のロジック...
    }

    template <typename T, typename... Args>
    T* addComponent(Args&&... args) {
        auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
        auto raw = ptr.get();
        components.emplace_back(std::move(ptr));
        return raw;
    }

private:
    std::vector<std::unique_ptr<Component>> components;
};

class MoveComponent : public Component {
public:
    void update(float dt) override {
        // ここで Actor の位置を更新する…
    }
};

この「Actor + Component」という設計は、ゲームエンジンでよく使われるパターンだ。

一方で、コンポーネント数が増えすぎると、

  • 関係が分かりにくい
  • 実行時ポリモーフィズムのオーバーヘッド
  • キャッシュの局所性が悪化

といった問題も出てくる。そこをどうバランスするかが、「設計力」の腕の見せ所になる。

4.2 データ指向設計と ECS の話

より大規模なゲームや、CPUキャッシュ効率を極端に追い込むような現場では、

  • Entity Component System(ECS)
  • データ指向設計(Data-Oriented Design, DOD

といった設計思想が登場する。

  • オブジェクトごとではなく、「ポジションを持つ全エンティティ」「速度を持つ全エンティティ」のようにデータ列をまとめる
  • ひとつのシステムが、対応するコンポーネント配列を一気に処理してループを回す

ことで、メモリアクセスを連続化し、CPU のパイプラインやキャッシュに優しい形にする。

たとえば、次のような雰囲気になる。

struct Position { float x, y; };
struct Velocity { float vx, vy; };

std::vector<Position> positions;
std::vector<Velocity> velocities;

void updateVelocity(float dt) {
    for (size_t i = 0; i < positions.size(); ++i) {
        positions[i].x += velocities[i].vx * dt;
        positions[i].y += velocities[i].vy * dt;
    }
}

オブジェクト指向 vs データ指向」はどちらか一方を選ぶ話ではなく、

  • 中規模までは「素直な OOP + コンポーネント
  • パフォーマンスが気になるホットパスだけ「データ指向」

といったハイブリッドも多い。


5. アルゴリズムとデータ構造:ゲームは「リアルタイムな大規模データ処理」

ゲームプログラムは、見た目にはグラフィックとサウンドの世界だが、中身はかなりストイックなデータ処理だ。

  • 物理シミュレーション:空間分割(QuadTree / Octree / BVH)、衝突判定
  • AI:グラフ探索(BFS / A*)、経路探索、ビヘイビアツリー
  • レンダリング:ソート(前後関係)、バッチング、LODの選択
  • オンラインゲーム:イベントログ、マッチング、ランキング

こうした処理には、アルゴリズムとデータ構造の知識がそのまま効いてくる。

5.1 空間分割と衝突判定

例えば 2D シューティングで、敵と弾の当たり判定を考えてみる。

  • 敵 100 体
  • 弾 200 発

がいたとすると、全ペアの衝突判定は 100 × 200 = 20,000 通り。
これはまだ可愛らしい数字だが、3D の広い世界で何千ものオブジェクトが動き回ると、
ナイーブな全探索はすぐ破綻する。

そこで使われるのが、空間分割のアルゴリズムだ。

  • グリッド分割:空間を等間隔のセルに分け、オブジェクトをセルに登録
  • QuadTree / Octree:再帰的に空間を分割して密度に応じて管理
  • BVH(Bounding Volume Hierarchy):包囲体の階層を構築して粗い当たり判定を高速化

これらは「大規模データセットのためのアルゴリズム」とほぼ同じ世界であり、
ゲームでもバックエンドでも知っておくと強い武器になる。

5.2 確率的データ構造とゲーム

ゲームサーバ側の話になるが、

  • プレイヤー数百万人分のログ
  • 圧倒的なイベント数(ログイン・戦闘・課金 etc.)
  • 不正検知・バランス調整のための統計・分析

といった世界では、メモリにすべて載せるのは現実的ではない。

そこで登場するのが、確率的データ構造だ。

  • Bloom Filter:集合に含まれているかどうかを高速にチェック(誤判定許容)
  • Count–Min Sketch:頻度統計を省メモリで近似
  • HyperLogLog:ユニーク要素数を効率的に推定

こうした構造は、オンラインゲームのバックエンド分析基盤で使われることが多い。
「ゲームクライアントのC++」とは少し距離があるが、
ゲーム全体を「サービス」として捉えると、サーバサイドのアルゴリズム知識も重要になってくる。


6. ブラウザで動くゲーム:Rust × WebAssembly という選択肢

近年、「ブラウザゲーム」と言うと JavaScript / TypeScript だけではなく、
Rust + WebAssembly で書かれたゲームも増えている。

  • Rust でロジックと描画処理を書く
  • WebAssembly にコンパイルしてブラウザで実行
  • ブラウザ側とは最低限のインターフェースだけ JS でつなぐ

といった構成だ。

6.1 C++ ゲームプログラマが Rust/Wasm から学べること

C++ をメインにしていても、Rust + Wasm には学べる点が多い。

  • メモリ安全性:所有権・借用の仕組みで、バグの多い領域をコンパイル時に縛る
  • 非同期処理:Future や async/await を使ったイベント駆動
  • Web プラットフォーム:ブラウザ・DOM・WebAudio との連携の考え方

C++ で書いたゲームロジックを、将来的に Web に持っていきたい場合、

  • コアロジックを「非プラットフォーム依存」に設計しておく
  • プラットフォーム依存の部分(描画 / 入力 / 音声)は抽象化して差し替え可能にする

といった工夫が効いてくる。
Rust + Wasm の本を読むと、「ゲームのコアとプラットフォーム依存部分をきれいに分ける」設計センスが磨かれる。


7. 学習ロードマップの一例

ゲームプログラミングをこれから本格的にやっていく場合、ざっくり次のようなステップが考えられる。

  1. C++ の基礎文法と標準ライブラリ

    • 変数・関数・クラス・テンプレート・スマートポインタ
    • std::vector, std::array, std::unique_ptr など
  2. 簡単な 2D ゲームの実装

    • SDL2 や SFML といったライブラリを使って、
      • ゲームループ
      • 入力処理
      • 簡単な当たり判定
      • スプライトアニメーション
        をひと通り体験する
  3. オブジェクト設計とリファクタリング

    • Actor/Component パターン
    • 責務分離・抽象化・依存関係の整理
    • テストしやすいコードへの書き換え
  4. アルゴリズム・データ構造・パフォーマンス

    • リスト / 配列 / マップなどの基礎
    • 空間分割・グラフ探索・確率的データ構造のさわり
    • プロファイリングとマイクロベンチマーク
  5. エンジンやバックエンドへの拡張

    • 既存のゲームエンジン(Unity / Unreal / Godot)の内部構造を覗いてみる
    • オンラインゲームのサーバサイド(API / DB / 分散処理)に触れてみる
    • Rust + Wasm でブラウザ側にゲームを持っていく実験をする

本や記事で理論を学びつつ、「小さいゲームを実際に作る」ことを繰り返すのが一番身につきやすい。


8. まとめ

ゲームプログラミングは、「ループを回しているだけ」と言ってしまえばそれまでだが、そのループの中には膨大な要素が詰まっている。

  • 時間の扱い(デルタタイム / 固定ステップ)
  • オブジェクト設計(Actor/Component, ECS)
  • アルゴリズムとデータ構造(空間分割・グラフ探索・確率的構造)
  • プラットフォームの違い(ネイティブ / ブラウザ / サーバ)
  • 設計とコーディングスタイル(読みやすさとパフォーマンスの両立)

C++ はそのすべてを、かなり低いレベルから高いレベルまで触らせてくれる言語だ。

まで視野に入る。

最初は「ゲームループ + 矢印キーで動くキャラクター」くらいの小さなところからでいい。
そこに少しずつ、物理・AI・ネットワーク・データ構造・設計テクニックを足していくことで、
ゲームプログラミングの世界は一気に立体的になっていく。


9. 参考書籍

C++でゲームを作る「基本」と「実装」を学ぶ

  • ゲームプログラミングC++
    C++ を使って、シンプルな 2D ゲームから 3D・サウンド・AI までを一通り実装していく教科書。ゲームループや Actor/Component など、本記事で触れたような構造も丁寧に解説されている。
    ゲームプログラミングC++

ゲーム開発者としての「設計力」と「コーディング力」を鍛える

大規模データとアルゴリズムのセンスを身につける

Rust + WebAssembly でブラウザゲームに挑戦する

  • RustとWebAssemblyによるゲーム開発
    Rust で書いたゲームロジックを WebAssembly にコンパイルし、ブラウザ上で動かす方法を、実際のミニゲーム開発を通して学べる本。C++ メインの人でも、「安全性の高いシステム言語で Web とどう付き合うか」のヒントが得られる。
    RustとWebAssemblyによるゲーム開発

これらの本を行き来しながら、自分の手で小さなゲームを作り、少しずつ規模を大きくしていく。
それが、ゲームプログラミングを長く楽しみながらスキルを磨いていく一番の近道だと思う。