作業ログ

Engineクラスはなぜ必要か

ApplicationがSubsystemを直接持つのではなく、間にEngineを挟んでいる理由を整理した。

  • game-engine
  • software-design
  • cpp

背景

現在の構成では、Applicationの下にEngineを置き、そのEngineが各Subsystemを所有する形にしている。

Application
 ├─ Engine
 │   ├─ WindowSystem
 │   ├─ TimeSystem
 │   ├─ InputSystem
 │   ├─ PhysicsSystem
 │   ├─ RenderSystem
 │   └─ LogSystem
 │
 └─ GameLogic

ここで一つ疑問になるのが、次の点。

Applicationが各Systemを直接持てばよいのではないか

実際、規模が小さいうちはその構成でも動かせる。

class Application
{
private:
    WindowSystem m_windowSystem;
    TimeSystem m_timeSystem;
    InputSystem m_inputSystem;
    PhysicsSystem m_physicsSystem;
    RenderSystem m_renderSystem;
    LogSystem m_logSystem;
    GameLogic m_gameLogic;
};

この形でも、各Systemを順番に呼び出してGame Loopを実装することはできる。

Engineを挟む理由

それでもEngineを分けているのは、Applicationと各Subsystemで役割を分けたいから。

Applicationには、

  • アプリケーションの起動
  • Game Loopの管理
  • 終了条件の管理
  • EngineとGameLogicの接続

を担当させる。一方、Engineには、

  • Subsystemを所有する
  • 初期化する
  • 終了する
  • 利用側へSubsystemの参照を提供する

という役割を持たせる。つまり、次のような境界になる。

Application
    アプリケーション全体の進行を管理

Engine
    ゲーム非依存のSubsystemを管理

Applicationが直接持つ場合

ApplicationがすべてのSystemを直接所有すると、Systemが増えるたびにApplication側の責務も広がっていく。たとえば、

Window
Input
Time
Physics
Rendering
Audio
Resource
Scene
Profiler

と増えていくと、Applicationがゲーム全体の進行だけでなく、Engine内部の構成まで詳しく知ることになる。

ゲーム側から見れば、本来必要なのは「Engineの機能を使うこと」であって、Engine内部にどのSystemがいくつ存在するかを毎回意識したいわけではない。そこで、各SubsystemをEngineの内側へまとめる。

Engineを入口にする

Engineを置くことで、ゲーム側から見たEngine機能への入口も一つにできる。

m_engine.Input().Update();
m_engine.Physics().Step(FixedDeltaTimeSeconds);
m_engine.Renderer().Render();

利用側はEngineを通して必要なSubsystemへアクセスする。これにより、Subsystemの所有関係をApplicationから少し切り離せる。

将来的にSubsystemの構成を変更した場合でも、その変更をEngine内部に閉じ込めやすくなる。

Engineは何でもやるクラスではない

ただし、Engineを作ったからといって、すべての処理をEngineへ集めるわけではない。たとえば、

m_engine.Render();
m_engine.CheckCollision();
m_engine.UpdateInput();

の内部で、Engine自身がRenderingやCollision、Input処理まで直接実装し始めると、今度はEngineが巨大なクラスになる。

現在の方針では、EngineはあくまでSubsystemを管理する側に置く。具体的な処理は、それぞれのSystemへ任せる。

Engine
 ├─ InputSystem
 ├─ PhysicsSystem
 ├─ RenderSystem
 └─ ...

まとめ

Engineを分けている理由は、単に1クラス増やしたいからではない。ApplicationとSubsystem管理の責務を分けるため。

Application
    Game Loopとアプリケーション全体の進行を管理

Engine
    ゲーム非依存のSubsystemを所有・管理

Subsystem
    それぞれの専門処理を実装

小規模な段階ではApplicationが各Systemを直接持っても動く。ただ、今後Systemが増えていくことや、完成後にVeEngineとして切り出すことを考えると、Engineを一つの境界として置いておく方が整理しやすいと考えている。