UnityEditorのEditorWindow上で動くマインスイーパー作ってみた

# はじめに

世の中にはUnityEditorなんていう素晴らしいゲームエンジンがあるそうじゃないですか！？
そんな素晴らしいゲームエンジンでゲームが作れるなんてきっと私は特別な存在なのでしょう。

というわけで、UnityEditorで動くマインスイーパーを作ってみました。

#できたもの

こんな感じのマインスイーパーができました。

操作方法は
* 左クリック : マスを開ける
* 右クリック : 旗切り替え
* 中央クリック : 周囲にその数字分の旗が置かれている状態で, 数字マスをクリックすると。旗を置いたマス以外のマスが全て開く。(普通のマインスイーパーだと左右同時クリック)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77177/59578716-2445-9bb8-dfec-82b9f50d3ffc.png)


なんと難易度設定画面まで作っちゃいました！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77177/0ad01588-e8da-cc49-e512-cbe6d9e24b7e.png)

GitHub上でダウンロードできるのでぜひ見てみてください！！（画像はいらすとやさんのものなので抜いています）
MineSweeperOnUnityEditor
https://github.com/Marimoiro/MineSweeperOnUnityEditor.git

# ゲームの開始方法

* ゲーム本体

Unityでこのプロジェクトを開くと MineSweeper > Open というメニューがあるのでそれで開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77177/7cc91655-b406-9422-ce5d-8a547a64ddc5.png)

* 設定画面

SampleSceneを開いてPlayを押せば設定画面になります。
Playしながらゲームも開けますよ！！

# なんかおかしくない？（やりたかったこと）

**UnityEditorWindowでゲーム作ったっていいじゃない！！**
Emacsだってマインスイーパーあるんだよ！！
UnityEditorにだってゲーム機能あってもいいじゃん！！

以上！！

# やってみた感想

最近Editorの方あんまり触れてないからやってみたけどさ…
やっぱりUnityEditor整備微妙だよね…

次から実装のあらすじだけ紹介しています

# 実装

# ステップ1 Windowを出す。

```
            var w = EditorWindow.GetWindow<Window>("MineSweeper");
```


# ステップ2 ボードを初期化する

ランダムに爆弾を規定数配置すればOK
（最初の1箇所だけ爆弾が置かれないようにします）


結果こんな感じに配置できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77177/0d025f57-5aa3-07c3-ee57-c7d416590343.png)


```
            var size = width * height;
            cells = new Cell[width, height];

            for (int i = 0; i < size; i++)
            {
                cells[i % width, i / width] = new Cell()
                {
                    IsBomb = i < bombs
                };
            }

            void Swap(int a, int b)
            {
                if (a == b) return;

                var a1 = a % width;
                var a2 = a / width;

                var b1 = b % width;
                var b2 = b / width;

                var t = cells[a1, a2];
                cells[a1, a2] = cells[b1, b2];
                cells[b1, b2] = t;
            }

            // random sort without last cell
            for (int i = 0; i < size; i++)
            {
                Swap(i, UnityEngine.Random.Range(i, size - 2));
            }

            // swap 1st clicked cell and last cell
            Swap(fy * width + fx,size -1);
```

fx,fyはプレイヤーが最初にクリックしたマス目でその位置のみ爆弾がないように配置します。

小ネタなのですがランダムな順列（配列をランダムに配置し直す）の作成は次のコードでできます。

```
            for (int i = 0; i < size; i++)
            {
                Swap(i, UnityEngine.Random.Range(i, size - 1));
            }
```

今回は最初にプレイヤーがクリックしたところに爆弾のないマスを保証したいため一番最後のみシャッフルから除いて最後にプレイヤーがクリックしたマスと交換しています。

## ステップ3 遊べるようにする

OnGUIでがんばります

長いのでこのあたり見てください
https://github.com/Marimoiro/MineSweeperOnUnityEditor/blob/master/Assets/Editor/MineSweeper/Window.cs#L286

### ステップ3.1 各マスのレイアウトを作る

マインスイーパーは数字のフォントの色を変えたほうがぽいのでそのようにします。
あとはbox styleを使ってます目を表現。
（隠しているときはただのボタンです。）

```
            if (styles == null)
            {
                //create fonts
                Color[] color = new Color[]
                {
                    Color.clear,
                    Color.blue,
                    Color.green,
                    new Color(0.1f,0.3f,0.4f),
                    new Color(0.5f,0.25f,0.12f),
                    Color.magenta,
                    Color.red,
                    new Color(0.6f,0f,0.8f),
                    Color.black,
                };

                styles = new GUIStyle[9];
                for (int i = 0; i < color.Length; i++)
                {
                    styles[i] = new GUIStyle(GUI.skin.box)
                    {
                        alignment = TextAnchor.MiddleCenter,
                        fontSize = 20
                    };
                    styles[i].normal.textColor = color[i];
                }
                
            }
```

## 設定画面作る

設定画面のボタンは自分自身のボタンのテキスト(Easyならeasy)をPlayerPrefsに保存しているだけです。

```
    [RequireComponent(typeof(Button))]
    public class SelectDifficultly : MonoBehaviour
    {
        public string Name;
        // Start is called before the first frame update
        void Start()
        {
            var b = GetComponent<Button>();
            b.onClick.AddListener(() =>
            {
                PlayerPrefs.SetString("difficultly",b.GetComponentInChildren<Text>().text.ToLower().Trim());
                PlayerPrefs.Save();
            });
        }

    }
```

ゲームウィンドウが開いたときにこのデータと同名の設定ファイル(ScriptableObject)を読み込んでいます。

```
            var d = PlayerPrefs.GetString("difficultly") ?? "easy";
            titleContent = new GUIContent("MineSweeper - " + d);

            var settings = Resources.Load<GameSettings>(d);

            width = settings.Width;
            height = settings.Height;
            bombs = settings.Bombs;
```