1. はじめに
準備編 で作成した Operandum1 の Script を編集して、時間スケジュールを作成します。基本的なことは準備編で一通り解説しているので、本記事では Operandum1 の Script の解説のみとなります。また、今回も「オペランダムへの反応」→「得点上昇」と「強化オペランダムへの反応」→「得点上昇」の2つの場合を考慮して解説したいと思います。
2. 反応率分化強化スケジュールとは
反応率分化強化スケジュールは、反応形成において反応を変容する手続きです。本記事では反応間時間 ( IRT ) を分化強化の対象とする反応型の分化強化スケジュールとして、低反応率分化強化スケジュール ( Differential reinforcement of low rate; DRL )と高反応率分化強化スケジュール ( Differential reinforcement of high rate; DRH )の2つを解説します。
2.1. DRLとは
DRLとは、「IRTがある値を超えた後の最初の反応に強化子が随伴する(坂上・井上, 2018, pp175)」強化スケジュールです。もしその値以内で反応した場合は、再びその時点からIRTが計測されます。無反応での一定時間の経過が強化の必要条件となっています。
Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、スケジュール値 ( x sec ) を超過してから反応しなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。また、スケジュール値 ( x sec ) 経過中に反応してしまうと時間を0にリセットするよう設定します。
2.2. DRHとは
DRHとは、DRLの逆にあたる強化スケジュールで(坂上・井上, 2018)、スケジュール値よりも短い時間での反応に強化子が随伴する強化スケジュールです(Ono & Iwabuchi, 1997)。もしスケジュール値以上で反応した場合は、再びその時点からIRTが計測されます。
Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、反応がスケジュール値 ( x sec ) 未満でなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。また、スケジュール値 ( x sec ) 以降に反応してしまうと時間を0にリセットするよう設定します。
3. DRL
3.1. 「オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。
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
float time;
public float DRL;
public AudioClip Operandum1SE;
void Start()
{
audioSource = GetComponent<AudioSource>();
}
// New
void Update()
{
if (Sd1_on.activeSelf)
{
time += Time.deltaTime;
if (Input.GetKeyDown(KeyCode.F))
{
audioSource.PlayOneShot(Operandum1SE);
if (time > DRL)
{
audioSource.PlayOneShot(PointSE);
CountText.text = "Point : " + Point.ToString();
Point += 1;
time = 0;
}
else
{
time = 0;
}
}
}
if (Sd1_off.activeSelf)
{
time = 0;
}
}
}
このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。
変数の宣言
- float time; ... float型の変数 time を宣言
- public float DRL; ... public な float型の変数 DRL を宣言
→ Editor上ではDRLのスケジュール値を入れてください - public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
→ Editor上では Operandum1 に反応したときに鳴るSEを入れてください
Update()
- **「if (Sd1_on.activeSelf)」**の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
- **「if (Input.GetKeyDown(KeyCode.F))」**の処理 ... キーボードのFキーが押されたときの処理
- 効果音( Operandum1SE )が鳴る
- **「if (time > DRL)」**の処理 ... timeがDRLのスケジュール値を超過したときの処理
- 効果音( PointSE )が鳴る
- 得点が1点上昇( Point += 1; )
- time を0にリセット
→ 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
→ 弁別刺激が点灯している間、DRLが走り続ける
- **「else」**の処理 ... timeがDRLのスケジュール値を超過したとき以外の処理
- time を0にリセット
→ オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる
- time を0にリセット
3.2. 「強化オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。
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
float time;
public float DRL;
void ResetTime()
{
time = 0;
}
void Start()
{
audioSource = GetComponent<AudioSource>();
Ramp = GameObject.Find("Ramp");
Ramp_Script = Ramp.GetComponent<Ramp_Script>();
}
void Update()
{
// New_1
if (Sd1_on.activeSelf)
{
time += Time.deltaTime;
if (Input.GetKeyDown(KeyCode.F))
{
audioSource.PlayOneShot(Operandum1SE);
// New_2
if (time > DRL)
{
Ramp_off.SetActive(false);
Ramp_on.SetActive(true);
Invoke("ResetTime", Ramp_Script.ReinforceableTime);
}
else
{
time = 0;
}
}
}
if (Sd1_off.activeSelf)
{
time = 0;
Ramp_off.SetActive(true);
Ramp_on.SetActive(false);
}
}
}
このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。
変数の宣言
- float time; ... float型の変数 time を宣言
- public float DRL; ... public な float型の変数 DRL を宣言
→ Editor上ではDRLのスケジュール値を入れてください
Update()
- New_1
- **「if (Sd1_on.activeSelf)」**の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
- **「if (Input.GetKeyDown(KeyCode.F))」**の処理 ... キーボードのFキーが押されたときの処理
- 効果音( Operandum1SE )が鳴る
- **「if (Sd1_on.activeSelf)」**の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- New_2
- **「if (time > DRL)」**の処理 ... timeがDRLのスケジュール値を超過したときの処理
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
→ 疑似的に強化可能ランプの点灯を表現 - Invoke("ResetTime", Ramp_Script.ReinforceableTime)
→ 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセット - **「else」**の処理 ... timeがDRLのスケジュール値を超過したとき以外の処理
- time を0にリセット
→ オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる
- time を0にリセット
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
- **「if (time > DRL)」**の処理 ... timeがDRLのスケジュール値を超過したときの処理
4. DRH
4.1. 「オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。
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
float time;
public float DRH;
public AudioClip Operandum1SE;
void Start()
{
audioSource = GetComponent<AudioSource>();
}
// New
void Update()
{
if (Sd1_on.activeSelf)
{
time += Time.deltaTime;
if (Input.GetKeyDown(KeyCode.F))
{
audioSource.PlayOneShot(Operandum1SE);
if (time < DRH)
{
audioSource.PlayOneShot(PointSE);
CountText.text = "Point : " + Point.ToString();
Point += 1;
time = 0;
}
else
{
time = 0;
}
}
}
if (Sd1_off.activeSelf)
{
time = 0;
}
}
}
このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。
変数の宣言
- float time; ... float型の変数 time を宣言
- public float DRH; ... public な float型の変数 DRH を宣言
→ Editor上ではDRHのスケジュール値を入れてください - public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
→ Editor上では Operandum1 に反応したときに鳴るSEを入れてください
Update()
- **「if (Sd1_on.activeSelf)」**の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
- **「if (Input.GetKeyDown(KeyCode.F))」**の処理 ... キーボードのFキーが押されたときの処理
- 効果音( Operandum1SE )が鳴る
- **「if (time < DRH)」**の処理 ... timeがDRHのスケジュール値未満だったときの処理
- 効果音( PointSE )が鳴る
- 得点が1点上昇( Point += 1; )
- time を0にリセット
→ 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
→ 弁別刺激が点灯している間、DRHが走り続ける
- **「else」**の処理 ... timeがDRHのスケジュール値未満だったとき以外の処理
- time を0にリセット
→ オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる
- time を0にリセット
4.2. 「強化オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。
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
float time;
public float DRH;
void ResetTime()
{
time = 0;
}
void Start()
{
audioSource = GetComponent<AudioSource>();
Ramp = GameObject.Find("Ramp");
Ramp_Script = Ramp.GetComponent<Ramp_Script>();
}
void Update()
{
// New_1
if (Sd1_on.activeSelf)
{
time += Time.deltaTime;
if (Input.GetKeyDown(KeyCode.F))
{
audioSource.PlayOneShot(Operandum1SE);
// New_2
if (time < DRH)
{
Ramp_off.SetActive(false);
Ramp_on.SetActive(true);
Invoke("ResetTime", Ramp_Script.ReinforceableTime);
}
else
{
time = 0;
}
}
}
if (Sd1_off.activeSelf)
{
time = 0;
Ramp_off.SetActive(true);
Ramp_on.SetActive(false);
}
}
}
このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。
変数の宣言
- float time; ... float型の変数 time を宣言
- public float DRH; ... public な float型の変数 DRH を宣言
→ Editor上ではDRHのスケジュール値を入れてください
Update()
- New_1
- **「if (Sd1_on.activeSelf)」**の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
- **「if (Input.GetKeyDown(KeyCode.F))」**の処理 ... キーボードのFキーが押されたときの処理
- 効果音( Operandum1SE )が鳴る
- **「if (Sd1_on.activeSelf)」**の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- New_2
- **「if (time < DRH)」**の処理 ... timeがDRHのスケジュール値未満だったときの処理
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
→ 疑似的に強化可能ランプの点灯を表現 - Invoke("ResetTime", Ramp_Script.ReinforceableTime)
→ 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセット - **「else」**の処理 ... timeがDRHのスケジュール値未満だったとき以外の処理
- time を0にリセット
→ オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる
- time を0にリセット
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
- **「if (time < DRH)」**の処理 ... timeがDRHのスケジュール値未満だったときの処理
4.3. x sec 間に n 回
「2.2. DRHとは」では、DRHはスケジュール値よりも短い時間での反応に強化子が随伴する強化スケジュールと書きましたが、x sec 間に n 回反応すると強化子が随伴する場合もあるようです(e.g., 小野, 1994)。
Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、スケジュール値 ( x sec ) 未満かつ n 回反応しなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。また、スケジュール値 ( x sec ) 以降に反応したり n 回に到達していない場合は、時間を0にリセットするよう設定します。
4.3.1. 「オペランダムへの反応」→「得点上昇」
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
float time;
int Counter = 0;
public float DRHTime;
public float DRHTimes;
public AudioClip Operandum1SE;
void Start()
{
audioSource = GetComponent<AudioSource>();
}
// New
void Update()
{
if (Sd1_on.activeSelf)
{
time += Time.deltaTime;
if (Input.GetKeyDown(KeyCode.F))
{
audioSource.PlayOneShot(Operandum1SE);
if (time < DRHTime)
{
if (Counter == DRHTimes)
{
audioSource.PlayOneShot(PointSE);
CountText.text = "Point : " + Point.ToString();
Point += 1;
time = 0;
Counter = 0;
}
}
else
{
time = 0;
Counter = 0;
}
}
}
if (Sd1_off.activeSelf)
{
time = 0;
Counter = 0;
}
}
}
4.3.2. 「強化オペランダムへの反応」→「得点上昇」
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
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
float time;
public float DRHTime;
public float DRHTimes;
int Counter = 0;
void ResetTime()
{
time = 0;
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)
{
time += Time.deltaTime;
Debug.Log(time);
if (Input.GetKeyDown(KeyCode.X))
{
audioSource.PlayOneShot(Operandum1SE);
Counter += 1;
// New_2
if (time < DRHTime)
{
if (Counter == DRHTimes)
{
Ramp_off.SetActive(false);
Ramp_on.SetActive(true);
Invoke("ResetTime", Ramp_Script.ReinforceableTime);
}
}
else
{
time = 0;
Counter = 0;
}
}
}
if (Sd1_off.activeSelf)
{
time = 0;
Counter = 0;
Ramp_off.SetActive(true);
Ramp_on.SetActive(false);
}
}
}
5. 最後に
「反応率分化強化スケジュール」の中の、「低反応率分化強化(Differential Reinforcement of Low rates)」と「低反応率分化強化(Differential Reinforcement of High rates)」を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/
引用文献
小野 浩一 (1994). 迷信行動と言語. 駒澤社会学研究, 26, 59-84.
Ono K, & Iwabuchi K. (1997). Effects of histories of differential reinforcement of response rate on variable-interval responding. Journal of the Experimental Analysis of Behavior. 67(3), 311–322.
坂上 貴之・井上 雅彦 (2018). 行動分析学──行動の科学的理解をめざして── 有斐閣