Unityでよく実行する処理

注意）このコードはあくまでUnityのAPIの宣言方法のメモです

オブジェクトの操作とコンポーネント内の値変更など
コルーチンによる徐々に値を変化させる処理

```.cs

        [SerializeField] private GameObject CreateButtonObj;
        [SerializeField] private GameObject DeleteButtonObj;
        private GameObject objChild;

        void Start()
        {
            // nullの場合エラーメッセージ出力
            Debug.Assert(CreateButtonObj, "CreateButtonObj が null");
            Debug.Assert(DeleteButtonObj, "DeleteButtonObj が null");

            // Buttonコンポーネントにクリックイベント追加
            CreateButtonObj.GetComponent<Button>().onClick.AddListener(OnClickCreateButton);
            DeleteButtonObj.GetComponent<Button>().onClick.AddListener(OnClickDeleteButton);

            // Resorcesフォルダからプレハブを取得
            GameObject resourceButtonObj = Resources.Load("Button") as GameObject;
            Debug.Assert(resourceButtonObj, "resourceButtonObj が null");
            // 不要になったらリソースは開放する
            // Resources.UnloadUnusedAssets();

            // コールバック処理
            System.Func<GameObject, int> callBack;
            callBack = delegate (GameObject callObj)
            {
                Debug.Log("No Delet Obj");
                return 1;
            };

            // 自身のオブジェクト取得
            GameObject obj = this.gameObject;

            // オブジェクトを検索して取得（非アクティブオブジェクトは検索できない）
            // GameObject.Findは処理が重いのでなるべく使わないこと
            GameObject canvas = GameObject.Find("Canvas");

            // ルートオブジェクト取得
            GameObject rootObj = this.transform.root.gameObject;

            // シーンが移動しても消えないオブジェクトを登録
            // シーンが呼ばれるごとに増えてしまうため登録するオブジェクトはシングルトンにする
            DontDestroyOnLoad(rootObj);

            // "cube"タグのついたオブジェクトをすべて格納
            GameObject[] cubes = GameObject.FindGameObjectsWithTag("cube");
            // "cube"タグのついたオブジェクトをすべて削除
            foreach (GameObject cube in cubes) {
                Destroy(cube);
            }

            // 孫オブジェクトを検索して取得（階層を/で指定）（非アクティブオブジェクトも検索可能）
            objChild = this.gameObject.transform.Find("Viewport/Content").gameObject;

            // 子オブジェクトの数取得
            int a = objChild.transform.childCount;

            // 子オブジェクトを複数取得
            int number = 0;
            foreach (Transform childTransform in objChild.transform)
            {
                GameObject child = childTransform.gameObject;

                // Buttonコンポーネントの取得
                Button button = child.GetComponent<Button>();
                // Buttonコンポーネントにクリックイベント追加（引数あり）
                button.onClick.AddListener(() => OnClickButton(child));

                // Buttonのテキスト変更（番号順）
                number++;
                child.transform.GetComponentInChildren<Text>().text += number;
            }
        }

        // Update is called once per frame
        void Update()
        {
            // Find系の処理は重いのでUpdate内では使わないこと
            
            // 現在選択中のオブジェクト（SetSelectedGameObject で変更できる）
            Debug.Log(UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject);

            // プラットフォームがアンドロイドかチェック
            if (Application.platform == RuntimePlatform.Android)
            {
                // バックボタンのチェック
                if (Input.GetKeyDown(KeyCode.Escape))
                {
                    // アプリケーション終了
                    Application.Quit();
                    return;
                }
            }
        }

        /// <summary>
        /// 新しいボタン追加
        /// </summary>
        public void OnClickCreateButton()
        {
            // プレハブを元にオブジェクトを生成する
            GameObject instance = Instantiate(resourceButtonObj, new Vector3(0.0f, 0.0f, 0.0f), Quaternion.identity);

            // Buttonの色を変える
            instance.GetComponent<Image>().color = new Color32(255, 0, 0, 128);

            // 生成したオブジェクトを子として登録
            instance.transform.SetParent(objChild.transform);
        }

        /// <summary>
        /// 新しいボタン追加
        /// </summary>
        public void OnClickCreateButtonMessage()
        {
            // メソッドを呼び出す（引数は１つだけ持たせられるが戻り値は持てない）
            // 参照関係がわからなくなるためなるべく使用しない（非アクティブなオブジェクトにも使えない）
            SendMessage("OnClickCreateButton");
        }

        /// <summary>
        /// ボタンオブジェクトを破棄
        /// </summary>
        public void OnClickDeleteButton(GameObject button)
        {
            // オブジェクトを破棄
            Destroy(button);
        }

        /// <summary>
        /// ボタンクリックイベント
        /// </summary>
        /// <param name="buttonObj">ボタンオブジェクト</param>
        public void OnClickButton(GameObject buttonObj)
        {
            // 同階層におけるオブジェクトの順序取得
            int index = buttonObj.transform.GetSiblingIndex();
            // 押されたボタンの名前出力
            Debug.Log(buttonObj.name + " index : " + index);

            if (index < 10)
            {
                // 親オブジェクトを取得
                GameObject parentObj = buttonObj.transform.parent.gameObject;

                // 子オブジェクトの数取得
                int childMax = parentObj.transform.childCount;

                // Buttonオブジェクトの順序を一番下に移動（セットした時点で移動する）
                // buttonObj.transform.SetAsLastSibling(); でもいい
                buttonObj.transform.SetSiblingIndex(childMax - 1);
            }
            else
            {
                // Buttonオブジェクトの順序を一番上に移動（セットした時点で移動する）
                // buttonObj.transform.SetAsFirstSibling(); でもいい
                buttonObj.transform.SetSiblingIndex(0);
            }

            // スクロールバーをコルーチンを使って移動
            StartCoroutine(ScrollDelay(index));
        }

        /// <summary>
        /// ゆっくりスクロール
        /// Animation使った方が滑らかなスクロールになるかも
        /// </summary>
        /// <param name="bar">Scrollbarコンポーネント</param>
        /// <returns></returns>
        private IEnumerator ScrollDelay(int index)
        {
            // 子オブジェクトを検索して取得
            GameObject obj = this.gameObject.transform.Find("Scrollbar Vertical").gameObject;
            // Scrollbarコンポーネント取得
            Scrollbar bar = obj.GetComponent<Scrollbar>();

            //コルーチンの内容
            while (true)
            {
                // 0.01秒処理を待つ
                yield return new WaitForSeconds(0.01f);

                // 1フレーム待つ（フレーム時間によって時間が変わるので注意）
                // yield return null;

                if (index < 10)
                {
                    // スクロールバーを徐々に1番下へ移動
                    bar.value -= 0.2f;
                }
                else
                {
                    // スクロールバーを徐々に1番上へ移動
                    bar.value += 0.2f;
                }

                if (bar.value <= 0.0f || bar.value >= 1.0f)
                {
                    // コルーチン終了
                    yield break;
                }
            }
        }

      
        public void Link(string url)
        {
            // ブラウザでwebページを開く
            Application.OpenURL(url);
        }
 
        // イメージファイルをダウンロードしてRawImageに表示
        private IEnumerator DownloadImage(string url)
        {
            WWW www = new WWW(url);
            yield return www;
            RawImage img = GameObject.Find("WebRawImage").GetComponent<RawImage>();
            img.texture = www.texture;
        }
```

