diff --git a/assets/store/crate.glb b/assets/store/crate.glb new file mode 100644 index 0000000..b479986 Binary files /dev/null and b/assets/store/crate.glb differ diff --git a/assets/store/crate.glb.import b/assets/store/crate.glb.import new file mode 100644 index 0000000..5605c14 --- /dev/null +++ b/assets/store/crate.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cu5trptulqcnq" +path="res://.godot/imported/crate.glb-24cac64f400a7a779f7206f210a4dd2a.scn" + +[deps] + +source_file="res://assets/store/crate.glb" +dest_files=["res://.godot/imported/crate.glb-24cac64f400a7a779f7206f210a4dd2a.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/assets/store/rack.glb b/assets/store/rack.glb new file mode 100644 index 0000000..3e004dc Binary files /dev/null and b/assets/store/rack.glb differ diff --git a/assets/store/rack.glb.import b/assets/store/rack.glb.import new file mode 100644 index 0000000..be9206a --- /dev/null +++ b/assets/store/rack.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bhtt5qprxj00t" +path="res://.godot/imported/rack.glb-0e5d76da917de3ede0eb2963da764fe8.scn" + +[deps] + +source_file="res://assets/store/rack.glb" +dest_files=["res://.godot/imported/rack.glb-0e5d76da917de3ede0eb2963da764fe8.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/assets/store/record.glb b/assets/store/record.glb new file mode 100644 index 0000000..80a9165 Binary files /dev/null and b/assets/store/record.glb differ diff --git a/assets/store/record.glb.import b/assets/store/record.glb.import new file mode 100644 index 0000000..bff7483 --- /dev/null +++ b/assets/store/record.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://b1hpf4j763ie0" +path="res://.godot/imported/record.glb-adf950a1184dabfc3a04addc4e1583a7.scn" + +[deps] + +source_file="res://assets/store/record.glb" +dest_files=["res://.godot/imported/record.glb-adf950a1184dabfc3a04addc4e1583a7.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/scripts/Main.gd b/scripts/Main.gd index 2e6aa73..0800c65 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -74,42 +74,87 @@ func _setup_world() -> void: _world.name = "World" add_child(_world) -# ---------------------------------------------------------------- the rack -# One frame (frozen) holding shelves; on each shelf, crates; in each crate, -# cardboard jackets standing up with brittle vinyl records tucked between them. +# ---------------------------------------------------------------- the store +# Real Monster Robot Party assets: wooden racks (frozen backdrop you can smash) and +# crates of records in front. Colliders auto-size to each GLB's mesh AABB and every +# piece is dropped so its bottom rests exactly on the floor — no hardcoded dimensions. +const RACK_GLB := "res://assets/store/rack.glb" +const CRATE_GLB := "res://assets/store/crate.glb" +const RECORD_GLB := "res://assets/store/record.glb" + func _build_rack(origin: Vector3) -> void: - var shelf_ys := [0.45, 1.15] + # two racks form a smashable back wall of the booth + _glb_piece(RACK_GLB, "wood", Vector2(origin.x - 0.95, origin.z - 0.7), true) + _glb_piece(RACK_GLB, "wood", Vector2(origin.x + 0.95, origin.z - 0.7), true) - # 4 posts (frozen wood) — the primary supports for BOTH shelves. - var posts: Array[Smashable] = [] - for sx in [-0.62, 0.62]: - for sz in [-0.28, 0.28]: - posts.append(_piece("wood", Vector3(0.06, 1.5, 0.06), - origin + Vector3(sx, 0.75, sz), true)) + # crates of records on the floor in front, in two rows + var spots := [ + Vector2(-0.8, 0.5), Vector2(0.0, 0.5), Vector2(0.8, 0.5), + Vector2(-0.4, 1.2), Vector2(0.4, 1.2), + ] + for spot in spots: + var xz: Vector2 = spot + Vector2(origin.x, origin.z) + var crate := _glb_piece(CRATE_GLB, "wood", xz, false) + var crate_top: float = crate["top"] + # a few records stacked on the crate; smashing the crate spills them + for i in range(3): + var jitter := Vector2(randf_range(-0.06, 0.06), randf_range(-0.06, 0.06)) + var rec := _glb_piece(RECORD_GLB, "vinyl", xz + jitter, false, crate_top + float(i) * 0.03) + (crate["piece"] as Smashable).supports.append(rec["piece"]) - for y in shelf_ys: - # frozen shelf slab — smash THIS (or any leg) to drop everything above it. - var shelf := _piece("wood", Vector3(1.34, 0.05, 0.62), origin + Vector3(0, y, 0), true) - # every leg holds up every shelf: knock ANY leg -> both shelves drop (arcade call). - for post in posts: - post.supports.append(shelf) +## Instance a GLB as the VISUAL of a Smashable, give it a box collider auto-sized to +## the mesh AABB, and drop it so its bottom rests at height `sit_on`. Returns +## { piece: Smashable, top: float } so a caller can stack the next thing on top. +func _glb_piece(path: String, kind: String, xz: Vector2, frozen: bool, sit_on := 0.0) -> Dictionary: + var s := Smashable.new() + s.kind = kind + s.start_frozen = frozen + var packed := load(path) as PackedScene + var vis: Node3D = packed.instantiate() + s.add_child(vis) + _world.add_child(s) # in-tree so transforms/AABBs resolve + var ab := _body_local_aabb(s, vis) + if ab.size == Vector3.ZERO: + ab = AABB(Vector3(-0.2, 0.0, -0.2), Vector3(0.4, 0.4, 0.4)) # fallback + var col := CollisionShape3D.new() + var box := BoxShape3D.new() + box.size = ab.size + col.shape = box + col.position = ab.get_center() + s.add_child(col) + s.global_position = Vector3(xz.x, sit_on - ab.position.y, xz.y) # AABB bottom lands on sit_on + s.smashed.connect(_on_smashed) + return {"piece": s, "top": sit_on + ab.size.y} - # two crates resting on the shelf; the shelf holds them up. - for cx in [-0.34, 0.34]: - var crate_pos: Vector3 = origin + Vector3(cx, y + 0.22, 0) - var crate := _piece("wood", Vector3(0.5, 0.34, 0.5), crate_pos, false) - shelf.supports.append(crate) # shelf gone -> crate released too - # jackets + a record sit loose in the crate — already dynamic, they fall on - # their own once the crate/shelf is gone. Do NOT add them to supports. - var idx := 0 - for jz in [-0.14, -0.05, 0.05, 0.14]: - _piece("cardboard", Vector3(0.34, 0.34, 0.03), - crate_pos + Vector3(0, 0.12, jz), false) - if idx == 1: - # a record tucked in, lying flat-ish; brittle -> cracks on hard landing - _piece("vinyl", Vector3(0.15, 0.02, 0.15), - crate_pos + Vector3(0, 0.24, jz + 0.02), false, true) - idx += 1 +## Combined AABB of every MeshInstance3D under `visual_root`, in `body`'s local space +## (independent of where `body` currently sits in the world). +func _body_local_aabb(body: Node3D, visual_root: Node3D) -> AABB: + var mis: Array[MeshInstance3D] = [] + _collect_meshes(visual_root, mis) + var inv := body.global_transform.affine_inverse() + var acc := AABB() + var started := false + for mi in mis: + var a := mi.get_aabb() + var rel := inv * mi.global_transform + for i in range(8): + var corner := a.position + Vector3( + a.size.x * float(i & 1), + a.size.y * float((i >> 1) & 1), + a.size.z * float((i >> 2) & 1)) + var p := rel * corner + if not started: + acc = AABB(p, Vector3.ZERO) + started = true + else: + acc = acc.expand(p) + return acc + +func _collect_meshes(n: Node, acc: Array[MeshInstance3D]) -> void: + if n is MeshInstance3D: + acc.append(n) + for c in n.get_children(): + _collect_meshes(c, acc) func _piece(kind: String, size: Vector3, pos: Vector3, frozen: bool, cylinder := false) -> Smashable: var s := Smashable.new()