334 lines
9.5 KiB
GDScript
334 lines
9.5 KiB
GDScript
class_name StaticObjectNode
|
|
extends Node3D
|
|
## A visual representation of a static world object within a chunk.
|
|
##
|
|
## This node is instantiated by [ChunkScene] for each static (non-dynamic)
|
|
## [WorldObject]. It renders the object's mesh (referenced by `asset_id`) at
|
|
## the object's transform. If the asset is not yet loaded, a placeholder mesh
|
|
## is shown.
|
|
##
|
|
## ## Structure
|
|
##
|
|
## ```
|
|
## StaticObjectNode (Node3D)
|
|
## └── MeshInstance3D # the object's visual mesh
|
|
## ```
|
|
|
|
## The world object data this node represents.
|
|
var world_object: WorldObject = null
|
|
|
|
## The loaded mesh instance (or placeholder).
|
|
var _mesh_instance: MeshInstance3D = null
|
|
|
|
|
|
func _ready() -> void:
|
|
if world_object == null:
|
|
return
|
|
_load_mesh()
|
|
|
|
|
|
## Load and attach the object's mesh.
|
|
func _load_mesh() -> void:
|
|
# 1. Try to load the asset directly if it exists on disk
|
|
var asset_node := _try_load_asset(world_object.asset_id)
|
|
if asset_node != null:
|
|
add_child(asset_node)
|
|
if asset_node is MeshInstance3D:
|
|
_mesh_instance = asset_node as MeshInstance3D
|
|
return
|
|
|
|
# 2. Build a stylized, recognizable 3D representation based on catalog ID / asset ID
|
|
var cat_id := _get_catalog_id()
|
|
_build_procedural_object(cat_id)
|
|
|
|
|
|
func _get_catalog_id() -> String:
|
|
if world_object == null:
|
|
return ""
|
|
if world_object.properties is Dictionary and world_object.properties.has("catalog_id"):
|
|
return str(world_object.properties["catalog_id"]).to_lower()
|
|
return world_object.asset_id.get_file().get_basename().to_lower()
|
|
|
|
|
|
func _try_load_asset(asset_id: String) -> Node:
|
|
if asset_id.is_empty():
|
|
return null
|
|
var path := "res://assets/%s" % asset_id
|
|
if ResourceLoader.exists(path):
|
|
var res := load(path)
|
|
if res is PackedScene:
|
|
return (res as PackedScene).instantiate()
|
|
elif res is Mesh:
|
|
var mi := MeshInstance3D.new()
|
|
mi.name = "mesh"
|
|
mi.mesh = res as Mesh
|
|
return mi
|
|
return null
|
|
|
|
|
|
func _build_procedural_object(cat_id: String) -> void:
|
|
var container := Node3D.new()
|
|
container.name = "visuals"
|
|
add_child(container)
|
|
|
|
if "lamp" in cat_id:
|
|
# Lamp post: dark iron vertical pole + glowing lantern + omni light
|
|
var pole := MeshInstance3D.new()
|
|
pole.name = "pole"
|
|
var pole_mesh := CylinderMesh.new()
|
|
pole_mesh.height = 3.2
|
|
pole_mesh.top_radius = 0.07
|
|
pole_mesh.bottom_radius = 0.1
|
|
var iron_mat := StandardMaterial3D.new()
|
|
iron_mat.albedo_color = Color(0.2, 0.22, 0.25)
|
|
iron_mat.metallic = 0.85
|
|
iron_mat.roughness = 0.35
|
|
pole_mesh.material = iron_mat
|
|
pole.mesh = pole_mesh
|
|
pole.position = Vector3(0, 1.6, 0)
|
|
container.add_child(pole)
|
|
_mesh_instance = pole
|
|
|
|
var cap := MeshInstance3D.new()
|
|
cap.name = "cap"
|
|
var cap_mesh := CylinderMesh.new()
|
|
cap_mesh.height = 0.15
|
|
cap_mesh.top_radius = 0.05
|
|
cap_mesh.bottom_radius = 0.3
|
|
cap_mesh.material = iron_mat
|
|
cap.mesh = cap_mesh
|
|
cap.position = Vector3(0, 3.2, 0)
|
|
container.add_child(cap)
|
|
|
|
var lantern := MeshInstance3D.new()
|
|
lantern.name = "lantern"
|
|
var lantern_mesh := BoxMesh.new()
|
|
lantern_mesh.size = Vector3(0.32, 0.45, 0.32)
|
|
var glow_mat := StandardMaterial3D.new()
|
|
glow_mat.albedo_color = Color(1.0, 0.95, 0.7)
|
|
glow_mat.emission_enabled = true
|
|
glow_mat.emission = Color(1.0, 0.9, 0.4)
|
|
glow_mat.emission_energy_multiplier = 4.0
|
|
lantern_mesh.material = glow_mat
|
|
lantern.mesh = lantern_mesh
|
|
lantern.position = Vector3(0, 2.9, 0)
|
|
container.add_child(lantern)
|
|
|
|
var light := OmniLight3D.new()
|
|
light.name = "light"
|
|
light.light_color = Color(1.0, 0.93, 0.72)
|
|
light.light_energy = 1.6
|
|
light.omni_range = 10.0
|
|
light.position = Vector3(0, 2.9, 0)
|
|
container.add_child(light)
|
|
|
|
var col := _create_static_collision()
|
|
var col_shape := CollisionShape3D.new()
|
|
var cyl := CylinderShape3D.new()
|
|
cyl.height = 3.2
|
|
cyl.radius = 0.2
|
|
col_shape.shape = cyl
|
|
col_shape.position = Vector3(0, 1.6, 0)
|
|
col.add_child(col_shape)
|
|
container.add_child(col)
|
|
|
|
elif "crate" in cat_id:
|
|
var crate := MeshInstance3D.new()
|
|
crate.name = "crate"
|
|
var crate_mesh := BoxMesh.new()
|
|
crate_mesh.size = Vector3(1.0, 1.0, 1.0)
|
|
var wood_mat := StandardMaterial3D.new()
|
|
wood_mat.albedo_color = Color(0.55, 0.36, 0.18)
|
|
wood_mat.roughness = 0.8
|
|
crate_mesh.material = wood_mat
|
|
crate.mesh = crate_mesh
|
|
crate.position = Vector3(0, 0.5, 0)
|
|
container.add_child(crate)
|
|
_mesh_instance = crate
|
|
|
|
var col := _create_static_collision()
|
|
var col_shape := CollisionShape3D.new()
|
|
var box := BoxShape3D.new()
|
|
box.size = Vector3(1.0, 1.0, 1.0)
|
|
col_shape.shape = box
|
|
col_shape.position = Vector3(0, 0.5, 0)
|
|
col.add_child(col_shape)
|
|
container.add_child(col)
|
|
|
|
elif "rock" in cat_id:
|
|
var rock := MeshInstance3D.new()
|
|
rock.name = "rock"
|
|
var rock_mesh := SphereMesh.new()
|
|
rock_mesh.radius = 0.65
|
|
rock_mesh.height = 1.0
|
|
rock_mesh.radial_segments = 8
|
|
rock_mesh.rings = 6
|
|
var stone_mat := StandardMaterial3D.new()
|
|
stone_mat.albedo_color = Color(0.42, 0.44, 0.46)
|
|
stone_mat.roughness = 0.9
|
|
rock_mesh.material = stone_mat
|
|
rock.mesh = rock_mesh
|
|
rock.position = Vector3(0, 0.5, 0)
|
|
container.add_child(rock)
|
|
_mesh_instance = rock
|
|
|
|
var col := _create_static_collision()
|
|
var col_shape := CollisionShape3D.new()
|
|
var sphere := SphereShape3D.new()
|
|
sphere.radius = 0.65
|
|
col_shape.shape = sphere
|
|
col_shape.position = Vector3(0, 0.5, 0)
|
|
col.add_child(col_shape)
|
|
container.add_child(col)
|
|
|
|
elif "tree" in cat_id:
|
|
var trunk := MeshInstance3D.new()
|
|
trunk.name = "trunk"
|
|
var trunk_mesh := CylinderMesh.new()
|
|
trunk_mesh.height = 1.4
|
|
trunk_mesh.top_radius = 0.2
|
|
trunk_mesh.bottom_radius = 0.25
|
|
var bark_mat := StandardMaterial3D.new()
|
|
bark_mat.albedo_color = Color(0.35, 0.22, 0.12)
|
|
trunk_mesh.material = bark_mat
|
|
trunk.mesh = trunk_mesh
|
|
trunk.position = Vector3(0, 0.7, 0)
|
|
container.add_child(trunk)
|
|
|
|
var leaves1 := MeshInstance3D.new()
|
|
leaves1.name = "leaves1"
|
|
var leaves1_mesh := CylinderMesh.new()
|
|
leaves1_mesh.height = 2.0
|
|
leaves1_mesh.top_radius = 0.1
|
|
leaves1_mesh.bottom_radius = 1.4
|
|
var leaves_mat := StandardMaterial3D.new()
|
|
leaves_mat.albedo_color = Color(0.12, 0.38, 0.15)
|
|
leaves1_mesh.material = leaves_mat
|
|
leaves1.mesh = leaves1_mesh
|
|
leaves1.position = Vector3(0, 2.0, 0)
|
|
container.add_child(leaves1)
|
|
_mesh_instance = leaves1
|
|
|
|
var leaves2 := MeshInstance3D.new()
|
|
leaves2.name = "leaves2"
|
|
var leaves2_mesh := CylinderMesh.new()
|
|
leaves2_mesh.height = 1.6
|
|
leaves2_mesh.top_radius = 0.0
|
|
leaves2_mesh.bottom_radius = 0.9
|
|
leaves2_mesh.material = leaves_mat
|
|
leaves2.mesh = leaves2_mesh
|
|
leaves2.position = Vector3(0, 3.2, 0)
|
|
container.add_child(leaves2)
|
|
|
|
var col := _create_static_collision()
|
|
var col_shape := CollisionShape3D.new()
|
|
var cyl := CylinderShape3D.new()
|
|
cyl.height = 4.0
|
|
cyl.radius = 0.35
|
|
col_shape.shape = cyl
|
|
col_shape.position = Vector3(0, 2.0, 0)
|
|
col.add_child(col_shape)
|
|
container.add_child(col)
|
|
|
|
elif "chest" in cat_id:
|
|
var chest := MeshInstance3D.new()
|
|
chest.name = "chest"
|
|
var chest_mesh := BoxMesh.new()
|
|
chest_mesh.size = Vector3(1.0, 0.8, 0.6)
|
|
var chest_mat := StandardMaterial3D.new()
|
|
chest_mat.albedo_color = Color(0.48, 0.26, 0.12)
|
|
chest_mesh.material = chest_mat
|
|
chest.mesh = chest_mesh
|
|
chest.position = Vector3(0, 0.4, 0)
|
|
container.add_child(chest)
|
|
_mesh_instance = chest
|
|
|
|
var trim := MeshInstance3D.new()
|
|
trim.name = "trim"
|
|
var trim_mesh := BoxMesh.new()
|
|
trim_mesh.size = Vector3(0.2, 0.25, 0.64)
|
|
var gold_mat := StandardMaterial3D.new()
|
|
gold_mat.albedo_color = Color(0.9, 0.75, 0.2)
|
|
trim_mesh.material = gold_mat
|
|
trim.mesh = trim_mesh
|
|
trim.position = Vector3(0, 0.4, 0)
|
|
container.add_child(trim)
|
|
|
|
var col := _create_static_collision()
|
|
var col_shape := CollisionShape3D.new()
|
|
var box := BoxShape3D.new()
|
|
box.size = Vector3(1.0, 0.8, 0.6)
|
|
col_shape.shape = box
|
|
col_shape.position = Vector3(0, 0.4, 0)
|
|
col.add_child(col_shape)
|
|
container.add_child(col)
|
|
|
|
elif "spawn" in cat_id or (world_object != null and world_object.type == "spawn_point"):
|
|
var pad := MeshInstance3D.new()
|
|
pad.name = "pad"
|
|
var pad_mesh := CylinderMesh.new()
|
|
pad_mesh.height = 0.08
|
|
pad_mesh.top_radius = 1.2
|
|
pad_mesh.bottom_radius = 1.2
|
|
var spawn_mat := StandardMaterial3D.new()
|
|
spawn_mat.albedo_color = Color(0.2, 0.75, 0.95)
|
|
spawn_mat.emission_enabled = true
|
|
spawn_mat.emission = Color(0.1, 0.6, 0.85)
|
|
spawn_mat.emission_energy_multiplier = 2.0
|
|
pad_mesh.material = spawn_mat
|
|
pad.mesh = pad_mesh
|
|
pad.position = Vector3(0, 0.04, 0)
|
|
container.add_child(pad)
|
|
_mesh_instance = pad
|
|
|
|
else:
|
|
var mi := MeshInstance3D.new()
|
|
mi.name = "mesh"
|
|
mi.mesh = _make_placeholder_mesh()
|
|
mi.position = Vector3(0, 0.5, 0)
|
|
container.add_child(mi)
|
|
_mesh_instance = mi
|
|
|
|
var col := _create_static_collision()
|
|
var col_shape := CollisionShape3D.new()
|
|
var box := BoxShape3D.new()
|
|
box.size = Vector3(1.0, 1.0, 1.0)
|
|
col_shape.shape = box
|
|
col_shape.position = Vector3(0, 0.5, 0)
|
|
col.add_child(col_shape)
|
|
container.add_child(col)
|
|
|
|
|
|
func _create_static_collision() -> StaticBody3D:
|
|
var sb := StaticBody3D.new()
|
|
sb.name = "collision"
|
|
sb.set_collision_layer_value(1, true)
|
|
sb.set_collision_mask_value(1, true)
|
|
return sb
|
|
|
|
|
|
## Attempt to load a mesh from the asset path.
|
|
func _load_asset_mesh(asset_id: String) -> Mesh:
|
|
if asset_id.is_empty():
|
|
return null
|
|
var path := "res://assets/%s" % asset_id
|
|
if ResourceLoader.exists(path):
|
|
var res := load(path)
|
|
if res is Mesh:
|
|
return res as Mesh
|
|
return null
|
|
|
|
|
|
## Create a simple placeholder box mesh when the real asset is unavailable.
|
|
func _make_placeholder_mesh() -> Mesh:
|
|
var mesh := BoxMesh.new()
|
|
mesh.size = Vector3(1.0, 1.0, 1.0)
|
|
return mesh
|
|
|
|
|
|
## Get the world object's display name.
|
|
func get_display_name() -> String:
|
|
if world_object == null:
|
|
return ""
|
|
return world_object.get_display_name()
|