i-school - 【2D】弾生成用クラスの設計デザインと抽象化
 2Dゲームにおける、弾の生成クラスの設計についての学習です。

 この記事では、2Dゲーム開発における弾の生成クラスの設計プロセスと抽象化について紹介します。
弾の生成は、アクションゲームやシューティングゲームなどで頻繁に使用される重要な要素であり、効果的な設計と抽象化によって柔軟で保守性の高いコードを実現することができます。

 ただし、この方法が設計のすべてではありません。
実際には自分のプロジェクトや規模によって検討すべき内容でなり、柔軟な対応が必要になります。

 この記事の学習内容が、そのための一助になることを願っています。



1.説明


 記事では、以下の内容に焦点を当てて解説します。

弾生成クラスの基本設計


 弾の生成と弾に対しての事前の設定など、弾の基本的な生成機能をどのように設計するかについて説明します。
オブジェクト指向の原則を適用して、共通の振る舞いを抽象クラスやインターフェースとして実装し、再利用性を高めるアプローチを探ります。


2.IGeneratable インターフェースの作成


 IGeneratable インターフェースは、シューティングゲームやアクションゲームなどで頻繁に使用される弾の生成を抽象化するために設計されています。
このインターフェースは、弾を生成するための基本的な操作を定義しており、具体的な弾の種類や挙動に依存せず共通のインターフェースを提供します。



IGeneratable.cs

<= クリックしたら開きます。


 定義しているメソッドは GenerateBullet メソッドのみです。
同名のメソッドを2つ、引数の情報を異なる状態にして定義しています。

 インターフェースのメソッドは特性上、自動的に public 扱いになります。



 具体的には、IGeneratable インターフェースは以下の役割を果たします:


1.弾の生成機能の定義

 
 IGeneratable インターフェースは、GenerateBulletメソッドを定義しています。
このメソッドは弾を生成するための基本的な手続きを表しており、生成に必要な発射方向をパラメータとして受け取ります。

 また引数のオーバーロードにより、同名のメソッドには追加の引数を受け取る方法も用意しています。


2.クラス間の共通性を確保


 IGeneratableインターフェースを実装するさまざまなクラスは、異なる種類の弾を生成する機能を提供することができます。
これにより、ゲーム内の異なる要素(プレイヤーキャラクター、敵キャラクター、砲台など)が共通の弾生成機能を実行できるようになります。


3.クラス間の疎結合性の促進


 IGeneratableインターフェースを介して弾生成の機能を定義することで、クラス間の疎結合性が向上します。
クラスがインターフェースを実装するだけで、他のクラスとの連携が容易になります。


4.拡張性と保守性の確保


 ゲーム内に新しい種類の弾を追加する際に、IGeneratableインターフェースを実装するだけで新しい弾の生成機能を追加できます。
これにより、新しい要素を簡単に統合し、保守性の高いコードを実現できます。



 総合すると、IGeneratableインターフェースは、ゲーム内で弾を生成する機能を共通化し、柔軟性と保守性を向上させるための基盤を提供する役割を果たしています。


3.GeneratorBase クラスの作成


 抽象クラスとして GeneratorBase クラスを作成し、IGeneratable インターフェースを実装します。

 実装にあたり、GenerateBullet メソッドを抽象メソッドとして定義します。
抽象メソッドも、あくまでもメソッドを定義するのみで、振る舞いについてはサブクラスに委ねています。

 通常のメソッドでインターフェースのメソッドを実装してしまうと、このクラス内での定義となり、サブクラスでの上書きが任意になってしまいますが、
抽象メソッドとして定義しておくことで、このクラスでは内部の実装をせず、サブクラスでの実装を強制することができます。



GeneratorBase.cs

<= クリックしたら開きます。


 インターフェースのメソッドを定義する順番に指定はありません。
例えば、GenerateBullet メソッドの定義をしてから SetUp メソッドの定義を行うこともできます。



 GeneratorBase クラスは、アクションゲームにおける弾の生成機能の基盤となる親クラスです。

 以下はその設計と実装についての説明です。


