GetComponentを使うときはインターフェースを使おう
Unityの教本を読んでいると、GetComponentを使うケースをよく見ます。GetComponent自体は直接アクセスと速度的には差がほとんど無いので、それ自体は良いのですが、GetComponentする際に型を直接指定している記述を散見します。
サンプルとして、「敵が自機にぶつかったら、敵はダメージを食らう」という処理を作ってみましょう。
public class Player
{
private void OnCollisionEnter2D(Collision2D collision)
{
var enemy = collision.gameObject.GetComponent<Enemy>();
if (enemy == null) return;
enemy.Hp -= 10;
}
}
public class Enemy : MonoBehaviour
{
public int Hp = 100;
}
これでとりあえず「プレイヤーが敵にぶつかったら、敵のHPを10減らす」という処理ができました。さて、実装を進めていると、ボスを作りたくなってきました。早速ボスのクラスを実装しましょう。ボスはHP以外にシールドを持っているので、シールドも付け加えます。
public class Boss : MonoBehaviour
{
public int Hp = 100;
public int Shield = 100;
}
では、Playerスクリプトを、Bossにダメージを与えられるよう修正しましょう。
public class Player
{
private void OnCollisionEnter2D(Collision2D collision)
{
var enemy = collision.gameObject.GetComponent<Enemy>();
if (enemy != null)
{
enemy.Hp -= 10;
}
// 追加
var boss = collision.gameObject.GetComponent<Boss>();
if(boss != null)
{
if(boss.Shield > 0)
{
boss.Shield -= 10;
}
else
{
boss.Hp -= 10;
}
}
}
}
ここまで書けば、問題が見えてくるかと思います。「プレイヤーが敵<T>にぶつかったら、敵<T>にアクションを起こす」という書き方をしていると、クラスが増えるごとにPlayer
のOnCollisionEnter
メソッドはどんどん肥大化していきます。
それを解消するために、インターフェイスを使います。
OnCollisionEnter
の共通点を考えてみましょう。基本的に、Player
はぶつかった敵に「ダメージを与える」よう実装されています。現状では、Enemy
とBoss
の記述しか無いので拡張性がありません。例えば、木や障害物に対してダメージを与えるためには、都度コードを追記する必要があります。これを抽象化すると、「接触した敵がダメージを与えられるのであれば、ダメージを与える」ということができます。
ですので、このようなインターフェースを定義しましょう。
public interface IDamageable // ダメージが与えられるのであれば
{
void TakeDamage(int damage); // ダメージを与える
}
「私はダメージを受けられますよ」と宣言するインターフェースになります。これをPlayer
は以下ように呼び出します。
public class Player
{
private void OnCollisionEnter2D(Collision2D collision)
{
var damagebale = collision.gameObject.GetComponent<IDamageable>();
if (damagebale != null)
{
damagebale.TakeDamage(10);
}
}
}
Enemy
とBoss
は、以下のように修正されます。
public class Enemy : MonoBehaviour, IDamageable
{
private int Hp = 100;
public void TakeDamage(int damage)
{
Hp -= damage;
}
}
public class Boss : MonoBehaviour, IDamageable
{
private int Hp = 100;
private int Shield = 100;
public void TakeDamage(int damage)
{
if(Shield > 0)
{
Shield -= damage;
}
else
{
Hp -= damage;
}
}
}
これで、Player
はダメージを与える際、シールドを持ってるかどうかや、個々のオブジェクトが抱える処理の差異を無視することができ、その処理を分離して管理することができるようになりました。
では先ほど、Player
は障害物にダメージを与えられないとしましたが、唐突に「フェンスは壊せたほうがいいな」と思い付いたとします。その場合でも、処理の追記はPlayer
に記載する必要は無く、フェンスにIDamageable
を背負わせれば実装は単純です。
public class Fence : MonoBehaviour, IDamageable
{
public void TakeDamage(int damage)
{
if(damage > 5)
{
Destroy(gameObject);
}
}
}
もちろんdamage
を無視してもいいですが、damage
を使ったとすると「5ダメージ以上の攻撃を食らったら、自分を破壊する」といった具合に実装が可能です。このようにインターフェースを使うことでPlayer
クラスが知る必要のない情報を取り出し、クラスごとに分けることができる上、publicな変数をガシガシ削ることもできます。
たった3行のコードで、これほど生産性があがるのだから、やらない手はありません。
インターフェースをもっと活用しましょう!
お役に立てましたらライク、サポートしていただけますと嬉しいです!🙇🙇
Discussion