Unityで行動分析 ～その0：準備編～

# 1. はじめに
　強化スケジュール課題をUnityで作成するにあたって、共通している手順を準備編として載せます。Unityで実験を行う場合の流れをフローチャートにしてみるとこんな感じです(下図)。これは実験手続きであって、Unityの作成手順ではないので、そこだけ注意してください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/7433d374-8ec6-7568-99b5-2fc12ff6abd1.png" width=40%>
　「オペランダムへの反応」から「強化可能ランプ」あるいは「得点上昇」にフローしていますが、もう少し詳しく書くと、強化スケジュールに従ってオペランダムへ反応すると「強化可能ランプ」あるいは「得点上昇」にフローします。
　その0では、Unityで「弁別刺激」から「得点上昇」までの工程を解説します。また、様々な実験状況を汲みして、「得点上昇」の方法を2つ考えてみました。下記の2つの手順を軸に解説したいと思います。

* 「オペランダムへの反応」→「得点上昇」
* 「強化オペランダムへの反応」→「得点上昇」


# 2. Unityでゲームを作る準備
## 2.1. そもそもUnityとは
　Unityとはゲームエンジンの一種で、ゲームなら基本的に何でも作れますし、対応プラットフォームも様々です。 **無料で**誰でも扱えます。詳しくは[ **こちら** ](https://qiita.com/nmxi/items/7950fb12ef925efa276d)を参照してください。
　Unity上で行う作業はざっくり書くと2つです。1つはゲーム画面にどのようなオブジェクトを設置するのか、もう1つはそのオブジェクトの動きをどう制御するかです。前者はUnityのEditor上で確認しながら設置でき、後者はC#を使って制御できます。
## 2.2. UnityのインストールとEditorのレイアウト設定
　Unityのインストール方法は[ **先ほどの記事** ](https://qiita.com/nmxi/items/7950fb12ef925efa276d)に載っていますので、そちらを参照してください。Unityのレイアウトについては、好みの問題なのでいじらなくても良いのですが、この記事では、[ **こちら** ](https://qiita.com/2dgames_jp/items/11bb76167fb44bb5af5f)にあるように「2 by 3」のレイアウトにしています。
## 2.3. 環境
　この記事で使用しているUnityのバージョンは、2018.4.20f1(008688490035)です。


# 3. 強化スケジュール課題の土台を作る
　強化スケジュール課題の土台を作成しますが、注意点を2点ほど書いておきます。1つは、UnityのEditor上で扱うGameObjectを「オブジェクト」と表記します。もう1つは、オブジェクト名は「*Object*」のように、イタリック体で表記します。
　すべて完成してからゲーム内容を確認するのでも良いですが、いつどのタイミングで不具合が起きるのかわからないです。そのため、画面中央上の「Play」を押して、こまめにゲームがどう動いているのか、思った通りに動いているのかを確認した方が良いと思います(下図の「▶」)。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/9ffdb934-37d1-8e72-8c2d-26f4f9551421.png" width=25%>

## 3.1. 「弁別刺激」
### 3.1.1. 弁別刺激を設置する
　フローチャートの1番上、弁別刺激を作成します。まずは、画面上に弁別刺激っぽいものを作成します。「[Hierarchy] >> [Create] >> [3D Object] >> [ *Sphere* ]」の順に選択してください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/4a65bfbc-41b7-17a5-7f98-08f9c6ae4c80.png" width=50%>

SceneタブとGameタブに、白色の球が表示されたと思います。これを消灯用と点灯用に、2つ作成します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/d1e49275-a38a-0715-0088-e3572eaac6bf.png" width=50%>

次に行うことは3つです。

* 消灯用と点灯用を区別するため、Sphereの名前を変更する(消灯用は *Sd1_off* , 点灯用は *Sd1_on* )
* 消灯用と点灯用を1つにまとめる
* 消灯用と点灯用に色をつける

#### 3.1.1.1. Sphereに名前をつける
　名前の変更方法は2通りあります。1つは「[Hierarchy] >> [ *Sphere* ] >> [ *Sphere* をゆっくり2回クリック]」です。もう1つは「[Hierarchy] >> [ *Sphere* ] >> [ *Sphere* をクリック] >> [Inspector]」です。名前はわかりやすいものであればなんでも良いですが、今回は **消灯用を「Sd1_off」**、 **点灯用を「Sd1_on」**にします。
　点灯用を *Sd1_on* にしたら、これを非アクティブにします。「[Hierarchy] >> [ *Sd1_on* ] >> [ *Sd1_on* をクリック] >> [Inspector] >> [チェックボックスからチェックを外す]」の順に選択してください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/063d6efd-8a48-a384-9d43-fcf08a262a48.png" width=50%>

#### 3.1.1.2. 消灯用と点灯用を1つにまとめる
　Hierarchyタブに *Sd1_off* と *Sd1_on* を放置してもかまわないのですが、オブジェクトが増えてごちゃごちゃになるのも良くないので、1つにまとめます。方法は、SceneタブやGameタブに影響しないオブジェクトを作成して、その中に入れ込みます。「[Hierarchy] >> [Create] >> [Create empty]」の順に選択すると、Hierarchyタブ上には存在しますが、SceneタブやGameタブには存在しないカラのオブジェクトを作成できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/a6cb233f-73ef-c0c6-ed66-74f01aaa5117.png" width=25%>
　作成したカラのオブジェクト(名前を **Sd1**に変更)に *Sd1_off* と *Sd1_on* をドラッグアンドドロップで入れ込みます。すると、下図のようになり、「▼」で *Sd1_off* と *Sd1_on* を省略できるようになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/8487c2ab-0592-ac48-b2b9-aef6c0f80e32.png" width=25%>

#### 3.1.1.3. 消灯用と点灯用に色をつける
　オブジェクトに色をつける場合、 Material を使用します。「[Project] >> [Create] >> [Material]」の順に選択してください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/c8ba71e0-c52e-5a3e-1533-065ce396e101.png" width=25%>
　Material を *Sd1_off* と *Sd1_on* の2つ分作成し、 Material フォルダーの中へ1つにまとめます。 Folder は「[Project] >> [Create] >> [Folder]」の順に選択すると作成できます(上図参照)。まとめると、下図のようになります。ここでは、 Base(Scene名) の中に Material を入れ、さらにその中に個々の Material を入れています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/b75b544f-1600-143e-2144-6d691e640ce7.png" width=40%>
　Material を設定してオブジェクトに色をつけていきます。 Material ( *Sd1_off* か *Sd1_on* )をクリックすると、Inspectorタブに Material の詳細が表示されます。 Albedo のとなりある白色の四角をクリックすると、色の詳細が表示されるので、そこで色を選択してください。ここでは、*Sd1_off* を黒色(Hexadecimal: 000000)に、*Sd1_on* を緑色(Hexadecimal: 00FF1B)にしています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/967f84ff-4c11-ad89-52d5-a8d72d42e41d.png" width=35%>
　オブジェクトに色をつける方法は、2通りあります。1つは、 Material をSceneタブにあるオブジェクトにドラッグアンドドロップする方法です。もう1つは、 Material をHierarchyタブにあるオブジェクトにドラッグアンドドロップする方法です。
　SceneタブやGameタブを確認すると、 *Sd1_off* と *Sd1_on* が重なっているので、Sceneタブにドラッグアンドドロップする場合は、 *Sd1_off* と *Sd1_on* を少し離した方が良いと思います。オブジェクトの位置操作等については、[ **こちら** ](http://tsubakit1.hateblo.jp/entry/2015/04/21/031048)を参照すると良いと思います。そして、もろもろ設定して色もつけると下図のようになります。左側が *Sd1_on* で右側が *Sd1_off* です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/3387fe68-dc70-2dfe-4509-d21b23806762.png" width=35%>
　上図では、 *Sd1_off* の設定を「[Transform] >> [Position] >> [x] >> [1]」に、 *Sd1_on* の設定を「[Transform] >> [Position] >> [x] >> [-1]」にしています。

### 3.1.2. 弁別刺激を制御する
　設置した弁別刺激を制御するには Script でコーディングする必要があります。いったん、コーディングの前に弁別刺激の点滅のイメージ図を下記に示します。注意点は2つあります。1つは、消灯から点灯までの時間(インターバル)は常に一定なことです。もう1つは、ゲーム開始時点から点灯までの時間は、消灯から点灯までの時間より短い(あるいは長い)ことです。つまり、コーディングの際に *x sec* と *y sec* という2つの処理を考える必要があります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/ca2867e8-e8e2-606a-e423-36ebdae2d53e.png" width=100%>

#### 3.1.2.1. 弁別刺激のScriptを作成する
　弁別刺激を制御するため、 *Sd1* に Script をつけていきます。「[Hierarchy] >> [ *Sd1* をクリック] >> [Inspector] >> [Add Component] >> [New Script]」の順に選択してください。。名前はわかりやすいものであればなんでも良いですが、今回は **「Sd1_Script」** にします。 Script も Material と同様、フォルダーを作成して1つにまとめます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/f729cc24-f381-6e21-4deb-4bbe556a7caa.png" width=70%>

#### 3.1.2.2. 弁別刺激のScriptに書き込む
　Script に書き込む内容ですが、先に内容を示してから解説を行います。

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

public class Sd1_Script : MonoBehaviour
{
    bool first = true;
    public GameObject discriminative1_off;
    public GameObject discriminative1_on;
    public float first_start;
    public float d1_finish;
    public float d1_interval;


    private void Start()
    {
        StartCoroutine("DiscriminativeStimuli");
    }

    IEnumerator DiscriminativeStimuli()
    {
        if (first == true)
        {
            yield return new WaitForSeconds(first_start);

            discriminative1_off.SetActive(false);
            discriminative1_on.SetActive(true);
            first = false;

            yield return new WaitForSeconds(d1_finish);

            discriminative1_off.SetActive(true);
            discriminative1_on.SetActive(false);
        }

        if (first == false)
        {
            while (true)
            {
                yield return new WaitForSeconds(d1_interval);

                discriminative1_off.SetActive(false);
                discriminative1_on.SetActive(true);

                yield return new WaitForSeconds(d1_finish);

                discriminative1_off.SetActive(true);
                discriminative1_on.SetActive(false);
            }
            
        }
    }
```
　このScriptの流れをざっくり書くと、変数の宣言 → Start() → DiscriminativeStimuli()となります。解説もこの順序で行います。ちなみに、6行目より上のコードは何を意味しているのかについては、[ **こちら** ](https://qiita.com/JunShimura/items/3c2e23bb77cc9085bfda)を参照してください。

 **変数の宣言**

* bool first = true; … bool型の変数 first が true であることを宣言
* public GameObject Sd1_off; ... public な GameObject として Sd1_off を宣言<br>
→ Editor上では *Sd1_off* を入れてください
* public GameObject Sd1_on; ... public な GameObject として Sd1_on を宣言<br>
→ Editor上では *Sd1_on* を入れてください
* public float first_start; ... public なfloat型の変数 first_start を宣言<br>
→ Editor上ではゲーム開始から最初に弁別刺激が点灯する時間を入力してください( *x sec* )
* public float Sd1_finish; ... public なfloat型の変数 Sd1_finish を宣言<br>
→ Editor上では弁別刺激点灯から弁別刺激消灯までの時間を入力してください
* public float Sd1_interval; ... public なfloat型の変数 Sd1_interval を宣言<br>
→ Editor上では弁別刺激消灯から弁別刺激点灯までのインターバルを入力してください( *y sec* )

　「publicな」という表現を多用していますが、publicの意味については[ **こちら** ](http://gengoshori.hatenablog.com/entry/2018/01/07/122654)を参照してください。「public ○○」と書くと、Editor上で値やオブジェクトを設定できるようになります。

 **Start()**

* StartCoroutine("DiscriminativeStimuli"); ... コルーチンがゲーム開始時に読み込まれる
        
 **IEnumerator DiscriminativeStimuli()**
　コルーチンという関数を使って *x sec* と *y sec* の2つを制御しています。コルーチンとは何かについては、[ **こちら** ](http://kimama-up.net/unity-functions-coroutine/)を、使い方については[ **こちら** ](https://www.sejuku.net/blog/83712)を参照してください。**「if (first == true)」**は *x sec* を制御していて、**「if (first == true)」**は *y sec* を制御しています。順に解説していきます。

* **「if (first == true)」**の処理 ... *x sec* に関する処理
    * ゲーム開始から first_start になるまで待機(現状維持)<br>
     → ゲーム開始から最初に弁別刺激が点灯する時間になると処理開始
    * discriminative1_off を非アクティブ化、discriminative1_on をアクティブ化<br>
     → 疑似的に弁別刺激の点灯を表現
    * first をfalseにすることで、2回目以降の「if (first == true)」の処理を阻止
    * first_start から d1_finish になるまで待機(現状維持)<br>
     → 弁別刺激が点灯してから弁別刺激が消灯する時間になると処理開始
    * discriminative1_off をアクティブ化、discriminative1_on を非アクティブ化<br>
     → 疑似的に弁別刺激の消灯を表現
* **「if (first == false)」**の処理 ... *y sec* に関する処理
    * **「while (true)」** ... 無限ループの作成(詳しくは[ **こちら** ](http://kimama-up.net/unity-while/))
        * 弁別刺激が消灯してから d1_interval になるまで待機(現状維持)<br>
     → 弁別刺激が消灯してからから次に弁別刺激が点灯する時間になると処理開始
        * discriminative1_off を非アクティブ化、discriminative1_on をアクティブ化<br>
     → 疑似的に弁別刺激の点灯を表現
        * d1_interval から d1_finish になるまで待機(現状維持)<br>
     → 弁別刺激が点灯してから弁別刺激が消灯する時間になると処理開始
        * discriminative1_off をアクティブ化、discriminative1_on を非アクティブ化<br>
     → 疑似的に弁別刺激の消灯を表現

## 3.2. 「オペランダムへの反応」→「得点上昇」
　フローチャートでは「オペランダムへの反応」→「得点上昇」となっていますが、作成順序は「得点上昇」→「オペランダムへの反応」となります。
### 3.2.1. 「得点上昇」
#### 3.2.1.1. テキストを表示
　オペランダムを作成する前に、得点を表示させます。得点はオブジェクトではなくテキストなので、テキストを作成して「得点」が表示できるように編集します。「[Hierarchy] >> [Create] >> [UI] >> [Text]」の順に選択してください。テキストを作成したら名前を「Text」から **「Point」** にします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/d4e9d8ac-ba25-44c7-b935-abead7f22b29.png" width=30%>

#### 3.2.1.2. 得点を表示
　テキストを編集します。基本的には[ **こちら** ](https://www.sejuku.net/blog/55029)を参照するとほぼほぼ解決すると思います。今回は下図のようにします。色は黒色(Hexadecimal: 000000)にしています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/564cf117-d24f-7117-2711-c600e44c1a70.png" width=45%>

### 3.2.2. 「オペランダムへの反応」
#### 3.2.2.1. オペランダムを設置する
　オペランダムを作成します。まずは、画面上にオペランダムっぽいものを作成します。「[Hierarchy] >> [Create] >> [3D Object] >> [ *Cube* ]」の順に選択してください。画面上にオペランダムを設置したら、弁別刺激同様、名前(Operandum1)と色( *Sd1_on* と同じ色)をつけます。場所は弁別刺激と異なる場所の方が良いです(例えば、X:0, Y;-.5, Z:0)。

#### 3.2.2.2. オペランダムのScriptに書き込む
　Script に書き込む内容ですが、先に内容を示してから解説を行います。

```C#:Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }


    void Update()
    {
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(PointSE);
                CountText.text = "Point : " + Point.ToString();
                Point += 1;
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
        }
    }
}
```

　このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説もこの順序で行います。4行目に「using UnityEngine.UI;」が増えていることに注意してください。
　ここでは、1回オペランダムに反応すると得点が上昇するようになっています。**もし個々の強化スケジュールを走らせる場合、「Operandum1_Script」に書き込むことになります。**

 **変数の宣言**

* int Point; ... int型の変数 Point を1に宣言
* public GameObject Sd1_off; ... public な GameObject として Sd1_off を宣言<br>
 → Editor上では *Sd1_off* を入れてください
* public GameObject Sd1_on; ... public な GameObject として Sd1_on を宣言<br>
 → Editor上では *Sd1_on* を入れてください
* public Text CountText; ... public な Text として CountText を宣言<br>
 → Editor上では *Point* を入れてください
* public AudioClip PointSE; ... public な AudioClip として PointSE を宣言<br>
 → Editor上では得点が上昇ときに鳴るSEを入れてください
* AudioSource audioSource; ... AudioSource として audioSource を宣言<br>
 → 詳しくは[ **こちら** ](https://www.sejuku.net/blog/83535)

 **Start()**

* audioSource = GetComponent<AudioSource>(); ... Inspectorタブの Audio Sourse を取得

 **Update()**

* **「if (Sd1_on.activeSelf)」**の処理 ...  Sd1_on がアクティブな時(弁別刺激点灯時)の処理
    * **「if (Input.GetKeyDown(KeyCode.F))」**の処理 ... キーボードのFキーが押されたときの処理
        * 効果音( PointSE )が鳴る
        * 得点が1点上昇( Point += 1; )
* CountText.textについて  ... テキストの表示を入れ替える処理
    * "Point : " ... 得点(数値)以外は *Point* のテキスト部分と同様にする
    * Point.ToString() ... 得点(数値)をstr型にすることで"Point : "と組み合わせられる
* **「if (Sd1_off.activeSelf)」** の処理 ... Sd1_off がアクティブな時(弁別刺激消灯時) の処理
    * time を0にリセット
     → 「if (Sd1_on.activeSelf)」の処理だけでは、弁別刺激消灯時に time が0になっていない場合があるので、弁別刺激消灯時に強制的に time を0にしています
   
#### 3.2.2.3. 効果音を鳴らす
　効果音を鳴らすには、Scriptを書くだけではなく、Audio Sourse が必要になります。「[Hierarchy] >> [ *Operandum1* をクリック] >> [Inspector] >> [Add Component] >> [Audio Sourse]」の順に選択してください。 Inspectorタブ 上に Audio Sourse が出現しますが、何もいじる必要はないです。後は、上記にある通り、Editor上で *Operandum1* の PointSE の中に得点が上昇ときに鳴るSEを入れると効果音が鳴るようになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/d49d1f17-1e85-ddca-d04a-5321e6d9e058.png" width=30%>


## 3.3. 「強化オペランダムへの反応」→「得点上昇」
　フローチャートでは「オペランダムへの反応」→「強化可能ランプ」→「点灯」→「強化オペランダムへの反応」→「得点上昇」となっていますが、作成順序は「得点上昇」→「強化可能ランプ」→「オペランダムへの反応」→「強化オペランダムへの反応」となります。

### 3.3.1. 「得点上昇」
#### 3.3.1.1. テキストを表示 (3.2.1.1. と同じ内容です)
　オペランダムを作成する前に、得点を表示させます。得点はオブジェクトではなくテキストなので、テキストを作成して「得点」が表示できるように編集します。「[Hierarchy] >> [Create] >> [UI] >> [Text]」の順に選択してください。テキストを作成したら名前を「Text」から **「Point」** にします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/d4e9d8ac-ba25-44c7-b935-abead7f22b29.png" width=30%>

#### 3.3.1.2. 得点を表示 (3.2.1.2. と同じ内容です)
　テキストを編集します。基本的には[ **こちら** ](https://www.sejuku.net/blog/55029)を参照するとほぼほぼ解決すると思います。今回は下図のようにします。色は黒色(Hexadecimal: 000000)にしています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/564cf117-d24f-7117-2711-c600e44c1a70.png" width=45%>

### 3.3.2. 「強化可能ランプ」
#### 3.3.2.1. 強化可能ランプと強化オペランダムを設置する
　強化可能ランプと強化オペランダムを作成します。まずは、画面上に強化可能ランプっぽいものを作成します。「[Hierarchy] >> [Create] >> [3D Object] >> [ *Sphere* ]」の順に選択してください。**強化可能ランプは、弁別刺激と同様に2つ作成して( *Ramp_off* と *Ramp_on* )、カラのオブジェクト「*Ramp*」の中に入れ、Ramp_onを非アクティブにしてください。**「Ramp」は「Reinfoeceable lamp」を略したオリジナルの略語です。位置は両方とも(X:0, Y:-2, Z:0)にしています。色は、*Ramp_off* を黒色( *Sd1_off* と同じ色)に、*Ramp_on* を赤色(Hexadecimal: FF0000)にしています。

　画面上に強化オペランダムっぽいものを作成します。「[Hierarchy] >> [Create] >> [3D Object] >> [ *Cube* ]」の順に選択してください。強化オペランダム( *Comsummatory* )の位置は(X:0, Y:-3.5, Z:0)に色は赤色( *Sd1_on* と同じ色)にしています。「Comsummatory」は、完了反応( comsummatory response )からとっています。

#### 3.3.2.2. 強化可能ランプ( *Ramp* )のScriptに書き込む
　Script に書き込む内容ですが、先に内容を示してから解説を行います。

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

public class Ramp_Script : MonoBehaviour
{
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public float ReinforceableTime;


    void ActiveReverse()
    {
        Ramp_off.SetActive(true);
        Ramp_on.SetActive(false);
        CancelInvoke();
    }


    void Update()
    {
        if (Ramp_on.activeSelf)
        {
            Invoke("ActiveReverse", ReinforceableTime);
        }
    }
}
```
　このScriptの流れをざっくり書くと、変数の宣言 → Update() → ActiveReverse()となります。解説は、変数の宣言 → ActiveReverse() → Update()の順序で行います。

 **変数の宣言**

* public GameObject Ramp_off; ... public な GameObject として Ramp_off を宣言<br>
 → Editor上では *Ramp_off* を入れてください
* public GameObject Ramp_on; ... public な GameObject として Ramp_on を宣言<br>
 → Editor上では *Ramp_on* を入れてください
* public float ReinforceableTime; ... public なfloat型として ReinforceableTime を宣言<br>
 → Editor上では強化可能時間を入れてください

 **ActiveReverse()**

* Ramp_off をアクティブ化、Ramp_on を非アクティブ化<br>
→ 疑似的に強化可能ランプの消灯を表現
* CancelInvoke()でInvokeの処理を取り消す

 **Update()**

* **「if (Ramp_on.activeSelf)」**の処理 ...  Ramp_on がアクティブな時(強化可能ランプ点灯時)の処理
    * Invoke("関数名", 時間)について ... 「時間」後に「関数」を呼び出す<br>
     → 詳しくは[ **こちら** ](https://www.sejuku.net/blog/83762)
        * "ActiveReverse" ... 関数ActiveReverse()を呼び出す
        * ReinforceableTime ... 強化可能時間経過後に、関数ActiveReverse()を呼び出す<br>
         → 強化可能ランプ点灯時から強化可能時間が経過すると、強化可能ランプが消灯

### 3.3.3. 「オペランダムへの反応」
#### 3.3.3.1. オペランダムを設置する (3.2.2.1.と同じ内容です)
　オペランダムを作成します。まずは、画面上にオペランダムっぽいものを作成します。「[Hierarchy] >> [Create] >> [3D Object] >> [ *Cube* ]」の順に選択してください。画面上にオペランダムを設置したら、弁別刺激同様、名前(Operandum1)と色( *Sd1_on* と同じ色)をつけます。場所は弁別刺激と異なる場所の方が良いです(例えば、X:0, Y;-.5, Z:0)。

#### 3.3.3.2. オペランダムのScriptに書き込む
　Script に書き込む内容ですが、先に内容を示してから解説を行います。

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

public class Operandum1_Script : MonoBehaviour
{
    float time;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;


    void ResetTime()
    {
        time = 0;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();
    }


    void Update()
    {
        time += Time.deltaTime;

        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Ramp_off.SetActive(false);
                Ramp_on.SetActive(true);
                Invoke("ResetTime", Ramp_Script.ReinforceableTime);
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
            Ramp_off.SetActive(true);
            Ramp_on.SetActive(false);
        }
    }
}
```
　このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update() → ResetTime()となります。解説は、変数の宣言 → Start() → ResetTime() → Update()の順序で行います。

 **変数の宣言**

* float time; ... float型の変数 time を宣言
* public GameObject Sd1_off; ... public な GameObject として Sd1_off を宣言<br>
 → Editor上では *Sd1_off* を入れてください
* public GameObject Sd1_on; ... public な GameObject として Sd1_on を宣言<br>
 → Editor上では *Sd1_on* を入れてください
* public GameObject Ramp_off; ... public な GameObject として Ramp_off を宣言<br>
 → Editor上では *Ramp_off* を入れてください
* public GameObject Ramp_on; ... public な GameObject として Ramp_on を宣言<br>
 → Editor上では *Ramp_on* を入れてください
* public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言<br>
 → Editor上では *Operandum1* に反応したときに鳴るSEを入れてください
* AudioSource audioSource; ... Audio Source として audioSource を宣言<br>
 → 詳しくは[ **こちら** ](https://www.sejuku.net/blog/83535)
* GameObject Ramp; ... GameObject として Ramp を宣言<br>
 → Editor上では得点が上昇ときに鳴るSEを入れてください
* Ramp_Script Ramp_Script; ... Ramp_Script として Ramp_Script; を宣言<br>

 **Start()**

* audioSource = GetComponent<AudioSource>(); ... Inspectorタブの Audio Sourse を取得
* 他のScriptで使っている変数を使用する処理
    * Ramp = GameObject.Find("Ramp"); ... オブジェクト「*Ramp*」を取得
    * Ramp_Script = Ramp.GetComponent<Ramp_Script>(); ... オブジェクト「*Ramp*」で使用しているScript「Ramp_Script」を取得

 **ResetTime()**

* time を0にリセット

 **Update()**

* time += Time.deltaTime; ... 制限時間をカウントアップ形式で作成(制限時間の作成については[ **こちら** ](https://xr-hub.com/archives/14465)、時間全般に関しては[ **こちら** ](http://kimama-up.net/unity-functions-time/))
 → UnityのUpdate()で使用する時間は、フレームレートが関係するため少々ややこしいため、データ出力や保存方法と一緒に別の記事で詳細を書こうと思います。

* **「if (Sd1_on.activeSelf)」**の処理 ...  Sd1_on がアクティブな時(弁別刺激点灯時)の処理
    * **「if (Input.GetKeyDown(KeyCode.F))」**の処理 ... キーボードのFキーが押されたときの処理
        * 効果音( operandum1SE )が鳴る
        * Ramp_off を非アクティブ化、Ramp_on をアクティブ化<br>
         → 疑似的に強化可能ランプの点灯を表現
        * Invoke("関数名", 時間)について
            * "ResetTime" ... 関数ResetTime()を呼び出す
            * Ramp_Script.ReinforceableTime ... *Ramp* で設定した強化可能時間経過後に、関数ResetTime()を呼び出す<br>
         → 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセット
* **「if (Sd1_off.activeSelf)」**の処理 ...  Sd1_off がアクティブな時(弁別刺激消灯時)の処理
    * time を0にリセット
    * Ramp_off をアクティブ化、Ramp_on を非アクティブ化<br>
     → 疑似的に強化可能ランプの消灯を表現<br>
     → 「if (Sd1_on.activeSelf)」の処理だけでは、弁別刺激消灯時に強化可能ランプも消灯していない場合があるため、弁別刺激消灯時に強制的に強化可能ランプも消灯させています

#### 3.3.3.3. 効果音を鳴らす (3.2.2.3. と同じ内容です)
　効果音を鳴らすには、Scriptを書くだけではなく、Audio Sourse が必要になります。「[Hierarchy] >> [ *Operandum1* をクリック] >> [Inspector] >> [Add Component] >> [Audio Sourse]」の順に選択してください。 Inspectorタブ 上に Audio Sourse が出現しますが、何もいじる必要はないです。後は、上記にある通り、Editor上で *Operandum1* の Operandum1SE の中に *Operandum1* に反応したときに鳴るSEを入れると効果音が鳴るようになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/d49d1f17-1e85-ddca-d04a-5321e6d9e058.png" width=30%>

### 3.3.4. 「強化オペランダムへの反応」
### 3.3.4.1. 強化可能ランプ( *Ramp_on* )のScriptに書き込む
　Script に書き込む内容ですが、先に内容を示してから解説を行います。ちなみに、「強化オペランダムへの反応」のScriptは、「*Comsummatory*」ではなく**「*Ramp_on*」に書き込む**ので注意してください。

```C#:Ramp_on
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Ramp_on_Script : MonoBehaviour
{
    int Point = 1;
    bool first = true;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;


    void OnEnable()
    {
        first = true;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }


    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Return))
        {
            if (first == true)
            {
                audioSource.PlayOneShot(PointSE);
                CountText.text = "Point : " + Point.ToString();
                Point += 1;
                first = false;
            }
        }
    }
}
```
　このScriptの流れをざっくり書くと、変数の宣言 → OnEnable() → Start() → Update() となります。解説もこの順序で行います。

 **変数の宣言

* int Point; ... int型の変数 Point を1に宣言
* bool first = true; … bool型の変数 first が true であることを宣言
* public Text CountText; ... public な Text として CountText を宣言<br>
 → Editor上では *Point* を入れてください
* public AudioClip PointSE; ... public な AudioClip として PointSE を宣言<br>
 → Editor上では得点が上昇ときに鳴るSEを入れてください
* AudioSource audioSource; ... Audio Source として audioSource を宣言<br>
 → 詳しくは[ **こちら** ](https://www.sejuku.net/blog/83535)

 **OnEnable()**

* first を true にする<br>
 → オブジェクトが有効になったタイミングで first が true になる(詳しくは[ **こちら** ](https://gametukurikata.com/basic/enabledisabledestroy))

 **Start()**

* audioSource = GetComponent<AudioSource>(); ... Inspectorタブの Audio Sourse を取得

 **Update()**

* **「if (Input.GetKeyDown(KeyCode.Return))」**の処理 ...  キーボードのEnterキー(Returnキー)が押されたときの処理
    * **「if (first == true)」**の処理 ... 強化に関する処理
        * 効果音( PointSE )が鳴る
        * 得点が1点上昇( Point += 1; )
        * first が false になる<br>
         → **1つ分だけ強化子が得られる(得点が1点上昇)** <br>
* CountText.textについて  ... テキストの表示を入れ替える処理
    * "Point : " ... 得点(数値)以外は *Point* のテキスト部分と同様にする
    * Point.ToString() ... 得点(数値)をstr型にすることで"Point : "と組み合わせられる

　Update()中の処理で first が false になっても、*Ramp_on* がアクティブである限り、OnEnable()が起動します。そのため、 first が false になった瞬間 first が true になります。たとえ *Ramp* が消灯しても、弁別刺激点灯時にFキーが押されると *Ramp* が点灯して、Enterキーを押すことでまた得点が1点上昇できるようなコードになっています。
　今回は、強化可能ランプ点灯中に強化オペランダムへ反応すると、1回だけ強化子が得られる(得点が1点上昇)するようなコードにしました。しかし、何回でも強化子が得られる状態にする場合は、下記のように書いてください。

```C#:Ramp_on
void Update()
{
    if (Input.GetKeyDown(KeyCode.Return))
    {
        audioSource.PlayOneShot(PointSE);
        CountText.text = "Point : " + Point.ToString();
        Point += 1;
    }
}
```

### 3.3.5. 「課題の終了」
　フローチャートには書いてませんが、実験場面では、時間が来たら課題が終了するようにしないといけません。というわけで、課題が終了するためのコーディングをしていきます。

#### 3.3.5.1. カラのオブジェクトを設置する
　「課題の終了」が実験課題中にみえる必要はないので、カラのオブジェクトを設置します。「[Hierarchy] >> [Create] >> [Create empty]」の順に選択してください。名前は**「TaskFinish」**にします。

#### 3.3.5.2. カラのオブジェクト( *TaskFinish* )のScriptに書き込む
　Script に書き込む内容ですが、先に内容を示してから解説を行います。

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

public class TaskFinish_Script : MonoBehaviour
{
    float TaskFinishTime;
    public float TaskFinishTime_hr;
    public float TaskFinishTime_m;
    public float TaskFinishTime_sec;

    void Start()
    {
        StartCoroutine("TaskFinish");
    }


    IEnumerator TaskFinish()
    {
        TaskFinishTime = (TaskFinishTime_hr * 3600) + (TaskFinishTime_m * 60) + TaskFinishTime_sec;

        yield return new WaitForSeconds(TaskFinishTime);

        // Applicationを強制終了
        Application.Quit();

        // Editorを強制終了
        UnityEditor.EditorApplication.isPlaying = false;
    }
}
```
　このScriptの流れをざっくり書くと、変数の宣言 → Start() → TaskFinish()となります。解説もこの順序で行います。

 **変数の宣言**

