LoginSignup
0
0

More than 3 years have passed since last update.

Unityで行動分析 ~その2:比率スケジュール編~

Last updated at Posted at 2021-02-26

1. はじめに

  準備編 で作成した Operandum1 の Script を編集して、比率スケジュールを作成します。基本的なことは準備編で一通り解説しているので、本記事では Operandum1 の Script の解説のみとなります。また、今回も「オペランダムへの反応」→「得点上昇」と「強化オペランダムへの反応」→「得点上昇」の2つの場合を考慮して解説したいと思います。

2. 比率スケジュールとは

 比率スケジュール(Ratio schedule)とは、「強化子提示後に一定回数自発された反応に強化子が随伴する(坂上・井上, 2018, pp.173)」強化スケジュールです。一定回数 ( スケジュール値 ) が固定である場合は固定比率スケジュール ( Fixed Ratio schedule; FR ) 、平均するとスケジュール値になる場合は変動比率スケジュール ( Variable Ratio schedule; VR ) と呼びます。
 Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、スケジュール値 ( x times ) に到達しなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。比率スケジュールのイメージ図を下に示します。下図の左は「オペランダムへの反応」→「得点上昇」を、下図の右は「強化オペランダムへの反応」→「得点上昇」を示しています。FRであれば x times が常に一定となり、VRであれば x times が毎回変動します。

3. FR

