0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++とSDL2によるゲーム開発:コンポーネントパターンの基礎と実装例

0
Posted at

免責事項
本記事の内容は個人の見解であり、所属する企業の公式な見解や方針を代表するものではありません。
また、本記事の内容は完全に個人の趣味・学習の範囲で行っているものです。
本記事で紹介する取り組みは、業務とは切り離された個人のローカル環境およびプライベートリポジトリで実行しています。
内容には正確性を期していますが、誤りや不適切な記述が含まれている可能性があります。お気づきの点がございましたら、ご指摘いただけますと幸いです。

現在C++とSDL2を用いて、ゲームエンジンに頼らないフルスクラッチの2Dゲーム(簡単なブロック崩しのようなもの)の開発を、学習目的で進めています。
あくまで技術の学習を目的としたプロジェクトであるため、ゲームデザイン自体の新規性よりも、実装への理解に重きを置いています。
開発のベースとして『ゲームプログラミングC++』を参照しつつ、これまでに学んだデザインパターンや最適化手法をアウトプットする場としてプロジェクトを進めています。
並行して『Game Programming Patterns』を読んでおり、とくにコンポーネントパターンの考え方が興味深いと感じました。
本記事では、コンポーネントパターンの基本的な概念のおさらいと、現在取り組んでいるゲーム開発プロジェクトへの適用例を紹介します。

コンポーネントパターンの基本概念

コンポーネントパターンは、1つのエンティティ(ゲーム内のオブジェクト)を複数のドメインに分割し、それぞれの振る舞いを「コンポーネント」として独立させる設計パターンです。
ドメインとは、描画、物理、入力などを指します。
目的は、巨大なクラスの発生を防ぎ、コードの再利用性と柔軟性を高めることです。

ゲーム開発では、「動くけれど描画されないオブジェクト」や、「描画されるけれど当たり判定がないオブジェクト」など、多様なオブジェクトが登場します。
これを単純なクラス継承で解決しようとすると、すぐにクラス階層が複雑化し、破綻してしまいます。
コンポーネントパターンを使えば、エンティティそのものは単なる「コンポーネントの入れ物」になります。
必要な機能だけを組み合わせることで、柔軟に新しいオブジェクトを定義できます。

フルスクラッチ開発プロジェクトへの適用

現在開発中のC++ゲームプロジェクトでも、このコンポーネントパターンをベースアーキテクチャとして採用しています。
ゲーム内のすべてのオブジェクトを Actor クラスとして表現し、個別の機能を Component クラスの派生クラスとして実装しています。

ActorとComponentの基本構造

Actor はコンポーネントのリストを保持し、毎フレームの更新処理(Update)で各コンポーネントの Update を呼び出します。

// Actor.h (一部抜粋)
class Actor {
public:
    // 毎フレーム呼ばれる更新処理
    void Update(float deltaTime);

    // コンポーネントの追加と削除
    void AddComponent(class Component* component);
    void RemoveComponent(class Component* component);

private:
    // コンポーネントのリスト
    std::vector<class Component*> mComponents;

    // 位置や回転などの基本情報
    Vector2 mPosition;
    float mRotation;
};

一方の Component は、自身を所有する Actor のポインタ(mOwner)を保持します。
これにより、必要に応じて Actor の座標などを参照・更新できるようにしています。
さらに、コンポーネントの更新順序を制御するための mUpdateOrder も持たせています。

// Component.h (一部抜粋)
class Component {
public:
    // コンストラクタで対象のアクターと更新順序を受け取る
    Component(Actor* owner, int updateOrder = 100);
    virtual ~Component();

    virtual void Update(float deltaTime);

protected:
    Actor* mOwner;
    int mUpdateOrder;
};

このように設計することで、「プレイヤーの移動」は MoveComponent として、「画像の描画」は SpriteComponent として分離できます。
新しい機能を追加したいときは、既存の Actor クラスに手を入れることなく、新しいコンポーネントクラスを作成してアタッチするだけで済みます。

今後の展望

コンポーネントパターンを取り入れたことで、機能の追加や変更が局所化され、設計の柔軟性が大きく向上したと感じています。
今後は、このベースアーキテクチャの上に(可能そうなら)オブジェクトプールや空間分割といった最適化手法も組み込んでみたいと考えています。

また、今回はフルスクラッチでの実装例を紹介しましたが、UnityやUnreal Engineといった既成のゲームエンジンで、このコンポーネントパターンが内部的にどう実装・活用されているかについても興味が湧いています。
これらについても調査を進め、次回以降の記事で知見としてまとめられればと考えています。

もし内容に誤りや、より良い設計のアプローチなどがあれば、コメント等でご指摘いただけますと幸いです。


参考資料:

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?