scad-gltf 0.1.2 → 0.1.3
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 +16 -8
- package/bin/scad-godot.js +1 -1
- package/bin/scad-web.js +1 -1
- package/editor/dist/assets/{OutputPass-Bvl6NigM.js → OutputPass-B6MrFnGE.js} +1 -1
- package/editor/dist/assets/{index-Cy6q5gqL.js → index-CgVih1C4.js} +1 -1
- package/editor/dist/assets/{preview-C1zc24MJ.js → preview-CA_S_fWF.js} +1 -1
- package/editor/dist/assets/{prompt-ui-mDTjxcQO.js → prompt-ui-B9QCNLfJ.js} +1 -1
- package/editor/dist/content.js +1 -1
- package/editor/dist/index.html +5 -5
- package/editor/dist/manifest.webmanifest +1 -1
- package/editor/dist/preview.html +2 -2
- package/editor/dist/sw.js +1 -1
- package/godot/README.md +5 -2
- package/godot/addons/scad_importer/scad_importer.gd +1 -1
- package/godot/examples/README.md +5 -0
- package/godot/examples/fruit_fusion_3d.js +229 -29
- package/godot/examples/horde_survivor_3d.js +65 -6
- package/godot/examples/naval_battle_3d.js +1 -1
- package/godot/export_presets.cfg +25 -0
- package/package.json +6 -2
- package/src/prompt.js +1 -1
|
@@ -73,7 +73,7 @@ func _import_scene(path: String, flags: int, options: Dictionary) -> Object:
|
|
|
73
73
|
args.append(temp_glb_path)
|
|
74
74
|
|
|
75
75
|
var output = []
|
|
76
|
-
print("Importing %s via scad-convert...
|
|
76
|
+
print("Importing %s via scad-convert..." % path.get_file())
|
|
77
77
|
|
|
78
78
|
var exit_code = -1
|
|
79
79
|
if OS.get_name() == "Windows":
|
|
@@ -848,6 +848,8 @@ signal game_over_triggered
|
|
|
848
848
|
@onready var aim_line: MeshInstance3D = $AimLine
|
|
849
849
|
@onready var container_visual: Node3D = $ContainerVisual
|
|
850
850
|
|
|
851
|
+
# Dimensions of the 3D glass box container (box_w = 12.0, walls at +/- 6.0)
|
|
852
|
+
const CONTAINER_HALF_WIDTH: float = 6.0
|
|
851
853
|
const BOX_HALF_WIDTH: float = 4.8
|
|
852
854
|
const DROP_HEIGHT: float = 14.8
|
|
853
855
|
|
|
@@ -863,6 +865,10 @@ var is_game_over: bool = false
|
|
|
863
865
|
var danger_timer: float = 0.0
|
|
864
866
|
const DANGER_THRESHOLD: float = 2.8
|
|
865
867
|
|
|
868
|
+
# Touch / Mouse Drag Controls
|
|
869
|
+
var is_pointer_dragging: bool = false
|
|
870
|
+
var active_touch_index: int = -1
|
|
871
|
+
|
|
866
872
|
var high_score_file = "user://fusion_highscore.save"
|
|
867
873
|
|
|
868
874
|
func _ready():
|
|
@@ -880,6 +886,18 @@ func _ready():
|
|
|
880
886
|
_spawn_preview_fruit()
|
|
881
887
|
_update_aim_line()
|
|
882
888
|
|
|
889
|
+
func _notification(what):
|
|
890
|
+
if what == NOTIFICATION_APPLICATION_FOCUS_OUT:
|
|
891
|
+
is_pointer_dragging = false
|
|
892
|
+
active_touch_index = -1
|
|
893
|
+
|
|
894
|
+
func _get_dropper_max_x() -> float:
|
|
895
|
+
var fruit_r: float = 0.55
|
|
896
|
+
if active_preview_tier >= 1 and active_preview_tier <= FruitData.FRUIT_RADII.size():
|
|
897
|
+
fruit_r = FruitData.FRUIT_RADII[active_preview_tier - 1]
|
|
898
|
+
# Keep the preview fruit completely within the glass container walls with safety clearance
|
|
899
|
+
return max(0.5, CONTAINER_HALF_WIDTH - fruit_r - 0.08)
|
|
900
|
+
|
|
883
901
|
func _setup_container_visuals():
|
|
884
902
|
if not is_instance_valid(container_visual):
|
|
885
903
|
return
|
|
@@ -925,11 +943,6 @@ func _build_procedural_glass_box():
|
|
|
925
943
|
glass_mat.roughness = 0.05
|
|
926
944
|
glass_mat.metallic = 0.05
|
|
927
945
|
|
|
928
|
-
var frame_mat = StandardMaterial3D.new()
|
|
929
|
-
frame_mat.albedo_color = Color(0.8, 0.85, 0.9, 1.0)
|
|
930
|
-
frame_mat.metallic = 0.85
|
|
931
|
-
frame_mat.roughness = 0.25
|
|
932
|
-
|
|
933
946
|
# Back Glass
|
|
934
947
|
var back_mesh = MeshInstance3D.new()
|
|
935
948
|
var bm = BoxMesh.new()
|
|
@@ -1019,19 +1032,81 @@ func save_high_score():
|
|
|
1019
1032
|
file.store_32(high_score)
|
|
1020
1033
|
file.close()
|
|
1021
1034
|
|
|
1035
|
+
func _update_dropper_x(pos):
|
|
1036
|
+
if not is_instance_valid(dropper):
|
|
1037
|
+
return
|
|
1038
|
+
|
|
1039
|
+
var screen_pos: Vector2
|
|
1040
|
+
if pos is Vector2:
|
|
1041
|
+
screen_pos = pos
|
|
1042
|
+
else:
|
|
1043
|
+
screen_pos = Vector2(float(pos), get_viewport().get_visible_rect().size.y * 0.5)
|
|
1044
|
+
|
|
1045
|
+
var max_x = _get_dropper_max_x()
|
|
1046
|
+
|
|
1047
|
+
# Raycast onto the glass container's central plane (Z = 0) where the fruits reside.
|
|
1048
|
+
# This ensures picking maps directly along the physical glass container's width.
|
|
1049
|
+
var camera = get_viewport().get_camera_3d()
|
|
1050
|
+
if camera:
|
|
1051
|
+
var ray_from = camera.project_ray_origin(screen_pos)
|
|
1052
|
+
var ray_dir = camera.project_ray_normal(screen_pos)
|
|
1053
|
+
var drop_plane = Plane(Vector3(0, 0, 1), 0.0)
|
|
1054
|
+
var hit = drop_plane.intersects_ray(ray_from, ray_dir)
|
|
1055
|
+
if hit != null:
|
|
1056
|
+
dropper.position.x = clamp(hit.x, -max_x, max_x)
|
|
1057
|
+
_update_aim_line()
|
|
1058
|
+
return
|
|
1059
|
+
|
|
1060
|
+
var vp_size = get_viewport().get_visible_rect().size
|
|
1061
|
+
if vp_size.x > 0.0:
|
|
1062
|
+
var norm_x = (screen_pos.x / vp_size.x) * 2.0 - 1.0
|
|
1063
|
+
dropper.position.x = clamp(norm_x * max_x, -max_x, max_x)
|
|
1064
|
+
_update_aim_line()
|
|
1065
|
+
|
|
1022
1066
|
func _unhandled_input(event: InputEvent):
|
|
1023
1067
|
if is_game_over or not is_instance_valid(dropper):
|
|
1024
1068
|
return
|
|
1025
1069
|
|
|
1070
|
+
# Touch input (mobile / touchscreens)
|
|
1071
|
+
if event is InputEventScreenTouch:
|
|
1072
|
+
if event.pressed:
|
|
1073
|
+
if not is_pointer_dragging:
|
|
1074
|
+
is_pointer_dragging = true
|
|
1075
|
+
active_touch_index = event.index
|
|
1076
|
+
_update_dropper_x(event.position)
|
|
1077
|
+
else:
|
|
1078
|
+
if is_pointer_dragging and event.index == active_touch_index:
|
|
1079
|
+
is_pointer_dragging = false
|
|
1080
|
+
active_touch_index = -1
|
|
1081
|
+
_update_dropper_x(event.position)
|
|
1082
|
+
try_drop_fruit()
|
|
1083
|
+
return
|
|
1084
|
+
|
|
1085
|
+
if event is InputEventScreenDrag:
|
|
1086
|
+
if is_pointer_dragging and event.index == active_touch_index:
|
|
1087
|
+
_update_dropper_x(event.position)
|
|
1088
|
+
return
|
|
1089
|
+
|
|
1090
|
+
# Mouse input (desktop & web)
|
|
1091
|
+
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
|
1092
|
+
if event.pressed:
|
|
1093
|
+
if not is_pointer_dragging:
|
|
1094
|
+
is_pointer_dragging = true
|
|
1095
|
+
_update_dropper_x(event.position)
|
|
1096
|
+
else:
|
|
1097
|
+
if is_pointer_dragging and active_touch_index == -1:
|
|
1098
|
+
is_pointer_dragging = false
|
|
1099
|
+
_update_dropper_x(event.position)
|
|
1100
|
+
try_drop_fruit()
|
|
1101
|
+
return
|
|
1102
|
+
|
|
1026
1103
|
if event is InputEventMouseMotion:
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
var target_x = clamp(norm_x * (BOX_HALF_WIDTH + 0.5), -BOX_HALF_WIDTH, BOX_HALF_WIDTH)
|
|
1031
|
-
dropper.position.x = target_x
|
|
1032
|
-
_update_aim_line()
|
|
1104
|
+
if active_touch_index == -1:
|
|
1105
|
+
_update_dropper_x(event.position)
|
|
1106
|
+
return
|
|
1033
1107
|
|
|
1034
|
-
|
|
1108
|
+
# Keyboard drop
|
|
1109
|
+
if event.is_action_pressed("ui_accept") or event.is_action_pressed("ui_down"):
|
|
1035
1110
|
try_drop_fruit()
|
|
1036
1111
|
|
|
1037
1112
|
func _physics_process(delta: float):
|
|
@@ -1041,9 +1116,10 @@ func _physics_process(delta: float):
|
|
|
1041
1116
|
if is_game_over:
|
|
1042
1117
|
return
|
|
1043
1118
|
|
|
1119
|
+
var max_x = _get_dropper_max_x()
|
|
1044
1120
|
var move_dir = Input.get_axis("ui_left", "ui_right")
|
|
1045
1121
|
if move_dir != 0.0:
|
|
1046
|
-
dropper.position.x = clamp(dropper.position.x + move_dir * 14.0 * delta, -
|
|
1122
|
+
dropper.position.x = clamp(dropper.position.x + move_dir * 14.0 * delta, -max_x, max_x)
|
|
1047
1123
|
_update_aim_line()
|
|
1048
1124
|
|
|
1049
1125
|
var fruits_in_danger = 0
|
|
@@ -1073,6 +1149,10 @@ func _spawn_preview_fruit():
|
|
|
1073
1149
|
preview_fruit_node.queue_free()
|
|
1074
1150
|
preview_fruit_node = null
|
|
1075
1151
|
|
|
1152
|
+
var max_x = _get_dropper_max_x()
|
|
1153
|
+
dropper.position.x = clamp(dropper.position.x, -max_x, max_x)
|
|
1154
|
+
_update_aim_line()
|
|
1155
|
+
|
|
1076
1156
|
var model_path = FruitData.get_model_path(active_preview_tier)
|
|
1077
1157
|
if ResourceLoader.exists(model_path):
|
|
1078
1158
|
var scene = load(model_path)
|
|
@@ -1169,6 +1249,8 @@ func trigger_game_over():
|
|
|
1169
1249
|
if is_game_over:
|
|
1170
1250
|
return
|
|
1171
1251
|
is_game_over = true
|
|
1252
|
+
is_pointer_dragging = false
|
|
1253
|
+
active_touch_index = -1
|
|
1172
1254
|
AudioManager.play_game_over_sound()
|
|
1173
1255
|
game_over_triggered.emit()
|
|
1174
1256
|
|
|
@@ -1180,6 +1262,8 @@ func restart_game():
|
|
|
1180
1262
|
danger_timer = 0.0
|
|
1181
1263
|
is_game_over = false
|
|
1182
1264
|
can_drop = true
|
|
1265
|
+
is_pointer_dragging = false
|
|
1266
|
+
active_touch_index = -1
|
|
1183
1267
|
score_updated.emit(current_score, high_score)
|
|
1184
1268
|
active_preview_tier = randi_range(1, 3)
|
|
1185
1269
|
next_tier = randi_range(1, 3)
|
|
@@ -1240,16 +1324,135 @@ func _on_restart_pressed():
|
|
|
1240
1324
|
`,
|
|
1241
1325
|
);
|
|
1242
1326
|
|
|
1327
|
+
// Procedural 2D Evolution Guide (No missing emojis on Web/Mobile)
|
|
1328
|
+
writeFile(
|
|
1329
|
+
"scripts/evolution_guide.gd",
|
|
1330
|
+
`class_name EvolutionGuide
|
|
1331
|
+
extends Control
|
|
1332
|
+
|
|
1333
|
+
func _ready():
|
|
1334
|
+
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
1335
|
+
resized.connect(queue_redraw)
|
|
1336
|
+
|
|
1337
|
+
func _draw():
|
|
1338
|
+
var w = size.x
|
|
1339
|
+
var h = size.y
|
|
1340
|
+
if w <= 0 or h <= 0:
|
|
1341
|
+
return
|
|
1342
|
+
|
|
1343
|
+
# Sleek pill-shaped background
|
|
1344
|
+
var bg_style = StyleBoxFlat.new()
|
|
1345
|
+
bg_style.bg_color = Color(0.06, 0.09, 0.15, 0.75)
|
|
1346
|
+
var cr = int(min(16.0, h * 0.45))
|
|
1347
|
+
bg_style.corner_radius_top_left = cr
|
|
1348
|
+
bg_style.corner_radius_top_right = cr
|
|
1349
|
+
bg_style.corner_radius_bottom_left = cr
|
|
1350
|
+
bg_style.corner_radius_bottom_right = cr
|
|
1351
|
+
bg_style.border_width_left = 1
|
|
1352
|
+
bg_style.border_width_top = 1
|
|
1353
|
+
bg_style.border_width_right = 1
|
|
1354
|
+
bg_style.border_width_bottom = 1
|
|
1355
|
+
bg_style.border_color = Color(1.0, 1.0, 1.0, 0.14)
|
|
1356
|
+
draw_style_box(bg_style, Rect2(Vector2.ZERO, size))
|
|
1357
|
+
|
|
1358
|
+
var fruit_count = 9
|
|
1359
|
+
var slot_width = w / float(fruit_count)
|
|
1360
|
+
var y_center = h * 0.5
|
|
1361
|
+
|
|
1362
|
+
var radii = [7.5, 9.0, 10.5, 12.0, 13.5, 15.0, 16.5, 18.0, 19.5]
|
|
1363
|
+
var max_r_allowed = h * 0.38
|
|
1364
|
+
var scale_factor = 1.0
|
|
1365
|
+
if radii[8] > max_r_allowed:
|
|
1366
|
+
scale_factor = max_r_allowed / radii[8]
|
|
1367
|
+
|
|
1368
|
+
for i in range(fruit_count):
|
|
1369
|
+
var r = radii[i] * scale_factor
|
|
1370
|
+
var cx = slot_width * (i + 0.5)
|
|
1371
|
+
var pos = Vector2(cx, y_center)
|
|
1372
|
+
var col = FruitData.FRUIT_COLORS[i]
|
|
1373
|
+
|
|
1374
|
+
# Shadow
|
|
1375
|
+
draw_circle(pos + Vector2(0, 1.5), r + 0.8, Color(0, 0, 0, 0.35))
|
|
1376
|
+
# Main body
|
|
1377
|
+
draw_circle(pos, r, col)
|
|
1378
|
+
# Specular reflection
|
|
1379
|
+
draw_circle(pos + Vector2(-r * 0.35, -r * 0.35), r * 0.32, Color(1.0, 1.0, 1.0, 0.38))
|
|
1380
|
+
|
|
1381
|
+
# Distinct fruit icon visual elements
|
|
1382
|
+
match i + 1:
|
|
1383
|
+
1: # Cherry: stem and leaf
|
|
1384
|
+
draw_line(pos + Vector2(0, -r), pos + Vector2(r * 0.4, -r - 4.5 * scale_factor), Color(0.3, 0.7, 0.15), 1.4, true)
|
|
1385
|
+
draw_circle(pos + Vector2(r * 0.45, -r - 4.5 * scale_factor), 1.8 * scale_factor, Color(0.3, 0.85, 0.2))
|
|
1386
|
+
2: # Strawberry: green sepal and seeds
|
|
1387
|
+
draw_circle(pos + Vector2(-r * 0.35, -r + 1), 1.8 * scale_factor, Color(0.2, 0.8, 0.25))
|
|
1388
|
+
draw_circle(pos + Vector2(0, -r - 1), 2.0 * scale_factor, Color(0.2, 0.8, 0.25))
|
|
1389
|
+
draw_circle(pos + Vector2(r * 0.35, -r + 1), 1.8 * scale_factor, Color(0.2, 0.8, 0.25))
|
|
1390
|
+
draw_circle(pos + Vector2(-r * 0.2, -r * 0.1), 0.9 * scale_factor, Color(1.0, 0.9, 0.35))
|
|
1391
|
+
draw_circle(pos + Vector2(r * 0.25, -r * 0.15), 0.9 * scale_factor, Color(1.0, 0.9, 0.35))
|
|
1392
|
+
draw_circle(pos + Vector2(0, r * 0.35), 0.9 * scale_factor, Color(1.0, 0.9, 0.35))
|
|
1393
|
+
3: # Grape: stem and cluster spots
|
|
1394
|
+
draw_line(pos + Vector2(0, -r), pos + Vector2(0, -r - 3.5 * scale_factor), Color(0.4, 0.7, 0.2), 1.4, true)
|
|
1395
|
+
draw_circle(pos + Vector2(-r * 0.25, -r * 0.15), r * 0.25, Color(0.4, 0.1, 0.55, 0.7))
|
|
1396
|
+
draw_circle(pos + Vector2(r * 0.25, -r * 0.1), r * 0.25, Color(0.4, 0.1, 0.55, 0.7))
|
|
1397
|
+
draw_circle(pos + Vector2(0, r * 0.3), r * 0.25, Color(0.4, 0.1, 0.55, 0.7))
|
|
1398
|
+
4: # Tangerine: leaf
|
|
1399
|
+
draw_line(pos + Vector2(0, -r), pos + Vector2(r * 0.25, -r - 3.0 * scale_factor), Color(0.3, 0.65, 0.2), 1.4, true)
|
|
1400
|
+
draw_circle(pos + Vector2(r * 0.35, -r - 2.5 * scale_factor), 2.2 * scale_factor, Color(0.25, 0.8, 0.2))
|
|
1401
|
+
5: # Apple: stem and leaf
|
|
1402
|
+
draw_circle(pos + Vector2(0, -r + 1), 2.0 * scale_factor, Color(0.6, 0.05, 0.08))
|
|
1403
|
+
draw_line(pos + Vector2(0, -r + 1), pos + Vector2(r * 0.2, -r - 4.0 * scale_factor), Color(0.45, 0.25, 0.1), 1.5, true)
|
|
1404
|
+
draw_circle(pos + Vector2(r * 0.35, -r - 3.0 * scale_factor), 2.3 * scale_factor, Color(0.25, 0.8, 0.2))
|
|
1405
|
+
6: # Peach: peach cleft
|
|
1406
|
+
draw_line(pos + Vector2(0, -r * 0.7), pos + Vector2(0, r * 0.7), Color(0.85, 0.32, 0.36, 0.85), 1.6, true)
|
|
1407
|
+
draw_circle(pos + Vector2(r * 0.25, -r - 1.5 * scale_factor), 2.2 * scale_factor, Color(0.25, 0.8, 0.2))
|
|
1408
|
+
7: # Melon: netting ribs
|
|
1409
|
+
draw_arc(pos, r * 0.65, -PI * 0.5, PI * 0.5, 10, Color(0.88, 0.98, 0.62, 0.75), 1.4, true)
|
|
1410
|
+
draw_arc(pos, r * 0.65, PI * 0.5, PI * 1.5, 10, Color(0.88, 0.98, 0.62, 0.75), 1.4, true)
|
|
1411
|
+
draw_line(pos + Vector2(0, -r), pos + Vector2(0, -r - 3.5 * scale_factor), Color(0.3, 0.6, 0.2), 1.8, true)
|
|
1412
|
+
8: # Watermelon: dark green wavy stripes
|
|
1413
|
+
var stripe_col = Color(0.06, 0.28, 0.10, 0.9)
|
|
1414
|
+
draw_arc(pos + Vector2(-r * 0.35, 0), r * 0.8, -PI * 0.4, PI * 0.4, 10, stripe_col, 1.8, true)
|
|
1415
|
+
draw_line(pos + Vector2(0, -r * 0.88), pos + Vector2(0, r * 0.88), stripe_col, 1.8, true)
|
|
1416
|
+
draw_arc(pos + Vector2(r * 0.35, 0), r * 0.8, PI * 0.6, PI * 1.4, 10, stripe_col, 1.8, true)
|
|
1417
|
+
9: # King Sun: glowing halo and crown
|
|
1418
|
+
draw_arc(pos, r + 2.5 * scale_factor, 0, TAU, 28, Color(1.0, 0.85, 0.2, 0.6), 1.4, true)
|
|
1419
|
+
var cb = pos + Vector2(0, -r + 2 * scale_factor)
|
|
1420
|
+
var crown_pts = PackedVector2Array([
|
|
1421
|
+
cb + Vector2(-6 * scale_factor, 0),
|
|
1422
|
+
cb + Vector2(-6 * scale_factor, -5 * scale_factor),
|
|
1423
|
+
cb + Vector2(-3 * scale_factor, -2 * scale_factor),
|
|
1424
|
+
cb + Vector2(0, -7 * scale_factor),
|
|
1425
|
+
cb + Vector2(3 * scale_factor, -2 * scale_factor),
|
|
1426
|
+
cb + Vector2(6 * scale_factor, -5 * scale_factor),
|
|
1427
|
+
cb + Vector2(6 * scale_factor, 0)
|
|
1428
|
+
])
|
|
1429
|
+
draw_colored_polygon(crown_pts, Color(1.0, 0.92, 0.3))
|
|
1430
|
+
draw_polyline(crown_pts, Color(0.85, 0.65, 0.1), 1.0, true)
|
|
1431
|
+
draw_circle(cb + Vector2(0, -7 * scale_factor), 1.4 * scale_factor, Color(0.2, 0.9, 1.0))
|
|
1432
|
+
|
|
1433
|
+
# Evolution progression arrow
|
|
1434
|
+
if i < fruit_count - 1:
|
|
1435
|
+
var arrow_x = slot_width * (i + 1)
|
|
1436
|
+
var arrow_pts = PackedVector2Array([
|
|
1437
|
+
Vector2(arrow_x - 3.0, y_center - 4.0),
|
|
1438
|
+
Vector2(arrow_x + 2.0, y_center),
|
|
1439
|
+
Vector2(arrow_x - 3.0, y_center + 4.0)
|
|
1440
|
+
])
|
|
1441
|
+
draw_polyline(arrow_pts, Color(1.0, 1.0, 1.0, 0.45), 1.5, true)
|
|
1442
|
+
`,
|
|
1443
|
+
);
|
|
1444
|
+
|
|
1243
1445
|
// ==========================================
|
|
1244
1446
|
// 4. MAIN SCENE FILE (.tscn)
|
|
1245
1447
|
// ==========================================
|
|
1246
1448
|
|
|
1247
1449
|
writeFile(
|
|
1248
1450
|
"scenes/main.tscn",
|
|
1249
|
-
`[gd_scene load_steps=
|
|
1451
|
+
`[gd_scene load_steps=16 format=3 uid="uid://c2suika0main"]
|
|
1250
1452
|
|
|
1251
1453
|
[ext_resource type="Script" path="res://scripts/game_manager.gd" id="1_gm"]
|
|
1252
1454
|
[ext_resource type="Script" path="res://scripts/ui_manager.gd" id="2_ui"]
|
|
1455
|
+
[ext_resource type="Script" path="res://scripts/evolution_guide.gd" id="3_evo"]
|
|
1253
1456
|
|
|
1254
1457
|
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sky"]
|
|
1255
1458
|
sky_top_color = Color(0.2, 0.45, 0.75, 1)
|
|
@@ -1440,23 +1643,20 @@ theme_override_colors/font_color = Color(1, 1, 1, 0.9)
|
|
|
1440
1643
|
text = "Strawberry"
|
|
1441
1644
|
horizontal_alignment = 1
|
|
1442
1645
|
|
|
1443
|
-
[node name="EvolutionGuide" type="
|
|
1646
|
+
[node name="EvolutionGuide" type="Control" parent="UI"]
|
|
1444
1647
|
layout_mode = 1
|
|
1445
1648
|
anchors_preset = 12
|
|
1446
1649
|
anchor_top = 1.0
|
|
1447
1650
|
anchor_right = 1.0
|
|
1448
1651
|
anchor_bottom = 1.0
|
|
1449
|
-
|
|
1652
|
+
offset_left = 20.0
|
|
1653
|
+
offset_top = -65.0
|
|
1654
|
+
offset_right = -20.0
|
|
1450
1655
|
offset_bottom = -15.0
|
|
1451
1656
|
grow_horizontal = 2
|
|
1452
1657
|
grow_vertical = 0
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
[node name="GuideLabel" type="Label" parent="UI/EvolutionGuide"]
|
|
1456
|
-
layout_mode = 2
|
|
1457
|
-
theme_override_font_sizes/font_size = 15
|
|
1458
|
-
theme_override_colors/font_color = Color(1, 1, 1, 0.75)
|
|
1459
|
-
text = "🍒 Cherry → 🍓 Strawberry → 🍇 Grape → 🍊 Orange → 🍎 Apple → 🍑 Peach → 🍈 Melon → 🍉 Watermelon → 👑 Sun"
|
|
1658
|
+
mouse_filter = 2
|
|
1659
|
+
script = ExtResource("3_evo")
|
|
1460
1660
|
|
|
1461
1661
|
[node name="DangerIndicator" type="ColorRect" parent="UI"]
|
|
1462
1662
|
visible = false
|
|
@@ -1551,12 +1751,12 @@ Drop different fruits into the transparent glass container. When two identical f
|
|
|
1551
1751
|
---
|
|
1552
1752
|
|
|
1553
1753
|
## Controls
|
|
1554
|
-
- **
|
|
1555
|
-
-
|
|
1556
|
-
-
|
|
1557
|
-
-
|
|
1754
|
+
- **Aim Dropper**:
|
|
1755
|
+
- Touch & Drag along the glass container (Mobile / Web)
|
|
1756
|
+
- Mouse Move / Click & Drag along the glass container (Desktop)
|
|
1757
|
+
- Left / Right Arrow keys / A & D keys
|
|
1558
1758
|
- **Drop Fruit**:
|
|
1559
|
-
- Left Click
|
|
1759
|
+
- Release Touch / Release Left Click
|
|
1560
1760
|
- Spacebar
|
|
1561
1761
|
- Enter
|
|
1562
1762
|
- Down Arrow
|
|
@@ -1826,6 +1826,13 @@ var proj_scene = preload("res://scenes/projectile.tscn")
|
|
|
1826
1826
|
var lightning_scene = preload("res://scenes/lightning_strike.tscn")
|
|
1827
1827
|
var invulnerable_timer: float = 0.0
|
|
1828
1828
|
|
|
1829
|
+
# --- INVISIBLE TOUCH CONTROLS ---
|
|
1830
|
+
var touch_index: int = -1
|
|
1831
|
+
var touch_start_pos: Vector2 = Vector2.ZERO
|
|
1832
|
+
var touch_input_vec: Vector2 = Vector2.ZERO
|
|
1833
|
+
const TOUCH_DEADZONE: float = 8.0
|
|
1834
|
+
const TOUCH_MAX_DIST: float = 65.0
|
|
1835
|
+
|
|
1829
1836
|
func _ready():
|
|
1830
1837
|
add_to_group("player")
|
|
1831
1838
|
current_health = max_health
|
|
@@ -1840,6 +1847,32 @@ func _ready():
|
|
|
1840
1847
|
orbit_shield.set_orb_count(orbit_count)
|
|
1841
1848
|
_setup_player_animations(model)
|
|
1842
1849
|
|
|
1850
|
+
func _unhandled_input(event: InputEvent) -> void:
|
|
1851
|
+
if event is InputEventScreenTouch:
|
|
1852
|
+
if event.pressed:
|
|
1853
|
+
if touch_index == -1:
|
|
1854
|
+
touch_index = event.index
|
|
1855
|
+
touch_start_pos = event.position
|
|
1856
|
+
touch_input_vec = Vector2.ZERO
|
|
1857
|
+
elif event.index == touch_index or event.is_canceled():
|
|
1858
|
+
_reset_touch()
|
|
1859
|
+
elif event is InputEventScreenDrag:
|
|
1860
|
+
if event.index == touch_index:
|
|
1861
|
+
var diff = event.position - touch_start_pos
|
|
1862
|
+
var dist = diff.length()
|
|
1863
|
+
if dist > TOUCH_DEADZONE:
|
|
1864
|
+
var move_dist = min(dist - TOUCH_DEADZONE, TOUCH_MAX_DIST)
|
|
1865
|
+
touch_input_vec = diff.normalized() * (move_dist / TOUCH_MAX_DIST)
|
|
1866
|
+
# Follow the finger smoothly if dragged past max threshold
|
|
1867
|
+
if dist > TOUCH_MAX_DIST + TOUCH_DEADZONE:
|
|
1868
|
+
touch_start_pos = event.position - diff.normalized() * (TOUCH_MAX_DIST + TOUCH_DEADZONE)
|
|
1869
|
+
else:
|
|
1870
|
+
touch_input_vec = Vector2.ZERO
|
|
1871
|
+
|
|
1872
|
+
func _reset_touch():
|
|
1873
|
+
touch_index = -1
|
|
1874
|
+
touch_input_vec = Vector2.ZERO
|
|
1875
|
+
|
|
1843
1876
|
func _setup_player_animations(node: Node):
|
|
1844
1877
|
if node is AnimationPlayer:
|
|
1845
1878
|
anim_player = node
|
|
@@ -1868,7 +1901,14 @@ func _physics_process(delta: float):
|
|
|
1868
1901
|
input_vec.x = Input.get_axis("move_left", "move_right")
|
|
1869
1902
|
input_vec.y = Input.get_axis("move_up", "move_down")
|
|
1870
1903
|
|
|
1871
|
-
|
|
1904
|
+
# Blend or apply touch screen input
|
|
1905
|
+
if touch_input_vec != Vector2.ZERO:
|
|
1906
|
+
input_vec += touch_input_vec
|
|
1907
|
+
|
|
1908
|
+
if input_vec.length() > 1.0:
|
|
1909
|
+
input_vec = input_vec.normalized()
|
|
1910
|
+
|
|
1911
|
+
var move_dir = Vector3(input_vec.x, 0, input_vec.y)
|
|
1872
1912
|
velocity.x = move_dir.x * move_speed
|
|
1873
1913
|
velocity.z = move_dir.z * move_speed
|
|
1874
1914
|
move_and_slide()
|
|
@@ -1973,6 +2013,7 @@ func take_damage(amount: int):
|
|
|
1973
2013
|
|
|
1974
2014
|
if current_health <= 0:
|
|
1975
2015
|
current_health = 0
|
|
2016
|
+
_reset_touch()
|
|
1976
2017
|
emit_signal("died")
|
|
1977
2018
|
|
|
1978
2019
|
func heal(amount: int):
|
|
@@ -1986,6 +2027,7 @@ func add_xp(amount: int):
|
|
|
1986
2027
|
level += 1
|
|
1987
2028
|
required_xp = int(required_xp * 1.4)
|
|
1988
2029
|
SoundManager.play_level_up()
|
|
2030
|
+
_reset_touch()
|
|
1989
2031
|
emit_signal("leveled_up", level)
|
|
1990
2032
|
emit_signal("xp_changed", current_xp, required_xp, level)
|
|
1991
2033
|
|
|
@@ -2157,6 +2199,7 @@ func _on_restart_pressed():
|
|
|
2157
2199
|
);
|
|
2158
2200
|
|
|
2159
2201
|
// scenes/ui.tscn
|
|
2202
|
+
// HUD controls set to mouse_filter = 2 (IGNORE) so touch events flow directly to player input
|
|
2160
2203
|
writeFile(
|
|
2161
2204
|
"scenes/ui.tscn",
|
|
2162
2205
|
`
|
|
@@ -2183,6 +2226,7 @@ anchors_preset = 10
|
|
|
2183
2226
|
anchor_right = 1.0
|
|
2184
2227
|
offset_bottom = 24.0
|
|
2185
2228
|
grow_horizontal = 2
|
|
2229
|
+
mouse_filter = 2
|
|
2186
2230
|
value = 30.0
|
|
2187
2231
|
show_percentage = false
|
|
2188
2232
|
|
|
@@ -2196,6 +2240,7 @@ offset_top = 28.0
|
|
|
2196
2240
|
offset_right = 60.0
|
|
2197
2241
|
offset_bottom = 54.0
|
|
2198
2242
|
grow_horizontal = 2
|
|
2243
|
+
mouse_filter = 2
|
|
2199
2244
|
theme_override_font_sizes/font_size = 22
|
|
2200
2245
|
text = "LVL 1"
|
|
2201
2246
|
horizontal_alignment = 1
|
|
@@ -2210,6 +2255,7 @@ offset_top = 58.0
|
|
|
2210
2255
|
offset_right = 60.0
|
|
2211
2256
|
offset_bottom = 90.0
|
|
2212
2257
|
grow_horizontal = 2
|
|
2258
|
+
mouse_filter = 2
|
|
2213
2259
|
theme_override_font_sizes/font_size = 28
|
|
2214
2260
|
text = "00:00"
|
|
2215
2261
|
horizontal_alignment = 1
|
|
@@ -2220,6 +2266,7 @@ offset_left = 24.0
|
|
|
2220
2266
|
offset_top = 36.0
|
|
2221
2267
|
offset_right = 180.0
|
|
2222
2268
|
offset_bottom = 68.0
|
|
2269
|
+
mouse_filter = 2
|
|
2223
2270
|
theme_override_font_sizes/font_size = 20
|
|
2224
2271
|
text = "Kills: 0"
|
|
2225
2272
|
|
|
@@ -2236,6 +2283,7 @@ offset_right = 200.0
|
|
|
2236
2283
|
offset_bottom = -28.0
|
|
2237
2284
|
grow_horizontal = 2
|
|
2238
2285
|
grow_vertical = 0
|
|
2286
|
+
mouse_filter = 2
|
|
2239
2287
|
value = 100.0
|
|
2240
2288
|
show_percentage = false
|
|
2241
2289
|
|
|
@@ -2246,6 +2294,7 @@ anchor_right = 1.0
|
|
|
2246
2294
|
anchor_bottom = 1.0
|
|
2247
2295
|
grow_horizontal = 2
|
|
2248
2296
|
grow_vertical = 2
|
|
2297
|
+
mouse_filter = 2
|
|
2249
2298
|
text = "HP: 100 / 100"
|
|
2250
2299
|
horizontal_alignment = 1
|
|
2251
2300
|
vertical_alignment = 1
|
|
@@ -2611,6 +2660,11 @@ SpatialGrid="*res://scripts/spatial_grid.gd"
|
|
|
2611
2660
|
|
|
2612
2661
|
enabled=PackedStringArray("res://addons/scad_importer/plugin.cfg")
|
|
2613
2662
|
|
|
2663
|
+
[input_devices]
|
|
2664
|
+
|
|
2665
|
+
pointing/emulate_touch_from_mouse=true
|
|
2666
|
+
pointing/emulate_mouse_from_touch=true
|
|
2667
|
+
|
|
2614
2668
|
[layer_names]
|
|
2615
2669
|
|
|
2616
2670
|
3d_physics/layer_1="World"
|
|
@@ -2676,19 +2730,24 @@ writeFile(
|
|
|
2676
2730
|
|
|
2677
2731
|
A 3D auto-shooter / horde survival game inspired by *Vampire Survivors*.
|
|
2678
2732
|
|
|
2733
|
+
## Touch Screen Controls
|
|
2734
|
+
- Touch and drag anywhere on the display to smoothly guide the hero with an invisible, dynamic virtual joystick.
|
|
2735
|
+
- Does not clutter the screen with visible on-screen buttons or virtual pads.
|
|
2736
|
+
- Upgrades and menu buttons respond immediately to direct taps.
|
|
2737
|
+
|
|
2679
2738
|
## Physics Optimization
|
|
2680
2739
|
- Replaced the CPU-heavy Physics Engine character body repulsion with an ultra-fast **Spatial Grid Hashing** setup.
|
|
2681
|
-
- Enemies manually separate and flock using 2D grid neighbor lookups, keeping
|
|
2740
|
+
- Enemies manually separate and flock using 2D grid neighbor lookups, keeping CharacterBody3D nodes running at high frame rates.
|
|
2682
2741
|
|
|
2683
2742
|
## Model & Asset Polish:
|
|
2684
|
-
- **The Brute**:
|
|
2685
|
-
- **The Bat (Swarmer)**:
|
|
2743
|
+
- **The Brute**: Spikes project forward (+Y) out of chest armor. Features spiked pauldrons, curved horns, and a two-handed war maul with a molten glowing core.
|
|
2744
|
+
- **The Bat (Swarmer)**: Designed with organic swept wings, elbow spars, wing claws, skeletal ribs, and flapping animations.
|
|
2686
2745
|
- **The Zombie (Walker)**: Detailed with sunken eye sockets, glowing red pupils, a decaying jaw with teeth, exposed ribs, and clawed reaching hands.
|
|
2687
|
-
- **The Hero (Knight)**: Wields an angled runic broadsword
|
|
2746
|
+
- **The Hero (Knight)**: Wields an angled runic broadsword in the right hand and an emblazoned heater kite shield on the left forearm.
|
|
2688
2747
|
`,
|
|
2689
2748
|
);
|
|
2690
2749
|
|
|
2691
2750
|
console.log("\n======================================================");
|
|
2692
|
-
console.log('Project "Horde Survivor 3D" updated with
|
|
2751
|
+
console.log('Project "Horde Survivor 3D" updated with Touch Controls!');
|
|
2693
2752
|
console.log(`Directory: ${ROOT_DIR}`);
|
|
2694
2753
|
console.log("======================================================\n");
|
|
@@ -75,7 +75,7 @@ func _import_scene(path: String, flags: int, options: Dictionary) -> Object:
|
|
|
75
75
|
args.append(temp_glb_path)
|
|
76
76
|
|
|
77
77
|
var output = []
|
|
78
|
-
print("Importing %s via scad-convert...
|
|
78
|
+
print("Importing %s via scad-convert..." % path.get_file())
|
|
79
79
|
|
|
80
80
|
var exit_code = -1
|
|
81
81
|
if OS.get_name() == "Windows":
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[preset.0]
|
|
2
|
+
name="Web"
|
|
3
|
+
platform="Web"
|
|
4
|
+
runnable=true
|
|
5
|
+
advanced_options=false
|
|
6
|
+
dedicated_server=false
|
|
7
|
+
custom_features=""
|
|
8
|
+
export_filter="all_resources"
|
|
9
|
+
include_filter=""
|
|
10
|
+
exclude_filter=""
|
|
11
|
+
export_path=""
|
|
12
|
+
|
|
13
|
+
[preset.0.options]
|
|
14
|
+
custom_template/debug=""
|
|
15
|
+
custom_template/release=""
|
|
16
|
+
variant/extensions_support=false
|
|
17
|
+
variant/thread_support=true
|
|
18
|
+
vram_compression/import_etc2_astc=false
|
|
19
|
+
html/export_icon=true
|
|
20
|
+
html/custom_html_shell=""
|
|
21
|
+
html/head_include=""
|
|
22
|
+
html/canvas_resize_policy=2
|
|
23
|
+
html/focus_canvas_on_start=true
|
|
24
|
+
html/experimental_virtual_keyboard=false
|
|
25
|
+
progressive_web_app/enabled=false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scad-gltf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "WASM build of a custom OpenSCAD fork that compiles .scad scripts directly to glTF/GLB with PBR and animation support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/ext/openscad.js",
|
|
@@ -49,7 +49,9 @@
|
|
|
49
49
|
"scripts": {
|
|
50
50
|
"format": "prettier --write .",
|
|
51
51
|
"mcp": "npx @modelcontextprotocol/inspector node ./bin/scad-mcp.js",
|
|
52
|
-
"prepublishOnly": "npm install --prefix editor && npm run build --prefix editor"
|
|
52
|
+
"prepublishOnly": "npm install --prefix editor && npm run build --prefix editor",
|
|
53
|
+
"build:godot": "node scripts/build-godot-web.js",
|
|
54
|
+
"deploy:godot": "gh-pages -d .godot-dist --repo git@github.com:iliagrigorevdev/scad-godot.git -b gh-pages"
|
|
53
55
|
},
|
|
54
56
|
"dependencies": {
|
|
55
57
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -58,6 +60,8 @@
|
|
|
58
60
|
"puppeteer": "^25.9.0"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|
|
63
|
+
"coi-serviceworker": "^0.1.7",
|
|
64
|
+
"gh-pages": "^6.3.0",
|
|
61
65
|
"prettier": "3.9.6"
|
|
62
66
|
}
|
|
63
67
|
}
|
package/src/prompt.js
CHANGED
|
@@ -117,7 +117,7 @@ export function generatePrompt(description, options = {}) {
|
|
|
117
117
|
prompt += `\n\nImportant Animation rules:
|
|
118
118
|
- Wrapping: Use the 'armature(animations=...)' module at the root to wrap all animated components.
|
|
119
119
|
- Hierarchies: Use the 'bone(name="BoneName", t=[x,y,z], r=[x,y,z])' module to define hierarchical animated parts.
|
|
120
|
-
- Auto-Unioning: Any child meshes (e.g., cube, cylinder, imported objects) placed directly inside an 'armature()' or 'bone()' node are automatically unioned together by the engine. Child bones remain separate nodes in the hierarchy.
|
|
120
|
+
- Auto-Unioning: Any child meshes (e.g., cube, cylinder, imported objects) placed directly inside an 'armature()' or 'bone()' node are automatically unioned together by the engine and rigidly attached to that bone's transform node. Child bones remain separate nodes in the hierarchy.
|
|
121
121
|
- Animation Data: The 'animations' property is an array of named animation sequences. Each sequence contains an array of tracks defining keyframes for each bone. Format:
|
|
122
122
|
animations = [
|
|
123
123
|
["AnimationName", [
|