Unityで学ぶVisitorパターン ~アイテムを拾うorドアを開けるor会話する~


[前の記事](https://qiita.com/Nakashima_Toshiki/items/b749188ddd649af6f9ac)　―　[目次](https://qiita.com/nebusokuhibari/items/f403b40bea0ad790176c)

今回はUnityにてVisitorパターンを使ったサンプルを作ってみました。

# Visitorパターンとは？

アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。
分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。

[ウィキペディア引用](https://ja.wikipedia.org/wiki/Visitor_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)

# サンプルの内容

オブジェクトの役割によってクリックした場合のアルゴリズムを変える仕組みを作ります。
アルゴリズムは以下の３つを作ります。

- アイテムのオブジェクト → アイテムを拾うアルゴリズム
- ドアのオブジェクトなら → ドアが開くアルゴリズム
- 人のオブジェクトなら   → メッセージウィンドウを出すアルゴリズム

# シーンの用意

シンプル過ぎるシーン。それぞれの役割は以下の通り。

四角・・・・アイテム
板・・・・・ドア
カプセル・・人

と見立てます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/291470/dbbf9e81-3859-59e0-6a4e-a6145fdc928e.png)

# 抽象クラスのコンポーネントを作成

このパターンには２つの抽象クラスが必要です。

Visitor・・・訪問者。
Acceptor・・引受人。

## Visitor

作成するアルゴリズムの数だけメソッドを用意します。本サンプルは３つ。

```C#:Visitor.cs
using UnityEngine;

public abstract class Visitor : MonoBehaviour {

    public abstract void Visit(ItemAcceptor acceptor);
    public abstract void Visit(DoorAcceptor acceptor);
    public abstract void Visit(ActorAcceptor acceptor);
}
```

## Acceptor

Visitor オブジェクトを受け入れる accept(Visitor visitor)メソッドを用意します。

```C#:Acceptor.cs
public abstract class Acceptor : MonoBehaviour
{
    public abstract void Accept(Visitor visitor);
}
```

# Visitorの実装コンポーネントを作成

ちょっと長いコードになるけど、
UpdateメソッドにてクリックしたゲームオブジェクトにAcceptorコンポーネントがあれば、
該当するVisitメソッドを呼び出しています。

## CursorVisitor

```C#:CursorVisitor.cs
using UnityEngine;

public class CursorVisitor : Visitor
{
    private Ray ray;

    public Camera mainCamera;

    public override void Visit(ItemAcceptor acceptor)
    {
        acceptor.Accept(this);
        Debug.Log("アイテムを取得する処理");
    }

    public override void Visit(DoorAcceptor acceptor)
    {
        acceptor.Accept(this);
        Debug.Log("フィールドを移動する処理");
    }

    public override void Visit(ActorAcceptor acceptor)
    {
        acceptor.Accept(this);
        Debug.Log("会話モードへ移行する処理");
    }
    
    void Start()
    {
        ray = new Ray();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            ray = mainCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                GameObject game = hit.collider.gameObject;

                ItemAcceptor item = game.GetComponent<ItemAcceptor>();
                if (item != null) Visit(item);

                DoorAcceptor door = game.GetComponent<DoorAcceptor>();
                if (door != null) Visit(door);

                ActorAcceptor actor = game.GetComponent<ActorAcceptor>();
                if (actor != null) Visit(actor);
            }
        }
    }
}

```

# Acceptorの実装コンポーネントを作成

アイテム、ドア、人 の３つコンポーネントを作成。
それぞれに個別の動作を記述することができます。

### ItemAcceptor

アイテムオブジェクト用。クリックしたオブジェクトを消すだけ。

```C#:ItemAcceptor.cs
using UnityEngine;

public class ItemAcceptor : Acceptor
{
    public override void Accept(Visitor visitor)
    {
        Destroy(gameObject, 0.1f);
    }
}
```

### DoorAcceptor

ドアオブジェクト用。親オブジェクトを回転させて、ドアが開いたかのような動作をさせる。

```C#:DoorAcceptor.cs
using UnityEngine;

public class DoorAcceptor : Acceptor
{
    public override void Accept(Visitor visitor)
    {
        transform.parent.Rotate(new Vector3(0, 90, 0));
    }
}
```

### ActorAcceptor

人オブジェクト用。テキスト表示のGameObjectをアクティブにするだけ。

```C#:ActorAcceptor.cs
using UnityEngine;

public class ActorAcceptor : Acceptor
{
    public GameObject text;

    public override void Accept(Visitor visitor)
    {
        text.SetActive(true);
    }
}
```

# 完成

シーンの作成手順については流石に面倒なので省略します。

サンプルは[Github](https://github.com/NakashimaToshiki/Adaptive-Code-Unity)に上げていますので、動作確認をしたい方はドーゾ。

ダウンロードすると他の記事で作ったサンプルが混じっていますが、
「08-Visitor-Cursor」というプロジェクトが本記事のサンプルです。

# 所感

スマホゲーだったら、タッチしたオブジェクトの役割によって処理を変えたい。というのは多そうですし、
このパターンは広い範囲で使われていそうですね。

イイ感じなので、自作ゲームで取り入れてみたいと思います。