scad-gltf 0.2.2 → 0.2.4
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/README.md +1 -5
- package/godot/README.md +0 -3
- package/package.json +1 -3
- package/bevy/README.md +0 -46
- package/bevy/examples/LICENSE +0 -21
- package/bevy/examples/README.md +0 -58
- package/bevy/examples/package.json +0 -5
- package/bevy/examples/packman_3d.js +0 -2415
- package/godot/examples/LICENSE +0 -21
- package/godot/examples/README.md +0 -91
- package/godot/examples/fruit_fusion_3d.js +0 -1774
- package/godot/examples/horde_survivor_3d.js +0 -2753
- package/godot/examples/kids_digit_writing.js +0 -2532
- package/godot/examples/naval_battle_3d.js +0 -1691
- package/godot/examples/package.json +0 -5
- package/godot/export_presets.cfg +0 -25
|
@@ -1,2532 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Procedural Generator for "kids_digit_writing"
|
|
5
|
-
* Godot 4 Kids Digit Writing Learning App with 3D Teacher Mouse Robot
|
|
6
|
-
* Calibrated lighting, soft PBR toy materials, and optimized 2D particle pipelines
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require("fs");
|
|
10
|
-
const path = require("path");
|
|
11
|
-
|
|
12
|
-
const ROOT_DIR = path.join(process.cwd(), "kids_digit_writing");
|
|
13
|
-
|
|
14
|
-
function ensureDir(dirPath) {
|
|
15
|
-
if (!fs.existsSync(dirPath)) {
|
|
16
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function writeFile(relativePath, content) {
|
|
21
|
-
const fullPath = path.join(ROOT_DIR, relativePath);
|
|
22
|
-
ensureDir(path.dirname(fullPath));
|
|
23
|
-
fs.writeFileSync(fullPath, content.trim() + "\n", "utf8");
|
|
24
|
-
console.log(` [CREATED] ${relativePath}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.log("Generating Godot 4 Project: kids_digit_writing ...\n");
|
|
28
|
-
|
|
29
|
-
// -------------------------------------------------------------------------
|
|
30
|
-
// 1. ADDON: godot/addons/scad_importer/*
|
|
31
|
-
// -------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
writeFile(
|
|
34
|
-
"addons/scad_importer/plugin.cfg",
|
|
35
|
-
`
|
|
36
|
-
[plugin]
|
|
37
|
-
|
|
38
|
-
name="OpenSCAD GLTF Importer"
|
|
39
|
-
description="Imports .scad files directly as 3D scenes using scad-gltf"
|
|
40
|
-
author="Ilia Grigorev"
|
|
41
|
-
version="0.1"
|
|
42
|
-
script="scad_plugin.gd"
|
|
43
|
-
license="MIT"
|
|
44
|
-
`,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
writeFile(
|
|
48
|
-
"addons/scad_importer/scad_importer.gd",
|
|
49
|
-
`# Copyright (c) 2026 Ilia Grigorev. Distributed under the MIT License.
|
|
50
|
-
# See LICENSE in the addon directory for details.
|
|
51
|
-
|
|
52
|
-
@tool
|
|
53
|
-
extends EditorSceneFormatImporter
|
|
54
|
-
|
|
55
|
-
func _get_extensions():
|
|
56
|
-
return PackedStringArray(["scad"])
|
|
57
|
-
|
|
58
|
-
func _get_import_flags():
|
|
59
|
-
return EditorSceneFormatImporter.IMPORT_SCENE
|
|
60
|
-
|
|
61
|
-
func _import_scene(path: String, flags: int, options: Dictionary) -> Object:
|
|
62
|
-
var global_source = ProjectSettings.globalize_path(path)
|
|
63
|
-
var unique_id = str(hash(path))
|
|
64
|
-
var temp_glb_path = ProjectSettings.globalize_path("user://scad_cache_" + unique_id + ".glb")
|
|
65
|
-
|
|
66
|
-
var args = PackedStringArray()
|
|
67
|
-
args.append(global_source)
|
|
68
|
-
args.append(temp_glb_path)
|
|
69
|
-
|
|
70
|
-
var output = []
|
|
71
|
-
print("Importing %s via scad-convert..." % path.get_file())
|
|
72
|
-
|
|
73
|
-
var exit_code = -1
|
|
74
|
-
if OS.get_name() == "Windows":
|
|
75
|
-
var win_args = PackedStringArray(["/c", "scad-convert"])
|
|
76
|
-
win_args.append_array(args)
|
|
77
|
-
exit_code = OS.execute("cmd.exe", win_args, output, true)
|
|
78
|
-
else:
|
|
79
|
-
exit_code = OS.execute("scad-convert", args, output, true)
|
|
80
|
-
|
|
81
|
-
if exit_code != 0:
|
|
82
|
-
print("scad-convert conversion failed for %s. Attempting fallback to local scad-serve..." % path.get_file())
|
|
83
|
-
var fallback_success = _try_scad_serve_fallback(global_source, temp_glb_path)
|
|
84
|
-
|
|
85
|
-
if not fallback_success:
|
|
86
|
-
push_error("Failed to compile SCAD file: %s. Ensure Node.js is installed or scad-serve is running." % path.get_file())
|
|
87
|
-
push_error("scad-convert output: ", "\\n".join(output))
|
|
88
|
-
return null
|
|
89
|
-
|
|
90
|
-
var gltf_doc = GLTFDocument.new()
|
|
91
|
-
var gltf_state = GLTFState.new()
|
|
92
|
-
var err = gltf_doc.append_from_file(temp_glb_path, gltf_state)
|
|
93
|
-
|
|
94
|
-
if FileAccess.file_exists(temp_glb_path):
|
|
95
|
-
DirAccess.remove_absolute(temp_glb_path)
|
|
96
|
-
|
|
97
|
-
if err != OK:
|
|
98
|
-
push_error("Failed to parse the generated GLB for %s." % path.get_file())
|
|
99
|
-
return null
|
|
100
|
-
|
|
101
|
-
var generated_scene = gltf_doc.generate_scene(gltf_state)
|
|
102
|
-
if generated_scene:
|
|
103
|
-
generated_scene.name = path.get_file().get_basename()
|
|
104
|
-
|
|
105
|
-
return generated_scene
|
|
106
|
-
|
|
107
|
-
func _get_relative_path(base: String, target: String) -> String:
|
|
108
|
-
var base_parts = base.replace("\\\\", "/").split("/", false)
|
|
109
|
-
var target_parts = target.replace("\\\\", "/").split("/", false)
|
|
110
|
-
|
|
111
|
-
if OS.get_name() == "Windows":
|
|
112
|
-
if base_parts.size() > 0 and target_parts.size() > 0:
|
|
113
|
-
if base_parts[0].nocasecmp_to(target_parts[0]) != 0:
|
|
114
|
-
return target
|
|
115
|
-
|
|
116
|
-
var common_count = 0
|
|
117
|
-
var min_len = min(base_parts.size(), target_parts.size())
|
|
118
|
-
for i in range(min_len):
|
|
119
|
-
if base_parts[i].nocasecmp_to(target_parts[i]) == 0:
|
|
120
|
-
common_count += 1
|
|
121
|
-
else:
|
|
122
|
-
break
|
|
123
|
-
|
|
124
|
-
var rel_parts = PackedStringArray()
|
|
125
|
-
for i in range(common_count, base_parts.size()):
|
|
126
|
-
rel_parts.append("..")
|
|
127
|
-
|
|
128
|
-
for i in range(common_count, target_parts.size()):
|
|
129
|
-
rel_parts.append(target_parts[i])
|
|
130
|
-
|
|
131
|
-
return "/".join(rel_parts)
|
|
132
|
-
|
|
133
|
-
func _get_dependencies_recursive(file_path: String, visited: Dictionary) -> void:
|
|
134
|
-
if visited.has(file_path):
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
visited[file_path] = ""
|
|
138
|
-
|
|
139
|
-
if not FileAccess.file_exists(file_path):
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
var file = FileAccess.open(file_path, FileAccess.READ)
|
|
143
|
-
if not file:
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
var content = file.get_as_text()
|
|
147
|
-
file.close()
|
|
148
|
-
|
|
149
|
-
visited[file_path] = content
|
|
150
|
-
|
|
151
|
-
var regex = RegEx.new()
|
|
152
|
-
regex.compile("(?:include|use)\\\\s*[<\\"]([^>\\"]+)[>\\"]")
|
|
153
|
-
|
|
154
|
-
var base_dir = file_path.get_base_dir()
|
|
155
|
-
for result in regex.search_all(content):
|
|
156
|
-
var dep_rel_path = result.get_string(1)
|
|
157
|
-
var dep_abs_path = base_dir.path_join(dep_rel_path).simplify_path()
|
|
158
|
-
_get_dependencies_recursive(dep_abs_path, visited)
|
|
159
|
-
|
|
160
|
-
func _get_dependencies(file_path: String) -> Dictionary:
|
|
161
|
-
var visited = {}
|
|
162
|
-
_get_dependencies_recursive(file_path, visited)
|
|
163
|
-
return visited
|
|
164
|
-
|
|
165
|
-
func _try_scad_serve_fallback(source_path: String, out_glb_path: String) -> bool:
|
|
166
|
-
var deps = _get_dependencies(source_path)
|
|
167
|
-
var content = deps.get(source_path, "")
|
|
168
|
-
deps.erase(source_path)
|
|
169
|
-
|
|
170
|
-
if content == "":
|
|
171
|
-
return false
|
|
172
|
-
|
|
173
|
-
var additional_files = {}
|
|
174
|
-
var base_dir = source_path.get_base_dir()
|
|
175
|
-
for dep_path in deps.keys():
|
|
176
|
-
var rel_path = _get_relative_path(base_dir, dep_path)
|
|
177
|
-
additional_files[rel_path] = deps[dep_path]
|
|
178
|
-
|
|
179
|
-
var http = HTTPClient.new()
|
|
180
|
-
var err = http.connect_to_host("127.0.0.1", 3000)
|
|
181
|
-
if err != OK:
|
|
182
|
-
return false
|
|
183
|
-
|
|
184
|
-
var max_wait = 500
|
|
185
|
-
var wait = 0
|
|
186
|
-
while http.get_status() in [HTTPClient.STATUS_CONNECTING, HTTPClient.STATUS_RESOLVING]:
|
|
187
|
-
http.poll()
|
|
188
|
-
OS.delay_msec(10)
|
|
189
|
-
wait += 1
|
|
190
|
-
if wait > max_wait:
|
|
191
|
-
return false
|
|
192
|
-
|
|
193
|
-
if http.get_status() != HTTPClient.STATUS_CONNECTED:
|
|
194
|
-
return false
|
|
195
|
-
|
|
196
|
-
var headers = PackedStringArray(["Content-Type: application/json"])
|
|
197
|
-
var payload = {
|
|
198
|
-
"content": content,
|
|
199
|
-
"options": {
|
|
200
|
-
"additionalFiles": additional_files
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
var body = JSON.stringify(payload)
|
|
205
|
-
err = http.request(HTTPClient.METHOD_POST, "/api/convert", headers, body)
|
|
206
|
-
if err != OK:
|
|
207
|
-
return false
|
|
208
|
-
|
|
209
|
-
max_wait = 6000
|
|
210
|
-
wait = 0
|
|
211
|
-
while http.get_status() == HTTPClient.STATUS_REQUESTING:
|
|
212
|
-
http.poll()
|
|
213
|
-
OS.delay_msec(10)
|
|
214
|
-
wait += 1
|
|
215
|
-
if wait > max_wait:
|
|
216
|
-
return false
|
|
217
|
-
|
|
218
|
-
if http.has_response() and http.get_response_code() == 200:
|
|
219
|
-
var rb = PackedByteArray()
|
|
220
|
-
while http.get_status() == HTTPClient.STATUS_BODY:
|
|
221
|
-
http.poll()
|
|
222
|
-
var chunk = http.read_response_body_chunk()
|
|
223
|
-
if chunk.size() == 0:
|
|
224
|
-
OS.delay_msec(10)
|
|
225
|
-
else:
|
|
226
|
-
rb.append_array(chunk)
|
|
227
|
-
|
|
228
|
-
if rb.is_empty():
|
|
229
|
-
return false
|
|
230
|
-
|
|
231
|
-
var out_file = FileAccess.open(out_glb_path, FileAccess.WRITE)
|
|
232
|
-
if not out_file:
|
|
233
|
-
return false
|
|
234
|
-
out_file.store_buffer(rb)
|
|
235
|
-
out_file.close()
|
|
236
|
-
|
|
237
|
-
print("Successfully compiled %s using scad-serve fallback." % source_path.get_file())
|
|
238
|
-
return true
|
|
239
|
-
|
|
240
|
-
return false
|
|
241
|
-
`,
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
writeFile(
|
|
245
|
-
"addons/scad_importer/scad_plugin.gd",
|
|
246
|
-
`# Copyright (c) 2026 Ilia Grigorev. Distributed under the MIT License.
|
|
247
|
-
# See LICENSE in the addon directory for details.
|
|
248
|
-
|
|
249
|
-
@tool
|
|
250
|
-
extends EditorPlugin
|
|
251
|
-
|
|
252
|
-
var import_plugin
|
|
253
|
-
|
|
254
|
-
func _enter_tree():
|
|
255
|
-
import_plugin = preload("res://addons/scad_importer/scad_importer.gd").new()
|
|
256
|
-
add_scene_format_importer_plugin(import_plugin)
|
|
257
|
-
|
|
258
|
-
func _exit_tree():
|
|
259
|
-
remove_scene_format_importer_plugin(import_plugin)
|
|
260
|
-
import_plugin = null
|
|
261
|
-
`,
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
// -------------------------------------------------------------------------
|
|
265
|
-
// 2. 3D MODELS (.scad)
|
|
266
|
-
// -------------------------------------------------------------------------
|
|
267
|
-
|
|
268
|
-
writeFile(
|
|
269
|
-
"assets/models/mouse_robot.scad",
|
|
270
|
-
`/* Model Name: mouse_robot */
|
|
271
|
-
// Note on Units: 1 unit ≈ 10 mm (1 cm). Total robot height is ~28 units (28 cm).
|
|
272
|
-
$fn = 36;
|
|
273
|
-
$asa = 45.0;
|
|
274
|
-
|
|
275
|
-
// -------------------------------------------------------------
|
|
276
|
-
// PBR Materials (Calibrated Anti-Glare Toy Enamel Shading)
|
|
277
|
-
// -------------------------------------------------------------
|
|
278
|
-
module mat_silver() {
|
|
279
|
-
color([0.65, 0.72, 0.80], metalness=0.08, roughness=0.52, $asa=45) children();
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
module mat_cyan() {
|
|
283
|
-
color([0.06, 0.66, 0.82], metalness=0.08, roughness=0.45, $asa=45) children();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
module mat_blue_rim() {
|
|
287
|
-
color([0.10, 0.32, 0.75], metalness=0.15, roughness=0.45, $asa=45) children();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
module mat_magenta() {
|
|
291
|
-
color([0.86, 0.16, 0.52], metalness=0.05, roughness=0.45, $asa=45) children();
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
module mat_purple() {
|
|
295
|
-
color([0.50, 0.14, 0.62], metalness=0.08, roughness=0.50, $asa=45) children();
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
module mat_tan() {
|
|
299
|
-
color([0.80, 0.64, 0.44], metalness=0.05, roughness=0.60, $asa=45) children();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
module mat_black() {
|
|
303
|
-
color([0.12, 0.13, 0.16], metalness=0.10, roughness=0.65, $asa=45) children();
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
module mat_white() {
|
|
307
|
-
color([0.82, 0.84, 0.87], metalness=0.0, roughness=0.45, $asa=45) children();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
module mat_dark_blue() {
|
|
311
|
-
color([0.08, 0.18, 0.45], metalness=0.15, roughness=0.45, $asa=45) children();
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// -------------------------------------------------------------
|
|
315
|
-
// Large Round Ears (Facing Forward along +Y)
|
|
316
|
-
// -------------------------------------------------------------
|
|
317
|
-
module robot_ear() {
|
|
318
|
-
rotate([90, 0, 0]) {
|
|
319
|
-
mat_cyan() {
|
|
320
|
-
difference() {
|
|
321
|
-
cylinder(r=4.8, h=0.9, center=true);
|
|
322
|
-
cylinder(r=3.8, h=1.2, center=true);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
mat_magenta() {
|
|
326
|
-
cylinder(r=3.9, h=0.7, center=true);
|
|
327
|
-
}
|
|
328
|
-
mat_silver() {
|
|
329
|
-
translate([0, -4.6, 0])
|
|
330
|
-
cylinder(r=0.6, h=1.6, center=true);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// -------------------------------------------------------------
|
|
336
|
-
// Robot Body Core & Face
|
|
337
|
-
// -------------------------------------------------------------
|
|
338
|
-
module robot_body() {
|
|
339
|
-
mat_silver() {
|
|
340
|
-
intersection() {
|
|
341
|
-
scale([1.0, 0.92, 1.08]) sphere(r=8.0);
|
|
342
|
-
translate([-10, -10, 1.0]) cube([20, 20, 10]);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
mat_silver() {
|
|
347
|
-
translate([0, 0, 8.5]) cylinder(r=0.45, h=4.2);
|
|
348
|
-
translate([0, 0, 13.0]) sphere(r=1.05);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
mat_cyan() {
|
|
352
|
-
intersection() {
|
|
353
|
-
scale([1.01, 0.93, 1.09]) sphere(r=8.0);
|
|
354
|
-
translate([-10, -10, -1.6]) cube([20, 20, 2.8]);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
mat_black() {
|
|
359
|
-
intersection() {
|
|
360
|
-
scale([1.03, 0.95, 1.11]) sphere(r=8.05);
|
|
361
|
-
translate([-10, -10, -3.4]) cube([20, 20, 2.0]);
|
|
362
|
-
}
|
|
363
|
-
translate([0, 7.7, -2.4]) rotate([78, 0, 0])
|
|
364
|
-
cylinder(r=0.9, h=0.7, center=true);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
mat_purple() {
|
|
368
|
-
intersection() {
|
|
369
|
-
scale([1.0, 0.92, 1.08]) sphere(r=7.95);
|
|
370
|
-
translate([-10, -10, -10]) cube([20, 20, 7.0]);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
for (s = [-1, 1]) {
|
|
375
|
-
translate([s * 2.8, 6.4, 5.2]) rotate([-6, s * 8, 0]) {
|
|
376
|
-
mat_white() sphere(r=1.25);
|
|
377
|
-
mat_dark_blue() translate([0, 0.68, 0]) sphere(r=0.68);
|
|
378
|
-
mat_white() translate([s * 0.3, 1.15, 0.3]) sphere(r=0.25);
|
|
379
|
-
|
|
380
|
-
mat_dark_blue() {
|
|
381
|
-
for (a = [-50, 0, 50]) {
|
|
382
|
-
rotate([0, a, 0]) translate([0, 0.7, 1.25])
|
|
383
|
-
cylinder(r=0.14, h=1.4);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
mat_dark_blue() {
|
|
390
|
-
translate([0, 7.35, 4.2]) sphere(r=0.55);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
mat_dark_blue() {
|
|
394
|
-
for (i = [-5 : 5]) {
|
|
395
|
-
let (
|
|
396
|
-
u = i / 5.0,
|
|
397
|
-
x = u * 1.15,
|
|
398
|
-
z = 3.0 + (u * u) * 0.35,
|
|
399
|
-
y = 7.55 - (u * u) * 0.15
|
|
400
|
-
) {
|
|
401
|
-
translate([x, y, z]) sphere(r=0.18);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
mat_white() {
|
|
407
|
-
translate([-0.30, 7.58, 2.55]) cube([0.26, 0.28, 0.55], center=true);
|
|
408
|
-
translate([0.30, 7.58, 2.55]) cube([0.26, 0.28, 0.55], center=true);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
translate([6.8, 0.2, 7.5]) rotate([0, 18, 0]) robot_ear();
|
|
412
|
-
translate([-6.8, 0.2, 7.5]) rotate([0, -18, 0]) robot_ear();
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// -------------------------------------------------------------
|
|
416
|
-
// Arm Segments & 5 Spread Fan Wire Fingers
|
|
417
|
-
// -------------------------------------------------------------
|
|
418
|
-
module arm_upper() {
|
|
419
|
-
mat_cyan() sphere(r=1.3);
|
|
420
|
-
mat_cyan() {
|
|
421
|
-
translate([0, 0, -1.4]) cylinder(r=1.1, h=2.4, center=true);
|
|
422
|
-
}
|
|
423
|
-
mat_tan() {
|
|
424
|
-
translate([0, 0, -2.8]) sphere(r=0.9);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
module arm_forearm() {
|
|
429
|
-
mat_tan() {
|
|
430
|
-
translate([0, 0, -1.3]) cylinder(r=0.85, h=2.2, center=true);
|
|
431
|
-
translate([0, 0, -2.6]) sphere(r=0.85);
|
|
432
|
-
}
|
|
433
|
-
mat_dark_blue() {
|
|
434
|
-
translate([0, 0, -2.8]) {
|
|
435
|
-
cylinder(r=0.85, h=0.45, center=true);
|
|
436
|
-
for (f = [-60, -30, 0, 30, 60]) {
|
|
437
|
-
rotate([0, f, 0]) {
|
|
438
|
-
translate([0, 0, -1.7]) cylinder(r=0.18, h=3.4, center=true);
|
|
439
|
-
translate([0, 0, -3.4]) sphere(r=0.32);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// -------------------------------------------------------------
|
|
447
|
-
// Leg Segments with Seamless Embedded Toe Claws
|
|
448
|
-
// -------------------------------------------------------------
|
|
449
|
-
module upper_leg() {
|
|
450
|
-
mat_purple() {
|
|
451
|
-
sphere(r=1.25);
|
|
452
|
-
translate([0, 0, -1.8]) cylinder(r=1.15, h=2.0, center=true);
|
|
453
|
-
}
|
|
454
|
-
mat_tan() {
|
|
455
|
-
translate([0, 0, -3.0]) sphere(r=0.9);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
module lower_leg() {
|
|
460
|
-
mat_tan() {
|
|
461
|
-
translate([0, 0, -1.8]) cylinder(r=0.85, h=2.2, center=true);
|
|
462
|
-
}
|
|
463
|
-
mat_cyan() {
|
|
464
|
-
translate([0, 0, -3.2]) sphere(r=1.05);
|
|
465
|
-
translate([0, 1.0, -3.8]) scale([1.0, 1.6, 0.75]) sphere(r=1.15);
|
|
466
|
-
|
|
467
|
-
translate([0, 1.8, -4.15])
|
|
468
|
-
rotate([-85, 0, 0]) cylinder(r1=0.36, r2=0.06, h=1.4);
|
|
469
|
-
translate([0.48, 1.7, -4.15])
|
|
470
|
-
rotate([-85, -12, 0]) cylinder(r1=0.34, r2=0.06, h=1.35);
|
|
471
|
-
translate([-0.48, 1.7, -4.15])
|
|
472
|
-
rotate([-85, 12, 0]) cylinder(r1=0.34, r2=0.06, h=1.35);
|
|
473
|
-
}
|
|
474
|
-
mat_blue_rim() {
|
|
475
|
-
translate([0, -0.4, -4.1]) cylinder(r=0.45, h=0.7);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// -------------------------------------------------------------
|
|
480
|
-
// Teacher Animations
|
|
481
|
-
// -------------------------------------------------------------
|
|
482
|
-
robot_anim = [
|
|
483
|
-
["Idle", [
|
|
484
|
-
["Root", [
|
|
485
|
-
[0.0, [ -5, 0, 0], [0, 0, 13.0]],
|
|
486
|
-
[1.0, [ -3, 0, 0], [0, 0, 13.4]],
|
|
487
|
-
[2.0, [ -5, 0, 0], [0, 0, 13.0]]
|
|
488
|
-
]],
|
|
489
|
-
["LeftArm", [
|
|
490
|
-
[0.0, [ 15, -30, 0]],
|
|
491
|
-
[1.0, [ 12, -32, 0]],
|
|
492
|
-
[2.0, [ 15, -30, 0]]
|
|
493
|
-
]],
|
|
494
|
-
["LeftForearm", [
|
|
495
|
-
[0.0, [ 30, 0, 0]],
|
|
496
|
-
[1.0, [ 35, 0, 0]],
|
|
497
|
-
[2.0, [ 30, 0, 0]]
|
|
498
|
-
]],
|
|
499
|
-
["RightArm", [
|
|
500
|
-
[0.0, [ 15, 30, 0]],
|
|
501
|
-
[1.0, [ 12, 32, 0]],
|
|
502
|
-
[2.0, [ 15, 30, 0]]
|
|
503
|
-
]],
|
|
504
|
-
["RightForearm", [
|
|
505
|
-
[0.0, [ 30, 0, 0]],
|
|
506
|
-
[1.0, [ 35, 0, 0]],
|
|
507
|
-
[2.0, [ 30, 0, 0]]
|
|
508
|
-
]],
|
|
509
|
-
["LeftLeg", [ [0.0, [ 15, 0, 6]] ]],
|
|
510
|
-
["LeftShin", [ [0.0, [-15, 0, 0]] ]],
|
|
511
|
-
["RightLeg", [ [0.0, [ 15, 0, -6]] ]],
|
|
512
|
-
["RightShin", [ [0.0, [-15, 0, 0]] ]]
|
|
513
|
-
]],
|
|
514
|
-
|
|
515
|
-
["Praise", [
|
|
516
|
-
["Root", [
|
|
517
|
-
[0.0, [ -5, 0, 0], [0, 0, 13.0]],
|
|
518
|
-
[0.3, [ 0, 0, 0], [0, 0, 17.0]],
|
|
519
|
-
[0.6, [ -5, 0, 0], [0, 0, 13.0]],
|
|
520
|
-
[0.9, [ 0, 0, 0], [0, 0, 17.0]],
|
|
521
|
-
[1.2, [ -5, 0, 0], [0, 0, 13.0]]
|
|
522
|
-
]],
|
|
523
|
-
["LeftArm", [
|
|
524
|
-
[0.0, [ 15, -30, 0]],
|
|
525
|
-
[0.15, [ 15, -90, 0]],
|
|
526
|
-
[0.3, [ 15, -150, 0]],
|
|
527
|
-
[0.6, [ 15, -150, 0]],
|
|
528
|
-
[0.9, [ 15, -150, 0]],
|
|
529
|
-
[1.05, [ 15, -90, 0]],
|
|
530
|
-
[1.2, [ 15, -30, 0]]
|
|
531
|
-
]],
|
|
532
|
-
["LeftForearm", [
|
|
533
|
-
[0.0, [ 30, 0, 0]],
|
|
534
|
-
[0.3, [ 10, 0, 0]],
|
|
535
|
-
[0.6, [ 10, 0, 0]],
|
|
536
|
-
[0.9, [ 10, 0, 0]],
|
|
537
|
-
[1.2, [ 30, 0, 0]]
|
|
538
|
-
]],
|
|
539
|
-
["RightArm", [
|
|
540
|
-
[0.0, [ 15, 30, 0]],
|
|
541
|
-
[0.15, [ 15, 90, 0]],
|
|
542
|
-
[0.3, [ 15, 150, 0]],
|
|
543
|
-
[0.6, [ 15, 150, 0]],
|
|
544
|
-
[0.9, [ 15, 150, 0]],
|
|
545
|
-
[1.05, [ 15, 90, 0]],
|
|
546
|
-
[1.2, [ 15, 30, 0]]
|
|
547
|
-
]],
|
|
548
|
-
["RightForearm", [
|
|
549
|
-
[0.0, [ 30, 0, 0]],
|
|
550
|
-
[0.3, [ 10, 0, 0]],
|
|
551
|
-
[0.6, [ 10, 0, 0]],
|
|
552
|
-
[0.9, [ 10, 0, 0]],
|
|
553
|
-
[1.2, [ 30, 0, 0]]
|
|
554
|
-
]],
|
|
555
|
-
["LeftLeg", [
|
|
556
|
-
[0.0, [ 15, 0, 6]],
|
|
557
|
-
[0.3, [ 35, 0, 6]],
|
|
558
|
-
[0.6, [ 15, 0, 6]],
|
|
559
|
-
[0.9, [ 35, 0, 6]],
|
|
560
|
-
[1.2, [ 15, 0, 6]]
|
|
561
|
-
]],
|
|
562
|
-
["LeftShin", [
|
|
563
|
-
[0.0, [-15, 0, 0]],
|
|
564
|
-
[0.3, [-45, 0, 0]],
|
|
565
|
-
[0.6, [-15, 0, 0]],
|
|
566
|
-
[0.9, [-45, 0, 0]],
|
|
567
|
-
[1.2, [-15, 0, 0]]
|
|
568
|
-
]],
|
|
569
|
-
["RightLeg", [
|
|
570
|
-
[0.0, [ 15, 0, -6]],
|
|
571
|
-
[0.3, [ 35, 0, -6]],
|
|
572
|
-
[0.6, [ 15, 0, -6]],
|
|
573
|
-
[0.9, [ 35, 0, -6]],
|
|
574
|
-
[1.2, [ 15, 0, -6]]
|
|
575
|
-
]],
|
|
576
|
-
["RightShin", [
|
|
577
|
-
[0.0, [-15, 0, 0]],
|
|
578
|
-
[0.3, [-45, 0, 0]],
|
|
579
|
-
[0.6, [-15, 0, 0]],
|
|
580
|
-
[0.9, [-45, 0, 0]],
|
|
581
|
-
[1.2, [-15, 0, 0]]
|
|
582
|
-
]]
|
|
583
|
-
]]
|
|
584
|
-
];
|
|
585
|
-
|
|
586
|
-
armature(animations=robot_anim) {
|
|
587
|
-
bone(name="Root", t=[0, 0, 13.0], r=[-5, 0, 0]) {
|
|
588
|
-
robot_body();
|
|
589
|
-
|
|
590
|
-
bone(name="LeftArm", t=[8.2, 0.5, 0.0], r=[15, -30, 0]) {
|
|
591
|
-
arm_upper();
|
|
592
|
-
bone(name="LeftForearm", t=[0, 0, -2.8], r=[30, 0, 0]) {
|
|
593
|
-
arm_forearm();
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
bone(name="RightArm", t=[-8.2, 0.5, 0.0], r=[15, 30, 0]) {
|
|
598
|
-
arm_upper();
|
|
599
|
-
bone(name="RightForearm", t=[0, 0, -2.8], r=[30, 0, 0]) {
|
|
600
|
-
arm_forearm();
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
bone(name="LeftLeg", t=[3.6, 0.0, -7.0], r=[15, 0, 6]) {
|
|
605
|
-
upper_leg();
|
|
606
|
-
bone(name="LeftShin", t=[0, 0, -3.0], r=[-15, 0, 0]) {
|
|
607
|
-
lower_leg();
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
bone(name="RightLeg", t=[-3.6, 0.0, -7.0], r=[15, 0, -6]) {
|
|
612
|
-
upper_leg();
|
|
613
|
-
bone(name="RightShin", t=[0, 0, -3.0], r=[-15, 0, 0]) {
|
|
614
|
-
lower_leg();
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
`,
|
|
620
|
-
);
|
|
621
|
-
|
|
622
|
-
writeFile(
|
|
623
|
-
"assets/models/toy_blocks.scad",
|
|
624
|
-
`/* Model Name: toy_blocks */
|
|
625
|
-
$fn = 24;
|
|
626
|
-
$asa = 45.0;
|
|
627
|
-
|
|
628
|
-
module toy_block(col) {
|
|
629
|
-
color(col, metalness=0.05, roughness=0.50, $asa=45) {
|
|
630
|
-
difference() {
|
|
631
|
-
cube([0.22, 0.22, 0.22], center=true);
|
|
632
|
-
for (rot = [[0,0,0], [90,0,0], [0,90,0], [-90,0,0], [0,-90,0], [180,0,0]]) {
|
|
633
|
-
rotate(rot) translate([0, 0, 0.11]) cube([0.16, 0.16, 0.02], center=true);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
color([0.90, 0.90, 0.92], metalness=0.0, roughness=0.35, $asa=45) {
|
|
638
|
-
sphere(r=0.06);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
module toy_blocks() {
|
|
643
|
-
translate([0, 0, 0.11])
|
|
644
|
-
rotate([0, 0, 12])
|
|
645
|
-
toy_block([0.85, 0.22, 0.25]);
|
|
646
|
-
|
|
647
|
-
translate([0.24, -0.04, 0.11])
|
|
648
|
-
rotate([0, 0, -15])
|
|
649
|
-
toy_block([0.12, 0.65, 0.85]);
|
|
650
|
-
|
|
651
|
-
translate([0.10, -0.02, 0.33])
|
|
652
|
-
rotate([0, 0, 6])
|
|
653
|
-
toy_block([0.92, 0.75, 0.15]);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
toy_blocks();
|
|
657
|
-
`,
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
// -------------------------------------------------------------------------
|
|
661
|
-
// 2B. PARTICLE SVG TEXTURES (Stand-alone vector assets)
|
|
662
|
-
// -------------------------------------------------------------------------
|
|
663
|
-
|
|
664
|
-
writeFile(
|
|
665
|
-
"assets/particles/confetti_star.svg",
|
|
666
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
|
667
|
-
<polygon points="32,2 41,21 62,24 47,40 50,61 32,51 14,61 17,40 2,24 23,21" fill="#FFFFFF"/>
|
|
668
|
-
</svg>`,
|
|
669
|
-
);
|
|
670
|
-
|
|
671
|
-
writeFile(
|
|
672
|
-
"assets/particles/confetti_ribbon.svg",
|
|
673
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="48" height="24" viewBox="0 0 48 24">
|
|
674
|
-
<rect x="2" y="3" width="44" height="18" rx="7" ry="7" fill="#FFFFFF"/>
|
|
675
|
-
</svg>`,
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
writeFile(
|
|
679
|
-
"assets/particles/sparkle.svg",
|
|
680
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
|
681
|
-
<path d="M32,0 Q32,32 64,32 Q32,32 32,64 Q32,32 0,32 Q32,32 32,0 Z" fill="#FFFFFF"/>
|
|
682
|
-
</svg>`,
|
|
683
|
-
);
|
|
684
|
-
|
|
685
|
-
// -------------------------------------------------------------------------
|
|
686
|
-
// 3. GDSCRIPT LOGIC
|
|
687
|
-
// -------------------------------------------------------------------------
|
|
688
|
-
|
|
689
|
-
writeFile(
|
|
690
|
-
"scripts/sound_manager.gd",
|
|
691
|
-
`class_name SoundManager
|
|
692
|
-
extends Node
|
|
693
|
-
|
|
694
|
-
var audio_players: Array[AudioStreamPlayer] = []
|
|
695
|
-
var max_polyphony: int = 8
|
|
696
|
-
var current_player: int = 0
|
|
697
|
-
var is_muted: bool = false
|
|
698
|
-
|
|
699
|
-
var chime_streams: Array[AudioStreamWAV] = []
|
|
700
|
-
var chalk_stream: AudioStreamWAV
|
|
701
|
-
var fanfare_stream: AudioStreamWAV
|
|
702
|
-
var pop_stream: AudioStreamWAV
|
|
703
|
-
var chatter_streams: Array[AudioStreamWAV] = []
|
|
704
|
-
|
|
705
|
-
func _ready() -> void:
|
|
706
|
-
for i in range(max_polyphony):
|
|
707
|
-
var player = AudioStreamPlayer.new()
|
|
708
|
-
add_child(player)
|
|
709
|
-
audio_players.append(player)
|
|
710
|
-
_generate_audio_assets()
|
|
711
|
-
|
|
712
|
-
func _generate_audio_assets() -> void:
|
|
713
|
-
var pentatonic_notes = [523.25, 587.33, 659.25, 783.99, 880.00, 1046.50, 1174.66, 1318.51]
|
|
714
|
-
for freq in pentatonic_notes:
|
|
715
|
-
chime_streams.append(_synthesize_bell(freq, 0.55))
|
|
716
|
-
|
|
717
|
-
chalk_stream = _synthesize_chalk_stroke()
|
|
718
|
-
fanfare_stream = _synthesize_fanfare()
|
|
719
|
-
pop_stream = _synthesize_pop()
|
|
720
|
-
|
|
721
|
-
for pitch in [620.0, 780.0, 950.0, 1100.0]:
|
|
722
|
-
chatter_streams.append(_synthesize_robot_blip(pitch))
|
|
723
|
-
|
|
724
|
-
func _play_stream(stream: AudioStreamWAV, volume_db: float = 0.0) -> void:
|
|
725
|
-
if is_muted or stream == null:
|
|
726
|
-
return
|
|
727
|
-
var player = audio_players[current_player]
|
|
728
|
-
current_player = (current_player + 1) % max_polyphony
|
|
729
|
-
player.stream = stream
|
|
730
|
-
player.volume_db = volume_db
|
|
731
|
-
player.play()
|
|
732
|
-
|
|
733
|
-
func play_chime(index: int) -> void:
|
|
734
|
-
if chime_streams.is_empty():
|
|
735
|
-
return
|
|
736
|
-
var idx = clamp(index, 0, chime_streams.size() - 1)
|
|
737
|
-
_play_stream(chime_streams[idx], -3.0)
|
|
738
|
-
|
|
739
|
-
func play_chalk() -> void:
|
|
740
|
-
_play_stream(chalk_stream, -14.0)
|
|
741
|
-
|
|
742
|
-
func play_fanfare() -> void:
|
|
743
|
-
_play_stream(fanfare_stream, 0.0)
|
|
744
|
-
|
|
745
|
-
func play_pop() -> void:
|
|
746
|
-
_play_stream(pop_stream, -4.0)
|
|
747
|
-
|
|
748
|
-
func play_robot_voice() -> void:
|
|
749
|
-
if chatter_streams.is_empty():
|
|
750
|
-
return
|
|
751
|
-
var idx = randi() % chatter_streams.size()
|
|
752
|
-
_play_stream(chatter_streams[idx], -6.0)
|
|
753
|
-
|
|
754
|
-
func toggle_mute() -> bool:
|
|
755
|
-
is_muted = !is_muted
|
|
756
|
-
return is_muted
|
|
757
|
-
|
|
758
|
-
func _synthesize_bell(freq: float, duration: float) -> AudioStreamWAV:
|
|
759
|
-
var sample_rate = 22050
|
|
760
|
-
var num_samples = int(duration * sample_rate)
|
|
761
|
-
var samples = PackedFloat32Array()
|
|
762
|
-
samples.resize(num_samples)
|
|
763
|
-
var two_pi = TAU
|
|
764
|
-
|
|
765
|
-
for i in range(num_samples):
|
|
766
|
-
var t = float(i) / float(sample_rate)
|
|
767
|
-
var env = exp(-t * 5.5)
|
|
768
|
-
var s1 = sin(two_pi * freq * t)
|
|
769
|
-
var s2 = 0.35 * sin(two_pi * (freq * 2.0) * t)
|
|
770
|
-
var s3 = 0.15 * sin(two_pi * (freq * 3.0) * t)
|
|
771
|
-
samples[i] = (s1 + s2 + s3) * env * 0.75
|
|
772
|
-
|
|
773
|
-
return _pack_wav(samples, sample_rate)
|
|
774
|
-
|
|
775
|
-
func _synthesize_pop() -> AudioStreamWAV:
|
|
776
|
-
var sample_rate = 22050
|
|
777
|
-
var duration = 0.08
|
|
778
|
-
var num_samples = int(duration * sample_rate)
|
|
779
|
-
var samples = PackedFloat32Array()
|
|
780
|
-
samples.resize(num_samples)
|
|
781
|
-
|
|
782
|
-
for i in range(num_samples):
|
|
783
|
-
var t = float(i) / float(sample_rate)
|
|
784
|
-
var progress = t / duration
|
|
785
|
-
var freq = lerp(680.0, 140.0, progress)
|
|
786
|
-
var env = 1.0 - progress
|
|
787
|
-
samples[i] = sin(TAU * freq * t) * env * 0.8
|
|
788
|
-
|
|
789
|
-
return _pack_wav(samples, sample_rate)
|
|
790
|
-
|
|
791
|
-
func _synthesize_chalk_stroke() -> AudioStreamWAV:
|
|
792
|
-
var sample_rate = 22050
|
|
793
|
-
var duration = 0.09
|
|
794
|
-
var num_samples = int(duration * sample_rate)
|
|
795
|
-
var samples = PackedFloat32Array()
|
|
796
|
-
samples.resize(num_samples)
|
|
797
|
-
|
|
798
|
-
var noise_prev = 0.0
|
|
799
|
-
for i in range(num_samples):
|
|
800
|
-
var t = float(i) / float(sample_rate)
|
|
801
|
-
var env = sin((t / duration) * PI)
|
|
802
|
-
var white = (randf() * 2.0 - 1.0)
|
|
803
|
-
noise_prev = lerp(noise_prev, white, 0.35)
|
|
804
|
-
samples[i] = noise_prev * env * 0.5
|
|
805
|
-
|
|
806
|
-
return _pack_wav(samples, sample_rate)
|
|
807
|
-
|
|
808
|
-
func _synthesize_robot_blip(freq: float) -> AudioStreamWAV:
|
|
809
|
-
var sample_rate = 22050
|
|
810
|
-
var duration = 0.12
|
|
811
|
-
var num_samples = int(duration * sample_rate)
|
|
812
|
-
var samples = PackedFloat32Array()
|
|
813
|
-
samples.resize(num_samples)
|
|
814
|
-
|
|
815
|
-
for i in range(num_samples):
|
|
816
|
-
var t = float(i) / float(sample_rate)
|
|
817
|
-
var env = sin((t / duration) * PI)
|
|
818
|
-
var chirp = freq + sin(TAU * 35.0 * t) * 70.0
|
|
819
|
-
samples[i] = sin(TAU * chirp * t) * env * 0.65
|
|
820
|
-
|
|
821
|
-
return _pack_wav(samples, sample_rate)
|
|
822
|
-
|
|
823
|
-
func _synthesize_fanfare() -> AudioStreamWAV:
|
|
824
|
-
var sample_rate = 22050
|
|
825
|
-
var duration = 1.25
|
|
826
|
-
var num_samples = int(duration * sample_rate)
|
|
827
|
-
var samples = PackedFloat32Array()
|
|
828
|
-
samples.resize(num_samples)
|
|
829
|
-
|
|
830
|
-
var arpeggio = [523.25, 659.25, 783.99, 1046.50, 1318.51]
|
|
831
|
-
for i in range(num_samples):
|
|
832
|
-
var t = float(i) / float(sample_rate)
|
|
833
|
-
var note_idx = int(clamp(t / 0.18, 0, arpeggio.size() - 1))
|
|
834
|
-
var note_freq = arpeggio[note_idx]
|
|
835
|
-
var note_local_t = fmod(t, 0.18)
|
|
836
|
-
if note_idx == arpeggio.size() - 1:
|
|
837
|
-
note_local_t = t - (0.18 * 4.0)
|
|
838
|
-
|
|
839
|
-
var env = exp(-note_local_t * 3.5)
|
|
840
|
-
var s = sin(TAU * note_freq * t) + 0.3 * sin(TAU * (note_freq * 2.0) * t)
|
|
841
|
-
samples[i] = s * env * 0.7
|
|
842
|
-
|
|
843
|
-
return _pack_wav(samples, sample_rate)
|
|
844
|
-
|
|
845
|
-
func _pack_wav(samples: PackedFloat32Array, sample_rate: int) -> AudioStreamWAV:
|
|
846
|
-
var wav = AudioStreamWAV.new()
|
|
847
|
-
wav.format = AudioStreamWAV.FORMAT_16_BITS
|
|
848
|
-
wav.mix_rate = sample_rate
|
|
849
|
-
wav.stereo = false
|
|
850
|
-
var byte_data = PackedByteArray()
|
|
851
|
-
byte_data.resize(samples.size() * 2)
|
|
852
|
-
|
|
853
|
-
for i in range(samples.size()):
|
|
854
|
-
var val = clampf(samples[i], -1.0, 1.0)
|
|
855
|
-
var s16 = int(val * 32767.0)
|
|
856
|
-
byte_data.encode_s16(i * 2, s16)
|
|
857
|
-
|
|
858
|
-
wav.data = byte_data
|
|
859
|
-
return wav
|
|
860
|
-
`,
|
|
861
|
-
);
|
|
862
|
-
|
|
863
|
-
writeFile(
|
|
864
|
-
"scripts/digit_data.gd",
|
|
865
|
-
`class_name DigitData
|
|
866
|
-
extends RefCounted
|
|
867
|
-
|
|
868
|
-
static func get_digit_info(digit: int) -> Dictionary:
|
|
869
|
-
match digit:
|
|
870
|
-
0:
|
|
871
|
-
return {
|
|
872
|
-
"digit": 0,
|
|
873
|
-
"name": "Zero",
|
|
874
|
-
"rhyme": "Around and around and around we go,\\nwhen we get home we have a zero!",
|
|
875
|
-
"tip": "Start at the upper right! Curve up and left all the way around, and join back at the start!",
|
|
876
|
-
"strokes": [
|
|
877
|
-
[
|
|
878
|
-
Vector2(0.68, 0.26),
|
|
879
|
-
Vector2(0.50, 0.16),
|
|
880
|
-
Vector2(0.32, 0.22),
|
|
881
|
-
Vector2(0.24, 0.40),
|
|
882
|
-
Vector2(0.24, 0.60),
|
|
883
|
-
Vector2(0.32, 0.78),
|
|
884
|
-
Vector2(0.50, 0.84),
|
|
885
|
-
Vector2(0.68, 0.78),
|
|
886
|
-
Vector2(0.76, 0.60),
|
|
887
|
-
Vector2(0.76, 0.40),
|
|
888
|
-
Vector2(0.68, 0.26)
|
|
889
|
-
]
|
|
890
|
-
]
|
|
891
|
-
}
|
|
892
|
-
1:
|
|
893
|
-
return {
|
|
894
|
-
"digit": 1,
|
|
895
|
-
"name": "One",
|
|
896
|
-
"rhyme": "Number one is like a stick,\\na straight line down that's very quick!",
|
|
897
|
-
"tip": "Start at the top and slide straight down!",
|
|
898
|
-
"strokes": [
|
|
899
|
-
[
|
|
900
|
-
Vector2(0.40, 0.28),
|
|
901
|
-
Vector2(0.50, 0.16),
|
|
902
|
-
Vector2(0.50, 0.40),
|
|
903
|
-
Vector2(0.50, 0.64),
|
|
904
|
-
Vector2(0.50, 0.84)
|
|
905
|
-
]
|
|
906
|
-
]
|
|
907
|
-
}
|
|
908
|
-
2:
|
|
909
|
-
return {
|
|
910
|
-
"digit": 2,
|
|
911
|
-
"name": "Two",
|
|
912
|
-
"rhyme": "Around and back on the railroad track,\\ntwo, two, two!",
|
|
913
|
-
"tip": "Curve around the top, slide down, and across!",
|
|
914
|
-
"strokes": [
|
|
915
|
-
[
|
|
916
|
-
Vector2(0.30, 0.28),
|
|
917
|
-
Vector2(0.45, 0.16),
|
|
918
|
-
Vector2(0.66, 0.20),
|
|
919
|
-
Vector2(0.70, 0.35),
|
|
920
|
-
Vector2(0.55, 0.55),
|
|
921
|
-
Vector2(0.32, 0.84),
|
|
922
|
-
Vector2(0.52, 0.84),
|
|
923
|
-
Vector2(0.72, 0.84)
|
|
924
|
-
]
|
|
925
|
-
]
|
|
926
|
-
}
|
|
927
|
-
3:
|
|
928
|
-
return {
|
|
929
|
-
"digit": 3,
|
|
930
|
-
"name": "Three",
|
|
931
|
-
"rhyme": "Around the tree and around the tree,\\nthat's the way we make a three!",
|
|
932
|
-
"tip": "Make two bouncy hops to the right!",
|
|
933
|
-
"strokes": [
|
|
934
|
-
[
|
|
935
|
-
Vector2(0.32, 0.22),
|
|
936
|
-
Vector2(0.50, 0.16),
|
|
937
|
-
Vector2(0.68, 0.24),
|
|
938
|
-
Vector2(0.64, 0.42),
|
|
939
|
-
Vector2(0.48, 0.48),
|
|
940
|
-
Vector2(0.66, 0.58),
|
|
941
|
-
Vector2(0.70, 0.74),
|
|
942
|
-
Vector2(0.52, 0.84),
|
|
943
|
-
Vector2(0.32, 0.78)
|
|
944
|
-
]
|
|
945
|
-
]
|
|
946
|
-
}
|
|
947
|
-
4:
|
|
948
|
-
return {
|
|
949
|
-
"digit": 4,
|
|
950
|
-
"name": "Four",
|
|
951
|
-
"rhyme": "Down and over, down once more,\\nthat's the way we make a four!",
|
|
952
|
-
"tip": "Two strokes! First an L, then a straight stick!",
|
|
953
|
-
"strokes": [
|
|
954
|
-
[
|
|
955
|
-
Vector2(0.64, 0.16),
|
|
956
|
-
Vector2(0.45, 0.42),
|
|
957
|
-
Vector2(0.28, 0.64),
|
|
958
|
-
Vector2(0.50, 0.64),
|
|
959
|
-
Vector2(0.74, 0.64)
|
|
960
|
-
],
|
|
961
|
-
[
|
|
962
|
-
Vector2(0.64, 0.38),
|
|
963
|
-
Vector2(0.64, 0.64),
|
|
964
|
-
Vector2(0.64, 0.84)
|
|
965
|
-
]
|
|
966
|
-
]
|
|
967
|
-
}
|
|
968
|
-
5:
|
|
969
|
-
return {
|
|
970
|
-
"digit": 5,
|
|
971
|
-
"name": "Five",
|
|
972
|
-
"rhyme": "Straight line down, around we go,\\nput on a hat for number five, oh!",
|
|
973
|
-
"tip": "Down the neck, around the belly, then the hat!",
|
|
974
|
-
"strokes": [
|
|
975
|
-
[
|
|
976
|
-
Vector2(0.38, 0.20),
|
|
977
|
-
Vector2(0.36, 0.44),
|
|
978
|
-
Vector2(0.56, 0.44),
|
|
979
|
-
Vector2(0.72, 0.56),
|
|
980
|
-
Vector2(0.70, 0.74),
|
|
981
|
-
Vector2(0.54, 0.84),
|
|
982
|
-
Vector2(0.34, 0.80)
|
|
983
|
-
],
|
|
984
|
-
[
|
|
985
|
-
Vector2(0.38, 0.20),
|
|
986
|
-
Vector2(0.54, 0.20),
|
|
987
|
-
Vector2(0.70, 0.20)
|
|
988
|
-
]
|
|
989
|
-
]
|
|
990
|
-
}
|
|
991
|
-
6:
|
|
992
|
-
return {
|
|
993
|
-
"digit": 6,
|
|
994
|
-
"name": "Six",
|
|
995
|
-
"rhyme": "Down to a loop,\\na six rolls in a hoop!",
|
|
996
|
-
"tip": "Slide down and roll inside a cozy loop!",
|
|
997
|
-
"strokes": [
|
|
998
|
-
[
|
|
999
|
-
Vector2(0.64, 0.16),
|
|
1000
|
-
Vector2(0.42, 0.30),
|
|
1001
|
-
Vector2(0.28, 0.54),
|
|
1002
|
-
Vector2(0.32, 0.76),
|
|
1003
|
-
Vector2(0.50, 0.84),
|
|
1004
|
-
Vector2(0.70, 0.74),
|
|
1005
|
-
Vector2(0.70, 0.56),
|
|
1006
|
-
Vector2(0.50, 0.50),
|
|
1007
|
-
Vector2(0.30, 0.60)
|
|
1008
|
-
]
|
|
1009
|
-
]
|
|
1010
|
-
}
|
|
1011
|
-
7:
|
|
1012
|
-
return {
|
|
1013
|
-
"digit": 7,
|
|
1014
|
-
"name": "Seven",
|
|
1015
|
-
"rhyme": "Across the sky and down from heaven,\\nadd a belt to make a seven!",
|
|
1016
|
-
"tip": "Two strokes! Slide across, slant down, and draw a belt across the middle!",
|
|
1017
|
-
"strokes": [
|
|
1018
|
-
[
|
|
1019
|
-
Vector2(0.28, 0.18),
|
|
1020
|
-
Vector2(0.50, 0.18),
|
|
1021
|
-
Vector2(0.74, 0.18),
|
|
1022
|
-
Vector2(0.60, 0.42),
|
|
1023
|
-
Vector2(0.48, 0.64),
|
|
1024
|
-
Vector2(0.38, 0.84)
|
|
1025
|
-
],
|
|
1026
|
-
[
|
|
1027
|
-
Vector2(0.42, 0.52),
|
|
1028
|
-
Vector2(0.66, 0.52)
|
|
1029
|
-
]
|
|
1030
|
-
]
|
|
1031
|
-
}
|
|
1032
|
-
8:
|
|
1033
|
-
return {
|
|
1034
|
-
"digit": 8,
|
|
1035
|
-
"name": "Eight",
|
|
1036
|
-
"rhyme": "Make an S and do not wait,\\nclimb back up to make an eight!",
|
|
1037
|
-
"tip": "Start at the upper right! Curve up and left, cross down like an S, loop the bottom, and climb back home!",
|
|
1038
|
-
"strokes": [
|
|
1039
|
-
[
|
|
1040
|
-
Vector2(0.64, 0.22),
|
|
1041
|
-
Vector2(0.50, 0.16),
|
|
1042
|
-
Vector2(0.34, 0.24),
|
|
1043
|
-
Vector2(0.36, 0.38),
|
|
1044
|
-
Vector2(0.50, 0.48),
|
|
1045
|
-
Vector2(0.64, 0.60),
|
|
1046
|
-
Vector2(0.66, 0.76),
|
|
1047
|
-
Vector2(0.50, 0.84),
|
|
1048
|
-
Vector2(0.34, 0.78),
|
|
1049
|
-
Vector2(0.34, 0.62),
|
|
1050
|
-
Vector2(0.50, 0.48),
|
|
1051
|
-
Vector2(0.64, 0.22)
|
|
1052
|
-
]
|
|
1053
|
-
]
|
|
1054
|
-
}
|
|
1055
|
-
9:
|
|
1056
|
-
return {
|
|
1057
|
-
"digit": 9,
|
|
1058
|
-
"name": "Nine",
|
|
1059
|
-
"rhyme": "Make a loop and come back round,\\nslide down and curl along the ground!",
|
|
1060
|
-
"tip": "Start at the upper right! Loop up and around, slide straight down, and curl the tail to the left!",
|
|
1061
|
-
"strokes": [
|
|
1062
|
-
[
|
|
1063
|
-
Vector2(0.66, 0.24),
|
|
1064
|
-
Vector2(0.50, 0.16),
|
|
1065
|
-
Vector2(0.34, 0.26),
|
|
1066
|
-
Vector2(0.34, 0.42),
|
|
1067
|
-
Vector2(0.50, 0.50),
|
|
1068
|
-
Vector2(0.66, 0.42),
|
|
1069
|
-
Vector2(0.66, 0.24),
|
|
1070
|
-
Vector2(0.66, 0.52),
|
|
1071
|
-
Vector2(0.62, 0.72),
|
|
1072
|
-
Vector2(0.50, 0.84),
|
|
1073
|
-
Vector2(0.36, 0.84),
|
|
1074
|
-
Vector2(0.24, 0.84)
|
|
1075
|
-
]
|
|
1076
|
-
]
|
|
1077
|
-
}
|
|
1078
|
-
_:
|
|
1079
|
-
return get_digit_info(0)
|
|
1080
|
-
`,
|
|
1081
|
-
);
|
|
1082
|
-
|
|
1083
|
-
writeFile(
|
|
1084
|
-
"scripts/teacher_robot.gd",
|
|
1085
|
-
`class_name TeacherRobot
|
|
1086
|
-
extends Node3D
|
|
1087
|
-
|
|
1088
|
-
signal speech_requested(text: String)
|
|
1089
|
-
|
|
1090
|
-
@export var look_target_node: Node3D
|
|
1091
|
-
var anim_player: AnimationPlayer = null
|
|
1092
|
-
|
|
1093
|
-
var base_rotation_y: float = deg_to_rad(215.0)
|
|
1094
|
-
var target_turn_y: float = 0.0
|
|
1095
|
-
var base_position_y: float = 0.08
|
|
1096
|
-
var idle_timer: float = 0.0
|
|
1097
|
-
var is_praising: bool = false
|
|
1098
|
-
var is_grounded: bool = false
|
|
1099
|
-
|
|
1100
|
-
func _ready() -> void:
|
|
1101
|
-
rotation.y = base_rotation_y
|
|
1102
|
-
position.y = base_position_y
|
|
1103
|
-
_find_animation_player(self)
|
|
1104
|
-
if anim_player:
|
|
1105
|
-
anim_player.animation_finished.connect(_on_anim_finished)
|
|
1106
|
-
play_idle()
|
|
1107
|
-
_ground_to_floor()
|
|
1108
|
-
|
|
1109
|
-
func _ground_to_floor() -> void:
|
|
1110
|
-
await get_tree().process_frame
|
|
1111
|
-
|
|
1112
|
-
var min_y = INF
|
|
1113
|
-
var visual_nodes = find_children("*", "VisualInstance3D", true)
|
|
1114
|
-
for node in visual_nodes:
|
|
1115
|
-
if node is VisualInstance3D:
|
|
1116
|
-
var aabb = node.get_aabb()
|
|
1117
|
-
if aabb.size.length_squared() < 0.0001:
|
|
1118
|
-
continue
|
|
1119
|
-
var trans = node.global_transform
|
|
1120
|
-
for i in range(8):
|
|
1121
|
-
var corner = aabb.get_endpoint(i)
|
|
1122
|
-
var world_corner = trans * corner
|
|
1123
|
-
min_y = minf(min_y, world_corner.y)
|
|
1124
|
-
|
|
1125
|
-
if min_y < INF and absf(min_y) > 0.001:
|
|
1126
|
-
position.y -= min_y
|
|
1127
|
-
elif min_y == INF:
|
|
1128
|
-
position.y = 0.08
|
|
1129
|
-
|
|
1130
|
-
base_position_y = position.y
|
|
1131
|
-
is_grounded = true
|
|
1132
|
-
|
|
1133
|
-
func _find_animation_player(node: Node) -> void:
|
|
1134
|
-
if node is AnimationPlayer:
|
|
1135
|
-
anim_player = node
|
|
1136
|
-
return
|
|
1137
|
-
for child in node.get_children():
|
|
1138
|
-
_find_animation_player(child)
|
|
1139
|
-
if anim_player != null:
|
|
1140
|
-
return
|
|
1141
|
-
|
|
1142
|
-
func play_idle() -> void:
|
|
1143
|
-
is_praising = false
|
|
1144
|
-
target_turn_y = 0.0
|
|
1145
|
-
if anim_player and anim_player.has_animation("Idle"):
|
|
1146
|
-
var anim = anim_player.get_animation("Idle")
|
|
1147
|
-
if anim:
|
|
1148
|
-
anim.loop_mode = Animation.LOOP_LINEAR
|
|
1149
|
-
anim_player.play("Idle")
|
|
1150
|
-
|
|
1151
|
-
func play_praise() -> void:
|
|
1152
|
-
is_praising = true
|
|
1153
|
-
target_turn_y = deg_to_rad(-15.0)
|
|
1154
|
-
if anim_player and anim_player.has_animation("Praise"):
|
|
1155
|
-
anim_player.play("Praise")
|
|
1156
|
-
else:
|
|
1157
|
-
var tween = create_tween()
|
|
1158
|
-
tween.tween_property(self, "position:y", base_position_y + 0.25, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
|
1159
|
-
tween.tween_property(self, "position:y", base_position_y, 0.25).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
|
|
1160
|
-
tween.tween_callback(func(): is_praising = false)
|
|
1161
|
-
|
|
1162
|
-
func _on_anim_finished(anim_name: StringName) -> void:
|
|
1163
|
-
if anim_name == "Praise":
|
|
1164
|
-
play_idle()
|
|
1165
|
-
elif anim_name == "Idle" and not is_praising:
|
|
1166
|
-
if anim_player and anim_player.has_animation("Idle"):
|
|
1167
|
-
anim_player.play("Idle")
|
|
1168
|
-
|
|
1169
|
-
func _process(delta: float) -> void:
|
|
1170
|
-
idle_timer += delta
|
|
1171
|
-
|
|
1172
|
-
if not is_praising and is_grounded:
|
|
1173
|
-
position.y = lerp(position.y, base_position_y, delta * 8.0)
|
|
1174
|
-
|
|
1175
|
-
rotation.y = lerp_angle(rotation.y, base_rotation_y + target_turn_y, delta * 4.0)
|
|
1176
|
-
|
|
1177
|
-
func look_at_canvas(turn: bool) -> void:
|
|
1178
|
-
if is_praising:
|
|
1179
|
-
return
|
|
1180
|
-
target_turn_y = deg_to_rad(15.0) if turn else 0.0
|
|
1181
|
-
`,
|
|
1182
|
-
);
|
|
1183
|
-
|
|
1184
|
-
writeFile(
|
|
1185
|
-
"scripts/writing_canvas.gd",
|
|
1186
|
-
`class_name WritingCanvas
|
|
1187
|
-
extends Control
|
|
1188
|
-
|
|
1189
|
-
signal checkpoint_hit(pos: Vector2, index: int)
|
|
1190
|
-
signal digit_completed(stars: int)
|
|
1191
|
-
signal stroke_started()
|
|
1192
|
-
signal stroke_finished()
|
|
1193
|
-
signal demo_finished()
|
|
1194
|
-
|
|
1195
|
-
var hit_radius: float = 46.0
|
|
1196
|
-
var line_width: float = 18.0
|
|
1197
|
-
var guide_visible: bool = true
|
|
1198
|
-
|
|
1199
|
-
var digit_data: Dictionary = {}
|
|
1200
|
-
var strokes_target: Array = []
|
|
1201
|
-
var active_stroke_idx: int = 0
|
|
1202
|
-
var active_checkpoint_idx: int = 0
|
|
1203
|
-
var completed_checkpoints: Array = []
|
|
1204
|
-
|
|
1205
|
-
var drawn_strokes: Array = []
|
|
1206
|
-
var current_stroke: PackedVector2Array = PackedVector2Array()
|
|
1207
|
-
var is_drawing: bool = false
|
|
1208
|
-
var brush_color: Color = Color(0.2, 0.85, 1.0)
|
|
1209
|
-
|
|
1210
|
-
var is_demo_playing: bool = false
|
|
1211
|
-
var demo_timer: float = 0.0
|
|
1212
|
-
var demo_stylus_pos: Vector2 = Vector2.ZERO
|
|
1213
|
-
|
|
1214
|
-
var pulse_time: float = 0.0
|
|
1215
|
-
var recent_hit_anim: float = 0.0
|
|
1216
|
-
|
|
1217
|
-
func _ready() -> void:
|
|
1218
|
-
set_process(true)
|
|
1219
|
-
queue_redraw()
|
|
1220
|
-
|
|
1221
|
-
func load_digit(data: Dictionary) -> void:
|
|
1222
|
-
digit_data = data
|
|
1223
|
-
strokes_target = data.get("strokes", [])
|
|
1224
|
-
reset_canvas()
|
|
1225
|
-
|
|
1226
|
-
func reset_canvas() -> void:
|
|
1227
|
-
drawn_strokes.clear()
|
|
1228
|
-
current_stroke.clear()
|
|
1229
|
-
completed_checkpoints.clear()
|
|
1230
|
-
active_stroke_idx = 0
|
|
1231
|
-
active_checkpoint_idx = 0
|
|
1232
|
-
is_drawing = false
|
|
1233
|
-
is_demo_playing = false
|
|
1234
|
-
queue_redraw()
|
|
1235
|
-
|
|
1236
|
-
func undo_last_stroke() -> void:
|
|
1237
|
-
if drawn_strokes.size() > 0:
|
|
1238
|
-
drawn_strokes.pop_back()
|
|
1239
|
-
if active_stroke_idx > 0:
|
|
1240
|
-
active_stroke_idx -= 1
|
|
1241
|
-
active_checkpoint_idx = 0
|
|
1242
|
-
_recompute_completed_checkpoints()
|
|
1243
|
-
else:
|
|
1244
|
-
completed_checkpoints.clear()
|
|
1245
|
-
active_checkpoint_idx = 0
|
|
1246
|
-
queue_redraw()
|
|
1247
|
-
|
|
1248
|
-
func _recompute_completed_checkpoints() -> void:
|
|
1249
|
-
completed_checkpoints.clear()
|
|
1250
|
-
for s_idx in range(active_stroke_idx):
|
|
1251
|
-
if s_idx < strokes_target.size():
|
|
1252
|
-
var pts = strokes_target[s_idx]
|
|
1253
|
-
for pt in pts:
|
|
1254
|
-
completed_checkpoints.append(pt * size)
|
|
1255
|
-
|
|
1256
|
-
func start_demo() -> void:
|
|
1257
|
-
reset_canvas()
|
|
1258
|
-
is_demo_playing = true
|
|
1259
|
-
demo_timer = 0.0
|
|
1260
|
-
queue_redraw()
|
|
1261
|
-
|
|
1262
|
-
func set_brush_color(col: Color) -> void:
|
|
1263
|
-
brush_color = col
|
|
1264
|
-
queue_redraw()
|
|
1265
|
-
|
|
1266
|
-
func toggle_guides() -> bool:
|
|
1267
|
-
guide_visible = !guide_visible
|
|
1268
|
-
queue_redraw()
|
|
1269
|
-
return guide_visible
|
|
1270
|
-
|
|
1271
|
-
func _gui_input(event: InputEvent) -> void:
|
|
1272
|
-
if is_demo_playing:
|
|
1273
|
-
return
|
|
1274
|
-
|
|
1275
|
-
if event is InputEventMouseButton:
|
|
1276
|
-
if event.button_index == MOUSE_BUTTON_LEFT:
|
|
1277
|
-
if event.pressed:
|
|
1278
|
-
_start_drawing(event.position)
|
|
1279
|
-
else:
|
|
1280
|
-
_finish_drawing()
|
|
1281
|
-
elif event is InputEventMouseMotion and is_drawing:
|
|
1282
|
-
_add_drawing_point(event.position)
|
|
1283
|
-
elif event is InputEventScreenTouch:
|
|
1284
|
-
if event.pressed:
|
|
1285
|
-
_start_drawing(event.position)
|
|
1286
|
-
else:
|
|
1287
|
-
_finish_drawing()
|
|
1288
|
-
elif event is InputEventScreenDrag and is_drawing:
|
|
1289
|
-
_add_drawing_point(event.position)
|
|
1290
|
-
|
|
1291
|
-
func _start_drawing(pos: Vector2) -> void:
|
|
1292
|
-
is_drawing = true
|
|
1293
|
-
current_stroke = PackedVector2Array([pos])
|
|
1294
|
-
_check_checkpoint_proximity(pos)
|
|
1295
|
-
stroke_started.emit()
|
|
1296
|
-
queue_redraw()
|
|
1297
|
-
|
|
1298
|
-
func _add_drawing_point(pos: Vector2) -> void:
|
|
1299
|
-
if current_stroke.is_empty():
|
|
1300
|
-
current_stroke.append(pos)
|
|
1301
|
-
else:
|
|
1302
|
-
var last_pt = current_stroke[current_stroke.size() - 1]
|
|
1303
|
-
if last_pt.distance_squared_to(pos) >= 16.0:
|
|
1304
|
-
current_stroke.append(pos)
|
|
1305
|
-
_check_checkpoint_proximity(pos)
|
|
1306
|
-
queue_redraw()
|
|
1307
|
-
|
|
1308
|
-
func _finish_drawing() -> void:
|
|
1309
|
-
if is_drawing and current_stroke.size() > 0:
|
|
1310
|
-
drawn_strokes.append(current_stroke)
|
|
1311
|
-
current_stroke = PackedVector2Array()
|
|
1312
|
-
is_drawing = false
|
|
1313
|
-
stroke_finished.emit()
|
|
1314
|
-
queue_redraw()
|
|
1315
|
-
|
|
1316
|
-
func _check_checkpoint_proximity(pos: Vector2) -> void:
|
|
1317
|
-
if active_stroke_idx >= strokes_target.size():
|
|
1318
|
-
return
|
|
1319
|
-
|
|
1320
|
-
var current_stroke_checkpoints = strokes_target[active_stroke_idx]
|
|
1321
|
-
if active_checkpoint_idx >= current_stroke_checkpoints.size():
|
|
1322
|
-
return
|
|
1323
|
-
|
|
1324
|
-
var target_norm = current_stroke_checkpoints[active_checkpoint_idx]
|
|
1325
|
-
var target_px = target_norm * size
|
|
1326
|
-
|
|
1327
|
-
if pos.distance_to(target_px) <= hit_radius:
|
|
1328
|
-
completed_checkpoints.append(target_px)
|
|
1329
|
-
checkpoint_hit.emit(target_px, active_checkpoint_idx)
|
|
1330
|
-
recent_hit_anim = 1.0
|
|
1331
|
-
|
|
1332
|
-
active_checkpoint_idx += 1
|
|
1333
|
-
if active_checkpoint_idx >= current_stroke_checkpoints.size():
|
|
1334
|
-
active_stroke_idx += 1
|
|
1335
|
-
active_checkpoint_idx = 0
|
|
1336
|
-
|
|
1337
|
-
if active_stroke_idx >= strokes_target.size():
|
|
1338
|
-
var stars = _calculate_stars()
|
|
1339
|
-
digit_completed.emit(stars)
|
|
1340
|
-
|
|
1341
|
-
func _calculate_stars() -> int:
|
|
1342
|
-
var total_checkpoints = 0
|
|
1343
|
-
for stroke in strokes_target:
|
|
1344
|
-
total_checkpoints += stroke.size()
|
|
1345
|
-
if completed_checkpoints.size() >= total_checkpoints:
|
|
1346
|
-
return 3
|
|
1347
|
-
elif completed_checkpoints.size() >= int(total_checkpoints * 0.75):
|
|
1348
|
-
return 2
|
|
1349
|
-
return 1
|
|
1350
|
-
|
|
1351
|
-
func _process(delta: float) -> void:
|
|
1352
|
-
var needs_redraw = false
|
|
1353
|
-
|
|
1354
|
-
if recent_hit_anim > 0.0:
|
|
1355
|
-
recent_hit_anim = max(0.0, recent_hit_anim - delta * 3.0)
|
|
1356
|
-
needs_redraw = true
|
|
1357
|
-
|
|
1358
|
-
if is_demo_playing:
|
|
1359
|
-
_process_demo(delta)
|
|
1360
|
-
needs_redraw = true
|
|
1361
|
-
|
|
1362
|
-
# Only animate pulsing guide circles when guides are visible and work remains
|
|
1363
|
-
if guide_visible and active_stroke_idx < strokes_target.size():
|
|
1364
|
-
pulse_time += delta
|
|
1365
|
-
needs_redraw = true
|
|
1366
|
-
|
|
1367
|
-
if needs_redraw:
|
|
1368
|
-
queue_redraw()
|
|
1369
|
-
|
|
1370
|
-
func _process_demo(delta: float) -> void:
|
|
1371
|
-
if strokes_target.is_empty():
|
|
1372
|
-
is_demo_playing = false
|
|
1373
|
-
return
|
|
1374
|
-
|
|
1375
|
-
var current_stroke_pts = strokes_target[active_stroke_idx]
|
|
1376
|
-
var total_pts = current_stroke_pts.size()
|
|
1377
|
-
demo_timer += delta * 1.8
|
|
1378
|
-
|
|
1379
|
-
var step_idx = int(demo_timer)
|
|
1380
|
-
var fract = fmod(demo_timer, 1.0)
|
|
1381
|
-
|
|
1382
|
-
if step_idx < total_pts - 1:
|
|
1383
|
-
var p0 = current_stroke_pts[step_idx] * size
|
|
1384
|
-
var p1 = current_stroke_pts[step_idx + 1] * size
|
|
1385
|
-
demo_stylus_pos = p0.lerp(p1, fract)
|
|
1386
|
-
|
|
1387
|
-
if current_stroke.is_empty():
|
|
1388
|
-
current_stroke.append(demo_stylus_pos)
|
|
1389
|
-
else:
|
|
1390
|
-
current_stroke.append(demo_stylus_pos)
|
|
1391
|
-
|
|
1392
|
-
if fract < 0.15 and active_checkpoint_idx == step_idx:
|
|
1393
|
-
completed_checkpoints.append(p0)
|
|
1394
|
-
checkpoint_hit.emit(p0, step_idx)
|
|
1395
|
-
active_checkpoint_idx += 1
|
|
1396
|
-
else:
|
|
1397
|
-
var last_pt = current_stroke_pts[total_pts - 1] * size
|
|
1398
|
-
demo_stylus_pos = last_pt
|
|
1399
|
-
completed_checkpoints.append(last_pt)
|
|
1400
|
-
checkpoint_hit.emit(last_pt, total_pts - 1)
|
|
1401
|
-
drawn_strokes.append(current_stroke)
|
|
1402
|
-
current_stroke = PackedVector2Array()
|
|
1403
|
-
|
|
1404
|
-
active_stroke_idx += 1
|
|
1405
|
-
active_checkpoint_idx = 0
|
|
1406
|
-
demo_timer = 0.0
|
|
1407
|
-
|
|
1408
|
-
if active_stroke_idx >= strokes_target.size():
|
|
1409
|
-
is_demo_playing = false
|
|
1410
|
-
demo_finished.emit()
|
|
1411
|
-
|
|
1412
|
-
func _draw() -> void:
|
|
1413
|
-
var canvas_rect = Rect2(Vector2.ZERO, size)
|
|
1414
|
-
|
|
1415
|
-
draw_rect(canvas_rect, Color(0.10, 0.14, 0.18), true)
|
|
1416
|
-
var line_color = Color(1.0, 1.0, 1.0, 0.10)
|
|
1417
|
-
draw_line(Vector2(0, size.y * 0.16), Vector2(size.x, size.y * 0.16), line_color, 2.0)
|
|
1418
|
-
draw_dashed_line(Vector2(0, size.y * 0.50), Vector2(size.x, size.y * 0.50), Color(0.3, 0.7, 1.0, 0.20), 3.0, 10.0)
|
|
1419
|
-
draw_line(Vector2(0, size.y * 0.84), Vector2(size.x, size.y * 0.84), line_color, 2.0)
|
|
1420
|
-
|
|
1421
|
-
if guide_visible:
|
|
1422
|
-
for s_idx in range(strokes_target.size()):
|
|
1423
|
-
var pts = strokes_target[s_idx]
|
|
1424
|
-
for i in range(pts.size() - 1):
|
|
1425
|
-
var p1 = pts[i] * size
|
|
1426
|
-
var p2 = pts[i + 1] * size
|
|
1427
|
-
var guide_color = Color(0.9, 0.9, 1.0, 0.25)
|
|
1428
|
-
if s_idx == active_stroke_idx:
|
|
1429
|
-
guide_color = Color(1.0, 0.9, 0.3, 0.50)
|
|
1430
|
-
draw_dashed_line(p1, p2, guide_color, 4.0, 8.0)
|
|
1431
|
-
var mid = p1.lerp(p2, 0.5)
|
|
1432
|
-
var dir = (p2 - p1).normalized()
|
|
1433
|
-
var normal = Vector2(-dir.y, dir.x)
|
|
1434
|
-
draw_line(mid - dir * 6 + normal * 6, mid, guide_color, 3.0)
|
|
1435
|
-
draw_line(mid - dir * 6 - normal * 6, mid, guide_color, 3.0)
|
|
1436
|
-
|
|
1437
|
-
for stroke in drawn_strokes:
|
|
1438
|
-
_draw_chalk_line(stroke, brush_color)
|
|
1439
|
-
|
|
1440
|
-
if current_stroke.size() > 1:
|
|
1441
|
-
_draw_chalk_line(current_stroke, brush_color)
|
|
1442
|
-
|
|
1443
|
-
if guide_visible:
|
|
1444
|
-
var default_font = get_theme_default_font()
|
|
1445
|
-
for s_idx in range(strokes_target.size()):
|
|
1446
|
-
var pts = strokes_target[s_idx]
|
|
1447
|
-
for i in range(pts.size()):
|
|
1448
|
-
var pt = pts[i] * size
|
|
1449
|
-
var is_completed = completed_checkpoints.has(pt)
|
|
1450
|
-
var is_next = (s_idx == active_stroke_idx and i == active_checkpoint_idx)
|
|
1451
|
-
|
|
1452
|
-
if is_completed:
|
|
1453
|
-
draw_circle(pt, 12.0, Color(0.2, 0.85, 0.4, 0.9))
|
|
1454
|
-
draw_circle(pt, 6.0, Color(1.0, 1.0, 1.0, 0.9))
|
|
1455
|
-
elif is_next:
|
|
1456
|
-
var pulse = 1.0 + sin(pulse_time * 6.0) * 0.18
|
|
1457
|
-
draw_circle(pt, hit_radius * 0.42 * pulse, Color(1.0, 0.85, 0.2, 0.35))
|
|
1458
|
-
draw_circle(pt, 14.0 * pulse, Color(1.0, 0.85, 0.15))
|
|
1459
|
-
draw_circle(pt, 7.0, Color(1.0, 1.0, 1.0))
|
|
1460
|
-
if i == 0 and default_font:
|
|
1461
|
-
draw_string(default_font, pt + Vector2(-4, -18), str(s_idx + 1), HORIZONTAL_ALIGNMENT_CENTER, -1, 16, Color(1, 1, 0.3))
|
|
1462
|
-
else:
|
|
1463
|
-
draw_circle(pt, 8.0, Color(1.0, 1.0, 1.0, 0.25))
|
|
1464
|
-
|
|
1465
|
-
if is_demo_playing:
|
|
1466
|
-
draw_circle(demo_stylus_pos, 16.0, Color(1.0, 0.4, 0.2, 0.7))
|
|
1467
|
-
draw_circle(demo_stylus_pos, 8.0, Color(0.2, 0.9, 1.0))
|
|
1468
|
-
|
|
1469
|
-
func _draw_chalk_line(points: PackedVector2Array, col: Color) -> void:
|
|
1470
|
-
if points.size() < 2:
|
|
1471
|
-
return
|
|
1472
|
-
draw_polyline(points, Color(col.r, col.g, col.b, 0.35), line_width + 8.0, true)
|
|
1473
|
-
draw_polyline(points, col, line_width, true)
|
|
1474
|
-
draw_circle(points[0], line_width * 0.5, col)
|
|
1475
|
-
draw_circle(points[points.size() - 1], line_width * 0.5, col)
|
|
1476
|
-
`,
|
|
1477
|
-
);
|
|
1478
|
-
|
|
1479
|
-
writeFile(
|
|
1480
|
-
"scripts/main_game.gd",
|
|
1481
|
-
`class_name MainGame
|
|
1482
|
-
extends Node3D
|
|
1483
|
-
|
|
1484
|
-
@onready var sound_manager: SoundManager = $SoundManager
|
|
1485
|
-
@onready var teacher_robot: TeacherRobot = $World3D/MouseRobot
|
|
1486
|
-
@onready var toy_blocks: Node3D = $World3D/ToyBlocks
|
|
1487
|
-
|
|
1488
|
-
@onready var writing_canvas: WritingCanvas = $CanvasLayer/UI/MainHBox/BoardContainer/BoardPanel/WritingCanvas
|
|
1489
|
-
@onready var speech_label: Label = $CanvasLayer/UI/SpeechBalloon/BalloonMargin/VBox/SpeechText
|
|
1490
|
-
@onready var speech_balloon: Control = $CanvasLayer/UI/SpeechBalloon
|
|
1491
|
-
@onready var celebration_modal: Control = $CanvasLayer/CelebrationModal
|
|
1492
|
-
@onready var digit_title: Label = $CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/DigitTitle
|
|
1493
|
-
@onready var star_count_label: Label = $CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/StarDisplay/StarMargin/HBox/StarLabel
|
|
1494
|
-
@onready var star_icon: Control = $CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/StarDisplay/StarMargin/HBox/StarIcon
|
|
1495
|
-
@onready var star_rating_box: Control = $CanvasLayer/CelebrationModal/ModalPanel/VBox/StarRatingBox
|
|
1496
|
-
@onready var digit_btn_container: HBoxContainer = $CanvasLayer/UI/BottomBar/DigitScroll/DigitHBox
|
|
1497
|
-
@onready var confetti_stars: CPUParticles2D = $CanvasLayer/ConfettiStars
|
|
1498
|
-
@onready var confetti_ribbons: CPUParticles2D = $CanvasLayer/ConfettiRibbons
|
|
1499
|
-
@onready var checkpoint_sparkles: CPUParticles2D = $CanvasLayer/CheckpointSparkles
|
|
1500
|
-
@onready var tools_bar: HBoxContainer = $CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar
|
|
1501
|
-
|
|
1502
|
-
var current_digit: int = 1
|
|
1503
|
-
var stars_earned_map: Dictionary = {}
|
|
1504
|
-
var total_stars: int = 0
|
|
1505
|
-
var current_modal_stars: int = 3
|
|
1506
|
-
|
|
1507
|
-
var chalk_colors = [
|
|
1508
|
-
{"name": "ColorCyan", "color": Color(0.20, 0.85, 1.00)},
|
|
1509
|
-
{"name": "ColorYellow", "color": Color(1.00, 0.88, 0.20)},
|
|
1510
|
-
{"name": "ColorPink", "color": Color(1.00, 0.45, 0.70)},
|
|
1511
|
-
{"name": "ColorGreen", "color": Color(0.35, 0.95, 0.35)},
|
|
1512
|
-
{"name": "ColorWhite", "color": Color(0.96, 0.96, 0.98)}
|
|
1513
|
-
]
|
|
1514
|
-
var active_color_index: int = 0
|
|
1515
|
-
|
|
1516
|
-
func _ready() -> void:
|
|
1517
|
-
teacher_robot.rotation_degrees.y = 215.0
|
|
1518
|
-
teacher_robot.position = Vector3(-0.85, 0.08, -0.2)
|
|
1519
|
-
|
|
1520
|
-
toy_blocks.rotation_degrees.y = 180.0
|
|
1521
|
-
toy_blocks.position = Vector3(-1.15, 0.0, 0.15)
|
|
1522
|
-
|
|
1523
|
-
if star_icon:
|
|
1524
|
-
star_icon.draw.connect(_on_star_icon_draw)
|
|
1525
|
-
if star_rating_box:
|
|
1526
|
-
star_rating_box.draw.connect(_on_star_rating_box_draw)
|
|
1527
|
-
|
|
1528
|
-
_setup_particle_textures()
|
|
1529
|
-
_init_star_map()
|
|
1530
|
-
_create_digit_buttons()
|
|
1531
|
-
_setup_color_swatches()
|
|
1532
|
-
_load_digit(1)
|
|
1533
|
-
|
|
1534
|
-
writing_canvas.checkpoint_hit.connect(_on_checkpoint_hit)
|
|
1535
|
-
writing_canvas.digit_completed.connect(_on_digit_completed)
|
|
1536
|
-
writing_canvas.stroke_started.connect(_on_stroke_started)
|
|
1537
|
-
writing_canvas.stroke_finished.connect(_on_stroke_finished)
|
|
1538
|
-
writing_canvas.demo_finished.connect(_on_demo_finished)
|
|
1539
|
-
|
|
1540
|
-
func _setup_particle_textures() -> void:
|
|
1541
|
-
var star_tex = _generate_star_texture(36)
|
|
1542
|
-
var ribbon_tex = _generate_ribbon_texture(30, 14)
|
|
1543
|
-
var sparkle_tex = _generate_sparkle_texture(32)
|
|
1544
|
-
|
|
1545
|
-
if confetti_stars:
|
|
1546
|
-
confetti_stars.texture = star_tex
|
|
1547
|
-
if confetti_ribbons:
|
|
1548
|
-
confetti_ribbons.texture = ribbon_tex
|
|
1549
|
-
if checkpoint_sparkles:
|
|
1550
|
-
checkpoint_sparkles.texture = sparkle_tex
|
|
1551
|
-
|
|
1552
|
-
func _init_star_map() -> void:
|
|
1553
|
-
for d in range(10):
|
|
1554
|
-
stars_earned_map[d] = 0
|
|
1555
|
-
|
|
1556
|
-
func _create_digit_buttons() -> void:
|
|
1557
|
-
for child in digit_btn_container.get_children():
|
|
1558
|
-
child.queue_free()
|
|
1559
|
-
|
|
1560
|
-
var order = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
|
|
1561
|
-
for d in order:
|
|
1562
|
-
var btn = Button.new()
|
|
1563
|
-
btn.text = str(d)
|
|
1564
|
-
btn.custom_minimum_size = Vector2(64, 60)
|
|
1565
|
-
btn.add_theme_font_size_override("font_size", 28)
|
|
1566
|
-
btn.focus_mode = Control.FOCUS_NONE
|
|
1567
|
-
btn.pressed.connect(func(): _load_digit(d))
|
|
1568
|
-
digit_btn_container.add_child(btn)
|
|
1569
|
-
|
|
1570
|
-
func _setup_color_swatches() -> void:
|
|
1571
|
-
for i in range(chalk_colors.size()):
|
|
1572
|
-
var info = chalk_colors[i]
|
|
1573
|
-
var btn = tools_bar.get_node_or_null(info.name) as Button
|
|
1574
|
-
if btn:
|
|
1575
|
-
btn.text = ""
|
|
1576
|
-
btn.custom_minimum_size = Vector2(36, 36)
|
|
1577
|
-
var idx = i
|
|
1578
|
-
btn.pressed.connect(func(): _on_color_swatch_selected(idx))
|
|
1579
|
-
_update_color_swatches_ui()
|
|
1580
|
-
|
|
1581
|
-
func _on_color_swatch_selected(idx: int) -> void:
|
|
1582
|
-
sound_manager.play_pop()
|
|
1583
|
-
active_color_index = idx
|
|
1584
|
-
writing_canvas.set_brush_color(chalk_colors[idx].color)
|
|
1585
|
-
_update_color_swatches_ui()
|
|
1586
|
-
|
|
1587
|
-
func _update_color_swatches_ui() -> void:
|
|
1588
|
-
for i in range(chalk_colors.size()):
|
|
1589
|
-
var info = chalk_colors[i]
|
|
1590
|
-
var btn = tools_bar.get_node_or_null(info.name) as Button
|
|
1591
|
-
if not btn:
|
|
1592
|
-
continue
|
|
1593
|
-
var is_active = (i == active_color_index)
|
|
1594
|
-
var style = StyleBoxFlat.new()
|
|
1595
|
-
style.set_corner_radius_all(18)
|
|
1596
|
-
style.bg_color = info.color
|
|
1597
|
-
if is_active:
|
|
1598
|
-
style.border_width_left = 3
|
|
1599
|
-
style.border_width_top = 3
|
|
1600
|
-
style.border_width_right = 3
|
|
1601
|
-
style.border_width_bottom = 3
|
|
1602
|
-
style.border_color = Color.WHITE
|
|
1603
|
-
style.shadow_size = 4
|
|
1604
|
-
style.shadow_color = Color(1, 1, 1, 0.4)
|
|
1605
|
-
else:
|
|
1606
|
-
style.border_width_left = 1
|
|
1607
|
-
style.border_width_top = 1
|
|
1608
|
-
style.border_width_right = 1
|
|
1609
|
-
style.border_width_bottom = 1
|
|
1610
|
-
style.border_color = Color(1, 1, 1, 0.25)
|
|
1611
|
-
btn.add_theme_stylebox_override("normal", style)
|
|
1612
|
-
btn.add_theme_stylebox_override("hover", style)
|
|
1613
|
-
btn.add_theme_stylebox_override("pressed", style)
|
|
1614
|
-
|
|
1615
|
-
func _load_digit(d: int) -> void:
|
|
1616
|
-
current_digit = d
|
|
1617
|
-
var data = DigitData.get_digit_info(d)
|
|
1618
|
-
writing_canvas.load_digit(data)
|
|
1619
|
-
|
|
1620
|
-
digit_title.text = "Trace Number " + str(d) + "!"
|
|
1621
|
-
_set_speech(data.rhyme)
|
|
1622
|
-
sound_manager.play_pop()
|
|
1623
|
-
sound_manager.play_robot_voice()
|
|
1624
|
-
_update_digit_buttons_ui()
|
|
1625
|
-
|
|
1626
|
-
func _update_digit_buttons_ui() -> void:
|
|
1627
|
-
var buttons = digit_btn_container.get_children()
|
|
1628
|
-
for btn in buttons:
|
|
1629
|
-
if not btn is Button:
|
|
1630
|
-
continue
|
|
1631
|
-
var digit_val = int(btn.text)
|
|
1632
|
-
var is_active = (digit_val == current_digit)
|
|
1633
|
-
var stars = stars_earned_map.get(digit_val, 0)
|
|
1634
|
-
|
|
1635
|
-
var style = StyleBoxFlat.new()
|
|
1636
|
-
style.set_corner_radius_all(12)
|
|
1637
|
-
if is_active:
|
|
1638
|
-
style.bg_color = Color(0.18, 0.45, 0.75, 0.95)
|
|
1639
|
-
style.border_width_left = 3
|
|
1640
|
-
style.border_width_top = 3
|
|
1641
|
-
style.border_width_right = 3
|
|
1642
|
-
style.border_width_bottom = 3
|
|
1643
|
-
style.border_color = Color(1.0, 0.85, 0.25, 1.0)
|
|
1644
|
-
btn.add_theme_color_override("font_color", Color(1.0, 0.95, 0.35))
|
|
1645
|
-
elif stars > 0:
|
|
1646
|
-
style.bg_color = Color(0.14, 0.24, 0.22, 0.90)
|
|
1647
|
-
style.border_width_left = 2
|
|
1648
|
-
style.border_width_top = 2
|
|
1649
|
-
style.border_width_right = 2
|
|
1650
|
-
style.border_width_bottom = 2
|
|
1651
|
-
style.border_color = Color(0.25, 0.70, 0.45, 0.7)
|
|
1652
|
-
btn.add_theme_color_override("font_color", Color(0.65, 0.95, 0.75))
|
|
1653
|
-
else:
|
|
1654
|
-
style.bg_color = Color(0.16, 0.20, 0.26, 0.90)
|
|
1655
|
-
style.border_width_left = 2
|
|
1656
|
-
style.border_width_top = 2
|
|
1657
|
-
style.border_width_right = 2
|
|
1658
|
-
style.border_width_bottom = 2
|
|
1659
|
-
style.border_color = Color(0.28, 0.34, 0.42, 0.7)
|
|
1660
|
-
btn.add_theme_color_override("font_color", Color(0.85, 0.90, 0.95))
|
|
1661
|
-
|
|
1662
|
-
btn.add_theme_stylebox_override("normal", style)
|
|
1663
|
-
btn.add_theme_stylebox_override("hover", style)
|
|
1664
|
-
btn.add_theme_stylebox_override("pressed", style)
|
|
1665
|
-
|
|
1666
|
-
func _set_speech(text: String) -> void:
|
|
1667
|
-
speech_label.text = text
|
|
1668
|
-
speech_balloon.visible = true
|
|
1669
|
-
speech_balloon.scale = Vector2(0.85, 0.85)
|
|
1670
|
-
var tween = create_tween()
|
|
1671
|
-
tween.tween_property(speech_balloon, "scale", Vector2.ONE, 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
|
1672
|
-
|
|
1673
|
-
func _on_stroke_started() -> void:
|
|
1674
|
-
sound_manager.play_chalk()
|
|
1675
|
-
teacher_robot.look_at_canvas(true)
|
|
1676
|
-
|
|
1677
|
-
func _on_stroke_finished() -> void:
|
|
1678
|
-
teacher_robot.look_at_canvas(false)
|
|
1679
|
-
|
|
1680
|
-
func _on_checkpoint_hit(pos: Vector2, index: int) -> void:
|
|
1681
|
-
sound_manager.play_chime(index)
|
|
1682
|
-
_spawn_checkpoint_sparkles(pos)
|
|
1683
|
-
|
|
1684
|
-
func _spawn_checkpoint_sparkles(canvas_local_pos: Vector2) -> void:
|
|
1685
|
-
if not checkpoint_sparkles:
|
|
1686
|
-
return
|
|
1687
|
-
checkpoint_sparkles.global_position = writing_canvas.global_position + canvas_local_pos
|
|
1688
|
-
checkpoint_sparkles.restart()
|
|
1689
|
-
checkpoint_sparkles.emitting = true
|
|
1690
|
-
|
|
1691
|
-
func _on_demo_finished() -> void:
|
|
1692
|
-
var tween = create_tween()
|
|
1693
|
-
tween.tween_interval(1.5)
|
|
1694
|
-
tween.tween_callback(func():
|
|
1695
|
-
if not writing_canvas.is_demo_playing:
|
|
1696
|
-
writing_canvas.reset_canvas()
|
|
1697
|
-
)
|
|
1698
|
-
|
|
1699
|
-
func _on_digit_completed(stars: int) -> void:
|
|
1700
|
-
stars_earned_map[current_digit] = max(stars_earned_map[current_digit], stars)
|
|
1701
|
-
_recalculate_stars()
|
|
1702
|
-
|
|
1703
|
-
sound_manager.play_fanfare()
|
|
1704
|
-
sound_manager.play_robot_voice()
|
|
1705
|
-
teacher_robot.play_praise()
|
|
1706
|
-
|
|
1707
|
-
_trigger_celebration_confetti()
|
|
1708
|
-
|
|
1709
|
-
var data = DigitData.get_digit_info(current_digit)
|
|
1710
|
-
_show_celebration(stars, data.rhyme)
|
|
1711
|
-
|
|
1712
|
-
func _trigger_celebration_confetti() -> void:
|
|
1713
|
-
if confetti_stars:
|
|
1714
|
-
confetti_stars.restart()
|
|
1715
|
-
confetti_stars.emitting = true
|
|
1716
|
-
if confetti_ribbons:
|
|
1717
|
-
confetti_ribbons.restart()
|
|
1718
|
-
confetti_ribbons.emitting = true
|
|
1719
|
-
|
|
1720
|
-
func _recalculate_stars() -> void:
|
|
1721
|
-
total_stars = 0
|
|
1722
|
-
for d in range(10):
|
|
1723
|
-
total_stars += stars_earned_map[d]
|
|
1724
|
-
star_count_label.text = str(total_stars) + " / 30"
|
|
1725
|
-
_update_digit_buttons_ui()
|
|
1726
|
-
|
|
1727
|
-
func _show_celebration(stars: int, rhyme: String) -> void:
|
|
1728
|
-
current_modal_stars = stars
|
|
1729
|
-
speech_balloon.visible = false
|
|
1730
|
-
|
|
1731
|
-
var modal_rhyme_label = $CanvasLayer/CelebrationModal/ModalPanel/VBox/RhymeText
|
|
1732
|
-
modal_rhyme_label.text = rhyme
|
|
1733
|
-
star_rating_box.queue_redraw()
|
|
1734
|
-
|
|
1735
|
-
celebration_modal.visible = true
|
|
1736
|
-
celebration_modal.scale = Vector2(0.6, 0.6)
|
|
1737
|
-
var tween = create_tween()
|
|
1738
|
-
tween.tween_property(celebration_modal, "scale", Vector2.ONE, 0.35).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
|
|
1739
|
-
|
|
1740
|
-
func _on_next_pressed() -> void:
|
|
1741
|
-
sound_manager.play_pop()
|
|
1742
|
-
celebration_modal.visible = false
|
|
1743
|
-
var next_d = (current_digit + 1) % 10
|
|
1744
|
-
_load_digit(next_d)
|
|
1745
|
-
|
|
1746
|
-
func _on_replay_pressed() -> void:
|
|
1747
|
-
sound_manager.play_pop()
|
|
1748
|
-
celebration_modal.visible = false
|
|
1749
|
-
writing_canvas.reset_canvas()
|
|
1750
|
-
teacher_robot.play_idle()
|
|
1751
|
-
speech_balloon.visible = true
|
|
1752
|
-
|
|
1753
|
-
func _on_demo_btn_pressed() -> void:
|
|
1754
|
-
sound_manager.play_pop()
|
|
1755
|
-
sound_manager.play_robot_voice()
|
|
1756
|
-
var data = DigitData.get_digit_info(current_digit)
|
|
1757
|
-
_set_speech("Watch closely! " + data.tip)
|
|
1758
|
-
writing_canvas.start_demo()
|
|
1759
|
-
|
|
1760
|
-
func _on_clear_btn_pressed() -> void:
|
|
1761
|
-
sound_manager.play_pop()
|
|
1762
|
-
writing_canvas.reset_canvas()
|
|
1763
|
-
|
|
1764
|
-
func _on_undo_btn_pressed() -> void:
|
|
1765
|
-
sound_manager.play_pop()
|
|
1766
|
-
writing_canvas.undo_last_stroke()
|
|
1767
|
-
|
|
1768
|
-
func _on_audio_toggle_pressed() -> void:
|
|
1769
|
-
var muted = sound_manager.toggle_mute()
|
|
1770
|
-
var btn = $CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/AudioBtn as Button
|
|
1771
|
-
if btn:
|
|
1772
|
-
btn.text = "Sound: OFF" if muted else "Sound: ON"
|
|
1773
|
-
|
|
1774
|
-
func _on_guide_toggle_pressed() -> void:
|
|
1775
|
-
var visible_guides = writing_canvas.toggle_guides()
|
|
1776
|
-
var btn = $CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/GuideBtn as Button
|
|
1777
|
-
if btn:
|
|
1778
|
-
btn.text = "Guides: ON" if visible_guides else "Guides: OFF"
|
|
1779
|
-
|
|
1780
|
-
# -------------------------------------------------------------
|
|
1781
|
-
# Procedural Vector Particle Textures
|
|
1782
|
-
# -------------------------------------------------------------
|
|
1783
|
-
func _generate_star_texture(size: int = 36) -> ImageTexture:
|
|
1784
|
-
var img = Image.create(size, size, false, Image.FORMAT_RGBA8)
|
|
1785
|
-
var center = Vector2(size * 0.5, size * 0.5)
|
|
1786
|
-
var r_outer = size * 0.46
|
|
1787
|
-
var r_inner = size * 0.20
|
|
1788
|
-
for y in range(size):
|
|
1789
|
-
for x in range(size):
|
|
1790
|
-
var pt = Vector2(x + 0.5, y + 0.5)
|
|
1791
|
-
var diff = pt - center
|
|
1792
|
-
var dist = diff.length()
|
|
1793
|
-
if dist > r_outer + 1.0:
|
|
1794
|
-
continue
|
|
1795
|
-
var angle = atan2(diff.y, diff.x)
|
|
1796
|
-
var a_norm = fmod(angle + PI * 0.5 + TAU * 2.0, TAU)
|
|
1797
|
-
var arm = fmod(a_norm, TAU / 5.0) / (TAU / 5.0)
|
|
1798
|
-
var factor = absf(arm - 0.5) * 2.0
|
|
1799
|
-
var r_star = lerp(r_inner, r_outer, factor)
|
|
1800
|
-
if dist <= r_star:
|
|
1801
|
-
var alpha = clampf((r_star - dist) * 2.0, 0.0, 1.0)
|
|
1802
|
-
img.set_pixel(x, y, Color(1.0, 1.0, 1.0, alpha))
|
|
1803
|
-
return ImageTexture.create_from_image(img)
|
|
1804
|
-
|
|
1805
|
-
func _generate_ribbon_texture(width: int = 30, height: int = 14) -> ImageTexture:
|
|
1806
|
-
var img = Image.create(width, height, false, Image.FORMAT_RGBA8)
|
|
1807
|
-
var radius = height * 0.40
|
|
1808
|
-
for y in range(height):
|
|
1809
|
-
for x in range(width):
|
|
1810
|
-
var cx = clampf(x + 0.5, radius, width - radius)
|
|
1811
|
-
var cy = clampf(y + 0.5, radius, height - radius)
|
|
1812
|
-
var dist = Vector2(x + 0.5 - cx, y + 0.5 - cy).length()
|
|
1813
|
-
if dist <= radius:
|
|
1814
|
-
var alpha = clampf((radius - dist) * 2.0, 0.0, 1.0)
|
|
1815
|
-
img.set_pixel(x, y, Color(1.0, 1.0, 1.0, alpha))
|
|
1816
|
-
return ImageTexture.create_from_image(img)
|
|
1817
|
-
|
|
1818
|
-
func _generate_sparkle_texture(size: int = 32) -> ImageTexture:
|
|
1819
|
-
var img = Image.create(size, size, false, Image.FORMAT_RGBA8)
|
|
1820
|
-
var center = Vector2(size * 0.5, size * 0.5)
|
|
1821
|
-
var half = size * 0.46
|
|
1822
|
-
for y in range(size):
|
|
1823
|
-
for x in range(size):
|
|
1824
|
-
var nx = absf((x + 0.5 - center.x) / half)
|
|
1825
|
-
var ny = absf((y + 0.5 - center.y) / half)
|
|
1826
|
-
var val = sqrt(nx) + sqrt(ny)
|
|
1827
|
-
if val <= 1.0:
|
|
1828
|
-
var alpha = clampf((1.0 - val) * 2.5, 0.0, 1.0)
|
|
1829
|
-
img.set_pixel(x, y, Color(1.0, 1.0, 1.0, alpha))
|
|
1830
|
-
return ImageTexture.create_from_image(img)
|
|
1831
|
-
|
|
1832
|
-
# -------------------------------------------------------------
|
|
1833
|
-
# Procedural Vector 5-Pointed Star UI Drawing
|
|
1834
|
-
# -------------------------------------------------------------
|
|
1835
|
-
func _on_star_icon_draw() -> void:
|
|
1836
|
-
if not star_icon:
|
|
1837
|
-
return
|
|
1838
|
-
var radius = minf(star_icon.size.x, star_icon.size.y) * 0.44
|
|
1839
|
-
var center = star_icon.size * 0.5
|
|
1840
|
-
_draw_5pt_star(star_icon, center, radius, Color(1.0, 0.85, 0.15), Color(1.0, 0.96, 0.55))
|
|
1841
|
-
|
|
1842
|
-
func _on_star_rating_box_draw() -> void:
|
|
1843
|
-
if not star_rating_box:
|
|
1844
|
-
return
|
|
1845
|
-
var star_w = 44.0
|
|
1846
|
-
var spacing = 18.0
|
|
1847
|
-
var total_w = 3.0 * star_w + 2.0 * spacing
|
|
1848
|
-
var start_x = (star_rating_box.size.x - total_w) * 0.5 + star_w * 0.5
|
|
1849
|
-
var cy = star_rating_box.size.y * 0.5
|
|
1850
|
-
for i in range(3):
|
|
1851
|
-
var cx = start_x + float(i) * (star_w + spacing)
|
|
1852
|
-
var is_filled = (i < current_modal_stars)
|
|
1853
|
-
var fill_col = Color(1.0, 0.85, 0.15) if is_filled else Color(0.25, 0.30, 0.38, 0.6)
|
|
1854
|
-
var line_col = Color(1.0, 0.95, 0.50) if is_filled else Color(0.40, 0.45, 0.55, 0.6)
|
|
1855
|
-
_draw_5pt_star(star_rating_box, Vector2(cx, cy), star_w * 0.5, fill_col, line_col)
|
|
1856
|
-
|
|
1857
|
-
static func _draw_5pt_star(ci: CanvasItem, center: Vector2, radius: float, fill_col: Color, line_col: Color) -> void:
|
|
1858
|
-
var pts = PackedVector2Array()
|
|
1859
|
-
for k in range(10):
|
|
1860
|
-
var r = radius if (k % 2 == 0) else (radius * 0.42)
|
|
1861
|
-
var angle = (float(k) * PI / 5.0) - (PI * 0.5)
|
|
1862
|
-
pts.append(center + Vector2(cos(angle), sin(angle)) * r)
|
|
1863
|
-
ci.draw_colored_polygon(pts, fill_col)
|
|
1864
|
-
pts.append(pts[0])
|
|
1865
|
-
ci.draw_polyline(pts, line_col, 2.2, true)
|
|
1866
|
-
`,
|
|
1867
|
-
);
|
|
1868
|
-
|
|
1869
|
-
// -------------------------------------------------------------------------
|
|
1870
|
-
// 4. GODOT 4 MAIN SCENE (.tscn)
|
|
1871
|
-
// -------------------------------------------------------------------------
|
|
1872
|
-
|
|
1873
|
-
writeFile(
|
|
1874
|
-
"scenes/main_game.tscn",
|
|
1875
|
-
`[gd_scene format=3 uid="uid://c8yjq01fkl23m"]
|
|
1876
|
-
|
|
1877
|
-
[ext_resource type="Script" path="res://scripts/main_game.gd" id="1_main"]
|
|
1878
|
-
[ext_resource type="Script" path="res://scripts/sound_manager.gd" id="2_sound"]
|
|
1879
|
-
[ext_resource type="PackedScene" path="res://assets/models/mouse_robot.scad" id="3_robot"]
|
|
1880
|
-
[ext_resource type="Script" path="res://scripts/teacher_robot.gd" id="4_teacher"]
|
|
1881
|
-
[ext_resource type="PackedScene" path="res://assets/models/toy_blocks.scad" id="5_blocks"]
|
|
1882
|
-
[ext_resource type="Script" path="res://scripts/writing_canvas.gd" id="6_canvas"]
|
|
1883
|
-
|
|
1884
|
-
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sky"]
|
|
1885
|
-
sky_top_color = Color(0.28, 0.50, 0.75, 1)
|
|
1886
|
-
sky_horizon_color = Color(0.60, 0.70, 0.80, 1)
|
|
1887
|
-
ground_bottom_color = Color(0.18, 0.16, 0.14, 1)
|
|
1888
|
-
ground_horizon_color = Color(0.55, 0.60, 0.65, 1)
|
|
1889
|
-
|
|
1890
|
-
[sub_resource type="Sky" id="Sky_env"]
|
|
1891
|
-
sky_material = SubResource("ProceduralSkyMaterial_sky")
|
|
1892
|
-
|
|
1893
|
-
[sub_resource type="Environment" id="Environment_main"]
|
|
1894
|
-
background_mode = 2
|
|
1895
|
-
sky = SubResource("Sky_env")
|
|
1896
|
-
ambient_light_source = 2
|
|
1897
|
-
ambient_light_color = Color(0.55, 0.60, 0.65, 1)
|
|
1898
|
-
tonemap_mode = 3
|
|
1899
|
-
tonemap_exposure = 0.6
|
|
1900
|
-
glow_enabled = true
|
|
1901
|
-
glow_bloom = 0.08
|
|
1902
|
-
|
|
1903
|
-
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_floor"]
|
|
1904
|
-
albedo_color = Color(0.65, 0.52, 0.38, 1)
|
|
1905
|
-
roughness = 0.70
|
|
1906
|
-
|
|
1907
|
-
[sub_resource type="PlaneMesh" id="PlaneMesh_floor"]
|
|
1908
|
-
material = SubResource("StandardMaterial3D_floor")
|
|
1909
|
-
size = Vector2(14, 14)
|
|
1910
|
-
|
|
1911
|
-
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_wall"]
|
|
1912
|
-
albedo_color = Color(0.48, 0.56, 0.64, 1)
|
|
1913
|
-
roughness = 0.85
|
|
1914
|
-
|
|
1915
|
-
[sub_resource type="BoxMesh" id="BoxMesh_wall"]
|
|
1916
|
-
material = SubResource("StandardMaterial3D_wall")
|
|
1917
|
-
size = Vector3(14, 5, 0.2)
|
|
1918
|
-
|
|
1919
|
-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_header"]
|
|
1920
|
-
bg_color = Color(0.12, 0.16, 0.22, 0.95)
|
|
1921
|
-
border_width_bottom = 2
|
|
1922
|
-
border_color = Color(0.25, 0.35, 0.48, 0.8)
|
|
1923
|
-
|
|
1924
|
-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_starbox"]
|
|
1925
|
-
bg_color = Color(0.18, 0.22, 0.30, 0.9)
|
|
1926
|
-
corner_radius_top_left = 14
|
|
1927
|
-
corner_radius_top_right = 14
|
|
1928
|
-
corner_radius_bottom_right = 14
|
|
1929
|
-
corner_radius_bottom_left = 14
|
|
1930
|
-
border_width_left = 1
|
|
1931
|
-
border_width_top = 1
|
|
1932
|
-
border_width_right = 1
|
|
1933
|
-
border_width_bottom = 1
|
|
1934
|
-
border_color = Color(1, 0.85, 0.25, 0.6)
|
|
1935
|
-
|
|
1936
|
-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_board"]
|
|
1937
|
-
bg_color = Color(0.10, 0.14, 0.18, 1)
|
|
1938
|
-
corner_radius_top_left = 14
|
|
1939
|
-
corner_radius_top_right = 14
|
|
1940
|
-
corner_radius_bottom_right = 14
|
|
1941
|
-
corner_radius_bottom_left = 14
|
|
1942
|
-
border_width_left = 3
|
|
1943
|
-
border_width_top = 3
|
|
1944
|
-
border_width_right = 3
|
|
1945
|
-
border_width_bottom = 3
|
|
1946
|
-
border_color = Color(0.22, 0.28, 0.38, 0.9)
|
|
1947
|
-
shadow_size = 10
|
|
1948
|
-
shadow_color = Color(0, 0, 0, 0.4)
|
|
1949
|
-
|
|
1950
|
-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tools"]
|
|
1951
|
-
bg_color = Color(0.12, 0.16, 0.22, 0.95)
|
|
1952
|
-
corner_radius_top_left = 14
|
|
1953
|
-
corner_radius_top_right = 14
|
|
1954
|
-
corner_radius_bottom_right = 14
|
|
1955
|
-
corner_radius_bottom_left = 14
|
|
1956
|
-
border_width_left = 1
|
|
1957
|
-
border_width_top = 1
|
|
1958
|
-
border_width_right = 1
|
|
1959
|
-
border_width_bottom = 1
|
|
1960
|
-
border_color = Color(0.25, 0.34, 0.46, 0.8)
|
|
1961
|
-
shadow_size = 6
|
|
1962
|
-
shadow_color = Color(0, 0, 0, 0.3)
|
|
1963
|
-
|
|
1964
|
-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_speech"]
|
|
1965
|
-
bg_color = Color(0.10, 0.14, 0.20, 0.92)
|
|
1966
|
-
corner_radius_top_left = 16
|
|
1967
|
-
corner_radius_top_right = 16
|
|
1968
|
-
corner_radius_bottom_right = 16
|
|
1969
|
-
corner_radius_bottom_left = 16
|
|
1970
|
-
border_width_left = 2
|
|
1971
|
-
border_width_top = 2
|
|
1972
|
-
border_width_right = 2
|
|
1973
|
-
border_width_bottom = 2
|
|
1974
|
-
border_color = Color(0.20, 0.75, 0.95, 0.60)
|
|
1975
|
-
shadow_size = 8
|
|
1976
|
-
shadow_color = Color(0, 0, 0, 0.35)
|
|
1977
|
-
|
|
1978
|
-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bottom"]
|
|
1979
|
-
bg_color = Color(0.10, 0.13, 0.18, 0.95)
|
|
1980
|
-
border_width_top = 2
|
|
1981
|
-
border_color = Color(0.22, 0.28, 0.38, 0.8)
|
|
1982
|
-
|
|
1983
|
-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_modal"]
|
|
1984
|
-
bg_color = Color(0.12, 0.16, 0.22, 0.98)
|
|
1985
|
-
corner_radius_top_left = 20
|
|
1986
|
-
corner_radius_top_right = 20
|
|
1987
|
-
corner_radius_bottom_right = 20
|
|
1988
|
-
corner_radius_bottom_left = 20
|
|
1989
|
-
border_width_left = 3
|
|
1990
|
-
border_width_top = 3
|
|
1991
|
-
border_width_right = 3
|
|
1992
|
-
border_width_bottom = 3
|
|
1993
|
-
border_color = Color(1.0, 0.82, 0.20, 0.95)
|
|
1994
|
-
shadow_size = 16
|
|
1995
|
-
shadow_color = Color(0, 0, 0, 0.5)
|
|
1996
|
-
|
|
1997
|
-
[sub_resource type="Curve" id="Curve_pop"]
|
|
1998
|
-
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.12, 1), 0.0, 0.0, 0, 0, Vector2(0.75, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
|
1999
|
-
point_count = 4
|
|
2000
|
-
|
|
2001
|
-
[sub_resource type="Gradient" id="Gradient_fade"]
|
|
2002
|
-
offsets = PackedFloat32Array(0, 0.15, 0.7, 1)
|
|
2003
|
-
colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
|
|
2004
|
-
|
|
2005
|
-
[node name="MainGame" type="Node3D"]
|
|
2006
|
-
script = ExtResource("1_main")
|
|
2007
|
-
|
|
2008
|
-
[node name="SoundManager" type="Node" parent="."]
|
|
2009
|
-
script = ExtResource("2_sound")
|
|
2010
|
-
|
|
2011
|
-
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
|
2012
|
-
environment = SubResource("Environment_main")
|
|
2013
|
-
|
|
2014
|
-
[node name="KeyLight" type="DirectionalLight3D" parent="."]
|
|
2015
|
-
transform = Transform3D(0.866, -0.287, 0.409, 0, 0.819, 0.574, -0.5, -0.497, 0.709, 1.2, 4, 3)
|
|
2016
|
-
light_color = Color(1, 0.98, 0.92, 1)
|
|
2017
|
-
light_energy = 0.5
|
|
2018
|
-
shadow_enabled = true
|
|
2019
|
-
shadow_opacity = 0.7
|
|
2020
|
-
directional_shadow_max_distance = 6.0
|
|
2021
|
-
directional_shadow_bias = 0.02
|
|
2022
|
-
directional_shadow_normal_bias = 1.0
|
|
2023
|
-
|
|
2024
|
-
[node name="FillLight" type="DirectionalLight3D" parent="."]
|
|
2025
|
-
transform = Transform3D(0.707, 0, -0.707, -0.353, 0.866, -0.353, 0.612, 0.5, 0.612, -2, 3, 2)
|
|
2026
|
-
light_color = Color(0.6, 0.8, 1, 1)
|
|
2027
|
-
light_energy = 0.5
|
|
2028
|
-
sky_mode = 1
|
|
2029
|
-
|
|
2030
|
-
[node name="RimLight" type="DirectionalLight3D" parent="."]
|
|
2031
|
-
transform = Transform3D(-0.866, 0, -0.5, 0, 1, 0, 0.5, 0, -0.866, -1, 2, -3)
|
|
2032
|
-
light_color = Color(1, 0.7, 0.9, 1)
|
|
2033
|
-
light_energy = 0.4
|
|
2034
|
-
sky_mode = 1
|
|
2035
|
-
|
|
2036
|
-
[node name="Camera3D" type="Camera3D" parent="."]
|
|
2037
|
-
transform = Transform3D(1, 0, 0, 0, 0.985, 0.174, 0, -0.174, 0.985, 0.2, 1.22, 2.3)
|
|
2038
|
-
current = true
|
|
2039
|
-
fov = 55.0
|
|
2040
|
-
|
|
2041
|
-
[node name="World3D" type="Node3D" parent="."]
|
|
2042
|
-
|
|
2043
|
-
[node name="ClassroomFloor" type="MeshInstance3D" parent="World3D"]
|
|
2044
|
-
mesh = SubResource("PlaneMesh_floor")
|
|
2045
|
-
|
|
2046
|
-
[node name="ClassroomWall" type="MeshInstance3D" parent="World3D"]
|
|
2047
|
-
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -2.4)
|
|
2048
|
-
mesh = SubResource("BoxMesh_wall")
|
|
2049
|
-
|
|
2050
|
-
[node name="MouseRobot" parent="World3D" instance=ExtResource("3_robot")]
|
|
2051
|
-
transform = Transform3D(-0.0508, 0, -0.0356, 0, 0.062, 0, 0.0356, 0, -0.0508, -0.85, 0.08, -0.2)
|
|
2052
|
-
script = ExtResource("4_teacher")
|
|
2053
|
-
|
|
2054
|
-
[node name="ToyBlocks" parent="World3D" instance=ExtResource("5_blocks")]
|
|
2055
|
-
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -1.15, 0, 0.15)
|
|
2056
|
-
|
|
2057
|
-
[node name="CanvasLayer" type="CanvasLayer" parent="."]
|
|
2058
|
-
|
|
2059
|
-
[node name="UI" type="Control" parent="CanvasLayer"]
|
|
2060
|
-
layout_mode = 3
|
|
2061
|
-
anchors_preset = 15
|
|
2062
|
-
anchor_right = 1.0
|
|
2063
|
-
anchor_bottom = 1.0
|
|
2064
|
-
grow_horizontal = 2
|
|
2065
|
-
grow_vertical = 2
|
|
2066
|
-
mouse_filter = 2
|
|
2067
|
-
|
|
2068
|
-
[node name="HeaderBar" type="PanelContainer" parent="CanvasLayer/UI"]
|
|
2069
|
-
layout_mode = 1
|
|
2070
|
-
anchors_preset = 10
|
|
2071
|
-
anchor_right = 1.0
|
|
2072
|
-
offset_bottom = 68.0
|
|
2073
|
-
grow_horizontal = 2
|
|
2074
|
-
theme_override_styles/panel = SubResource("StyleBoxFlat_header")
|
|
2075
|
-
|
|
2076
|
-
[node name="HeaderMargin" type="MarginContainer" parent="CanvasLayer/UI/HeaderBar"]
|
|
2077
|
-
layout_mode = 2
|
|
2078
|
-
theme_override_constants/margin_left = 20
|
|
2079
|
-
theme_override_constants/margin_top = 8
|
|
2080
|
-
theme_override_constants/margin_right = 20
|
|
2081
|
-
theme_override_constants/margin_bottom = 8
|
|
2082
|
-
|
|
2083
|
-
[node name="HBox" type="HBoxContainer" parent="CanvasLayer/UI/HeaderBar/HeaderMargin"]
|
|
2084
|
-
layout_mode = 2
|
|
2085
|
-
theme_override_constants/separation = 16
|
|
2086
|
-
|
|
2087
|
-
[node name="AppTitle" type="Label" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox"]
|
|
2088
|
-
layout_mode = 2
|
|
2089
|
-
theme_override_colors/font_color = Color(1, 0.85, 0.25, 1)
|
|
2090
|
-
theme_override_font_sizes/font_size = 22
|
|
2091
|
-
text = "Professor Pip's Digit Lab"
|
|
2092
|
-
|
|
2093
|
-
[node name="DigitTitle" type="Label" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox"]
|
|
2094
|
-
layout_mode = 2
|
|
2095
|
-
size_flags_horizontal = 3
|
|
2096
|
-
theme_override_colors/font_color = Color(0.3, 0.9, 1, 1)
|
|
2097
|
-
theme_override_font_sizes/font_size = 22
|
|
2098
|
-
text = "Trace Number 0!"
|
|
2099
|
-
horizontal_alignment = 1
|
|
2100
|
-
|
|
2101
|
-
[node name="StarDisplay" type="PanelContainer" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox"]
|
|
2102
|
-
layout_mode = 2
|
|
2103
|
-
theme_override_styles/panel = SubResource("StyleBoxFlat_starbox")
|
|
2104
|
-
|
|
2105
|
-
[node name="StarMargin" type="MarginContainer" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/StarDisplay"]
|
|
2106
|
-
layout_mode = 2
|
|
2107
|
-
theme_override_constants/margin_left = 10
|
|
2108
|
-
theme_override_constants/margin_top = 4
|
|
2109
|
-
theme_override_constants/margin_right = 12
|
|
2110
|
-
theme_override_constants/margin_bottom = 4
|
|
2111
|
-
|
|
2112
|
-
[node name="HBox" type="HBoxContainer" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/StarDisplay/StarMargin"]
|
|
2113
|
-
layout_mode = 2
|
|
2114
|
-
theme_override_constants/separation = 6
|
|
2115
|
-
|
|
2116
|
-
[node name="StarIcon" type="Control" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/StarDisplay/StarMargin/HBox"]
|
|
2117
|
-
custom_minimum_size = Vector2(24, 24)
|
|
2118
|
-
layout_mode = 2
|
|
2119
|
-
|
|
2120
|
-
[node name="StarLabel" type="Label" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/StarDisplay/StarMargin/HBox"]
|
|
2121
|
-
layout_mode = 2
|
|
2122
|
-
theme_override_colors/font_color = Color(1, 0.88, 0.2, 1)
|
|
2123
|
-
theme_override_font_sizes/font_size = 18
|
|
2124
|
-
text = "0 / 30"
|
|
2125
|
-
|
|
2126
|
-
[node name="DemoBtn" type="Button" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox"]
|
|
2127
|
-
layout_mode = 2
|
|
2128
|
-
theme_override_font_sizes/font_size = 16
|
|
2129
|
-
text = "Watch Pip"
|
|
2130
|
-
|
|
2131
|
-
[node name="GuideBtn" type="Button" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox"]
|
|
2132
|
-
layout_mode = 2
|
|
2133
|
-
theme_override_font_sizes/font_size = 16
|
|
2134
|
-
text = "Guides: ON"
|
|
2135
|
-
|
|
2136
|
-
[node name="AudioBtn" type="Button" parent="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox"]
|
|
2137
|
-
layout_mode = 2
|
|
2138
|
-
theme_override_font_sizes/font_size = 16
|
|
2139
|
-
text = "Sound: ON"
|
|
2140
|
-
|
|
2141
|
-
[node name="MainHBox" type="HBoxContainer" parent="CanvasLayer/UI"]
|
|
2142
|
-
layout_mode = 1
|
|
2143
|
-
anchors_preset = 15
|
|
2144
|
-
anchor_right = 1.0
|
|
2145
|
-
anchor_bottom = 1.0
|
|
2146
|
-
offset_top = 74.0
|
|
2147
|
-
offset_bottom = -96.0
|
|
2148
|
-
grow_horizontal = 2
|
|
2149
|
-
grow_vertical = 2
|
|
2150
|
-
mouse_filter = 2
|
|
2151
|
-
|
|
2152
|
-
[node name="LeftSpacer" type="Control" parent="CanvasLayer/UI/MainHBox"]
|
|
2153
|
-
layout_mode = 2
|
|
2154
|
-
size_flags_horizontal = 3
|
|
2155
|
-
mouse_filter = 2
|
|
2156
|
-
|
|
2157
|
-
[node name="BoardContainer" type="VBoxContainer" parent="CanvasLayer/UI/MainHBox"]
|
|
2158
|
-
layout_mode = 2
|
|
2159
|
-
size_flags_horizontal = 3
|
|
2160
|
-
theme_override_constants/separation = 10
|
|
2161
|
-
|
|
2162
|
-
[node name="BoardPanel" type="PanelContainer" parent="CanvasLayer/UI/MainHBox/BoardContainer"]
|
|
2163
|
-
layout_mode = 2
|
|
2164
|
-
size_flags_vertical = 3
|
|
2165
|
-
theme_override_styles/panel = SubResource("StyleBoxFlat_board")
|
|
2166
|
-
|
|
2167
|
-
[node name="WritingCanvas" type="Control" parent="CanvasLayer/UI/MainHBox/BoardContainer/BoardPanel"]
|
|
2168
|
-
layout_mode = 2
|
|
2169
|
-
size_flags_horizontal = 3
|
|
2170
|
-
size_flags_vertical = 3
|
|
2171
|
-
script = ExtResource("6_canvas")
|
|
2172
|
-
|
|
2173
|
-
[node name="ToolsContainer" type="PanelContainer" parent="CanvasLayer/UI/MainHBox/BoardContainer"]
|
|
2174
|
-
layout_mode = 2
|
|
2175
|
-
theme_override_styles/panel = SubResource("StyleBoxFlat_tools")
|
|
2176
|
-
|
|
2177
|
-
[node name="ToolsMargin" type="MarginContainer" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer"]
|
|
2178
|
-
layout_mode = 2
|
|
2179
|
-
theme_override_constants/margin_left = 14
|
|
2180
|
-
theme_override_constants/margin_top = 6
|
|
2181
|
-
theme_override_constants/margin_right = 14
|
|
2182
|
-
theme_override_constants/margin_bottom = 6
|
|
2183
|
-
|
|
2184
|
-
[node name="ToolsBar" type="HBoxContainer" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin"]
|
|
2185
|
-
layout_mode = 2
|
|
2186
|
-
theme_override_constants/separation = 12
|
|
2187
|
-
alignment = 1
|
|
2188
|
-
|
|
2189
|
-
[node name="ClearBtn" type="Button" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2190
|
-
custom_minimum_size = Vector2(85, 38)
|
|
2191
|
-
layout_mode = 2
|
|
2192
|
-
theme_override_font_sizes/font_size = 16
|
|
2193
|
-
text = "Clear"
|
|
2194
|
-
|
|
2195
|
-
[node name="UndoBtn" type="Button" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2196
|
-
custom_minimum_size = Vector2(85, 38)
|
|
2197
|
-
layout_mode = 2
|
|
2198
|
-
theme_override_font_sizes/font_size = 16
|
|
2199
|
-
text = "Undo"
|
|
2200
|
-
|
|
2201
|
-
[node name="ColorLabel" type="Label" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2202
|
-
layout_mode = 2
|
|
2203
|
-
theme_override_colors/font_color = Color(0.85, 0.90, 0.96, 1)
|
|
2204
|
-
theme_override_font_sizes/font_size = 16
|
|
2205
|
-
text = "Chalk:"
|
|
2206
|
-
|
|
2207
|
-
[node name="ColorCyan" type="Button" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2208
|
-
custom_minimum_size = Vector2(36, 36)
|
|
2209
|
-
layout_mode = 2
|
|
2210
|
-
focus_mode = 0
|
|
2211
|
-
|
|
2212
|
-
[node name="ColorYellow" type="Button" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2213
|
-
custom_minimum_size = Vector2(36, 36)
|
|
2214
|
-
layout_mode = 2
|
|
2215
|
-
focus_mode = 0
|
|
2216
|
-
|
|
2217
|
-
[node name="ColorPink" type="Button" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2218
|
-
custom_minimum_size = Vector2(36, 36)
|
|
2219
|
-
layout_mode = 2
|
|
2220
|
-
focus_mode = 0
|
|
2221
|
-
|
|
2222
|
-
[node name="ColorGreen" type="Button" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2223
|
-
custom_minimum_size = Vector2(36, 36)
|
|
2224
|
-
layout_mode = 2
|
|
2225
|
-
focus_mode = 0
|
|
2226
|
-
|
|
2227
|
-
[node name="ColorWhite" type="Button" parent="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar"]
|
|
2228
|
-
custom_minimum_size = Vector2(36, 36)
|
|
2229
|
-
layout_mode = 2
|
|
2230
|
-
focus_mode = 0
|
|
2231
|
-
|
|
2232
|
-
[node name="RightSpacer" type="Control" parent="CanvasLayer/UI/MainHBox"]
|
|
2233
|
-
layout_mode = 2
|
|
2234
|
-
size_flags_horizontal = 3
|
|
2235
|
-
size_flags_stretch_ratio = 0.1
|
|
2236
|
-
mouse_filter = 2
|
|
2237
|
-
|
|
2238
|
-
[node name="SpeechBalloon" type="PanelContainer" parent="CanvasLayer/UI"]
|
|
2239
|
-
layout_mode = 1
|
|
2240
|
-
anchors_preset = 2
|
|
2241
|
-
anchor_top = 1.0
|
|
2242
|
-
anchor_bottom = 1.0
|
|
2243
|
-
offset_left = 24.0
|
|
2244
|
-
offset_top = -280.0
|
|
2245
|
-
offset_right = 380.0
|
|
2246
|
-
offset_bottom = -110.0
|
|
2247
|
-
grow_vertical = 0
|
|
2248
|
-
theme_override_styles/panel = SubResource("StyleBoxFlat_speech")
|
|
2249
|
-
|
|
2250
|
-
[node name="BalloonMargin" type="MarginContainer" parent="CanvasLayer/UI/SpeechBalloon"]
|
|
2251
|
-
layout_mode = 2
|
|
2252
|
-
theme_override_constants/margin_left = 16
|
|
2253
|
-
theme_override_constants/margin_top = 12
|
|
2254
|
-
theme_override_constants/margin_right = 16
|
|
2255
|
-
theme_override_constants/margin_bottom = 12
|
|
2256
|
-
|
|
2257
|
-
[node name="VBox" type="VBoxContainer" parent="CanvasLayer/UI/SpeechBalloon/BalloonMargin"]
|
|
2258
|
-
layout_mode = 2
|
|
2259
|
-
theme_override_constants/separation = 4
|
|
2260
|
-
|
|
2261
|
-
[node name="SpeakerLabel" type="Label" parent="CanvasLayer/UI/SpeechBalloon/BalloonMargin/VBox"]
|
|
2262
|
-
layout_mode = 2
|
|
2263
|
-
theme_override_colors/font_color = Color(1, 0.85, 0.25, 1)
|
|
2264
|
-
theme_override_font_sizes/font_size = 15
|
|
2265
|
-
text = "Professor Pip says:"
|
|
2266
|
-
|
|
2267
|
-
[node name="SpeechText" type="Label" parent="CanvasLayer/UI/SpeechBalloon/BalloonMargin/VBox"]
|
|
2268
|
-
layout_mode = 2
|
|
2269
|
-
theme_override_colors/font_color = Color(0.95, 0.98, 1, 1)
|
|
2270
|
-
theme_override_font_sizes/font_size = 17
|
|
2271
|
-
text = "Around and around and around we go,\\nwhen we get home we have a zero!"
|
|
2272
|
-
autowrap_mode = 3
|
|
2273
|
-
|
|
2274
|
-
[node name="BottomBar" type="PanelContainer" parent="CanvasLayer/UI"]
|
|
2275
|
-
layout_mode = 1
|
|
2276
|
-
anchors_preset = 12
|
|
2277
|
-
anchor_top = 1.0
|
|
2278
|
-
anchor_right = 1.0
|
|
2279
|
-
anchor_bottom = 1.0
|
|
2280
|
-
offset_top = -88.0
|
|
2281
|
-
grow_horizontal = 2
|
|
2282
|
-
grow_vertical = 0
|
|
2283
|
-
theme_override_styles/panel = SubResource("StyleBoxFlat_bottom")
|
|
2284
|
-
|
|
2285
|
-
[node name="DigitScroll" type="ScrollContainer" parent="CanvasLayer/UI/BottomBar"]
|
|
2286
|
-
layout_mode = 2
|
|
2287
|
-
vertical_scroll_mode = 0
|
|
2288
|
-
|
|
2289
|
-
[node name="DigitHBox" type="HBoxContainer" parent="CanvasLayer/UI/BottomBar/DigitScroll"]
|
|
2290
|
-
layout_mode = 2
|
|
2291
|
-
size_flags_horizontal = 3
|
|
2292
|
-
size_flags_vertical = 3
|
|
2293
|
-
theme_override_constants/separation = 14
|
|
2294
|
-
alignment = 1
|
|
2295
|
-
|
|
2296
|
-
[node name="CelebrationModal" type="Control" parent="CanvasLayer"]
|
|
2297
|
-
visible = false
|
|
2298
|
-
layout_mode = 3
|
|
2299
|
-
anchors_preset = 15
|
|
2300
|
-
anchor_right = 1.0
|
|
2301
|
-
anchor_bottom = 1.0
|
|
2302
|
-
grow_horizontal = 2
|
|
2303
|
-
grow_vertical = 2
|
|
2304
|
-
|
|
2305
|
-
[node name="Backdrop" type="ColorRect" parent="CanvasLayer/CelebrationModal"]
|
|
2306
|
-
layout_mode = 1
|
|
2307
|
-
anchors_preset = 15
|
|
2308
|
-
anchor_right = 1.0
|
|
2309
|
-
anchor_bottom = 1.0
|
|
2310
|
-
grow_horizontal = 2
|
|
2311
|
-
grow_vertical = 2
|
|
2312
|
-
color = Color(0.05, 0.08, 0.12, 0.70)
|
|
2313
|
-
|
|
2314
|
-
[node name="ModalPanel" type="PanelContainer" parent="CanvasLayer/CelebrationModal"]
|
|
2315
|
-
layout_mode = 1
|
|
2316
|
-
anchors_preset = 8
|
|
2317
|
-
anchor_left = 0.5
|
|
2318
|
-
anchor_top = 0.5
|
|
2319
|
-
anchor_right = 0.5
|
|
2320
|
-
anchor_bottom = 0.5
|
|
2321
|
-
offset_left = -250.0
|
|
2322
|
-
offset_top = -170.0
|
|
2323
|
-
offset_right = 250.0
|
|
2324
|
-
offset_bottom = 170.0
|
|
2325
|
-
grow_horizontal = 2
|
|
2326
|
-
grow_vertical = 2
|
|
2327
|
-
theme_override_styles/panel = SubResource("StyleBoxFlat_modal")
|
|
2328
|
-
|
|
2329
|
-
[node name="VBox" type="VBoxContainer" parent="CanvasLayer/CelebrationModal/ModalPanel"]
|
|
2330
|
-
layout_mode = 2
|
|
2331
|
-
theme_override_constants/separation = 14
|
|
2332
|
-
alignment = 1
|
|
2333
|
-
|
|
2334
|
-
[node name="CongratsTitle" type="Label" parent="CanvasLayer/CelebrationModal/ModalPanel/VBox"]
|
|
2335
|
-
layout_mode = 2
|
|
2336
|
-
theme_override_colors/font_color = Color(1, 0.85, 0.2, 1)
|
|
2337
|
-
theme_override_font_sizes/font_size = 30
|
|
2338
|
-
text = "SUPER WRITER!"
|
|
2339
|
-
horizontal_alignment = 1
|
|
2340
|
-
|
|
2341
|
-
[node name="StarRatingBox" type="Control" parent="CanvasLayer/CelebrationModal/ModalPanel/VBox"]
|
|
2342
|
-
custom_minimum_size = Vector2(220, 52)
|
|
2343
|
-
layout_mode = 2
|
|
2344
|
-
|
|
2345
|
-
[node name="RhymeText" type="Label" parent="CanvasLayer/CelebrationModal/ModalPanel/VBox"]
|
|
2346
|
-
layout_mode = 2
|
|
2347
|
-
theme_override_font_sizes/font_size = 19
|
|
2348
|
-
text = "Great job writing your digit!"
|
|
2349
|
-
horizontal_alignment = 1
|
|
2350
|
-
autowrap_mode = 3
|
|
2351
|
-
|
|
2352
|
-
[node name="BtnHBox" type="HBoxContainer" parent="CanvasLayer/CelebrationModal/ModalPanel/VBox"]
|
|
2353
|
-
layout_mode = 2
|
|
2354
|
-
theme_override_constants/separation = 20
|
|
2355
|
-
alignment = 1
|
|
2356
|
-
|
|
2357
|
-
[node name="ReplayBtn" type="Button" parent="CanvasLayer/CelebrationModal/ModalPanel/VBox/BtnHBox"]
|
|
2358
|
-
custom_minimum_size = Vector2(130, 46)
|
|
2359
|
-
layout_mode = 2
|
|
2360
|
-
theme_override_font_sizes/font_size = 17
|
|
2361
|
-
text = "Try Again"
|
|
2362
|
-
|
|
2363
|
-
[node name="NextBtn" type="Button" parent="CanvasLayer/CelebrationModal/ModalPanel/VBox/BtnHBox"]
|
|
2364
|
-
custom_minimum_size = Vector2(160, 46)
|
|
2365
|
-
layout_mode = 2
|
|
2366
|
-
theme_override_colors/font_color = Color(0.2, 1, 0.4, 1)
|
|
2367
|
-
theme_override_font_sizes/font_size = 18
|
|
2368
|
-
text = "Next Number >"
|
|
2369
|
-
|
|
2370
|
-
[node name="ConfettiStars" type="CPUParticles2D" parent="CanvasLayer"]
|
|
2371
|
-
position = Vector2(640, 360)
|
|
2372
|
-
emitting = false
|
|
2373
|
-
amount = 22
|
|
2374
|
-
lifetime = 1.2
|
|
2375
|
-
one_shot = true
|
|
2376
|
-
explosiveness = 0.85
|
|
2377
|
-
emission_shape = 1
|
|
2378
|
-
emission_sphere_radius = 50.0
|
|
2379
|
-
direction = Vector2(0, -1)
|
|
2380
|
-
spread = 180.0
|
|
2381
|
-
gravity = Vector2(0, 380)
|
|
2382
|
-
initial_velocity_min = 220.0
|
|
2383
|
-
initial_velocity_max = 440.0
|
|
2384
|
-
angle_min = 0.0
|
|
2385
|
-
angle_max = 360.0
|
|
2386
|
-
angular_velocity_min = -200.0
|
|
2387
|
-
angular_velocity_max = 200.0
|
|
2388
|
-
scale_amount_min = 0.7
|
|
2389
|
-
scale_amount_max = 1.2
|
|
2390
|
-
scale_amount_curve = SubResource("Curve_pop")
|
|
2391
|
-
color = Color(1, 0.88, 0.2, 1)
|
|
2392
|
-
color_ramp = SubResource("Gradient_fade")
|
|
2393
|
-
|
|
2394
|
-
[node name="ConfettiRibbons" type="CPUParticles2D" parent="CanvasLayer"]
|
|
2395
|
-
position = Vector2(640, 360)
|
|
2396
|
-
emitting = false
|
|
2397
|
-
amount = 24
|
|
2398
|
-
lifetime = 1.3
|
|
2399
|
-
one_shot = true
|
|
2400
|
-
explosiveness = 0.88
|
|
2401
|
-
emission_shape = 1
|
|
2402
|
-
emission_sphere_radius = 60.0
|
|
2403
|
-
direction = Vector2(0, -1)
|
|
2404
|
-
spread = 180.0
|
|
2405
|
-
gravity = Vector2(0, 300)
|
|
2406
|
-
initial_velocity_min = 180.0
|
|
2407
|
-
initial_velocity_max = 400.0
|
|
2408
|
-
angle_min = 0.0
|
|
2409
|
-
angle_max = 360.0
|
|
2410
|
-
angular_velocity_min = -280.0
|
|
2411
|
-
angular_velocity_max = 280.0
|
|
2412
|
-
scale_amount_min = 0.7
|
|
2413
|
-
scale_amount_max = 1.3
|
|
2414
|
-
scale_amount_curve = SubResource("Curve_pop")
|
|
2415
|
-
color = Color(0.2, 0.85, 1, 1)
|
|
2416
|
-
color_ramp = SubResource("Gradient_fade")
|
|
2417
|
-
|
|
2418
|
-
[node name="CheckpointSparkles" type="CPUParticles2D" parent="CanvasLayer"]
|
|
2419
|
-
position = Vector2(0, 0)
|
|
2420
|
-
emitting = false
|
|
2421
|
-
amount = 14
|
|
2422
|
-
lifetime = 0.55
|
|
2423
|
-
one_shot = true
|
|
2424
|
-
explosiveness = 0.92
|
|
2425
|
-
emission_shape = 0
|
|
2426
|
-
spread = 180.0
|
|
2427
|
-
gravity = Vector2(0, 100)
|
|
2428
|
-
initial_velocity_min = 80.0
|
|
2429
|
-
initial_velocity_max = 180.0
|
|
2430
|
-
angle_min = 0.0
|
|
2431
|
-
angle_max = 360.0
|
|
2432
|
-
angular_velocity_min = -180.0
|
|
2433
|
-
angular_velocity_max = 180.0
|
|
2434
|
-
scale_amount_min = 0.5
|
|
2435
|
-
scale_amount_max = 1.0
|
|
2436
|
-
scale_amount_curve = SubResource("Curve_pop")
|
|
2437
|
-
color = Color(1, 0.90, 0.35, 1)
|
|
2438
|
-
color_ramp = SubResource("Gradient_fade")
|
|
2439
|
-
|
|
2440
|
-
[connection signal="pressed" from="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/DemoBtn" to="." method="_on_demo_btn_pressed"]
|
|
2441
|
-
[connection signal="pressed" from="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/GuideBtn" to="." method="_on_guide_toggle_pressed"]
|
|
2442
|
-
[connection signal="pressed" from="CanvasLayer/UI/HeaderBar/HeaderMargin/HBox/AudioBtn" to="." method="_on_audio_toggle_pressed"]
|
|
2443
|
-
[connection signal="pressed" from="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar/ClearBtn" to="." method="_on_clear_btn_pressed"]
|
|
2444
|
-
[connection signal="pressed" from="CanvasLayer/UI/MainHBox/BoardContainer/ToolsContainer/ToolsMargin/ToolsBar/UndoBtn" to="." method="_on_undo_btn_pressed"]
|
|
2445
|
-
[connection signal="pressed" from="CanvasLayer/CelebrationModal/ModalPanel/VBox/BtnHBox/ReplayBtn" to="." method="_on_replay_pressed"]
|
|
2446
|
-
[connection signal="pressed" from="CanvasLayer/CelebrationModal/ModalPanel/VBox/BtnHBox/NextBtn" to="." method="_on_next_pressed"]
|
|
2447
|
-
`,
|
|
2448
|
-
);
|
|
2449
|
-
|
|
2450
|
-
// -------------------------------------------------------------------------
|
|
2451
|
-
// 5. PROJECT SETTINGS, GITIGNORE & README
|
|
2452
|
-
// -------------------------------------------------------------------------
|
|
2453
|
-
|
|
2454
|
-
writeFile(
|
|
2455
|
-
"project.godot",
|
|
2456
|
-
`; Engine configuration file.
|
|
2457
|
-
; It's best edited using the editor UI and not directly,
|
|
2458
|
-
; but it can be edited here for direct configuration.
|
|
2459
|
-
|
|
2460
|
-
config_version=5
|
|
2461
|
-
|
|
2462
|
-
[application]
|
|
2463
|
-
|
|
2464
|
-
config/name="Robot Digit Writing Lab"
|
|
2465
|
-
config/description="Interactive 3D Kids Digit Writing App with Teacher Mouse Robot"
|
|
2466
|
-
run/main_scene="res://scenes/main_game.tscn"
|
|
2467
|
-
config/features=PackedStringArray("4.3", "GL Compatibility")
|
|
2468
|
-
|
|
2469
|
-
[display]
|
|
2470
|
-
|
|
2471
|
-
window/size/viewport_width=1280
|
|
2472
|
-
window/size/viewport_height=720
|
|
2473
|
-
window/stretch/mode="canvas_items"
|
|
2474
|
-
window/stretch/aspect="expand"
|
|
2475
|
-
window/handheld/orientation=6
|
|
2476
|
-
|
|
2477
|
-
[editor_plugins]
|
|
2478
|
-
|
|
2479
|
-
enabled=PackedStringArray("res://addons/scad_importer/plugin.cfg")
|
|
2480
|
-
|
|
2481
|
-
[rendering]
|
|
2482
|
-
|
|
2483
|
-
renderer/rendering_method="gl_compatibility"
|
|
2484
|
-
renderer/rendering_method.web="gl_compatibility"
|
|
2485
|
-
anti_aliasing/quality/msaa_3d=2
|
|
2486
|
-
`,
|
|
2487
|
-
);
|
|
2488
|
-
|
|
2489
|
-
writeFile(
|
|
2490
|
-
".gitignore",
|
|
2491
|
-
`
|
|
2492
|
-
.godot/
|
|
2493
|
-
*.tmp
|
|
2494
|
-
*.log
|
|
2495
|
-
`,
|
|
2496
|
-
);
|
|
2497
|
-
|
|
2498
|
-
writeFile(
|
|
2499
|
-
"README.md",
|
|
2500
|
-
`# Robot Digit Writing Lab
|
|
2501
|
-
|
|
2502
|
-
An educational, interactive 3D digit writing learning app for kids created with Godot 4 and procedural OpenSCAD 3D assets.
|
|
2503
|
-
|
|
2504
|
-
## Features
|
|
2505
|
-
|
|
2506
|
-
- **3D Teacher Mouse Robot ("Professor Pip")**:
|
|
2507
|
-
- Procedural hierarchical bone armature animation.
|
|
2508
|
-
- Idle breathing loop and joyful jumping "Praise" animation when kids complete digits!
|
|
2509
|
-
- Fully calibrated anti-glare toy materials and soft directional contact shadows.
|
|
2510
|
-
- Reacts dynamically: turns towards the board while tracing, faces the child during celebrations!
|
|
2511
|
-
|
|
2512
|
-
- **Optimized Vector Particle System**:
|
|
2513
|
-
- Anti-aliased Star, Ribbon, and Sparkle procedural textures.
|
|
2514
|
-
- Benchmarked for continuous 60 FPS in GL Compatibility mode.
|
|
2515
|
-
- Lightweight celebration bursts and touch feedback with zero frame hitching.
|
|
2516
|
-
|
|
2517
|
-
- **Preschool Digit Tracing Curriculum (0 through 9)**:
|
|
2518
|
-
- Guided stroke paths with numbered directional arrows and checkpoint beads.
|
|
2519
|
-
- Traditional preschool digit rhymes for all digits 0-9.
|
|
2520
|
-
- Multi-stroke support for complex digits like 4, 5, and 7.
|
|
2521
|
-
- "Watch Pip" Demo Mode: Professor Pip animates a magic stylus along the strokes.
|
|
2522
|
-
`,
|
|
2523
|
-
);
|
|
2524
|
-
|
|
2525
|
-
console.log(
|
|
2526
|
-
'\nProject "kids_digit_writing" successfully updated with performance optimizations!',
|
|
2527
|
-
);
|
|
2528
|
-
console.log("You can now run:");
|
|
2529
|
-
console.log(" node kids_digit_writing.js");
|
|
2530
|
-
console.log(
|
|
2531
|
-
" ~/Documents/projects/external/godot/bin/godot.linuxbsd.editor.x86_64 -e",
|
|
2532
|
-
);
|