ゲームプログラミングパターンでコードをレベルアップさせよう


オブジェクト指向プログラミング言語の経験がある方なら、SOLID の原則や、MVP、シングルトン、ファクトリ、オブザーバーなどのパターンについて聞いたことがあると思います。今回新しく公開する e ブックでは、これらの原則やパターンを使って皆さんが Unity プロジェクトでスケーラブルなゲームコードのアーキテクチャを構築するためのベストプラクティスを紹介しています。
皆さんが遭遇するソフトウェア設計の問題は、1000 人の開発者がかつて遭遇した問題です。その開発者たちに直接アドバイスを求めることはできませんが、デザインパターンを通じて、そうした開発者がどのような決断を下したのかを学ぶことができます。
Unity プロジェクトで一般的なゲームプログラミングにおけるデザインパターンを実装することで、クリーンで整理された読みやすいコードベースを効率的に構築および維持することができ、ゲームそのものや開発チーム、およびビジネスを拡大するための強固な基盤を構築できます。
私たちのコミュニティでは、デザインパターンや、SOLID や KISS などの原則を日々の開発に取り入れる方法を学ぶのは気が重いという声をよく聞きます。そこで、無料の e ブック「Level up your code with game programming patterns」を公開しました。この e ブックはよく知られたデザインパターンを解説し、皆さんの Unity プロジェクトでそれらのパターンを使うための実践的な例を共有するものです。
社内外の Unity のエキスパートが執筆に関わったこの e ブックは、開発者のツールボックスを拡張し、プロジェクトを成功に向かって加速させるうえで役立つリソースです。記事の続きをお読みいただき、このガイドの内容をご確認ください。
デザインパターンは、ソフトウェア工学でよく見られる問題に対する一般的な解決策です。これらは、コピーしてコードに貼り付ければ動く完成されたソリューションではなく、正しく使用すれば、より大規模でスケーラブルなアプリケーションの構築を助けてくれる追加のツールなのです。
パターンを一貫してプロジェクトに組み込むことで、コードの可読性を向上させ、コードベースをよりクリーンにすることができます。デザインパターンは、リファクタリングやテストにかかる時間を短縮するだけでなく、オンボーディングや開発プロセスのスピードアップを実現します。
しかし、どのようなデザインパターンにもトレードオフがあります。メンテナンスするべき構造が増えたり、最初のセットアップが大変になったりします。その利点が要求される追加の作業を正当化するかどうか、コストベネフィット評価を行う必要があります。もちろん、この評価はプロジェクトによって異なります。
KISS とは「keep it simple, stupid」(シンプルにしておけ)の略です。この原則の目的は、システムの不必要な複雑化を避けることです。なぜなら、シンプルであればあるほど、ユーザーの受容度やインタラクションが高まるからです。
なお、「シンプル」は「簡単」と同じではありません。何かをシンプルにするということは、集中させるということです。パターンを使わなくても同じ機能を(多くの場合、より早く)作ることはできますが、早く簡単にできるものがシンプルなものになるとは限りません。
もし、そのパターンが自分の問題に当てはまるかどうかわからない場合は、より自然にフィットすると感じられるまで保留にすることもできます。自分にとって新しいから、斬新だからという理由でパターンを使わないでください。必要なときに使いましょう。
このような精神で、この e ブックは作られました。このガイドは、コードを整理する別の方法を探すときの使いやすいインスピレーションの源泉として手元に置いていただければと思います。皆さんが従わなければならない厳格なルール集ではありません。
さて、次にソフトウェア設計の重要な原則について説明します。

