よくわかってなかったcacaponがまとめたインターフェースの作り方

突然ですがUnityでゲームを作っていくときに、下のような状況になりました。

f:id:cacapon:20210702100339p:plain

既にタップ用のスクリプトは作ってありまして、


本用のオブジェクトをタップしたら

「本を読んだ!」関数が呼ばれ、


キャラクターをタップしたら、

「ターゲットに選ばれた!」関数が呼ばれる。


そんな感じで同じタップでも呼ばれる処理を変えたかったのです。


ところが、あらかじめ作ってあったタッチ用スクリプトは下のような感じでありまして

if (タッチされてる?){
    デバッグ用本を読む処理();
}

…そう、タッチスクリプトの中に

デバッグ用の本を読む処理を呼び出す処理をそのまま書いていたために、

切り替えが出来ない作りだったのです。


このまま本用タッチイベント、ターゲット用タッチイベント…と、それぞれに作るのはナンセンスだし、

if文の中にさらに分岐するのも何だかなぁと思って調べた時、

インターフェースというのが今回の問題を解消するのに良さそうだぞ

という事が分かりまして、実装してみることに。


今回のブログでは、インターフェースについて今までよく理解できてなかったcacaponが

実際に作ったことでようやく理解できたので、それをまとめた内容になります。

理解したてなので、言葉足らずな点などもあるかと思いますが、

よければ見て頂けたら幸いです。それでは行ってみましょう♪

インターフェースとはなんぞや?

インターフェースは呼び出し側と呼び出し元を繋ぐ規格にあたるものです。

身近な例でいうと、USBなどが分かりやすいかなと思います。


スマホの充電器を思い浮かべて頂けると分かりやすいかと思いますが、

あれってパソコンにも電源*1にも指すことできますよね?

全く違う機器なのに接続できるのは、USBという共通の規格があるからなのです。

USBが分かりにくかったら電池でも。

単三という同じくくりなら

どのメーカーの電池でも、どんな機器にも使えますよね?

これも単三という規格が設けられて、電池側も機器側もそれに合わせて作ってあるから使えるのです。


では、プログラミングでいうインターフェースとはどんなものなのでしょう?

今回の「本とキャラクターをタップするときの処理」で考えてみましょうか。


まず、関係として呼び出し元、呼び出し先、そして、インターフェースの3つがあります。

ここで呼び出し元に当たるのは、タッチ用のスクリプトです。

このスクリプトが特定条件の操作を行った時、タップされたと判断します。


次に呼び出し先はとなると、これは本とキャラクターですね。

本はタップされると「本を読む処理」を行い、

キャラクターはタップされると「キャラクターをターゲットにする処理」が呼ばれます。


そして、それを繋ぐインターフェースが何かとなるとタップですね。

なので、この関係をうまく定義出来れば、何とかできそうな気がします。

cacaponが感じたC#のインターフェースの特徴

という訳で、いざ実装…と行きたいところですが、

C#で定義している特徴を加味すると理解が深まる気がするので、

それも紹介したいと思います。

私の中で印象に残った特徴は下記の通りです。

  • インターフェースは一切中身を持たない
  • インターフェースは大体がメソッドの宣言だけを持つ*2
  • インターフェースは一般的な継承と異なり、幾つもスーパークラス扱いにできる
  • インターフェースの呼び出し先は必ず実装する必要がある。

私が理解した範囲で、という注釈は付きますが、

インターフェースは基本的にメソッドの宣言のみ持っています。

中身はありません。


C#の場合は、インターフェース作成後に呼び出し先に継承するような形で記載することで

呼び出し先にインターフェースで定義したメソッドを必ず実装しなければならないようになります。

こうすることで、インターフェースのメソッドさえわかれば、

例えばTargetInstance.TAP();のような形で、呼ぶことができるようになるみたいです。

C#のインターフェースの実装例

Interface

public interface ITap{ //インターフェースには最初にIを付けるのがお約束らしいです。
    void Tap();
}

using UnityEngine;

public class Book: MonoBehaviour,ITap //ITapを記載するのを忘れずに
{
    private void Read()
    {
        Debug.Log("本を読みます");
    }

    //ITapで定義されたメソッド
    public void Tap()
    {
        Read();
    }
}

キャラクター

using UnityEngine;

public class Character : MonoBehaviour,ITap //ITapを記載するのを忘れずに
{
    private void SetTarget()
    {
        Debug.Log("ターゲットとして選択されました");
    }

    //ITapで定義されたメソッド
    public void Tap()
    {
        SetTarget();
    }
}

タッチ用スクリプト

using UnityEngine;
using UnityEngine.EventSystems;

public class TouchEvent : MonoBehaviour, IPointerDownHandler
{
    public GameObject target;

    private void TapCallBack(ITap instance)
    {
        instance.Tap();
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        //オブジェクトがクリックやタップされたときに呼ばれるメソッド
        //これはIPointerDownHandlerインターフェースで定義されたメソッドです。

        //本来は座標やスピードの判定を行っていたのですが、説明のために処理を簡略化しています。
        TapCallBack(target.GetComponent<ITap>());
    }
}

呼び出し元のTapCallBackの挙動だけ少し複雑なので説明します。


まずTapCallBackの処理自体ですが、

アタッチしたtargetのTapメソッドを呼び出す、といったものです。


これ自体はほかのメソッド呼び出しと同じですが、

指定しているのがITap型、つまりインターフェースの型を指定しています。

このように指定することで、ITapで定義しておいたメソッドを呼びますよ~とやっているみたいです。


それで、どうやってITap型の変数を入れるんじゃ、って話ですが、

Unityの場合、ITapを継承しているスクリプトを追加してあるGameObjectから

GetComponent<ITap>()をすることで取得できるようです。

因みに、C#単体なら、インターフェースを含む newで作ったインスタンスを入れればOKです。*3

スクリプトをUnityでアタッチしてみました。

Unityで実際に動かしてみました。 

本とキャラクターが四角なのはご愛敬、という事で(笑)

f:id:cacapon:20210702121347g:plain

まとめ

共通の規格を定めれば、呼び出し先を変えるだけで処理を切り替えられる。

インターフェースの強力な面はここにあるのかなと思います。

今回の件で理解は少し進んだので、他の実装でもうまく使っていければなと思います。

それではまた。

参考文献

www.atmarkit.co.jp

*1:正しくは電源プラグとUSBを繋ぐ変換器には、ですが

*2:ほかにも持つ事は出来るらしいですがよくわかっておらず…

*3:詳しくは参考文献にて説明されています