3.1. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

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;

    //New
    int Counter = 0;
    public int FRTimes;
    public AudioClip Operandum1SE;


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


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

        if (Sd1_off.activeSelf)
        {
            Counter = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • int Counter; ... int型の変数 Counter を宣言
  • public int FRTimes; ... public な float型の変数 FRTimes を宣言
    → Editor上ではFRのスケジュール値を入れてください
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Update()

  • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
    • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
      • 効果音( Operandum1SE )が鳴る
      • Counterのカウントが1増加
      • 「if (Counter == FR)」の処理 ... CounterがFRのスケジュール値に到達したときの処理
        • 効果音( PointSE )が鳴る
        • 得点が1点上昇( Point += 1; )
        • Counter を0にリセット
          → 得点が上昇すると回数がリセットされ、もう一度時間を計測しはじめる
          → 弁別刺激が点灯している間、FRが走り続ける

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

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Operandum1_Script : MonoBehaviour
{
    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;

    //New
    int Counter = 0;
    public int FRTimes;

    void ResetTimes()
    {
        Counter = 0;
    }


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


    void Update()
    {
        // New_1
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Counter += 1;

                // New_2
                if (Counter == FRTimes)
                {
                    Ramp_off.SetActive(false);
                    Ramp_on.SetActive(true);
                    Invoke("ResetTimes", Ramp_Script.ReinforceableTime);
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            Counter = 0;
            Ramp_off.SetActive(true);
            Ramp_on.SetActive(false);
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • int Counter; ... int型の変数 Counter を宣言
  • public int FRTimes; ... public な float型の変数 FRTimes を宣言
    → Editor上ではFRのスケジュール値を入れてください

Update()

  • New_1
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
        • Counterのカウントが1増加
  • New_2
    • 「if (Counter == FR)」の処理 ... CounterがFRのスケジュール値に到達したときの処理
      • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
        → 疑似的に強化可能ランプの点灯を表現
      • Invoke("ResetTimes", Ramp_Script.ReinforceableTime)
        → 強化可能ランプ点灯時から強化可能時間が経過すると、Counter を0にリセット

4. VR

4.1. Pythonで x sec のリストを作成する

 VRでは、FRとは異なり、x times が一定ではなく変動します。この変動した値をUnity上で作成しても良いのですが、先にPythonで x times のリストを作成してCsv形式で出力しておきます。その後、作成したCsv形式の *x times * のリストをUnityで読み込みます。

4.1.1. x times のリストを作成する関数

  *x times * のリストを作成する関数は下記のとおりです。環境について、Pythonのバージョンは「Python 3.7.1」で、Jupyter Notebookを使用しています。

Python
import numpy as np

def variable(value, value_min, value_max, reinforcement):
    for i in range(100**100):
        random_ = np.random.randint(value_min, value_max, reinforcement)
        if random_.mean()==value:
            variable = random_
            break
    return variable
  • forの中の処理
    • スケジュール値の範囲 ( value_min から value_max まで ) の乱数(一様分布)を reinforcement 分作成して1次元の行列にする
      • 乱数生成については こちら を参照してください
    • ifの中の処理
      • random_ の平均値がスケジュール値と同じになった場合、variable に random_ を格納
      • variable に random_ を格納したらforループを中断
        → スケジュール値の範囲がよほど無茶なものでない限り、100の100乗回のforループは行われない

4.1.2. *x times * のリストをCsvファイルに出力

Python
# 「_」には、value, range_min, range_max, reinforcementの値を入れてください
value, range_min, range_max, reinforcement = _, _, _, _
variable = variable(value, range_min, range_max, reinforcement)

# 「/」の前にデータの出力先を入れてください
np.savetxt('/Variable.csv', variable, delimiter=',') 

 作成したCsvファイルは、Assetの中のResourcesというファイルを作成して、その中に入れます。

4.2. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;

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

    //New
    bool first = true;
    int Counter = 0;
    int i;
    int CsvCounter = 0;
    private List<string> CsvVariable = new List<string>();
    public AudioClip Operandum1SE;


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

        //New_1
        TextAsset Csv = Resources.Load("Variable") as TextAsset;
        StringReader reader = new StringReader(Csv.text);
        while (reader.Peek() != -1)
        {
            string line = reader.ReadLine();
            string[] values = line.Split(',');

            // New_2
            for (i = 0; i < values.Length; i++)
            {
                CsvVariable.Add(values[i]);
            }
        }
    }


    void Update()
    {
        // New_3
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Counter += 1;

                // New_4
                if (first)
                {
                    if (Counter == int.Parse(CsvVariable[CsvCounter]))
                    {
                        audioSource.PlayOneShot(PointSE);
                        CountText.text = "Point : " + Point.ToString();
                        Point += 1;
                        CsvCounter += 1;
                        Counter = 0;
                    }
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            Counter = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。5行目に「using System.IO;」が追加されているので注意してください。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • bool first = true; ... bool型の変数 first が true であることを宣言
  • int Counter; ... int型の変数 Counter を宣言
  • int i; ... int型の変数 i を宣言
  • int CsvCounter = 0; ... int型の変数 CsvCounter が 0 であることを宣言
  • private List CsvVariable = new List(); ... string型の List として CsvVariable を宣言
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Start()

  • New_1
    • CsvファイルをUnityに読み込ませる
    • こちらの記事 とやっていることは全く同じで、詳しい解説も載っているのでここでは割愛します
  • New_2
    • 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
      • C# のforループの書き方については こちら を参照してください

Update()

  • New_3
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
        • Counterのカウントが1増加
  • New_4
    • 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
      • 「if (Counter == int.Parse(CsvVariable[CsvCounter]))」の処理 ... CounterがVRのスケジュール値に到達したときの処理
        • 効果音( PointSE )が鳴る
        • 得点が1点上昇( Point += 1; )
        • CsvCounterが1つ増加
          → 取得するList内の要素の順番を1つずらす
        • Coutner を0にリセット
          → 得点が上昇すると回数がリセットされ、もう一度時間を計測しはじめる
          → 弁別刺激が点灯している間、VRが走り続ける

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

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class Operandum1_Script : MonoBehaviour
{
    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;

    //New
    bool first = true;
    int Counter = 0;
    int i;
    int CsvCounter = 0;
    private List<string> CsvVariable = new List<string>();


    void ResetTimes()
    {
        Counter = 0;
        first = true;
    }


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

        //New_1
        TextAsset Csv = Resources.Load("Variable") as TextAsset;
        StringReader reader = new StringReader(Csv.text);
        while (reader.Peek() != -1)
        {
            string line = reader.ReadLine();
            string[] values = line.Split(',');

            // New_2
            for (i = 0; i < values.Length; i++)
            {
                CsvVariable.Add(values[i]);
            }
        }
    }


    void Update()
    {
        // New_3
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Counter += 1;

                // New_4
                if (first)
                {
                    if (Counter == int.Parse(CsvVariable[CsvCounter]))
                    {
                        Ramp_off.SetActive(false);
                        Ramp_on.SetActive(true);
                        CsvCounter += 1;
                        first = false;
                        Invoke("ResetTimes", Ramp_Script.ReinforceableTime);
                    }
                }
            }

            if (Sd1_off.activeSelf)
            {
                Counter = 0;
                Ramp_off.SetActive(true);
                Ramp_on.SetActive(false);
            }
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update() → ResetTime()となります。5行目に「using System.IO;」が追加されているので注意してください。また、ResetTime()では「time = 0;」だけではなく「first = true;」も書かれてあるので注意してください。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • bool first = true; ... bool型の変数 first が true であることを宣言
  • int Counter; ... int型の変数 Counter を宣言
  • int i; ... int型の変数 i を宣言
  • int CsvCounter = 0; ... int型の変数 CsvCounter が 0 であることを宣言
  • private List CsvVariable = new List(); ... string型の List として CsvVariable を宣言

Start()

  • New_1
    • CsvファイルをUnityに読み込ませる
    • こちらの記事 とやっていることは全く同じで、詳しい解説も載っているのでここでは割愛します
  • New_2
    • 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
      • C# のforループの書き方については こちら を参照してください

Update()

  • New_3
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
        • Counterのカウントが1増加
  • New_4
    • 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
      • 「if (Counter == int.Parse(CsvVariable[CsvCounter]))」の処理 ... CounterがVRのスケジュール値に到達したときの処理
        • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
          → 疑似的に強化可能ランプの点灯を表現
        • CsvCounterが1つ増加
          → 取得するList内の要素の順番を1つずらす
        • first を false にする → 取得するList内の要素の順番が2つ以上ずれないようにする
        • Invoke("ResetTimes", Ramp_Script.ReinforceableTime)
          → 強化可能ランプ点灯時から強化可能時間が経過すると、Counter を0にリセットして、first を true に戻す

5. 最後に

 「比率スケジュール(Ratio schedule)」の中の、「固定比率スケジュール(Fixed Ratio schedule)」と「変動比率スケジュール(Variable Ratio schedule)」をUnityで作る方法の解説を行いました。コードや用語等で間違っている点があれば、ご指摘いただけると幸いです。

参考URL

・NumPy, randomで様々な種類の乱数の配列を生成
https://note.nkmk.me/python-numpy-random/

・Unity で CSV ファイルを読み込む方法
https://note.com/macgyverthink/n/n83943f3bad60

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

引用文献

坂上 貴之・井上 雅彦 (2018). 行動分析学──行動の科学的理解をめざして── 有斐閣

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0