データを宣言する ScriptableObject
参考ページ
https://kan-kikuchi.hatenablog.com/entry/What%27s_Scriptableobject
http://tsubakit1.hateblo.jp/entry/2017/06/19/233000

```.cs
/// <summary>
/// データを宣言するクラス（ScriptableObject）
/// 定数データ、URL、ファイルパスなど
/// </summary>
public class StatusData : ScriptableObject
{
    public int PlayerHP { get; private set; } = 200;

    public int EnemyHP { get; private set; } = 100;

    public bool IsFlag = false;
}

/// <summary>
/// プレイヤーキャラクター
/// </summary>
public class PlayerChar : MonoBehaviour
{
    void Start()
    {
        StatusData data = ScriptableObject.CreateInstance<StatusData>();

        this.gameObject.transform.GetComponentInChildren<Text>().text = data.PlayerHP.ToString();
    }
}
```

セーブデータの保存と読み込み
セーブデータを改変して使われたくなければ暗号化する
参考ページ
https://gametukurikata.com/program/savedata
https://freelyapps.net/delete-data-of-playerprefs-in-windows/

```.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// セーブデータクラス
/// セーブされないのは プロパティ, static, private, なデータ
/// </summary>
public class SaveData
{ 
    public int PlayerHP;

    public int EnemyHP;
 
    [SerializeField] private GameObject obj;
    public GameObject Obj
    {
        set { this.obj = value; }
        get { return obj; }
    }
}

以下は別クラス

     SaveData save = new SaveData();

     // PlayerHPデータを保存
     PlayerPrefs.SetInt("Player", save.PlayerHP);

     // セーブデータをJSON形式に変換
     string json = JsonUtility.ToJson(save);
     // セーブデータを保存
     PlayerPrefs.SetString("SaveData", json);
   
     // セーブデータを読み込み
     string json = PlayerPrefs.GetString("SaveData");
     // 読み込んだセーブデータをsaveに上書き
     JsonUtility.FromJsonOverwrite(json, save);

     // セーブデータを削除
     PlayerPrefs.DeleteKey("SaveData");
```

