├── .gitignore
├── 20220519_133814_P01_Experiment Result.csv
├── Images
├── Trial Manager Flow.png
├── Trial Mng Flow.JPG
├── csv format.png
└── prefab.JPG
├── README.md
├── Trial Manager Script
├── Script
│ ├── CsvFileCommon.cs
│ ├── CsvFileWriter.cs
│ ├── DataLogger.cs
│ ├── ExperimentManager.cs
│ ├── Timer.cs
│ ├── TrialData.cs
│ └── csvReader.cs
└── Trial_P01.csv
└── experimentManager.unitypackage
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
4 | #
5 | /[Ll]ibrary/
6 | /[Tt]emp/
7 | /[Oo]bj/
8 | /[Bb]uild/
9 | /[Bb]uilds/
10 | /[Ll]ogs/
11 | /[Mm]emoryCaptures/
12 |
13 | # Asset meta data should only be ignored when the corresponding asset is also ignored
14 | !/[Aa]ssets/**/*.meta
15 |
16 | # Uncomment this line if you wish to ignore the asset store tools plugin
17 | # /[Aa]ssets/AssetStoreTools*
18 |
19 | # Autogenerated Jetbrains Rider plugin
20 | [Aa]ssets/Plugins/Editor/JetBrains*
21 |
22 | # Visual Studio cache directory
23 | .vs/
24 |
25 | # Gradle cache directory
26 | .gradle/
27 |
28 | # Autogenerated VS/MD/Consulo solution and project files
29 | ExportedObj/
30 | .consulo/
31 | *.csproj
32 | *.unityproj
33 | *.sln
34 | *.suo
35 | *.tmp
36 | *.user
37 | *.userprefs
38 | *.pidb
39 | *.booproj
40 | *.svd
41 | *.pdb
42 | *.mdb
43 | *.opendb
44 | *.VC.db
45 |
46 | # Unity3D generated meta files
47 | *.pidb.meta
48 | *.pdb.meta
49 | *.mdb.meta
50 |
51 | # Unity3D generated file on crash reports
52 | sysinfo.txt
53 |
54 | # Builds
55 | *.apk
56 | *.unitypackage
57 |
58 | # Crashlytics generated file
59 | crashlytics-build.properties
60 |
61 |
--------------------------------------------------------------------------------
/20220519_133814_P01_Experiment Result.csv:
--------------------------------------------------------------------------------
1 | partiNumber,trialNumber,opticDirection,opticRatio,soundEnabled,responseDirection,currentTime,responseIntensity,responseTime
2 | P01,0,-1,100,1,-1,2022-05-12 12:30:22,3,5642
3 | P01,1,1,80,2,-1,2022-05-12 12:30:22,3,5642
4 | P01,2,1,60,2,-1,2022-05-12 12:30:22,3,5642
5 | P01,3,1,60,2,-1,2022-05-12 12:30:22,3,5642
6 | P01,4,1,60,0,1,2022-05-12 12:30:22,3,5642
7 | P01,5,-1,80,1,0,2022-05-12 12:30:22,3,5642
8 | P01,6,-1,80,0,0,2022-05-12 12:30:22,3,5642
9 | P01,7,-1,80,0,0,2022-05-12 12:30:22,3,5642
10 | P01,8,-1,40,1,0,2022-05-12 12:30:22,3,5642
11 | P01,9,1,60,2,-1,2022-05-12 12:30:22,3,5642
12 | P01,10,-1,100,1,0,2022-05-12 12:30:22,3,5642
13 |
--------------------------------------------------------------------------------
/Images/Trial Manager Flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinwook31/Unity-Experiment-Trial-Manager/1b3c209c766d21b94b2a2e1ab9a5f2e658c4481d/Images/Trial Manager Flow.png
--------------------------------------------------------------------------------
/Images/Trial Mng Flow.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinwook31/Unity-Experiment-Trial-Manager/1b3c209c766d21b94b2a2e1ab9a5f2e658c4481d/Images/Trial Mng Flow.JPG
--------------------------------------------------------------------------------
/Images/csv format.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinwook31/Unity-Experiment-Trial-Manager/1b3c209c766d21b94b2a2e1ab9a5f2e658c4481d/Images/csv format.png
--------------------------------------------------------------------------------
/Images/prefab.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinwook31/Unity-Experiment-Trial-Manager/1b3c209c766d21b94b2a2e1ab9a5f2e658c4481d/Images/prefab.JPG
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unity-Experiment-Trial-Manager
2 |
3 | A Unity library for 2D/3D/VR/AR Behavioral Experiment. Recent HCI and cognitive, behavioral research use Unity to implement their experiment. There are already various frameworks (https://github.com/immersivecognition/unity-experiment-framework) on Github that provide a simple and faster implementation environment. However, those frameworks do not provide functions that restart from a specific point. Escpecially for VR experiment, there are various components that could occur error such as tracking, power and connection etc. Thus, if there is an error during the experiment, it needs to be done from the beginning.
4 |
5 | To resolve this issue, we developed a library that provides a restart function from a specific trial number (where experiment is stopped by accident or error) and separated the trial randomization process by reading the pre-generated CSV file. Also, we tried to embed the reading and save the trial data, so users could focus on the stimuli design and ease the burden on those who are not familiar with Unity programming, and save time.
6 |
7 |
8 | ## Citation
9 |
10 | Kim, J., Kim, Y.J., & Lee, J. (2022). ExpTrialMng: A Universal Experiment Trial Manager for AR/VR/MR Experiments based on Unity. arXiv preprint arXiv:2209.02966.
11 |
12 | https://arxiv.org/abs/2209.02966
13 |
14 |
15 |
16 | ## Requirement
17 |
18 | - Unity
19 | - Pre-generated Trial CSV (in correct format)
20 | - Experiment Trial Manager.unitypackage
21 |
22 | ## Usage
23 |
24 | import the package into your Unity project and include Experiment Manager.prefab to your scene.
25 |
26 | 
27 |
28 | You need to type in 4 components in Trial Data (i.e., CSV File Name, Input Idx, Output Idx, Start From)
29 |
30 | - CSV File Name: Type the name of the pre-generated trial CSV.
31 |
32 | - Input Idx: Type the index of input size in the pre-generated trial CSV. (ex. 6 for the CSV format image)
33 |
34 | - Output Idx: Type the index of output size in the pre-generated trial CSV. (ex. 3 for the CSV format image)
35 |
36 | - Start From Type the trial number that you want to start from.
37 |
38 | (Regarding generating the CSV file (for me python), You need to add an empty input slot (ex. trial = [pNum, trialNum, ..., '', '', '', '', '', '', '', '']))
39 | After the experiment, you can find the result CSV file in the Asset folder.
40 |
41 |
42 | ## Experiment Manager
43 |
44 | After setting parameters, all you need to do with this template is to link your stimuli code (visual, audio, haptic etc.) with the trial info in the ExperimentManager.
45 |
46 | Please see the example in the 'ExperimentManager.cs'. You do not need to modify other scripts in the template! (Except when you want to modify the result CSV path.)
47 |
48 | - Save the trial results
49 | ~~~
50 | string[] trialResult = {"2022-05-12 12:30:22","3","5642"};
51 | bool readDone = TrialData.td.trialDone(false, trialResult);
52 | ~~~
53 |
54 | - Read the stimuli for next trial
55 | ~~~
56 | string stim0 = TrialData.td.getTrialInfo(2);
57 | string stim1 = TrialData.td.getTrialInfo(3);
58 | ~~~
59 |
60 |
61 |
62 | ### Pre-generated Trial CSV Format
63 |
64 | - Location : ./Assets/(your file name).csv
65 |
66 | The CSV must include 'partiNumber', 'trialNumber' in 0 and 1 header index in order to initiate the experiment. Also, the slots for output needs to be empty as the 'currentTime', 'responseIntensity' and 'responseTime' figure below.
67 |
68 | 
69 |
70 |
71 |
72 | ## Trial Manager Work Flow
73 |
74 | 
75 |
76 |
--------------------------------------------------------------------------------
/Trial Manager Script/Script/CsvFileCommon.cs:
--------------------------------------------------------------------------------
1 | public abstract class CsvFileCommon
2 | {
3 | ///
4 | /// These are special characters in CSV files. If a column contains any
5 | /// of these characters, the entire column is wrapped in double quotes.
6 | ///
7 | protected char[] SpecialChars = new char[] { ',', '"', '\r', '\n' };
8 |
9 | // Indexes into SpecialChars for characters with specific meaning
10 | private const int DelimiterIndex = 0;
11 | private const int QuoteIndex = 1;
12 |
13 | ///
14 | /// Gets/sets the character used for column delimiters.
15 | ///
16 | public char Delimiter
17 | {
18 | get { return SpecialChars[DelimiterIndex]; }
19 | set { SpecialChars[DelimiterIndex] = value; }
20 | }
21 |
22 | ///
23 | /// Gets/sets the character used for column quotes.
24 | ///
25 | public char Quote
26 | {
27 | get { return SpecialChars[QuoteIndex]; }
28 | set { SpecialChars[QuoteIndex] = value; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Trial Manager Script/Script/CsvFileWriter.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System;
3 | using System.IO;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 |
7 | public class CsvFileWriter : CsvFileCommon, IDisposable
8 | {
9 | // Private members
10 | private StreamWriter Writer;
11 | private string OneQuote = null;
12 | private string TwoQuotes = null;
13 | private string QuotedFormat = null;
14 |
15 | ///
16 | /// Initializes a new instance of the CsvFileWriter class for the
17 | /// specified stream.
18 | ///
19 | /// The stream to write to
20 | public CsvFileWriter(Stream stream)
21 | {
22 | Writer = new StreamWriter(stream);
23 | }
24 |
25 | ///
26 | /// Initializes a new instance of the CsvFileWriter class for the
27 | /// specified file path.
28 | ///
29 | /// The name of the CSV file to write to
30 | public CsvFileWriter(string path)
31 | {
32 | Writer = new StreamWriter(path);
33 | }
34 |
35 | ///
36 | /// Writes a row of columns to the current CSV file.
37 | ///
38 | /// The list of columns to write
39 | public void WriteRow(List columns)
40 | {
41 | // Verify required argument
42 | if (columns == null)
43 | throw new ArgumentNullException("columns");
44 |
45 | // Ensure we're using current quote character
46 | if (OneQuote == null || OneQuote[0] != Quote)
47 | {
48 | OneQuote = String.Format("{0}", Quote);
49 | TwoQuotes = String.Format("{0}{0}", Quote);
50 | QuotedFormat = String.Format("{0}{{0}}{0}", Quote);
51 | }
52 |
53 | // Write each column
54 | for (int i = 0; i < columns.Count; i++)
55 | {
56 | // Add delimiter if this isn't the first column
57 | if (i > 0)
58 | Writer.Write(Delimiter);
59 | // Write this column
60 | if (columns[i].IndexOfAny(SpecialChars) == -1)
61 | Writer.Write(columns[i]);
62 | else
63 | Writer.Write(QuotedFormat, columns[i].Replace(OneQuote, TwoQuotes));
64 | }
65 | Writer.WriteLine();
66 | }
67 |
68 | // Propagate Dispose to StreamWriter
69 | public void Dispose()
70 | {
71 | Writer.Dispose();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Trial Manager Script/Script/DataLogger.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using System.IO;
5 | using System;
6 |
7 | public class DataLogger : MonoBehaviour{
8 | public static DataLogger dataLogger;
9 |
10 | // New CSV Info
11 | private string testStartTime, participantNum;
12 | private string[] header;
13 |
14 | // DataLogger param
15 | private string[][] result;
16 | private int countRes = 0, allTrialNum;
17 |
18 | //Start is called before the first frame update
19 | void Start(){
20 | if(dataLogger && dataLogger != this)
21 | Destroy(this);
22 | else
23 | dataLogger = this;
24 | }
25 |
26 | public void initDataLogger(int trialNum, string participantNum){
27 | this.participantNum = participantNum;
28 | this.allTrialNum = trialNum;
29 | result = new string[allTrialNum][];
30 | }
31 |
32 | //Add line to result array
33 | public void writeLine(string[] line){
34 | printArray(line);
35 |
36 | if(countRes < allTrialNum)
37 | result[countRes++] = line;
38 |
39 | makeCSV();
40 | }
41 |
42 | public void writeHeader(string[] header){
43 | this.header = (string[])header.Clone();
44 | }
45 |
46 | //Save it to CSV File
47 | private void makeCSV() {
48 | if(countRes == 1) testStartTime = Timer.timer.currentTime4Filepath();
49 |
50 | using (var writer = new CsvFileWriter(Application.dataPath + "/" + testStartTime + "_" + participantNum + "_Experiment Result.csv")){
51 | List columns = new List(header); // making Index Row
52 | writer.WriteRow(columns);
53 | columns.Clear();
54 |
55 | for(int i = 0; i < countRes; i++){
56 | for(int j = 0; j < result[i].Length; j++){
57 | columns.Add(result[i][j]);
58 | }
59 | writer.WriteRow(columns);
60 | columns.Clear();
61 | }
62 | }
63 | }
64 |
65 | private void printArray(string[] line){
66 | string res = "";
67 | for(int i=0; i trialData;
12 | public string csvFileName = "Trial_P01";
13 | private int lineIdx = 0;
14 |
15 | // Input Trial Data, Output Trial Data
16 | private string[] trialInput, trialOutput;
17 | public int inputIdx = 6, outputIdx = 3, startFrom = 0;
18 |
19 | // Start is called before the first frame update
20 | void Start(){
21 | if(td && td != this)
22 | Destroy(this);
23 | else
24 | td = this;
25 |
26 | // Read Pre-generated CSV File
27 | csvreader = new csvReader();
28 | trialData = csvreader.Read(csvFileName.ToString() + ".csv");
29 | }
30 |
31 | public bool initTrialData(){
32 | string[] header = trialData[lineIdx];
33 | DataLogger.dataLogger.writeHeader(header);
34 | DataLogger.dataLogger.initDataLogger(trialData.Count, trialData[1][0]); //Get total trial number & participant ID
35 |
36 | trialInput = new string[inputIdx];
37 | trialOutput = new string[outputIdx];
38 |
39 | // END Experiment
40 | if(!trialDone(true, null)){
41 | Quit();
42 | }
43 |
44 | Debug.Log("EXPERIMENT TRIALS INITIATED (Read Values with getTrialInfo(idx))");
45 | return true;
46 | }
47 |
48 | private void writeData2CSV(string[] output){
49 | trialOutput = output;
50 |
51 | var fullLine = new List();
52 | fullLine.AddRange(trialInput);
53 | fullLine.AddRange(trialOutput);
54 | string[] res = fullLine.ToArray();
55 |
56 | // Write CSV line (lineIdx)
57 | DataLogger.dataLogger.writeLine(res);
58 | }
59 |
60 | private string[] readNextLine(){
61 | while(lineIdx < trialData.Count){
62 | string[] curLine = null;
63 |
64 | try{
65 | curLine = trialData[++lineIdx];
66 | }catch(ArgumentOutOfRangeException e){
67 | Debug.Log(e);
68 | Debug.Log("Check the length of the trial CSV Input and Output Idx!!");
69 | Quit();
70 | }
71 |
72 | // Check the index of CSV, where to start from. (In case of restarted exp.)
73 | if(string.Compare(curLine[trialInput.Length], "") == 0 && int.Parse(curLine[1]) >= startFrom){
74 | return curLine;
75 | }
76 | }
77 | return null;
78 | }
79 |
80 | public bool trialDone(bool initExp, string[] output){
81 | if(!initExp) writeData2CSV(output);
82 |
83 | string[] line = readNextLine();
84 |
85 | if(line == null){ //EXP ENDED
86 | Quit();
87 | return false;
88 | }else{
89 | // Assign line to Input arrays
90 | trialInput = new string[trialInput.Length];
91 | for(int i=0; i Read(string file)
9 | {
10 | var list = new List();
11 | StreamReader sr = new StreamReader(Application.dataPath + "/" + file);
12 |
13 | bool endOfFile = false;
14 | while (!endOfFile){
15 |
16 | string data_String = sr.ReadLine();
17 |
18 | if (data_String == null){
19 | endOfFile = true;
20 | break;
21 | }
22 |
23 | var data_values = data_String.Split(','); //string, string Type
24 | list.Add(data_values);
25 | }
26 |
27 | return list;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Trial Manager Script/Trial_P01.csv:
--------------------------------------------------------------------------------
1 | partiNumber,trialNumber,opticDirection,opticRatio,soundEnabled,responseDirection,currentTime,responseIntensity,responseTime
2 | P01,0,-1,100,1,-1,,,
3 | P01,1,1,80,2,-1,,,
4 | P01,2,1,60,2,-1,,,
5 | P01,3,1,60,2,-1,,,
6 | P01,4,1,60,0,1,,,
7 | P01,5,-1,80,1,0,,,
8 | P01,6,-1,80,0,0,,,
9 | P01,7,-1,80,0,0,,,
10 | P01,8,-1,40,1,0,,,
11 | P01,9,1,60,2,-1,,,
12 | P01,10,-1,100,1,0,,,
13 | P01,11,1,40,1,-1,,,
14 | P01,12,-1,60,1,0,,,
15 | P01,13,1,20,2,-1,,,
16 | P01,14,1,40,0,-1,,,
17 | P01,15,-1,40,2,0,,,
18 | P01,16,-1,40,2,0,,,
19 | P01,17,1,80,1,1,,,
20 | P01,18,1,80,1,1,,,
21 | P01,19,1,100,2,1,,,
22 | P01,20,1,80,0,-1,,,
23 | P01,21,1,20,1,-1,,,
24 | P01,22,-1,40,1,-1,,,
25 | P01,23,-1,80,2,0,,,
26 | P01,24,-1,60,1,0,,,
27 | P01,25,-1,80,0,0,,,
28 | P01,26,-1,60,0,-1,,,
29 | P01,27,1,20,0,-1,,,
30 | P01,28,1,100,0,1,,,
31 | P01,29,1,80,1,1,,,
32 | P01,30,-1,100,0,0,,,
33 | P01,31,-1,20,0,-1,,,
34 | P01,32,-1,80,0,0,,,
35 | P01,33,1,60,1,-1,,,
36 | P01,34,-1,100,1,0,,,
37 | P01,35,-1,80,1,0,,,
38 | P01,36,1,60,1,-1,,,
39 | P01,37,1,40,0,-1,,,
40 | P01,38,1,80,1,1,,,
41 | P01,39,1,20,1,-1,,,
42 | P01,40,-1,80,1,0,,,
43 | P01,41,1,100,1,-1,,,
44 | P01,42,-1,60,2,-1,,,
45 | P01,43,1,40,2,-1,,,
46 | P01,44,1,40,2,-1,,,
47 | P01,45,1,20,0,-1,,,
48 | P01,46,-1,60,0,0,,,
49 | P01,47,-1,60,0,0,,,
50 | P01,48,-1,40,1,-1,,,
51 | P01,49,-1,60,0,0,,,
52 | P01,50,-1,100,0,0,,,
53 | P01,51,-1,60,1,-1,,,
54 | P01,52,-1,60,2,0,,,
55 | P01,53,-1,40,2,-1,,,
56 | P01,54,-1,100,0,0,,,
57 | P01,55,1,80,0,1,,,
58 | P01,56,1,40,1,-1,,,
59 | P01,57,1,60,0,1,,,
60 | P01,58,1,100,0,1,,,
61 | P01,59,1,60,0,-1,,,
62 | P01,60,-1,40,1,0,,,
63 | P01,61,-1,20,1,-1,,,
64 | P01,62,1,100,2,1,,,
65 | P01,63,-1,80,2,0,,,
66 | P01,64,-1,100,2,0,,,
67 | P01,65,-1,60,0,0,,,
68 | P01,66,-1,40,0,0,,,
69 | P01,67,1,40,2,-1,,,
70 | P01,68,-1,100,1,0,,,
71 | P01,69,-1,40,2,0,,,
72 | P01,70,1,60,2,1,,,
73 | P01,71,1,100,1,1,,,
74 | P01,72,-1,60,1,0,,,
75 | P01,73,-1,100,2,0,,,
76 | P01,74,1,100,0,1,,,
77 | P01,75,1,100,0,1,,,
78 | P01,76,1,60,2,1,,,
79 | P01,77,-1,100,2,0,,,
80 | P01,78,-1,40,0,0,,,
81 | P01,79,1,60,1,1,,,
82 | P01,80,1,100,1,1,,,
83 | P01,81,1,40,0,-1,,,
84 | P01,82,1,40,1,-1,,,
85 | P01,83,-1,60,1,0,,,
86 | P01,84,-1,100,2,0,,,
87 | P01,85,-1,100,1,0,,,
88 | P01,86,-1,40,1,0,,,
89 | P01,87,1,100,1,1,,,
90 | P01,88,1,60,2,1,,,
91 | P01,89,-1,60,0,0,,,
92 | P01,90,1,20,2,-1,,,
93 | P01,91,1,20,1,-1,,,
94 | P01,92,1,60,2,1,,,
95 | P01,93,-1,60,0,0,,,
96 | P01,94,1,40,2,-1,,,
97 | P01,95,-1,80,1,0,,,
98 | P01,96,1,40,2,1,,,
99 | P01,97,1,60,2,1,,,
100 | P01,98,1,100,0,1,,,
101 | P01,99,-1,60,1,0,,,
102 | P01,100,1,40,2,-1,,,
103 | P01,101,-1,80,1,0,,,
104 | P01,102,1,100,0,1,,,
105 | P01,103,-1,20,2,-1,,,
106 | P01,104,1,40,1,-1,,,
107 | P01,105,1,80,0,1,,,
108 | P01,106,1,40,0,1,,,
109 | P01,107,-1,80,2,0,,,
110 | P01,108,-1,100,2,0,,,
111 | P01,109,1,80,1,1,,,
112 | P01,110,-1,80,0,0,,,
113 | P01,111,1,40,1,-1,,,
114 | P01,112,1,60,1,1,,,
115 | P01,113,-1,20,1,-1,,,
116 | P01,114,1,80,0,1,,,
117 | P01,115,-1,80,0,0,,,
118 | P01,116,-1,100,0,0,,,
119 | P01,117,1,100,1,1,,,
120 | P01,118,-1,80,2,0,,,
121 | P01,119,1,20,1,-1,,,
122 | P01,120,1,40,0,1,,,
123 | P01,121,-1,40,0,0,,,
124 | P01,122,1,100,2,1,,,
125 | P01,123,1,80,2,1,,,
126 | P01,124,-1,20,1,0,,,
127 | P01,125,-1,80,2,0,,,
128 | P01,126,-1,100,2,0,,,
129 | P01,127,1,20,1,-1,,,
130 | P01,128,1,80,2,1,,,
131 | P01,129,1,100,2,1,,,
132 | P01,130,-1,20,0,0,,,
133 | P01,131,1,20,0,-1,,,
134 | P01,132,1,40,0,1,,,
135 | P01,133,-1,40,0,0,,,
136 | P01,134,-1,80,2,0,,,
137 | P01,135,1,20,2,-1,,,
138 | P01,136,1,80,2,1,,,
139 | P01,137,-1,20,2,0,,,
140 | P01,138,1,20,0,-1,,,
141 | P01,139,1,20,0,-1,,,
142 | P01,140,1,20,2,-1,,,
143 | P01,141,-1,20,2,0,,,
144 | P01,142,1,20,0,-1,,,
145 | P01,143,-1,20,0,0,,,
146 | P01,144,1,20,0,-1,,,
147 | P01,145,1,20,1,-1,,,
148 | P01,146,1,20,1,-1,,,
149 | P01,147,-1,20,2,0,,,
150 | P01,148,1,20,2,-1,,,
151 | P01,149,-1,20,2,0,,,
152 |
--------------------------------------------------------------------------------
/experimentManager.unitypackage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinwook31/Unity-Experiment-Trial-Manager/1b3c209c766d21b94b2a2e1ab9a5f2e658c4481d/experimentManager.unitypackage
--------------------------------------------------------------------------------