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:
parent
cc5b118d71
commit
7c88e8371d
BIN
assets/store/crate.glb
Normal file
BIN
assets/store/crate.glb
Normal file
Binary file not shown.
45
assets/store/crate.glb.import
Normal file
45
assets/store/crate.glb.import
Normal 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
BIN
assets/store/rack.glb
Normal file
Binary file not shown.
45
assets/store/rack.glb.import
Normal file
45
assets/store/rack.glb.import
Normal 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
BIN
assets/store/record.glb
Normal file
Binary file not shown.
45
assets/store/record.glb.import
Normal file
45
assets/store/record.glb.import
Normal 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
|
||||
109
scripts/Main.gd
109
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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user