* float TaskFinishTime; … float型の変数 TaskFinishTime を宣言
* public float TaskFinishTime_hr; ... public なfloat型の変数 TaskFinishTime_hr を宣言<br>
→ Editor上では実験終了時間のうち「時間」を入れてください
* public float TaskFinishTime_m; ... public なfloat型の変数 TaskFinishTime_m を宣言<br>
→ Editor上では実験終了時間のうち「分」を入れてください
* public float TaskFinishTime_sec; ... public なfloat型の変数 TaskFinishTime_sec を宣言<br>
→ Editor上では実験終了時間のうち「秒」を入れてください

 **Start()**

* StartCoroutine("TaskFinish"); ... コルーチンがゲーム開始時に読み込まれる
        
 **IEnumerator TaskFinish()**

* TaskFinishTime ... 実験終了時間(hr、m、secを足し合わせたもの)
* TaskFinishTimeが来たらゲームが強制終了する

**課題の終了(Escキー)**
　キーボードの何かしらのキーを押してから終了する場合は、下記のように書いてください。

```C#:TaskFinish
float time

void Update()
{
    time = Time.time;
    TaskFinishTime = (TaskFinishTime_hr * 3600) + (TaskFinishTime_m * 60) + TaskFinishTime_sec;

    if (time >= TaskFinishTime)
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            // Applicationを強制終了
            Application.Quit();
 
            // Editorを強制終了
            UnityEditor.EditorApplication.isPlaying = false;
        }
    }
}
```