SOLID とは、ソフトウェア設計の核となる 5 つの基本事項の頭文字をとって作られた略語です。これは、オブジェクト指向設計を柔軟かつ保守可能なものにするために、コーディング時に留意すべき 5 つの基本ルールと考えることができます。
SOLID の原則は、Robert C. Martin 氏が論文「Design Principles and Design Patterns」で初めて紹介したものです。この論文は 2000 年に初めて公開されましたが、そこに記載されている原則は現在でも、そして Unity での C# スクリプティングにも適用可能です。
- Single responsibility(単一責任)は、各モジュール、クラス、関数が 1 つのことに責任を持ち、ロジックのその部分のみをカプセル化することを述べています。
- Open-closed(オープン/クローズド)は、クラスは拡張に対してはオープンでなければならないが、変更に対してはクローズでなければならないというものです。これは、元のコードを変更せずに新しい動作を作成するためにクラスを構造化することを意味します。
- Liskov substitution(リスコフの置換)は、継承を使用する場合、派生クラスはその基底クラスと置換可能でなければならないことを述べています。
- Interface segragation(インターフェース分離)は、どのクライアントも自分が使っていないメソッドに依存することを強制されるべきではないと述べています。クライアントは必要なものだけを実装するべきということです。
- Dependency inversion(依存性逆転)は、高レベルのモジュールは低レベルのモジュールから直接何かをインポートすべきではないことを述べています。どちらも抽象に依存するべきとしています。
e ブックには、各原則の図解付きの例が、Unity で使用するためのわかりやすい解説と共に収録されています。SOLID を遵守することで、前もって追加の作業を行うことになる場合もあります。そこで機能の一部を抽象やインターフェイスにリファクタリングする必要があるかもしれませんが、長期的なコスト削減という見返りがある場合が多いのです。
これらの原則は、スケールする大規模アプリケーションに非常に適しているため、エンタープライズ水準のソフトウェア設計を 20 年近く支配してきました。これらの原則の使い方に迷ったら、KISS の原則を参考にしましょう。物事はシンプルに、そして原則を実践するためだけに無理にスクリプトに組み込もうとしないことです。必要に応じて、適した場所で有機的に活用しましょう。
さらに深堀りしたい方は、Productive Edge 社の Dan Sagmiller 氏による、Unite Austin 2017 の SOLID に関するプレゼンテーションをご覧ください。
デザイン原則とデザインパターンはどう違うのでしょうか。それに答える方法の 1 つは、SOLIDをオブジェクト指向のコードを書くためのフレームワーク、あるいは基本となるアプローチとして考えることです。デザインパターンは、日常に遭遇するソフトウェアの問題を回避するために導入できる解決策およびツールですが、既製のレシピや、特定の結果を得るための特定の手順を持つアルゴリズムではないことを忘れないでください。
デザインパターンは、設計図と考えることができます。それは実際の施工はおまかせの、大まかなプランです。例えば、2 つのプログラムが同じパターンに従っていても、まったく異なるコードからできていることがあります。
開発者が同じ問題に遭遇すると、そのうちの多くが必然的に同じような解決策を思いつきます。ある解決策が十分な回数繰り返し出現したとき、誰かがそのパターンを「発見」し、正式に名前を付けることがあります。
今日のソフトウェアのデザインパターンの多くは、Erich Gamma 氏、Richard Helm 氏、Ralph Johnson 氏、John Vlissides 氏の代表的な著作『Design Patterns: Elements of Reusable Object-Oriented Software』(邦訳名『オブジェクト指向における再利用のためのデザインパターン』)に由来しています。この本では、日々目にするさまざまなアプリケーションで確認された、問題解決のための 23 のパターンが解説されています。
この本の原著者は「ギャング・オブ・フォー」(GoF)と呼ばれることが多く、この本で紹介されたパターンが GoF パターンと呼ばれているのを耳にしたこともあるでしょう。引用されている例はほとんど C++(と Smalltalk)ですが、彼らのアイデアは C# など、どのオブジェクト指向言語にも適用できます。
1994 年に GoF がデザインパターンを発表して以来、ゲーム開発を含むさまざまな分野で数多くのオブジェクト指向のパターンが確立されています。


