【Unity】便利なメッセージウィンドウを作る

# 1. 概要
簡単に呼び出せるメッセージウィンドウを作りたい
具体的には以下の通り
- メソッドだけで呼び出せる（GetComponent<T>などが不要）
- テキストと表示時間を指定できる
- 初期状態は非表示
- 呼び出されると勝手に表示されて、秒数経過で勝手に消える

# 2. 作成したもの
1000カウント毎に「Push!!!」と書かれたメッセージウィンドウが表示され、1秒後に消える
![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706027/fbe8e475-ff06-d9fb-4eae-7c524bfaeab9.gif)

# 3. 実装
## Unity側
デモの作成にはUnity 2021.3.6を使用した
ヒエラルキーは以下の通り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706027/901fcc7b-adc0-b063-56a1-6f3dfa2a2048.png)
- Image：メッセージウィンドウそのもの
- Image下のText(TMP)：メッセージ
- Canvas下のText(TMP)；カウント表示用

全てヒエラルキーを右クリック→「UI」から生成

## スクリプト
MessageWindow.csを作成→Imageにアタッチする
```MessageWindow.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class MessageWindow : MonoBehaviour
{
	public static MessageWindow instance;

	Coroutine coroutine;
	bool isRunning = false;

	GameObject textWindow;
	TextMeshProUGUI text;

	private void Awake() {
		if(instance == null) {
			instance = this;
		} else {
			Destroy(gameObject);
		}
	}

    // 初期化
	void Start()
    {
		textWindow = this.gameObject;
		text = textWindow.transform.GetChild(0).GetComponent<TextMeshProUGUI>();
		DisableTextWindow();
    }

    // このメソッドを呼び出してメッセージウィンドウを更新する
	public void SetMessage(string str, float sec) {
		if (isRunning) {
			StopCoroutine(coroutine);
			coroutine = null;
		}

		coroutine = StartCoroutine(UpdateMessageWindow(str, sec));
	}

    // メッセージウィンドウの更新をコルーチンで実装
	private IEnumerator UpdateMessageWindow(string str, float sec) {
		isRunning = true;
		text.text = str;

		EnableTextWindow();

		yield return new WaitForSeconds(sec);

		DisableTextWindow();
		isRunning = false;
	}

    // メッセージウィンドウを表示する
	private void EnableTextWindow() {
		Image img = textWindow.GetComponent<Image>();
		img.color = new Color(img.color.r, img.color.g, img.color.b, 1);
		text.color = new Color(text.color.r, text.color.g, text.color.b, 1);
	}

    // メッセージウィンドウを非表示にする
	private void DisableTextWindow() {
		Image img = textWindow.GetComponent<Image>();
		img.color = new Color(img.color.r, img.color.g, img.color.b, 0);
		text.color = new Color(text.color.r, text.color.g, text.color.b, 0);
	}
}
```
# 解説
- シングルトン
MessageWindowクラスはシングルトンとして実装している
該当コードは以下
```MessageWindow.cs
public static MessageWindow instance;

// (中略）

private void Awake() {
	if(instance == null) {
		instance = this;
	} else {
		Destroy(gameObject);
	}
}

```
シングルトンはデザインパターンの一種で、シーン内に1つしか存在しないインスタンスである
自身をstaticのインスタンスとして持ち、すでにインスタンスとして存在している場合は自身を破棄することで実現している
参考（[[Unity]シングルトンを使ってみよう](https://qiita.com/Teach/items/c146c7939db7acbd7eee))

- Start メソッド
初期化処理
Unityでの操作を減らすため、アタッチされたオブジェクトからGetComponentでオブジェクトを取得する
（ただこうするとヒエラルキー側の変更に対応できない...）
DisableTextWindowメソッドで非表示にしておく
```MessageWindow.cs
void Start()
    {
		textWindow = this.gameObject;
		text = textWindow.transform.GetChild(0).GetComponent<TextMeshProUGUI>();
		DisableTextWindow();
    }
```

- EnableTextWindow / DisableTextWindow メソッド
Imageコンポーネントを取得→ウィンドウのImageとテキストのColorを再設定している
``` new Color(r, g, b, a)``` の```a```が不透明度に対応している
```MessageWindow.cs
private void EnableTextWindow() {
	Image img = textWindow.GetComponent<Image>();
	img.color = new Color(img.color.r, img.color.g, img.color.b, 1);
	text.color = new Color(text.color.r, text.color.g, text.color.b, 1);
}

private void DisableTextWindow() {
	Image img = textWindow.GetComponent<Image>();
	img.color = new Color(img.color.r, img.color.g, img.color.b, 0);
	text.color = new Color(text.color.r, text.color.g, text.color.b, 0);
}
```

- UpdateMessageWindow メソッド
引数に```str（メッセージ）```と```sec（表示時間[s]）```を渡す
コルーチンとして実装しており、```yield return new WaitForSeconds(sec);```で指定秒数処理を止める
```MessageWindow.cs
private IEnumerator UpdateMessageWindow(string str, float sec) {
	isRunning = true;
	text.text = str;

	EnableTextWindow();

	yield return new WaitForSeconds(sec);

	DisableTextWindow();
	isRunning = false;
}
```

- SetMessage メソッド
引数に```str（メッセージ）```と```sec（表示時間[s]）```を渡す
```isRunning```は、UpdateMessageWindowコルーチンが実行中かどうか判定している
どこからでも呼び出すことができるため、衝突した場合は実行中のコルーチンを停止させて新しいコルーチンを開始する
```MessageWindow.cs
public void SetMessage(string str, float sec) {
	if (isRunning) {
		StopCoroutine(coroutine);
		coroutine = null;
	}

	coroutine = StartCoroutine(UpdateMessageWindow(str, sec));
}
```

# 4. つかいかた
呼び出すときは次の1行でどこからでも呼び出すことができる
```
MessageWindow.instance.SetMessage(str, sec);
```
## 例
```TestScript.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class TestScript : MonoBehaviour
{

	[SerializeField] TextMeshProUGUI text;
	int count;


    // Update is called once per frame
    void Update()
    {
		count++;
		text.text = "Count:" + count + "\n";

		if(count % 1000 == 0) {
			MessageWindow.instance.SetMessage("Push!!!", 1f);
		}
    }
}
```
1000カウント毎にSetMessageメソッドを呼び出している

# おわりに
便利なテキストウィンドウを作ってみた
Imageに画像を設定しても動作するのでぜひ使ってみてほしい

（拡張性とかはあまり考えてないので、色々試してみてほしかったり）