Unityを使ってゲームを作ると、「同じような処理を持つけど、ちょっとずつ違うオブジェクト」 が出てきます。
例えば、プレイヤーが「E」キーを押してドアを開けたり、スイッチを押したり、アイテムを拾ったりする場合、
「E」キーで何かを実行するという共通点はあるけど、オブジェクトごとに処理が違うんですよね。
そんなときに役立つのが今回紹介する「インターフェース」です。
ここでは、インターフェースとは何か?どう使うと便利なのか?を初心者向けに解説したいと思います。
Unityでインターフェースを使うメリットを初心者向けに解説!
インターフェースとは?
インターフェースとは、「この機能を持っているよ!」という約束を決めるものです。
例えば、ゲーム内で 「Eキーで何かできるオブジェクト」を作りたい場合、こんなことを考えますよね?
- ドアなら → 開く
- スイッチなら → ON/OFFを切り替える
- アイテムなら → 拾う
すべて違う動きをするけど、共通するのは「Eキーで動く」ことです。
インターフェースを使うと、「Eキーで何かする」というルールを一括管理できるようになります。
インターフェースの作り方
それでは、実際にインターフェースを作ってみたいと思います。
私も、部屋の中をプレイヤーが徘徊してあちこちに存在するドアを「E」キーを押すことで、個々に設定されているアニメーションを再生してドアを開けるようにしたいと思います。
Interactableインターフェースを作る
public interface Interactable
{
void Interact(); // Eキーを押したら実行される関数
}
まず、「Eキーで動作するオブジェクトは Interact() という関数を持つ」というルールを作ります。これをインターフェースで定義しましょう。
interfaceを使うと、クラスに 「この関数を必ず作らなきゃダメ!」 というルールを設定できます。
続いて、それぞれのオブジェクトに設定するInteractableObjectスクリプトを作成します。
using UnityEngine;
public class InteractableObject : MonoBehaviour, Interactable
{
private Animator animator;
public string animationTriggerName; // 各オブジェクトのアニメーショントリガー名
void Start()
{
animator = GetComponent(); // Animator を取得
}
public void Interact()
{
if (animator != null && !string.IsNullOrEmpty(animationTriggerName))
{
animator.SetTrigger(animationTriggerName); // 指定したアニメーションを再生
}
else
{
Debug.LogWarning("アニメーションがありません");
}
}
}
こちらは、インターフェースが実装されている側になりますので、Interactableを継承してあげる必要があります。
部屋のあちこちにドアが配置されており、プレイヤーが一定距離まで近づいた時にEキーを押すことで指定したアニメーションを再生するようにします。
既に各ドアには個別でPositionやRotationをいじったアニメーションを設定しています。
このスクリプトを動かしたいオブジェクトにアタッチして、animationTriggerNameを自由に設定してあげれば良いですね。
ドアの場合は、各ドア毎に「Door1」「Door2」「Door3」といったようにParametersからTriggerを作ってあげればOKです。
最後に、インターフェースを呼ぶ側となる「PlayerInteraction」スクリプトを作ります。
using UnityEngine;
public class PlayerInteraction : MonoBehaviour
{
public float interactRange = 2.0f; // インタラクト可能な範囲
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
TryInteract();
}
}
void TryInteract()
{
Collider[] hitColliders = Physics.OverlapSphere(transform.position, interactRange);
foreach (var collider in hitColliders)
{
Interactable interactable = collider.GetComponent();
if (interactable != null)
{
interactable.Interact(); // 近くのオブジェクトのInteract()を実行
return;
}
}
Debug.Log("近くにインタラクション可能なオブジェクトがありません");
}
}
私の場合は、FPSControllerをアタッチしているプレイヤーがいるので、そこにこのスクリプトをアタッチします。
やってることは、Eキーを押した際に指定した範囲内にて動かしたいオブジェクトがあるかチェックして、オブジェクトが存在したらそのオブジェクトのInteract()を実行しています。
このようにすることで、複数のオブジェクトを一括管理できるので、スクリプトを増やす必要がなくなりますね。
インターフェースを使うメリット
① 共通の仕組みで複数のオブジェクトを管理できる
インターフェースを使わない場合、「ドア用のスクリプト」「スイッチ用のスクリプト」…と分けて管理する必要がありますが、インターフェースを使えば、Interact()さえ呼び出せばOKです。
② 新しいオブジェクトを追加しても、既存のコードを変更しなくて済む
新しくアイテム等のオブジェクトを追加しても、Interactableを実装すればすぐに対応可能です。
③ スクリプトの量を減らして、管理しやすくなる
インターフェースを使うことで、DoorManager、SwitchManager、ItemManager等といったようにバラバラのスクリプトが必要になることもありません。
今回の場合は、Interactableにて統一することが出来ます。
インターフェースを使う上での注意点 & デメリット
インターフェースはとても便利ですが、万能ではありません。
使い方を間違えると逆にコードが複雑になったり、メンテナンスが難しくなったりすることがあります。
① 実装のコードが増える
例えば、Interactableを使っているオブジェクト(ドア・椅子等異なるオブジェクト)が10個あると、それぞれにInteract()を書く必要があります。
解決策としては、抽象クラスを使う事で処理を共通化してまとめることが出来るかと思います。
② UnityのインスペクターではInteractableを直接設定できない
Unityのインスペクターでは、インターフェースを持つオブジェクトを
public Interactable interactable; のように設定できない。
これは、Unityのシリアライズではインターフェースが扱えないからです。
③ インターフェースは変数を持てない
インターフェースは「メソッドの定義」しかできないので、変数を持つことが出来ません。
getter/setterといったプロパティと使ったり、抽象クラスを使用することでも変数を持てます。
④ 変更が多いときは「イベント」や「ScriptableObject」の方が便利
インターフェースは「Interact()を呼び出すと決まった処理を実行する」だけなので、「イベントを管理したい」場合はちょっと使いにくいかもしれません。
例えば、プレイヤーがEキーを押したときに複数のスクリプトを通知したい場合、インターフェースだと1つのInteract()に処理が固定されてしまいます。
この場合は、UnityEventを一緒に組み合わせると便利に使えるかと思います。
まとめ
共有の仕組みが作れるし、拡張しやすいのでアクションゲームやホラーゲームといった様々な仕掛けが必要となるゲームでは便利に使える機能です。
コードがスッキリするの上に、オブジェクトの追加や管理がすごく楽になるので、使えそうな場面でぜひ活用してみてください。