UnityでScriptを使い、イベントトリガーの動作が出来るようになるまで


## はじめに

趣味でUnityを触ってみているのですが、クリック等のイベントトリガーでの処理が中々想定通りに動かず、仕組みの理解に苦労したので覚書。
こんな風にクリックで画像が変わったり、場面を切り替えたりすることが出来るようになるまでにハマったポイントになります。

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252277/713ec8c2-199e-b7d9-7e9c-9d81ae15aed2.gif)

- 対象読者:Unity初心者で、Scriptの扱い方がいまいちピンとこない方
- Unity version: 1029.1.5f.1 Personal
- OS: Windows 10
- 今回のケース:2D

## Scriptを活用する前に:Unity内のデータ構造はどうなっているのか？

まずは、Unityプログラム内でScriptを扱う為のGameObjectとComponentの説明です。

### Sceneの中にGameObjectを置いてキャラクター等を配置
Unityでゲームを作るためには、**まずSceneを作ります**。
`File > New Scene`でSceneを作成すると、左側にGameObjectを追加出来る枠が出てきます。ここを`右クリック or GameObject選択 > 任意のGameObjectを選択`して追加することで、Sceneにキャラクター等のGameObjectを追加していくことが出来ます。カメラの位置やキャラクター情報、管理システム用のインスタンスもGameObjectとして扱われます。
なので、データとしては**Sceneの中にたくさんのGameObjectが詰まっている**構造になります。

<img width="917" alt="GameObject.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252277/1644e341-8097-5474-616d-ba869dd60aeb.png">

### GameObjectの中にComponentを設定して動きを付ける

