Bring in real store art: Monster Robot Party racks, crates, records

- game/assets/store/{rack,crate,record}.glb from the 3D-STORE library
- _glb_piece(): instances a GLB as a Smashable's visual, auto-fits a box
  collider to the mesh AABB, and drops it flush to the floor (no hardcoded dims)
- _build_rack now assembles two smashable racks + crates of records on the floor
- cascade preserved: smashing a crate spills the records stacked on it

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Monster Robot Party 2026-07-04 00:39:46 +10:00
parent cc5b118d71
commit 7c88e8371d
7 changed files with 212 additions and 32 deletions

BIN
assets/store/crate.glb Normal file

Binary file not shown.

View File

@ -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

BIN
assets/store/rack.glb Normal file

Binary file not shown.

View File

@ -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

BIN
assets/store/record.glb Normal file

Binary file not shown.

View File

@ -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

View File

@ -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()