1. はじめに
準備編 で作成した Operandum1の Script を編集して、時間スケジュールを作成します。基本的なことは準備編で一通り解説しているので、本記事では Operandum1の Script の解説のみとなります。また、今回も「オペランダムへの反応」→「得点上昇」と「強化オペランダムへの反応」→「得点上昇」の2つの場合を考慮して解説したいと思います。
2. 時間スケジュールとは
時間スケジュール(Time schedule)とは、1つ前の強化子の提示から一定時間経過後に強化子が随伴する強化スケジュールです(坂上・井上, 2018)。一定時間 ( スケジュール値 ) が固定である場合は固定時間スケジュール ( Fixed Time schedule; FT ) 、平均するとスケジュール値になる場合は変動時間スケジュール ( Variable Time schedule; VT ) と呼びます。
Unityで作成する場合は、スケジュール値 ( x sec ) に到達すると強化子が提示される ( あるいは Rampが点灯する ) ようにすれば良いです。時間スケジュールのイメージ図を下に示します。下図の左は「オペランダムへの反応」→「得点上昇」を、下図の右は「強化オペランダムへの反応」→「得点上昇」を示しています。FTであれば x secが常に一定となり、VTであれば x secが毎回変動します。
3. FT
3.1. 「オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1の Script にはなかったコードです。
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingUnityEngine.UI;publicclassOperandum1_Script:MonoBehaviour{intPoint=1;publicGameObjectSd1_off;publicGameObjectSd1_on;publicTextCountText;publicAudioClipPointSE;AudioSourceaudioSource;//Newfloattime;publicfloatFTTime;publicAudioClipOperandum1SE;voidStart(){audioSource=GetComponent<AudioSource>();}// NewvoidUpdate(){if(Sd1_on.activeSelf){time+=Time.deltaTime;if(Input.GetKeyDown(KeyCode.F)){audioSource.PlayOneShot(Operandum1SE);}if(time>=FTTime){audioSource.PlayOneShot(PointSE);CountText.text="Point : "+Point.ToString();Point+=1;time=0;}}if(Sd1_off.activeSelf){time=0;}}}このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。
変数の宣言
- float time; ... float型の変数 time を宣言
- public float FTTime; ... public な float型の変数 FTTime を宣言
→ Editor上ではFTのスケジュール値を入れてください - 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 >= FTTime)」の処理 ... timeがFTのスケジュール値に到達したときの処理
* 効果音( PointSE )が鳴る
* 得点が1点上昇( Point += 1; )
* time を0にリセット
→ 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
→ 弁別刺激が点灯している間、FTが走り続ける
3.2. 「強化オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1の Script にはなかったコードです。
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassOperandum1_Script:MonoBehaviour{publicGameObjectSd1_off;publicGameObjectSd1_on;publicGameObjectRamp_off;publicGameObjectRamp_on;publicAudioClipOperandum1SE;AudioSourceaudioSource;GameObjectRamp;Ramp_ScriptRamp_Script;//Newfloattime;publicfloatFTTime;voidResetTime(){time=0;CancelInvoke();}voidStart(){audioSource=GetComponent<AudioSource>();Ramp=GameObject.Find("Ramp");Ramp_Script=Ramp.GetComponent<Ramp_Script>();}voidUpdate(){// New_1if(Sd1_on.activeSelf){time+=Time.deltaTime;if(Input.GetKeyDown(KeyCode.F)){audioSource.PlayOneShot(Operandum1SE);}// New_2if(time>=FTTime){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()となります。解説は「// New」と書かれている箇所のみ行います。ResetTime()に、「CancelInvoke();」が追加されているので注意してください。CancelInvoke()については、こちらを参照してください。
変数の宣言
- float time; ... float型の変数 time を宣言
- public float FTTime; ... public な float型の変数 FTTime を宣言
→ Editor上ではFTのスケジュール値を入れてください
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 >= FTTime)」の処理 ... timeがFTのスケジュール値に到達したときの処理
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
→ 疑似的に強化可能ランプの点灯を表現 - Invoke("ResetTime", Ramp_Script.ReinforceableTime)
→ 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセット
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
- 「if (time >= FTTime)」の処理 ... timeがFTのスケジュール値に到達したときの処理
3. VT
3.1. Pythonで x secのリストを作成する
VTでは、FTとは異なり、x secが一定ではなく変動します。この変動した値をUnity上で作成しても良いのですが、先にPythonで x secのリストを作成してCsv形式で出力しておきます。その後、作成したCsv形式の x secのリストをUnityで読み込みます。
3.1.1. x secのリストを作成する関数
x secのリストを作成する関数は下記のとおりです。環境について、Pythonのバージョンは「Python 3.7.1」で、Jupyter Notebookを使用しています。
importnumpyasnpdefvariable(value,value_min,value_max,reinforcement):foriinrange(100**100):random_=np.random.randint(value_min,value_max,reinforcement)ifrandom_.mean()==value:variable=random_breakreturnvariable- forの中の処理
- スケジュール値の範囲 ( value_min から value_max まで ) の乱数(一様分布)を reinforcement 分作成して1次元の行列にする
- 乱数生成についてはこちらを参照してください
- ifの中の処理
- random_ の平均値がスケジュール値と同じになった場合、variable に random_ を格納
- variable に random_ を格納したらforループを中断
→ スケジュール値の範囲がよほど無茶なものでない限り、100の100乗回のforループは行われない
- スケジュール値の範囲 ( value_min から value_max まで ) の乱数(一様分布)を reinforcement 分作成して1次元の行列にする
3.1.2. x secのリストをCsvファイルに出力
# 「_」には、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というファイルを作成して、その中に入れます。
3.2. 「オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1の Script にはなかったコードです。
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingUnityEngine.UI;usingSystem.IO;publicclassOperandum1_Script:MonoBehaviour{intPoint=1;publicGameObjectSd1_off;publicGameObjectSd1_on;publicTextCountText;publicAudioClipPointSE;AudioSourceaudioSource;//Newboolfirst=true;floattime;inti;intCsvCounter=0;privateList<string>CsvVariable=newList<string>();publicAudioClipOperandum1SE;voidStart(){audioSource=GetComponent<AudioSource>();//New_1TextAssetCsv=Resources.Load("Variable")asTextAsset;StringReaderreader=newStringReader(Csv.text);while(reader.Peek()!=-1){stringline=reader.ReadLine();string[]values=line.Split(',');// New_2for(i=0;i<values.Length;i++){CsvVariable.Add(values[i]);}}}voidUpdate(){// New_3if(Sd1_on.activeSelf){time+=Time.deltaTime;if(Input.GetKeyDown(KeyCode.F)){audioSource.PlayOneShot(Operandum1SE);}// New_4if(first){if(time>=int.Parse(CsvVariable[CsvCounter])){audioSource.PlayOneShot(PointSE);CountText.text="Point : "+Point.ToString();Point+=1;CsvCounter+=1;time=0;}}}if(Sd1_off.activeSelf){time=0;}}}このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。5行目に「using System.IO;」が追加されているので注意してください。解説は「// New」と書かれている箇所のみ行います。
変数の宣言
- bool first = true; ... bool型の変数 first が true であることを宣言
- float time; ... float型の変数 time を宣言
- 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ループの書き方についてはこちらを参照してください
- 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
Update()
- New_3
- 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
- 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
- 効果音( Operandum1SE )が鳴る
- 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- New_4
- 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
- 「if (time >= int.Parse(CsvVariable[CsvCounter]))」の処理 ... timeがVTのスケジュール値に到達したときの処理
- 効果音( PointSE )が鳴る
- 得点が1点上昇( Point += 1; )
- CsvCounterが1つ増加
→ 取得するList内の要素の順番を1つずらす - time を0にリセット
→ 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
→ 弁別刺激が点灯している間、VTが走り続ける
- 「if (time >= int.Parse(CsvVariable[CsvCounter]))」の処理 ... timeがVTのスケジュール値に到達したときの処理
- 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
3.3. 「強化オペランダムへの反応」→「得点上昇」
Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1の Script にはなかったコードです。
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingSystem.IO;publicclassOperandum1_Script:MonoBehaviour{publicGameObjectSd1_off;publicGameObjectSd1_on;publicGameObjectRamp_off;publicGameObjectRamp_on;publicAudioClipOperandum1SE;AudioSourceaudioSource;GameObjectRamp;Ramp_ScriptRamp_Script;//Newboolfirst=true;floattime;inti;intCsvCounter=0;privateList<string>CsvVariable=newList<string>();voidResetTime(){time=0;first=true;}voidStart(){audioSource=GetComponent<AudioSource>();Ramp=GameObject.Find("Ramp");Ramp_Script=Ramp.GetComponent<Ramp_Script>();//New_1TextAssetCsv=Resources.Load("Variable")asTextAsset;StringReaderreader=newStringReader(Csv.text);while(reader.Peek()!=-1){stringline=reader.ReadLine();string[]values=line.Split(',');// New_2for(i=0;i<values.Length;i++){CsvVariable.Add(values[i]);}}}voidUpdate(){// New_3if(Sd1_on.activeSelf){time+=Time.deltaTime;if(Input.GetKeyDown(KeyCode.F)){audioSource.PlayOneShot(Operandum1SE);}// New_4if(first){if(time>=int.Parse(CsvVariable[CsvCounter])){Ramp_off.SetActive(false);Ramp_on.SetActive(true);CsvCounter+=1;first=false;Invoke("ResetTime",Ramp_Script.ReinforceableTime);}}if(Sd1_off.activeSelf){time=0;Ramp_off.SetActive(true);Ramp_on.SetActive(false);}}}} このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update() → ResetTime()となります。5行目に「using System.IO;」が追加されているので注意してください。また、ResetTime()では「time = 0;」だけではなく「first = true;」や「CancelInvoke();」も書かれてあるので注意してください。CancelInvoke()については、こちらを参照してください。
解説は「// New」と書かれている箇所のみ行います。
変数の宣言
- bool first = true; ... bool型の変数 first が true であることを宣言
- float time; ... float型の変数 time を宣言
- 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ループの書き方についてはこちらを参照してください
- 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
Update()
- New_3
- 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
- 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
- 効果音( Operandum1SE )が鳴る
- 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
- New_4
- 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
- 「if (time >= int.Parse(CsvVariable[CsvCounter]))」の処理 ... timeがVTのスケジュール値に到達したときの処理
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
→ 疑似的に強化可能ランプの点灯を表現 - CsvCounterが1つ増加
→ 取得するList内の要素の順番を1つずらす - first を false にする → 取得するList内の要素の順番が2つ以上ずれないようにする
- Invoke("ResetTime", Ramp_Script.ReinforceableTime)
→ 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセットして、first を true に戻す
- Ramp_off を非アクティブ化、Ramp_on をアクティブ化
- 「if (time >= int.Parse(CsvVariable[CsvCounter]))」の処理 ... timeがVTのスケジュール値に到達したときの処理
- 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
4. 最後に
「時間スケジュール(time schedule)」の中の、「固定時間スケジュール(Fixed Time schedule)」と「変動時間スケジュール(Variable Time 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/
・【Unity】Invokeの使い方!実行タイミングを自在に操ろう
https://www.sejuku.net/blog/83762
引用文献
坂上 貴之・井上 雅彦 (2018). 行動分析学──行動の科学的理解をめざして── 有斐閣