111
79

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-18

概要

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

作りたい物と仕様

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

仕様

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

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

課題

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

原因

ダメージを与える処理の実行 現在のhp ダメージ処理の管理 がクラスをまたがって分散している点。
(2つのクラスで「ダメージに関する処理」を扱っている)

インターフェース実用

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

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

IDamagable.cs
public interface IDamagable
{
    void AddDamage(float damage);
}

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

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

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 (インターフェースが実装されてる側)

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 (インターフェースが実装されてる側)

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 (インターフェースが実装されてる側)

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(というかゲーム制作)においても、インターフェースを使った方がいい場面はあるので見極めてゆきたい…設計できる自信ない…

参考リンク

GetComponentを使うときはインターフェースを使おう

インタフェース完全に理解した

インタフェースの命名パターンから知るインタフェースの役割

111
79
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
111
79

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?