1.抽象クラスとしての GeneratorBase


 GeneratorBase クラスは抽象クラスとして実装されます。
抽象クラスは、一部のメソッドが未実装(抽象メソッド)であり、具体的な振る舞いはサブクラス(子クラス)で提供されることを意味します。

 弾を生成する、という実行処理自体は共通していますが、内部的な弾の生成処理については、個々のサブクラスに任せる形です。

 この設計により、共通の振る舞いを抽象クラスで定義し、個別の振る舞いをサブクラスでカスタマイズすることができます。


2.IGeneratable インターフェースの実装


 IGeneratableインターフェースは弾を生成する機能を規定するものです。
GeneratorBase クラスはこのインターフェースを実装します。
これにより、弾を生成する機能を共通のインターフェースで提供し、異なる種類の弾に対しても統一的な操作が可能となります。


3.GenerateBullet メソッドの抽象定義


 GeneratorBase クラスには、IGeneratableインターフェースの一部としてGenerateBulletメソッドがあります。
このメソッドは抽象メソッドとして定義されます。
抽象メソッドはメソッドの実装を持たず、サブクラスにおいて必ずオーバーライドされることを強制します。

 これにより、サブクラスでの弾の生成機能のカスタマイズを保証し、実装忘れを防ぐことが出来ます。


4.MonoBehaviour クラスの継承


 インターフェース自体は実際にはシリアライズできませんが、インターフェースを実装するクラスはシリアライズ可能です。
したがって、シリアライズが必要な場合、インターフェースを実装したクラスをシリアライズすることで表現できます。

 Unity の場合、MonoBehaviour クラスを継承しているクラスや、System.Serializable 属性を付与したクラスはシリアライズが可能となり、
それらのクラスはインスペクターに表示する恩恵を受けることが出来ます。

 インターフェース単独の場合には、このシリアライズに対応ができないため、例えば、インスペクターでの確認や、アサインなどが行えません。

 今回、GeneratorBase クラスにインターフェースを実装することにより、このクラスを親クラスとした子クラスの情報はインスペクターに表示されるようになります。

 子クラスに直接インターフェースを実装せずに、親クラスにインターフェースを実装していることで、このインターフェースのシリアライズに関する問題も回避しています。
 


 GeneratorBase を抽象クラスとして設計し、IGeneratableインターフェースを実装することができます。
これにより、GeneratorBase のサブクラスはIGeneratableインターフェースの実装を強制されるだけでなく、シリアライズに関する問題も回避できます。
各サブクラスはIGeneratableを実装する必要があるため、再利用性や保守性が高まります。

 この設計により、GeneratorBase クラスを継承する具体的な弾のクラスは、必ずGenerateBulletメソッドを実装する必要があります。
これによって、弾の生成の流れを統一的に管理しつつ、個別の弾生成クラスごとに異なる生成を提供することができます。


4.サンプルクラスによるシリアライズ情報の比較例


 具体的なサブクラスの作成に入る前に、サンプルクラスを作成して、インスペクターでのシリアライズについて確認しておきましょう。

 クラス内に IGeneratable インターフェースの List と GeneratorBase クラスの List をそれぞれ作成し、SerializeField属性を付与します。
このサンプルクラスを任意のゲームオブジェクトにアタッチしてみてください。それぞれの挙動が異なることが分かります。



TestInspector.cs

 <= クリックすると開きます



1.IGeneratable インターフェースの List


 インターフェースは SerializeField属性を付与しても、public 修飾子で宣言していてもインスペクターには表示されません。






 デバッグモードにしても表示されません。



 


2.GeneratorBase クラスの List


 こちらはクラスであるため、インスペクターに表示されます。





 今回のように MonoBehaviour クラスを継承していない場合には自動的にシリアライズされますが、
MonoBehaviour クラスを継承していない場合には自動的にはシリアライズされないため、インターフェースと同じようにインスペクターに表示されません。
その場合、クラスに System.Serializable 属性を付与する必要があります。


 以上で完成です。

 引き続き、リファクタリング手法を学習しましょう。