@m2c2kit/cli 0.1.5 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.env +1 -1
- package/dist/cli.js +107 -18
- package/dist/fonts/roboto/LICENSE.txt +202 -202
- package/dist/templates/README.md.handlebars +17 -17
- package/dist/templates/index.html.handlebars +4 -4
- package/dist/templates/launch.json.handlebars +15 -15
- package/dist/templates/m2c2kit.json.handlebars +4 -4
- package/dist/templates/package.json.handlebars +23 -23
- package/dist/templates/rollup.config.js.handlebars +1 -1
- package/dist/templates/starter.ts.handlebars +462 -295
- package/package.json +4 -4
|
@@ -1,295 +1,462 @@
|
|
|
1
|
-
import {
|
|
2
|
-
WebColors,
|
|
3
|
-
Action,
|
|
4
|
-
Game,
|
|
5
|
-
Scene,
|
|
6
|
-
Sprite,
|
|
7
|
-
Point,
|
|
8
|
-
Label,
|
|
9
|
-
LabelHorizontalAlignmentMode,
|
|
10
|
-
Shape,
|
|
11
|
-
Rect,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
Action.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
1
|
+
import {
|
|
2
|
+
WebColors,
|
|
3
|
+
Action,
|
|
4
|
+
Game,
|
|
5
|
+
Scene,
|
|
6
|
+
Sprite,
|
|
7
|
+
Point,
|
|
8
|
+
Label,
|
|
9
|
+
LabelHorizontalAlignmentMode,
|
|
10
|
+
Shape,
|
|
11
|
+
Rect,
|
|
12
|
+
GameOptions,
|
|
13
|
+
GameParameters,
|
|
14
|
+
TrialSchema,
|
|
15
|
+
Session,
|
|
16
|
+
GameTrialEvent,
|
|
17
|
+
GameLifecycleEvent,
|
|
18
|
+
SessionLifecycleEvent,
|
|
19
|
+
EventBase,
|
|
20
|
+
EventType
|
|
21
|
+
} from "@m2c2kit/core";
|
|
22
|
+
import { Button, Instructions } from "@m2c2kit/addons";
|
|
23
|
+
|
|
24
|
+
class {{className}} extends Game {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
constructor() {
|
|
27
|
+
const defaultParameters: GameParameters = {
|
|
28
|
+
ReadyTime: {
|
|
29
|
+
value: 1000,
|
|
30
|
+
description:
|
|
31
|
+
"How long the 'get ready' message scene is shown, milliseconds",
|
|
32
|
+
},
|
|
33
|
+
TrialNum: { value: 3, description: "How many trials to run" },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const demoTrialSchema: TrialSchema = {
|
|
37
|
+
colorChosen: { type: "string", description: "the color that was picked" },
|
|
38
|
+
correct: { type: "boolean", description: "was the answer correct?" },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const options: GameOptions = {
|
|
42
|
+
name: "{{appName}}",
|
|
43
|
+
version: "1.0.0",
|
|
44
|
+
uri: "https://your-repo-or-webpage-here",
|
|
45
|
+
shortDescription: "A brief couple sentence description.",
|
|
46
|
+
longDescription: "An extended, many-sentence description.",
|
|
47
|
+
showFps: true,
|
|
48
|
+
trialSchema: demoTrialSchema,
|
|
49
|
+
parameters: defaultParameters,
|
|
50
|
+
// set this color so we can see the boundaries of the game during development,
|
|
51
|
+
// but typically we would not set this
|
|
52
|
+
bodyBackgroundColor: WebColors.Wheat,
|
|
53
|
+
// note: using 2:1 aspect ratio, because that is closer to modern phones
|
|
54
|
+
width: 400,
|
|
55
|
+
height: 800,
|
|
56
|
+
// set stretch to true if you want to fill the screen on large windows
|
|
57
|
+
// (e.g., iPad, desktop browser)
|
|
58
|
+
stretch: false,
|
|
59
|
+
// Roboto is included by default. Leave fontUrls unchanged, unless you want to use different font
|
|
60
|
+
fontUrls: ["./fonts/roboto/Roboto-Regular.ttf"],
|
|
61
|
+
// for each image below, you specify either a tag of an svg in the property "svgString"
|
|
62
|
+
// or you specify a URL to an image in the property "url",
|
|
63
|
+
// such as url: 'https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg'
|
|
64
|
+
images: [
|
|
65
|
+
{
|
|
66
|
+
name: "star",
|
|
67
|
+
height: 60,
|
|
68
|
+
width: 60,
|
|
69
|
+
svgString:
|
|
70
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="304" height="290"> <path d="M2,111 h300 l-242.7,176.3 92.7,-285.3 92.7,285.3 z" style="fill:#00FF00;stroke:#0000FF;stroke-width:15;stroke-linejoin:round"/></svg>',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "smiley",
|
|
74
|
+
height: 64,
|
|
75
|
+
width: 64,
|
|
76
|
+
svgString:
|
|
77
|
+
'<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="451.451 -28.549 1657.098 1657.098" enable-background="new 451.451 -28.549 1657.098 1657.098" xml:space="preserve"><g><g><circle cx="992.682" cy="640.47" r="66.818"/></g><g><circle cx="1558.728" cy="640.47" r="66.818"/></g><g><path d="M1280,1600c-107.984,0-212.756-21.157-311.407-62.883c-95.268-40.295-180.819-97.973-254.279-171.432c-73.459-73.459-131.137-159.01-171.432-254.278C501.157,1012.757,480,907.984,480,800s21.157-212.756,62.883-311.407c40.295-95.268,97.973-180.819,171.432-254.279c73.46-73.459,159.011-131.137,254.279-171.432C1067.244,21.157,1172.016,0,1280,0s212.757,21.157,311.407,62.883c95.268,40.295,180.819,97.973,254.278,171.432c73.459,73.46,131.137,159.011,171.432,254.279C2058.843,587.244,2080,692.016,2080,800s-21.157,212.757-62.883,311.407c-40.295,95.268-97.973,180.819-171.432,254.278s-159.01,131.137-254.278,171.432C1492.757,1578.843,1387.984,1600,1280,1600z M1280,71.591c-401.646,0-728.409,326.763-728.409,728.409S878.354,1528.409,1280,1528.409S2008.409,1201.646,2008.409,800C2008.409,398.354,1681.646,71.591,1280,71.591z"/></g><g><path d="M1665.953,1071.004c-213.156,213.156-558.75,213.156-771.905,0c20.534-20.534,41.068-41.068,61.602-61.602c179.134,179.134,469.567,179.134,648.7,0C1624.884,1029.936,1645.418,1050.47,1665.953,1071.004z"/></g><g><path d="M1167.066,463.279c-96.31-96.31-252.459-96.31-348.768,0c16.971,16.971,33.941,33.941,50.912,50.912c68.192-68.192,178.753-68.192,246.945,0C1133.125,497.22,1150.096,480.249,1167.066,463.279z"/></g><g><path d="M1733.112,463.279c-96.31-96.31-252.459-96.31-348.768,0c16.971,16.971,33.941,33.941,50.912,50.912c68.192-68.192,178.753-68.192,246.945,0C1699.171,497.22,1716.142,480.249,1733.112,463.279z"/></g></g></svg>',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
super(options);
|
|
83
|
+
// just for convenience, alias the variable game to "this"
|
|
84
|
+
// (even though eslint doesn't like it)
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
86
|
+
const game = this;
|
|
87
|
+
|
|
88
|
+
const instructionsScenes = Instructions.Create({
|
|
89
|
+
sceneNamePrefix: "instructions",
|
|
90
|
+
backgroundColor: WebColors.White,
|
|
91
|
+
nextButtonBackgroundColor: WebColors.Black,
|
|
92
|
+
backButtonBackgroundColor: WebColors.Black,
|
|
93
|
+
instructionScenes: [
|
|
94
|
+
{
|
|
95
|
+
title: "{{appName}} Demo",
|
|
96
|
+
text: "For this task, you will click the red rectangle. Open the developer console to see the trial data and other output.",
|
|
97
|
+
textFontSize: 24,
|
|
98
|
+
titleFontSize: 30,
|
|
99
|
+
image: "smiley",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
title: "{{appName}} Demo",
|
|
103
|
+
text: "Press START to begin!",
|
|
104
|
+
textFontSize: 24,
|
|
105
|
+
titleFontSize: 30,
|
|
106
|
+
textAlignmentMode: LabelHorizontalAlignmentMode.center,
|
|
107
|
+
textVerticalBias: 0.25,
|
|
108
|
+
nextButtonText: "START",
|
|
109
|
+
nextButtonBackgroundColor: WebColors.Green,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
// this is the name of the scene to go to after the last instructions scene
|
|
113
|
+
postInstructionsScene: "getReady",
|
|
114
|
+
});
|
|
115
|
+
game.addScenes(instructionsScenes);
|
|
116
|
+
|
|
117
|
+
const getReadyScene = new Scene({
|
|
118
|
+
// Because this is the scene after the instructions, we must give
|
|
119
|
+
// it a name and provide it to postInstructionsScene, above
|
|
120
|
+
name: "getReady",
|
|
121
|
+
backgroundColor: WebColors.White,
|
|
122
|
+
});
|
|
123
|
+
game.addScene(getReadyScene);
|
|
124
|
+
|
|
125
|
+
const getReadyMessage = new Label({
|
|
126
|
+
text: "Get Ready",
|
|
127
|
+
fontSize: 24,
|
|
128
|
+
position: new Point(200, 400),
|
|
129
|
+
});
|
|
130
|
+
getReadyScene.addChild(getReadyMessage);
|
|
131
|
+
|
|
132
|
+
// example of how to use an image. The image must be previously loaded
|
|
133
|
+
const starSprite = new Sprite({
|
|
134
|
+
imageName: "star",
|
|
135
|
+
position: new Point(200, 500),
|
|
136
|
+
});
|
|
137
|
+
getReadyScene.addChild(starSprite);
|
|
138
|
+
|
|
139
|
+
// getReadyScene.setup() has a callback that is executed each time this scene is shown
|
|
140
|
+
getReadyScene.setup(() => {
|
|
141
|
+
getReadyScene.run(
|
|
142
|
+
Action.Sequence([
|
|
143
|
+
// Get the wait duration from the default game parameters, defined above
|
|
144
|
+
Action.Wait({ duration: game.getParameter("ReadyTime") }),
|
|
145
|
+
Action.Custom({
|
|
146
|
+
callback: () => {
|
|
147
|
+
game.presentScene(chooseRectangleScene);
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
])
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// these entities before the setup() can be defined outside of a setup()
|
|
155
|
+
// because they exist through multiple trials
|
|
156
|
+
// Their position and how they respond to interactions may differ across trials,
|
|
157
|
+
// and that logic will be written within a setup()
|
|
158
|
+
const chooseRectangleScene = new Scene({
|
|
159
|
+
backgroundColor: WebColors.LightGray,
|
|
160
|
+
});
|
|
161
|
+
game.addScene(chooseRectangleScene);
|
|
162
|
+
const redRect = new Shape({
|
|
163
|
+
rect: new Rect({ width: 150, height: 100 }),
|
|
164
|
+
fillColor: WebColors.Red,
|
|
165
|
+
});
|
|
166
|
+
chooseRectangleScene.addChild(redRect);
|
|
167
|
+
const blueRect = new Shape({
|
|
168
|
+
rect: new Rect({ width: 150, height: 100 }),
|
|
169
|
+
fillColor: WebColors.Blue,
|
|
170
|
+
});
|
|
171
|
+
chooseRectangleScene.addChild(blueRect);
|
|
172
|
+
const correctMessage = new Label({
|
|
173
|
+
text: "CORRECT!",
|
|
174
|
+
position: new Point(200, 500),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const chooseMessage = new Label({
|
|
178
|
+
text: "Choose the red rectangle",
|
|
179
|
+
position: new Point(200, 200),
|
|
180
|
+
});
|
|
181
|
+
chooseRectangleScene.addChild(chooseMessage);
|
|
182
|
+
|
|
183
|
+
correctMessage.hidden = true;
|
|
184
|
+
chooseRectangleScene.addChild(correctMessage);
|
|
185
|
+
const wrongMessage = new Label({
|
|
186
|
+
text: "WRONG!",
|
|
187
|
+
position: new Point(200, 500),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
wrongMessage.hidden = true;
|
|
191
|
+
chooseRectangleScene.addChild(wrongMessage);
|
|
192
|
+
|
|
193
|
+
// chooseRectangleScene.setup() is passed a callback that is executed each
|
|
194
|
+
// time this scene is shown. Within setup(), We will randomly decide on
|
|
195
|
+
// what side the red rectangle is shown
|
|
196
|
+
chooseRectangleScene.setup(() => {
|
|
197
|
+
let redOnLeft = true;
|
|
198
|
+
if (Math.random() > 0.5) {
|
|
199
|
+
redOnLeft = false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (redOnLeft) {
|
|
203
|
+
redRect.position = new Point(100, 300);
|
|
204
|
+
blueRect.position = new Point(300, 300);
|
|
205
|
+
} else {
|
|
206
|
+
redRect.position = new Point(300, 300);
|
|
207
|
+
blueRect.position = new Point(100, 300);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// helper function to record the user's choice and
|
|
211
|
+
// decide if we are done
|
|
212
|
+
function recordUserInput(choseRedRect: boolean) {
|
|
213
|
+
game.addTrialData("correct", choseRedRect);
|
|
214
|
+
if (choseRedRect) {
|
|
215
|
+
game.addTrialData("colorChosen", "red");
|
|
216
|
+
} else {
|
|
217
|
+
game.addTrialData("colorChosen", "blue");
|
|
218
|
+
}
|
|
219
|
+
game.trialComplete();
|
|
220
|
+
// have we finished the number of trials?
|
|
221
|
+
if (game.trialIndex < game.getParameter<number>("TrialNum")) {
|
|
222
|
+
game.presentScene(getReadyScene);
|
|
223
|
+
} else {
|
|
224
|
+
game.presentScene(endScene);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
redRect.isUserInteractionEnabled = true;
|
|
229
|
+
redRect.onTapDown(() => {
|
|
230
|
+
redRect.run(
|
|
231
|
+
Action.Sequence([
|
|
232
|
+
Action.Custom({
|
|
233
|
+
callback: () => {
|
|
234
|
+
// once a choice is made, don't allow additional taps
|
|
235
|
+
redRect.isUserInteractionEnabled = false;
|
|
236
|
+
blueRect.isUserInteractionEnabled = false;
|
|
237
|
+
},
|
|
238
|
+
}),
|
|
239
|
+
// short animation to shrink and grow button
|
|
240
|
+
Action.Scale({ scale: 0.8, duration: 250 }),
|
|
241
|
+
Action.Scale({ scale: 1, duration: 250 }),
|
|
242
|
+
// Show the "CORRECT" message
|
|
243
|
+
Action.Custom({
|
|
244
|
+
callback: () => {
|
|
245
|
+
correctMessage.hidden = false;
|
|
246
|
+
},
|
|
247
|
+
}),
|
|
248
|
+
Action.Wait({ duration: 1000 }),
|
|
249
|
+
// Handle the logic of recording data, and deciding if we should
|
|
250
|
+
// do another trial in recordUserInput
|
|
251
|
+
Action.Custom({
|
|
252
|
+
callback: () => {
|
|
253
|
+
correctMessage.hidden = true;
|
|
254
|
+
recordUserInput(true);
|
|
255
|
+
},
|
|
256
|
+
}),
|
|
257
|
+
])
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
blueRect.isUserInteractionEnabled = true;
|
|
262
|
+
blueRect.onTapDown(() => {
|
|
263
|
+
blueRect.run(
|
|
264
|
+
Action.Sequence([
|
|
265
|
+
Action.Custom({
|
|
266
|
+
callback: () => {
|
|
267
|
+
redRect.isUserInteractionEnabled = false;
|
|
268
|
+
blueRect.isUserInteractionEnabled = false;
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
Action.Scale({ scale: 0.8, duration: 250 }),
|
|
272
|
+
Action.Scale({ scale: 1, duration: 250 }),
|
|
273
|
+
Action.Custom({
|
|
274
|
+
callback: () => {
|
|
275
|
+
wrongMessage.hidden = false;
|
|
276
|
+
},
|
|
277
|
+
}),
|
|
278
|
+
Action.Wait({ duration: 1000 }),
|
|
279
|
+
Action.Custom({
|
|
280
|
+
callback: () => {
|
|
281
|
+
wrongMessage.hidden = true;
|
|
282
|
+
recordUserInput(false);
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
])
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const endScene = new Scene();
|
|
291
|
+
game.addScene(endScene);
|
|
292
|
+
const doneLabel = new Label({
|
|
293
|
+
text: `This will be reassigned in the setup() callback. If you see this, something went wrong!`,
|
|
294
|
+
position: new Point(200, 300),
|
|
295
|
+
});
|
|
296
|
+
endScene.addChild(doneLabel);
|
|
297
|
+
|
|
298
|
+
const startOverButton = new Button({
|
|
299
|
+
text: "Start over",
|
|
300
|
+
position: new Point(200, 600),
|
|
301
|
+
});
|
|
302
|
+
startOverButton.isUserInteractionEnabled = true;
|
|
303
|
+
startOverButton.onTapDown(() => {
|
|
304
|
+
// in the setup() for the end scene, we animate the smiley sprite with
|
|
305
|
+
// a move action. if the user taps the start over button before the
|
|
306
|
+
// animation is completed, we should remove it by calling
|
|
307
|
+
// removeAllActions()
|
|
308
|
+
smileySprite.removeAllActions();
|
|
309
|
+
game.initData();
|
|
310
|
+
game.presentScene(getReadyScene);
|
|
311
|
+
});
|
|
312
|
+
endScene.addChild(startOverButton);
|
|
313
|
+
|
|
314
|
+
const exitButton = new Button({
|
|
315
|
+
text: "Exit",
|
|
316
|
+
position: new Point(200, 675),
|
|
317
|
+
});
|
|
318
|
+
exitButton.isUserInteractionEnabled = true;
|
|
319
|
+
exitButton.onTapDown(() => {
|
|
320
|
+
// hide the start over button
|
|
321
|
+
startOverButton.hidden = true;
|
|
322
|
+
// don't allow repeated taps on exit button
|
|
323
|
+
exitButton.isUserInteractionEnabled = false;
|
|
324
|
+
game.end();
|
|
325
|
+
});
|
|
326
|
+
endScene.addChild(exitButton);
|
|
327
|
+
|
|
328
|
+
const smileySprite = new Sprite({ imageName: "smiley" });
|
|
329
|
+
endScene.addChild(smileySprite);
|
|
330
|
+
|
|
331
|
+
endScene.setup(() => {
|
|
332
|
+
doneLabel.text = `You did ${game.trialIndex} trials. You're done!`;
|
|
333
|
+
|
|
334
|
+
// example of how to position a sprite and create an action to move it
|
|
335
|
+
smileySprite.position = new Point(200, 500);
|
|
336
|
+
smileySprite.run(
|
|
337
|
+
Action.Move({ point: new Point(200, 100), duration: 3000 })
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
game.entryScene = "instructions-01";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ===========================================================================
|
|
346
|
+
|
|
347
|
+
//#region to support m2c2kit in Android WebView
|
|
348
|
+
/** When running within an Android WebView, the below defines how the session
|
|
349
|
+
* can communicate events back to the Android app. Note: names of this Android
|
|
350
|
+
* namespace and its functions must match the corresponding Android code
|
|
351
|
+
* in addJavascriptInterface() and @JavascriptInterface */
|
|
352
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
353
|
+
declare namespace Android {
|
|
354
|
+
function onGameTrialComplete(gameTrialEventAsString: string): void;
|
|
355
|
+
function onGameLifecycleChange(gameLifecycleEventAsString: string): void;
|
|
356
|
+
function onSessionLifecycleChange(
|
|
357
|
+
sessionLifecycleEventAsString: string
|
|
358
|
+
): void;
|
|
359
|
+
/** if the Android native app will control the session execution and be
|
|
360
|
+
* able to set custom game paraemters (which is probably what you want),
|
|
361
|
+
* be sure that sessionManualStart() in the native code returns true */
|
|
362
|
+
function sessionManualStart(): boolean;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function contextIsAndroidWebView(): boolean {
|
|
366
|
+
return typeof Android !== "undefined";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function sendEventToAndroid(event: EventBase) {
|
|
370
|
+
switch (event.eventType) {
|
|
371
|
+
case EventType.sessionLifecycle: {
|
|
372
|
+
Android.onSessionLifecycleChange(JSON.stringify(event));
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case EventType.gameTrial: {
|
|
376
|
+
Android.onGameTrialComplete(JSON.stringify(event));
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
case EventType.gameLifecycle: {
|
|
380
|
+
Android.onGameLifecycleChange(JSON.stringify(event));
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
default:
|
|
384
|
+
throw new Error(
|
|
385
|
+
`attempt to send unknown event ${event.eventType} to Android`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
|
|
391
|
+
const game1 = new {{className}}();
|
|
392
|
+
// default was 3 trials; this is how we can specify a different value
|
|
393
|
+
game1.setParameters({ TrialNum: 2 });
|
|
394
|
+
|
|
395
|
+
const session = new Session({
|
|
396
|
+
activities: [game1],
|
|
397
|
+
sessionCallbacks: {
|
|
398
|
+
// onSessionLifecycleChange() will be called on events such
|
|
399
|
+
// as when the session initialization is complete. Once initialized,
|
|
400
|
+
// the session will automatically start, unless we're running
|
|
401
|
+
// in an Android WebView and a manual start is desired.
|
|
402
|
+
onSessionLifecycleChange: (event: SessionLifecycleEvent) => {
|
|
403
|
+
if (event.initialized) {
|
|
404
|
+
//#region to support m2c2kit in Android WebView
|
|
405
|
+
if (contextIsAndroidWebView()) {
|
|
406
|
+
sendEventToAndroid(event);
|
|
407
|
+
}
|
|
408
|
+
if (contextIsAndroidWebView() && Android.sessionManualStart()) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
session.start();
|
|
413
|
+
}
|
|
414
|
+
if (event.ended) {
|
|
415
|
+
console.log("session ended");
|
|
416
|
+
//#region to support m2c2kit in Android WebView
|
|
417
|
+
if (contextIsAndroidWebView()) {
|
|
418
|
+
sendEventToAndroid(event);
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
gameCallbacks: {
|
|
425
|
+
// onGameTrialComplete() is where you insert code to post data to an API
|
|
426
|
+
// or interop with a native function in the host app, if applicable
|
|
427
|
+
onGameTrialComplete: (event: GameTrialEvent) => {
|
|
428
|
+
console.log(`********** trial (index ${event.trialIndex}) complete`);
|
|
429
|
+
console.log("data: " + JSON.stringify(event.gameData));
|
|
430
|
+
console.log("trial schema: " + JSON.stringify(event.trialSchema));
|
|
431
|
+
console.log("game parameters: " + JSON.stringify(event.gameParameters));
|
|
432
|
+
|
|
433
|
+
//#region to support m2c2kit in Android WebView
|
|
434
|
+
if (contextIsAndroidWebView()) {
|
|
435
|
+
sendEventToAndroid(event);
|
|
436
|
+
}
|
|
437
|
+
//#endregion
|
|
438
|
+
},
|
|
439
|
+
onGameLifecycleChange: (event: GameLifecycleEvent) => {
|
|
440
|
+
if (event.ended) {
|
|
441
|
+
console.log(`ended game ${event.gameName}`);
|
|
442
|
+
if (session.nextActivity) {
|
|
443
|
+
session.advanceToNextActivity();
|
|
444
|
+
} else {
|
|
445
|
+
session.end();
|
|
446
|
+
}
|
|
447
|
+
//#region to support m2c2kit in Android WebView
|
|
448
|
+
if (contextIsAndroidWebView()) {
|
|
449
|
+
sendEventToAndroid(event);
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
/** make session also available on window in case we want to control
|
|
458
|
+
* the session through another means, such as other javascript or
|
|
459
|
+
* browser code, or the Android WebView loadUrl() method */
|
|
460
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
461
|
+
(window as unknown as any).session = session;
|
|
462
|
+
session.init();
|