インターフェースとは
インターフェースのメリット
具体的な実装を持たないため、処理の抽象化が可能になり、同じ処理をする場合でも状況に応じて分岐させることができます。
ゲーム開発においては衝突判定が当てはまります。衝突する物体に対して振る舞う処理を変えたい場合にインターフェースを介して、抽象的な処理を挟むことによって、汎用性の高い実装が可能になります。
インターフェースの実装例
今回はアクションゲームでよく見られる、敵の攻撃をプレイヤーが受けた場合の処理をインターフェースを用いて記述していきます。
Unityの衝突判定と言えば、タグを使用することが私は多かったですが、タグを使用してしまうと、敵の攻撃対象がプレイヤーに固定されてしまって、プレイヤー以外に攻撃した場合はif文をまた別で書く必要があり、汎用性に欠けます。このような場合に、インターフェースは便利なのでその実装例を書いていきます。
設計の例
今回は以下のような設計で実装を進めます。敵の攻撃を管理するEnemyAttackControllerからIDamagebleインターフェースを実装したPlayerDamageControllerに対してプレイヤーへの攻撃を呼び出して、PlayerHealthManagerで体力減少の処理を行います。
実装していく設計は以下の通りです。
EnemyAttackController
using UnityEngine;
public class EnemyAttackController : MonoBehaviour
{
[SerializeField] private int power = 1;
private void OnCollisionEnter2D(Collision2D collision)
{
//IDamagebleインターフェースを実装しているオブジェクトなら
if (collision.gameObject.TryGetComponent(out IDamageble damageble))
{
damageble.TakeDamage(power);
}
}
}
攻撃対象のGameObjectがIDamagebleインターフェースを実装しているかを確認して、実装している場合は攻撃処理を呼び出します。
IDamageble
public interface IDamageble
{
void TakeDamage(int damage);
}
インターフェースの実装です。interfaceキーワードをクラス名の前に記述することで定義できます。
ここに抽象メソッドのTakeDamageを宣言します。
PlayerDamageController
using UnityEngine;
public class PlayerDamageController : MonoBehaviour, IDamageble
{
[SerializeField] private PlayerHealthManager playerHealthManager;
public void TakeDamage(int damage)
{
playerHealthManager.ApplyDamage(damage);
}
}
ここでプレイヤーがダメージを受けた際の具体的な処理を記述します。本来ならばヒットストップなどの処理も必要ですが、今回は体力を減らす処理をPlayerHealthManagerから呼び出す処理のみ記述します。
PlayerHealthManager
using UnityEngine;
using UniRx;
public class PlayerHealthManager : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
private ReactiveProperty _health;
private void Awake()
{
_health = new ReactiveProperty(maxHealth);
}
//外部から購読できるようにする
public IReadOnlyReactiveProperty Health => _health;
//ダメージを受けたらHealthを減らす
public void ApplyDamage(int damage)
{
_health.Value -= damage;
}
}
ここで実際にプレイヤーの体力管理して、体力を減らす処理を記述します。
PlayerHealthUI
using UnityEngine;
using TMPro;
using UniRx;
public class PlayerHealthUI : MonoBehaviour
{
[SerializeField] private PlayerHealthManager playerHealthManager;
[SerializeField] private TextMeshProUGUI healthText;
private void Start()
{
playerHealthManager.Health
.Subscribe(health => healthText.text = $"HP: {health}");
}
}
補足ですが、PlayerHealthManagerの体力状態を購読して、プレイヤーの体力UIを
更新する処理を記述しています。
オブジェクトにスクリプトを割り当てる
- PlayerのゲームオブジェクトにPlayerHealthManagerとPlayerDamageController
- EnemyのゲームオブジェクトにEnemyAttackController
- 空のゲームオブジェクトにPlayerHealthUI
実行結果
プレイヤーが敵に触れるとダメージを受けて、UIが更新されます。また、Playerにはタグを設定することなく、処理が実行できました。
まとめ
今回はインターフェースを用いて衝突判定の処理を実装してみました。
これ以外にもゲーム制作では抽象的な処理を導入することで、汎用性が高くなり、急な仕様変更にも対応しやすくなります。
今後も様々な設計を学んで取り入れていこうと思います。