デザインパターンを勉強しなくてもゲームプログラマーとして働くことはできますが、デザインパターンを学ぶことで、より良い開発者になることができます。結局のところ、デザインパターンはよく知られた問題に対する一般的な解決策であるため、そのようなラベルが付けられているのです。
ソフトウェア技術者は、普段の開発の中で常にそれを再発見しているのです。皆さんもすでに、これらのパターンのいくつかを無意識のうちに実装しているかもしれません。
意識的にパターンを探す訓練をしましょう。それには、以下のようなことを実践することが有効です。
- オブジェクト指向プログラミングを学ぶ:デザインパターンは、難解な StackOverflow の投稿の奥底に埋もれている秘密ではありません。それらは、開発で日々出会うハードルを乗り越えるための一般的な方法です。皆さんがパターンを使っていなくても、他に多くの開発者が同じ問題に取り組んでいることを知ることができます。自分で使っていなくても、他の誰かがパターンを使っているのです。
- 他の開発者と話す:パターンは、チームとしてコミュニケーションしようとするときに、話を手短に済ます手段として役立ちます。「コマンドパターン」や「オブジェクトプール」と言えば、経験豊富な Unity 開発者なら、何を実装しようとしているのかがわかるでしょう。
- 新しいフレームワークを試す:ビルトインパッケージやアセットストアから何かをインポートするとき、必然的にここで紹介するパターンの 1 つ、あるいは複数に出会って戸惑うことになります。デザインパターンを理解することで、新しいフレームワークがどのように動作し、どのような思考プロセスで作られたかを理解することができます。
先に示したように、すべてのデザインパターンがすべてのゲームアプリケーションに適用されるわけではありません。マズローのハンマーを手に物事を見てはいけません。さもなくば、すべてが釘にしか見えなくなってしまうこともあります。
他のツールと同様に、デザインパターンの有用性は文脈に依存します。それぞれ、特定の場面でメリットがある一方で、欠点もあります。ソフトウェア開発におけるすべての決断には、妥協がつきものです。
ゲームオブジェクトをオンザフライで大量に生成しているとして、それはパフォーマンスに影響はないのでしょうか。コードの再構築で解決できるでしょうか。これらのデザインパターンを常に頭に置き、しかるべきタイミングで皆さんのゲーム開発技術の引き出しから取り出し、問題を解決してください。
GoF のデザインパターンに加えて、ロバート・ナイストロム氏の『Game Programming Patterns』はもう 1 つの特筆すべきリソースといえるでしょう。現在、ウェブベース版が無料で利用できます。著者は、さまざまなソフトウェア・パターンを淡々と詳述していきます。
私たちの新しい e ブックでは、ファクトリ、オブジェクトプール、シングルトン、コマンド、ステート、オブザーバーパターンなどの一般的なデザインパターンに加え、MVP(Model-View-Presenter)などの解説をセクションに分けて行っています。各セクションでは、パターンの長所と短所を説明し、Unity での実装例を示しているので、皆さんのプロジェクトで最適な使い方を検討していただけます。
Unity は元々いくつかの確立されたゲーム開発パターンを実装しており、皆さんが自分で書く手間を省くことができます。これらには以下が含まれます。
- ゲームループ:すべてのゲームの核となるのは無限ループで、ゲームアプリケーションを動かすハードウェアには大きなばらつきがあるため、クロック速度に依存せずに機能する必要があります。ゲーム開発者は、さまざまな速度のコンピューターに対応するため、しばしば固定時間ステップ(1 秒あたりのフレーム数を設定)と可変時間ステップ(前のフレームからどれだけ時間が経過したかを測定)を使い分ける必要があります。 Unity がこの問題に対応してくれるので、自分で実装する必要はありません。Update、LateUpdate、FixedUpdate といった MonoBehaviour のメソッドを使ってゲームプレイを管理すればよいのです。
- 更新:ゲームアプリケーションでは、各オブジェクトの挙動を 1 フレームずつ更新していくことが多いです。Unity でこれを手動で再現することもできますが、MonoBehaviour クラスはこれを自動的に行ってくれます。Update、LateUpdate、FixedUpdate メソッドを適切に使い分け、ゲームオブジェクトやコンポーネントにゲームクロックの 1 ティックに対応した変更を加えます。
- プロトタイプ:オブジェクトを元のオブジェクトに影響を与えずにコピーする必要があることがよくあります。この生成パターンは、あるオブジェクトを複製して、それ自身に似た他のオブジェクトを作るという問題を解決するものです。こうすることで、ゲーム内のあらゆる種類のオブジェクトを生成するために個別のクラスを定義する必要がなくなります。 Unity のプレハブシステムは、ゲームオブジェクトのためのプロトタイピングの実装の一種です。これにより、テンプレートとなるオブジェクトをそのコンポーネントごと複製することができます。特定のプロパティをオーバーライドして、プレハブバリアントまたはネスト状のプレハブ を他の Prefab の中に入れ子にして階層を作成します。特別なプレハブ編集モードを使って、プレハブを単独で、またはコンテキスト内で編集することができます。
- コンポーネント:このパターンは、Unity で開発をしている人のほとんどが知っていると思います。複数の責任を持つ大きなクラスを作るのではなく、それぞれが 1 つのことを行う小さなコンポーネントを作りましょう。 そしてコンポジションを使ってコンポーネントを選んで組み合わせることで、複雑な動作をさせることも可能です。物理演算のための Rigidbody や Collider コンポーネント、3D ジオメトリのための MeshFilter や MeshRenderer を追加することができます。ゲームオブジェクトは、それを構成するコンポーネントの集合体としてのみ、リッチでユニークな存在となります。

デザインパターンの活用に関する e ブックとサンプルプロジェクトの両方が、現在無料でダウンロード可能です。サンプルを見て、どのデザインパターンが自分のプロジェクトに最も適しているかを判断してください。このサンプルを使いこなすうちに、いつ、どのように開発プロセスを改善することができるかを認識できるようになります。いつものように、フォーラムのスレッドをご覧になることをお勧めいたします。ここで、e ブックやサンプルについてのご感想もぜひお寄せください。
