【Unity】C# インターフェースを使う実例 (備忘録)

### 概要
C#の機能にインターフェースというものがあります。
詳しい説明は先人の方の説明を読まれるのがいいと思いますが…簡単に説明すると**「インターフェースはクラスが実装すべき機能を定める機能」**です。
よくわからないので、実例を書いてみます。

### 作りたい物と仕様
作りたいものはアクションゲームで、プレイヤーは敵と戦闘します。
ここではプレイヤーが敵にダメージを与える処理を作りたいです。

#### 仕様
Player は敵に対してダメージを与える事ができる。
敵は Enemy と Boss の二種類が存在する。(敵は追加になる可能性がある)
Enemy　は　HitPoint が0以下になると倒せる。(復活しない)
Boss は HitPoint を0にすると RestorableCount(復活可能な回数) が1減る。
Boss は RestorableCount が0の時に HitPoint を0にすると倒せる。

Player と Boss と Enemy の３のクラスに分かれます。
もしも、Playerクラスに「ダメージを与える処理」を実装し、BossとEnemyにHitPointを実装、BossだけにRestorableCountを実装し、それぞれ管理する…といった場合、非常に煩雑になります。
ここに「(バリアを解除するまで)ダメージを与えられない」といった類の敵が追加された場合、 Playerクラスのダメージを与える処理は**「相手のクラスを判定して処理を分ける」**といった事をしないといけません。

##### 課題
敵クラスの判定に文字列やswitch文を使う場合、型安全でなくなりバグのリスクが増える。
（文字列の打ち間違いなどは、コンパイルエラーにならない）
敵を追加する場合、新規に追加した敵とPlayerの両方を保守する必要がある。煩雑である。
おそらくPlayerが神クラスになるポテンシャルを秘めている。

##### 原因
**ダメージを与える処理の実行　現在のhp　ダメージ処理の管理**　がクラスをまたがって分散している点。
（２つのクラスで「ダメージに関する処理」を扱っている）

### インターフェース実用

では、インターフェースを使って実際に実装してゆきます。

#### IDamagable (インターフェース部分)

```c#:IDamagable.cs
public interface IDamagable
{
    void AddDamage(float damage);
}
```

「私はダメージを与えれますよ！」というルールを決めたインターフェースです。
中身はコレだけで「IDamagable」を継承したクラスは「floatを引数にしたAddDamageメソッドを持っている」という意味になります。
つまり、IDamagableを持ってるかどうかの判定を行えば、固有のAddDamageメソッドを呼び出せる事が分かります。

#### PlayerCharacter (インターフェースを呼ぶ側)

```c#:PlayerCharacter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerCharacter : MonoBehaviour
{
    //ダメージを与える敵をアタッチする
	public GameObject _attakTarget;
	public float _attackPoint = 10.0f;
	
	void Update () {
		if (Input.GetKeyDown(KeyCode.A))
		{
			// _attakTarget にセットされたオブジェクトから、IDamagable を呼ぶ
			var damagetarget = _attakTarget.GetComponent<IDamagable>();

			//IDamagable は AddDamage の処理が必須
			if (damagetarget != null)
			{
				_attakTarget.GetComponent<IDamagable>().AddDamage(_attackPoint);
			}
		}
	}
}
```

_attakTarget にアタッチされたゲームオブジェクトが IDamagable のインターフェースを持ってる場合、ダメージを与える事ができます。
このインターフェースの利点は、**敵が追加された場合でもPlayerCharacterクラスを変更する事なく、IDamagableを持ったクラス(敵)にはダメージが与えられる**という点です。
（本来は接触したときにダメージを与える～といった風にすべきですが、簡略化しました）

#### Enemy (インターフェースが実装されてる側)

```c#:Enemy.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour, IDamagable
{
	public float _HitPoint = 100.0f;

	public void AddDamage(float damage)
	{
		_HitPoint -= damage;
		Debug.Log("add: " + damage + "hp: " + _HitPoint);

		if (_HitPoint <= 0)
		{
			Debug.Log("Enemyを倒した");
		}
	}
}

```

MonoBehaviourをカンマで区切って、IDamagableを継承させます。
Enemyの場合、AddDamageはそのまま処理されます。

#### BarrieEnemy (インターフェースが実装されてる側)

```c#:BarrieEnemy.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BarrieEnemy : MonoBehaviour, IDamagable
{
	public float _HitPoint = 100.0f;

	public bool _BarrieIsEnable = true;

	public void AddDamage(float damage)
	{
		if (_BarrieIsEnable)
		{
			Debug.Log("バリアを張っている ダメージを与えられない");
			return;
		}
		
		_HitPoint -= damage;
		Debug.Log("add: " + damage + "hp: " + _HitPoint);

		if (_HitPoint <= 0)
		{
			Debug.Log("BarrieEnemyを倒した");
		}
	}
}

```


BarrieEnemyの場合、_BarrieIsEnableがtrueだとダメージを与える事ができません。
重要なのは、**Player側はダメージを与える側**でしかなく、バリアの概念や、バリアに関する処理はBarrieEnemyが管理している点です。

#### Boss (インターフェースが実装されてる側)

```c#:Boss.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Boss : MonoBehaviour, IDamagable
{
	public float _HitPoint = 120.0f;
	
	public int _RestorableCount = 1;

	public void AddDamage(float damage)
	{		
		_HitPoint -= damage;
		Debug.Log("add: " + damage + "hp: " + _HitPoint);
		
		if (_HitPoint < 0 && _RestorableCount > 0)
		{
			_RestorableCount -= 1;
			_HitPoint = 120.0f;
		}

		if (_HitPoint < 0 && _RestorableCount <= 0)
		{
			Debug.Log("Bossを倒した");
		}
	}
}

```

Bossは復活回数の概念と、復活回数に応じた処理がありますが
PlayerCharacterクラスは、それを気にする事無くAddDamageを呼べばいいだけです。
switch文や文字列による判定を行わずに、EnemyとBossで挙動を分ける事ができました。

### まとめ

**switch文や文字列による判定をする事無く**、処理を分ける事ができました。
もしも**IDamagableを継承しているのにAddDamageの処理が無い場合はコンパイルエラーになります。**
SOLID原則を守りやすくなるという利点があります。

ただし、注意点があります。
まずコードの再利用性がないという事。元々**（分けたい！）**という欲求から実装されてるので**コードが共通化できない**のは当然は当然ですが、**設計の段階で本当にインターフェースを分ける必要があるのか？**はちゃんと考える必要があります。

Unity（というかゲーム制作）においても、インターフェースを使った方がいい場面はあるので見極めてゆきたい…設計できる自信ない…

### 参考リンク

<a target=”_blank” href="https://qiita.com/Akira_Kido_N/items/7cd18944173cd4cd7229">GetComponentを使うときはインターフェースを使おう</a>

<a target=”_blank” href="https://niconare.nicovideo.jp/watch/kn3404">インタフェース完全に理解した
</a>

<a target=”_blank” href="https://qiita.com/muniel/items/669a82ef9cb336985bd8">インタフェースの命名パターンから知るインタフェースの役割</a>