**課題の終了(得点数)**
　特定の得点に到達したら終了する場合は、下記のように書いてください。下の Script は、「強化オペランダムへの反応」→「得点上昇」の場合となっています。

```C#:TaskFinish
public int FinishPoint
GameObject Ramp_on;
Ramp_on_Script Ramp_on_Script;


void Start()
{
    Ramp_on = GameObject.Find("Ramp_on");
    Ramp_on_Script = Ramp.GetComponent<Ramp_on_Script>();
}


void Update()
{
    if (Ramp_on_Script.Point == FinishPoint)
    {
        // Applicationを強制終了
        Application.Quit();
 
        // Editorを強制終了
        UnityEditor.EditorApplication.isPlaying = false;
    }
}
```



# 4. 完成図
　これで、強化スケジュール課題の土台が完成しました。下図のようになっているかと思います。実際の課題ではGemeタブのような並びではなく、“それっぽい”配置にしてください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555056/34593387-ae76-37a1-3cd0-b88d944fd8a7.png" width=75%>
　さて、Unityで課題は作成できましたが、データをどこに出力して保存すればよいのかについてはここでは触れていません。データの出力や保存については、また別の記事で書きたいと思います。
　最後に、作成した課題をアプリケーション化(ビルド)する方法は、[ **こちら** ](http://www.monobitengine.com/doc/mun/contents/Platform/Build_Windows.htm)を参照してください。おそらく、「UnityEditor.EditorApplication.isPlaying = false;」が有効になっているとビルドできないので、「// UnityEditor.EditorApplication.isPlaying = false;」のように無効にしておいた方が良いと思います。

# 5. 最後に
　準備編として、強化スケジュール課題をUnityで作成する際に共通している手順をコーディングしてみましたが、いかかだったでしょうか。思いのほか長くなってしまったため、分かりづらくなってしまったかもしれません。また、コードや用語等で間違っている点があれば、ご指摘いただけると幸いです。
　次回から個々の強化スケジュールを作成していきます。基本的に、*Operandum1* の内容を編集するだけなので、おそらく今回ほど長くはならないです。

# 参考URL
・今日からはじめるUnity
https://qiita.com/nmxi/items/7950fb12ef925efa276d

・【Unity2D】Unityで2Dミニゲームを作るチュートリアル（第１回）
https://qiita.com/2dgames_jp/items/11bb76167fb44bb5af5f

・Unity初心者が知っておくと少しだけ幸せになれる、シーンにオブジェクトを配置する時に使える18のTips+10
http://tsubakit1.hateblo.jp/entry/2015/04/21/031048

・[超初心者向け]やっと納得、Unityを初めて触ると出てくるC#の何だあれの答え
https://qiita.com/JunShimura/items/3c2e23bb77cc9085bfda

・privateとpublicの違いについて図付きで説明
http://gengoshori.hatenablog.com/entry/2018/01/07/122654

・【Unity】関数一覧『コルーチン』
http://kimama-up.net/unity-functions-coroutine/

・【Unity】はじめてのコルーチン!これさえ読めば基礎はカンペキ
https://www.sejuku.net/blog/83712

・【Unity】C#の基本構文『while』
http://kimama-up.net/unity-while/

・【Unity入門】1分でTextを表示しよう!スコアの表示まで簡単解説!
https://www.sejuku.net/blog/55029

・【Unity入門】効果音(SE)の鳴らし方!複数管理する場合についても!
https://www.sejuku.net/blog/83535

・【Unity】Invokeの使い方!実行タイミングを自在に操ろう
https://www.sejuku.net/blog/83762

・【Unity 入門】Time.deltaTimeを使って制限時間を設定する｜カウントアップ・カウントダウン
https://xr-hub.com/archives/14465

・【Unity】関数一覧『時間全般』
http://kimama-up.net/unity-functions-time/

・UnityのOnEnable、OnDisable、OnDestroyメソッドについて
https://gametukurikata.com/basic/enabledisabledestroy

・【Unity】関数一覧『アプリケーション全般』
http://kimama-up.net/unity-functions-application/