例外発生時の処理
参考ページ
https://qiita.com/tricrow/items/0c9eed86f3f75cd272ba

```.cs
        void OnEnable()
        {
            Application.logMessageReceived += HandleLog;
        }

        void OnDisable()
        {
            Application.logMessageReceived -= HandleLog;
        }

        /// <summary>
        /// 例外検出時のコールバック
        /// 例外をわざと発生させるには throw new Exception();
        /// </summary>
        /// <param name="logString"></param>
        /// <param name="stackTrace"></param>
        /// <param name="type"></param>
        void HandleLog(string logString, string stackTrace, LogType type)
        {
            if (type == LogType.Exception)
            {
                Debug.Log(logString);
                Debug.Log(stackTrace);
                Debug.Log(type);

                // エラーログをファイルに出力

                // アプリ終了
#if UNITY_EDITOR
                UnityEditor.EditorApplication.isPlaying = false;
#else
                UnityEngine.Application.Quit();
#endif
            }
        }
```

FPS表示（設定方法は以下を参考）
https://ameblo.jp/santiago-bernabeu/entry-11974077338.html

```.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FPS : MonoBehaviour
{
    int fps;
    int frameCount;
    float nextTime;

    void Start()
    {
        nextTime = Time.time + 1;
    }

    void Update()
    {
        frameCount++;

        if (Time.time >= nextTime)
        {
            fps = frameCount;
            frameCount = 0;
            nextTime += 1;
        }
    }

    void OnGUI()
    {
        GUILayout.Label("FPS: " + fps.ToString());
    }
}

```

デザインパターンでは以下のページでよく解説されてた
https://qiita.com/English_Story/items/1ea0d16763878f382c2c

シングルトン（サウンドマネージャー）
http://esprog.hatenablog.com/entry/2016/02/28/122938
https://moon-bear.com/2019/03/30/%E3%80%90unity%E3%80%91%E3%82%B5%E3%82%A6%E3%83%B3%E3%83%89%E7%AE%A1%E7%90%86%E3%82%AF%E3%83%A9%E3%82%B9%EF%BC%88soundmanager%EF%BC%89%E3%82%92%E4%BD%9C%E3%82%8B/
https://naichilab.blogspot.com/2013/11/unitymanager.html
https://qiita.com/waken/items/a0288c9b160a20022635
https://qiita.com/weakboar/items/cfc922392542c66db43a
http://zyyxlabo.blogspot.com/2013/03/unitysoundmanager-ver.html
https://yumineko.com/unity-singletonmonobehaviour/
https://qiita.com/Teach/items/c146c7939db7acbd7eee

stateパターン
https://atas10703.hatenablog.com/entry/2019/02/16/220831
https://befool.co.jp/blog/raharu/design-patern-001/

参考ページ
http://baba-s.hatenablog.com/entry/2018/01/12/140000
https://www.hanachiru-blog.com/entry/2019/04/10/235036