インターフェース
いくつもの種類のものを同じコードで扱う方法と、クラス向け・コンポーネント向けの使い分け。
インターフェースは「何であるか」ではなく「何ができるか」の約束です。約束に向けてコードを書けば、その約束を守るものすべてで動きます。
public interface IShape
{
int Area();
}
public class Square : IShape
{
public int side;
public int Area()
{
return side * side;
}
}
public class Rect : IShape
{
public int width;
public int height;
public int Area()
{
return width * height;
}
}
IShape を持っている側は、それがどの種類かを知らないまま面積を訊けます。答えは実際にそこにあるクラスから返ってきます。
IShape shape = square ? (IShape)new Square() : new Rect();
int area = shape.Area();
この決定はワールドが動いている最中に行えます。これが日常的な使い方であり、インターフェースが働く場所です。ひとつのコードで、何種類ものものを扱えます。
効いてくる場面
型を表すフィールドに対して長い if を書くことになりそうな場所すべてです。
- インベントリのアイテム。使ったときの動きがそれぞれ違う
- クエストの手順。完了の判定がそれぞれ違う
- プレイヤーへの効果。ルールがそれぞれ違う
- 武器。撃つという意味が武器ごとに違う
インターフェースで書いておけば、新しい種類のアイテムを足すのはクラスをひとつ足すことです。IItem を使う側は何も変わりません。
コンポーネントは別: 基底クラスを使う
インターフェースはコードの中で作るクラスのためのものです。コンポーネントは事情が違います。GameObject にドラッグして Unity にその参照を保存させる必要があり、Unity はインターフェースのフィールドを保存できないからです。インターフェース型にすると、そのフィールドはインスペクタに現れません。
なので共有したいものがビヘイビアなら、抽象基底クラスを使います。
public abstract class Door : UdoniteBehaviour
{
public abstract void Open();
}
public class SlidingDoor : Door
{
public override void Open() { /* ... */ }
}
public class SwingingDoor : Door
{
public override void Open() { /* ... */ }
}
スイッチはフィールドひとつでどちらのドアにも対応できます。
public class Switch : UdoniteBehaviour
{
public Door door; // SlidingDoor でも SwingingDoor でもここにドラッグできる
public override void Interact()
{
door.Open();
}
}
フィールドはインスペクタに現れ、どちらのドアも受け取り、door.Open() は正しいほうを動かします。基底クラスは共有の状態や共有のコードも持てます。インターフェースにはできないことです。
これは普通の Unity でも同じ理由で同じようにする話です。
使い分け
| したいこと | 使うもの |
|---|---|
| コードの中で作る複数のクラスに同じ約束を持たせる | インターフェース |
| GameObject 上の複数のビヘイビアに同じ約束を持たせる | 抽象基底クラス |
| 状態やメソッドの中身を共有する | 抽象基底クラス |
基底クラスがインターフェースを実装することもできるので、本当に必要なときはビヘイビアが両方を兼ねられます。