初心者VRエンジニアがコードをモリモリ読めるようになる方法

##ワーキングメモリーを鍛えればコードもりもり読める説～！！


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">着想を得たきっかけはこの論文です！<br><br>単純な記憶力のテストではVR空間内の方が2Dでのテストに比べて8.8%高い結果であったとのことです👏<a href="https://t.co/yTiIj49E0S">https://t.co/yTiIj49E0S</a></p>&mdash; KENTO⚽️VRエンジニア😎年内にQiita100記事💻現在75/100 (@okprogramming) <a href="https://twitter.com/okprogramming/status/1183052197396791296?ref_src=twsrc%5Etfw">October 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


もっと遡るとこちらの記事でNバック課題について知りました。

→[初心者プログラマーがコードをモリモリ読めるようになる方法](https://qiita.com/rnitta/items/ace513d4b11dac49095d)

タイトルは**パクりではなくリスペクトの証**です。


##Nバック課題って？

何個か前の記憶を引っ張り出す訓練をすることで、
ワーキングメモリーが鍛えられて、コードとの追いかけっこが上手になるらしいです。


##遊び方

**★準備**
Oculus Quest にGithubからダウンロードしてきたapkを突っ込んで起動して終わりです。
→[Github](https://github.com/ForJobOk/NBack)

最初に光ったパネルを記憶して、思い出しながら選択するだけです。
２バックくらいから始めるとルールがわかり易いかと思います。
![NBackDemo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/a37f417e-652e-f08d-1add-780ecc075e3d.gif)


##オレオレNバック実装

ここから本題です。強いみなさんはどのように実装するのでしょうか。
**まずは脳トレとして遊んでみて、自分だったらどう実装するだろう、、、
という視点でもう一度遊ぶと二度おいしい**です。


###スライダーで選んだレベルを取得
まずはスライダーで選んだレベルを取得します。
下記コードの`GetLevel()`で取得した回数分だけ最初にパネルが光ります。

```C#:LevelGetFromSlider.cs
using UnityEngine;
using UnityEngine.UI;

public class LevelGetFromSlider : MonoBehaviour
{
    Slider levelSlider;
    
    void Start()
    {
        levelSlider = this.gameObject.GetComponent<Slider>();
    }

    public int GetLevel()
    {
        return (int)levelSlider.value;
    }
}
```
　
###パネルがレベルの回数分自動でランダムに光る
次のコードでは以下を行っています。
①全てのパネルを取得し、リスト化。
②作成したリストからランダムに1つを選択。
③選択されたオブジェクト側にアタッチされたクラスから、光る・音が鳴る、などのメソッドを呼び出す。
④選択されたオブジェクトをリスト化する。

上記を`GetLevel()`で取得した回数分だけ繰り返します。

また、次のパネル側で呼び出す予定の`ToNextSelect()`もここに書いておきます。

```C#:PanelSelected.cs

    [SerializeField, Header("選ばれる間隔を操作")] 
    float m_interval = 2;

    List<GameObject> m_tmpListObj = new List<GameObject>();

    [HideInInspector]
    public List<GameObject> g_listObj = new List<GameObject>();

    float m_randomValue;
   
    void Start()
    {
        //子のオブジェクトを全部リストに入れとく
        foreach(Transform child in this.gameObject.transform)
        {
            m_tmpListObj.Add(child.gameObject);
        }
    }

    //Startボタン押したら呼ばれる
    public void AutoSelectStart()
    {
        StartCoroutine(AutoSelectStartCoroutine());
    }

    //レベルに応じて自動で選ばれる回数を変える
    IEnumerator AutoSelectStartCoroutine()
    {
        for(int i = 0; i < m_levelGetFromSlider.GetLevel(); i++)
        {
            yield return new WaitForSeconds(m_interval);

            //ここでどれが選ばれるかランダムに決める
            m_randomValue = Random.Range(0, m_tmpListObj.Count);

            //選ばれたときのイベント呼び出し
            m_tmpListObj[(int)m_randomValue].GetComponent<PanelSelectedEvent>().BlinkPanel();
            m_tmpListObj[(int)m_randomValue].GetComponent<PanelSelectedEvent>().CorrectSoundPlay();

            //選んだオブジェクトをリスト化する
            g_listObj.Add(m_tmpListObj[(int)m_randomValue]);
        }

    }

    //レーザーで選んだら次が光ってリストを更新
    public void  ToNextSelect()
    {
        //ここでどれが選ばれるかランダムに決める
        m_randomValue = Random.Range(0, m_tmpListObj.Count);

        //選ばれたときのイベント呼び出し
        m_tmpListObj[(int)m_randomValue].GetComponent<PanelSelectedEvent>().BlinkPanel();

        //選んだオブジェクトをリスト化する
        g_listObj.Add(m_tmpListObj[(int)m_randomValue]);
    }
```

ゲーム開始までの処理が完了しました。

###パネルを選択し、正解不正解の判定
ここからは実際にパネルを選択した後の判定に移ります。
判定はパネル側で個々に行います。

行っていることは非常にシンプルで、
①先程のリストを、用意したインデックスで指定して呼び出し、自分と照合する。
②合致していたら正解音と次のパネルが選ばれる処理を実行し、用意したインデックスをインクリメント。

間違っていたらインクリメントはせずに、間違いSEを再生するだけです。
また、インクリメントしたインデックスの数からゲームクリア時の判定も行っています。

```C#:PanelSelectedEvent.cs
    //このスクリプトはパネルにアタッチされたスクリプトです

    //何回選ぶか　エンドレスモードがあればここをめちゃくちゃでかい数字に変えればOK
    int m_playTime = 10;
    static int m_selectedCount;

    //先程のリストを取ってくる
    PanelSelected m_panelSelected;

    void Start()
    {
        m_selectedCount = 0;
        m_panelSelected = this.gameObject.GetComponentInParent<PanelSelected>();
    }

    //選んだものが同じかどうか判定して選択カウント増加
    public void IncrementSelectedCount()
    {
        if (m_selectedCount < m_playTime)
        {
            if (m_panelSelected.g_listObj[m_selectedCount] == this.gameObject)
            {
                m_panelSelected.ToNextSelect();
                CorrectSoundPlay();
                m_selectedCount++;
            }
            else
            {
                WrongSoundPlay();
            }
        }
        else
        {
            if (m_panelSelected.g_listObj[m_selectedCount] == this.gameObject)
            {
                CorrectSoundPlay();
                m_selectedCount++;

                if (m_selectedCount == m_playTime + m_levelGetFromSlider.GetLevel())
                {
                    //ここにクリア時の処理
                    m_clear.ClearTextAppear();
                }
            }
            else
            {
                WrongSoundPlay();
            }
        }
    }
```

##まとめ

作っておいてこんなこと言うのもなんですが、
プログラムを読めるようになりたいならプログラムを読めばいいと思います。
ありがとうございました。