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

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

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

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



1.説明


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

弾制御クラスの基本設計


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


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


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



IShootable.cs

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


 定義しているメソッドは Shoot メソッドのみです。

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



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


1.弾の発射操作の定義

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


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


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


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


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


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


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



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


3.BulletBase クラスの作成


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

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

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



BulletBase.cs

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


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



 BulletBaseクラスは、アクションゲームにおける弾の制御の基盤となる親クラスです。

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


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


 BulletBaseクラスは抽象クラスとして実装されます。
抽象クラスは、一部のメソッドが未実装(抽象メソッド)であり、具体的な振る舞いはサブクラス(子クラス)で提供されることを意味します。
この設計により、共通の振る舞いを抽象クラスで定義し、個別の振る舞いをサブクラスでカスタマイズすることができます。



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


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


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


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

 これにより、サブクラスでの弾の発射操作のカスタマイズを保証することができます。


4.MonoBehaviour クラスの継承


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

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

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

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

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


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

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


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


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

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



TestInspector.cs

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



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


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






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



 


2.BulletBase クラスの List


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





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


 以上で完成です。

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