GameObjectをただ置くだけでもかわいいキャラクター画像を設定したりすることが出来ますが、キャラクターに動きが無いとゲームとしてはちょっと寂しい。
そこで、各GameObjectは[**Component**](https://docs.unity3d.com/ja/current/Manual/class-Animator.html)と呼ばれる要素を追加することで、ゲームらしい挙動を実現します。
Scene枠内のGameObjectをクリックすると、右側にそのGameObjectと対応するInspector画面が表示され、その中で各Componentを設定することが出来ます。
というわけで、データとしては**GameObjectにはたくさんのComponentが詰まっている**構造になります。

<img width="1123" alt="Component.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252277/9466ef7d-2da2-0ac4-d4e7-a4c6ff7e9dd8.png">

#### ScriptはGameObjectのComponentとして好きに追加することが出来る。

先ほどの画像に、「Fox Action Event (Script)」というComponentが見えると思います。こちらは今回使用しているScriptで、このようにComponentとしてGameObjectに追加して使用します。
`Add Component > New Script`を選ぶ、もしくは`下に表示されるProject内の適切なフォルダ(Scripts等)で右クリック > Create > C# Script`を選択すると、このようなScriptが出来ます。
後は中身をガリガリ書いてGameObjectに追加すれば、好きな処理をゲームに組み込むことが出来ます。

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BackgroundCreater: MonoBehaviour
{
    void Start()
    {
          //シーン起動後に勝手に呼ばれる
    }

    void Update()
    {
          //フレーム単位で呼ばれる
    }
}
```

### システム内部では：Scene⇒GameObject⇒Componentのクラスを毎回生成

Scriptを書く際に注意する必要があるのが、そのクラスのインスタンスがどう扱われるか？です。
マニュアルやComponentに追加したScriptの挙動より、**Sceneが起動する度に生成され、Sceneが終わると削除**されていました。

![UnitySequence.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252277/3cb05e52-2ea7-6718-7bb6-1d86b43f759c.png)

また、**自身を生成してくれたGameObjectを参照することが出来る**というのもポイントです。

*`SceneManager.LoadScene();`はシーンを切り替えるためのメソッドです。

## Scriptを利用してイベントを扱う:クリックでキャラクター画像を切り替える

### 下準備：カメラとEventSystemの設定
イベントを扱うには、いくつかのGameObjectに対して設定が必要です。まずはカメラに[Physics 2D Raycaster(Script)](https://docs.unity3d.com/ja/current/Manual/script-Physics2DRaycaster.html)を追加します。
受けるイベントのMaskが出来ますが、必要なければEverythingを選択。

<img width="738" alt="CameraSetting.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252277/f823fdca-992d-5db9-b80e-0a6ef7571f75.png">

これでイベントが検知できるようになりました。次に[EventSystem](https://docs.unity3d.com/ja/current/Manual/EventSystem.html)というGameObjectを追加し、GameObjectまでイベントが届くようにします。

### GameObjectのイベント処理

準備が終わったら後は各GameObjectでEventを扱う為の設定をします。

- [collider](https://docs.unity3d.com/ja/current/Manual/CollidersOverview.html) componentを追加して当たり判定を付ける。(画像ではBox collider 2Dを設定)
- [Event Trigger](https://docs.unity3d.com/ja/current/Manual/script-EventTrigger.html)で、受けたいイベントとその際に呼ぶメソッドを指定する。

今回はFox Action EventというScript内、FoxClickメソッドを呼ぶようにします。

<img width="737" alt="event_setting.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252277/6036c3bd-e039-efae-0714-7cad58491630.png">

#### イベント処理のサンプル

切り替える画像は[Sprite Renderer](https://docs.unity3d.com/ja/current/Manual/class-SpriteRenderer.html)で制御するので、ScriptではSpriteRendererを書き換えるようにしています。
また、Script内のpublic変数でSpriteを指定すると、それぞれの画像を指定することが出来ます。

```C#:FoxActionEvent.cs
using UnityEngine;
using System.Collections.Generic;

public class FoxActionEvent : MonoBehaviour
{
    //書き換えるSpriteRenderer用の変数
    SpriteRenderer MainSpriteRenderer;
    //Sprite切り替え用のDictionary
    public Dictionary<int, Sprite> SpriteDictionary = null;
    //今どっちを表示しているかのstate
    public static int state = 0;

    //表示Spriteの本体
    public Sprite foxIdle;
    public Sprite foxSurprised;

    //最初に呼ばれるメソッドで初期化
    private void Start()
    {
        //MonoBehaviour.gameObjectでcomponentを持っているGameObjectを参照できる。
        MainSpriteRenderer = gameObject.GetComponent<SpriteRenderer>();
        FoxActionEvent.SpriteDictionary = new Dictionary<int, Sprite>();
        FoxActionEvent.SpriteDictionary[0] = foxIdle;
        FoxActionEvent.SpriteDictionary[1] = foxSurprised;
        //Spriteの書き換え
        setCurrentSprite();
    }

    public void FoxClick()
    {
        Debug.Log("FoxClick " + this + "\n" + count);
        //stateを変えて
        goToNextState();
        //Spriteの書き換え
        setCurrentSprite();
    }

    private void goToNextState()
    {
        state = (state + 1) % 2;
    }

    private void setCurrentSprite()
    {
        MainSpriteRenderer.sprite = FoxActionEvent.SpriteDictionary[state];
    }
}
```

また、上記をシーケンスに直すとこんな感じです。**FoxActionEventをComponentとして持つGameObject**のfoxが、Scriptから参照できます。
(上のコードだとstartでとってるけど)

![FoxSequence.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252277/401c5bda-41aa-b7cd-de25-558293563d56.png)



## データ構造の話から見るハマりポイント

上記でイベントの説明は終わりなんですが、データ構造の話と何が関係してるか？以下2点が関係してきます。

- **シーンが起動する度に生成され、シーンが終わると削除**
- **自身を生成してくれたGameObjectを参照することが出来る**

### Sceneが起動する度に生成され、Sceneが終わると削除⇒シーン切り替え時の状態を覚える工夫がいる

気付いてる方は多いと思いますが、毎回Sceneを切り替える度にインスタンスをdelete/newするので、**Sceneを跨いでも必要なデータはstaticにする等で消えないようにする**必要があります。
`DontDestroyOnLoad`といったGameObjectを別Sceneに受け渡すメソッドもありますが、今度はscene開始時のnewで余分にインスタンスを作らないよう気を付けないといけないです。
[[Unity]DontDestroyOnLoadでオブジェクトが増えていく](https://qiita.com/weakboar/items/cfc922392542c66db43a)

じゃあとりあえずシングルトンにするか～ってやりすぎるとそれもメモリを食っちゃうので、最小限で収まるよう気を付けたいところですね。

### 自身を生成してくれたGameObjectを参照することが出来る⇒参照されるのはEventTriggerを持っているGameObjectではない

イベント処理の話で書いてたEvent Trigger、実はPublicなメソッドであれば、**自分のComponentでないScriptのものでも呼ぶことが出来ます**。
これはこれで便利なんですが注意点。その際の**gameObjectはScriptをcomponentに持つGameObject**で、EventTriggerをcomponentに持つGameObjectではないです。

ScriptはGameObjectAに設定し、EventTriggerをGameObjectBに設定してScriptのメソッドを呼ぶと、Script内のgameObjectはGameObjectAの物になります。
そのgameObjectに対してSpriteをいくら書き換えても、GameObjectBのSpriteが変更されることはないので注意です。

## 最後に

新しいことをはじめる際は、結構完全に理解したまで行くのにも躓くポイントが出てきますよね。
そこを越えて必要な情報が腹落ちしてくれば楽しみが増えてきますね！

>「完全に理解した」
>製品を利用をするためのチュートリアルを完了できたという意味。
>
>「なにもわからない」
>製品が本質的に抱える問題に直面するほど熟知が進んだという意味。
>
>「チョットデキル」
>同じ製品を自分でも１から作れるという意味。または開発者本人。


## 参考

起動周り(awake書いてないけど)
[UnityのメソッドAwakeとStartの違い]
(https://gametukurikata.com/program/awakestart)

イベント
[Unity(3D・2D) EventSystemでクリックイベントの制御](https://qiita.com/Tachibana446/items/547a299c8829d1177a4d)



Sprite切り替えの参考諸々
[Unity Spriteの切り替え](https://teratail.com/questions/125963)
[【Unity】 Unity2D-スプライト画像をスクリプトから切り替える方法](https://webhoric.com/apple/mac/unity-2d-sprite-change/#st-toc-h-3)
[Unity オブジェクトの画像（Sprite)を動的に変更する方法](https://qiita.com/motsat/items/927a4d2682765555b80d)

Sprite指定の参考
[UnityのInspectorで変数を表示する方法まとめ](https://qiita.com/k_yanase/items/3bb59963a6f477f5a523)

画面切り替えでデータを残す
[Unityでシーンを移動した時のデータやゲームオブジェクトの引き継ぎ](https://gametukurikata.com/program/data)
[[Unity]DontDestroyOnLoadでオブジェクトが増えていく](https://qiita.com/weakboar/items/cfc922392542c66db43a)

使ったAsset
Sunny Land