feat: stream chunks in-game, render 3D catalog props, and ground chunk coordinates
checkpoint / checkpoint (push) Successful in 5m54s
checkpoint / checkpoint (push) Successful in 5m54s
This commit is contained in:
+14
-17
@@ -54,20 +54,16 @@ var is_dirty: bool = false
|
|||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Position this chunk scene at its world-space origin
|
# Position this chunk scene at its 3D world ground coordinates
|
||||||
global_position = bounds.position
|
global_position = Vector3(
|
||||||
# Set up collision shape for the chunk bounds
|
float(coord.x * Chunk.CHUNK_SIZE_X),
|
||||||
_setup_collision()
|
float(coord.z * Chunk.CHUNK_SIZE_Z),
|
||||||
|
float(coord.y * Chunk.CHUNK_SIZE_Y)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _setup_collision() -> void:
|
func _setup_collision() -> void:
|
||||||
# Add a collision shape for the chunk's bounding box
|
pass
|
||||||
var collision := CollisionShape3D.new()
|
|
||||||
var shape := BoxShape3D.new()
|
|
||||||
shape.size = bounds.size
|
|
||||||
collision.shape = shape
|
|
||||||
collision.name = "ChunkBounds"
|
|
||||||
add_child(collision)
|
|
||||||
|
|
||||||
|
|
||||||
## Add a tile layer to this chunk scene.
|
## Add a tile layer to this chunk scene.
|
||||||
@@ -91,9 +87,10 @@ func add_object(obj: WorldObject) -> void:
|
|||||||
func _add_static_object(obj: WorldObject) -> void:
|
func _add_static_object(obj: WorldObject) -> void:
|
||||||
var static_obj := StaticObjectNode.new()
|
var static_obj := StaticObjectNode.new()
|
||||||
static_obj.world_object = obj
|
static_obj.world_object = obj
|
||||||
static_obj.position = obj.position
|
# Map chunk coordinates (X_ground, Y_ground, Z_height) to Godot 3D (X, Y_height, Z_ground)
|
||||||
static_obj.quaternion = Quaternion(obj.rotation.x, obj.rotation.y, obj.rotation.z, obj.rotation.w)
|
static_obj.position = Vector3(obj.position.x, obj.position.z, obj.position.y)
|
||||||
static_obj.scale = obj.scale
|
static_obj.quaternion = Quaternion(obj.rotation.x, obj.rotation.z, obj.rotation.y, obj.rotation.w)
|
||||||
|
static_obj.scale = Vector3(obj.scale.x, obj.scale.z, obj.scale.y)
|
||||||
static_obj.name = "static_%s" % obj.id
|
static_obj.name = "static_%s" % obj.id
|
||||||
add_child(static_obj)
|
add_child(static_obj)
|
||||||
|
|
||||||
@@ -101,9 +98,9 @@ func _add_static_object(obj: WorldObject) -> void:
|
|||||||
func _add_dynamic_object(obj: WorldObject) -> void:
|
func _add_dynamic_object(obj: WorldObject) -> void:
|
||||||
var dynamic_obj := DynamicObjectNode.new()
|
var dynamic_obj := DynamicObjectNode.new()
|
||||||
dynamic_obj.world_object = obj
|
dynamic_obj.world_object = obj
|
||||||
dynamic_obj.position = obj.position
|
dynamic_obj.position = Vector3(obj.position.x, obj.position.z, obj.position.y)
|
||||||
dynamic_obj.quaternion = Quaternion(obj.rotation.x, obj.rotation.y, obj.rotation.z, obj.rotation.w)
|
dynamic_obj.quaternion = Quaternion(obj.rotation.x, obj.rotation.z, obj.rotation.y, obj.rotation.w)
|
||||||
dynamic_obj.scale = obj.scale
|
dynamic_obj.scale = Vector3(obj.scale.x, obj.scale.z, obj.scale.y)
|
||||||
dynamic_obj.name = "dynamic_%s" % obj.id
|
dynamic_obj.name = "dynamic_%s" % obj.id
|
||||||
add_child(dynamic_obj)
|
add_child(dynamic_obj)
|
||||||
|
|
||||||
|
|||||||
+91
-12
@@ -37,12 +37,65 @@ var _status_label: Label = null
|
|||||||
var _build_editor: InGameEditor = null
|
var _build_editor: InGameEditor = null
|
||||||
|
|
||||||
|
|
||||||
|
var _stream_manager: ChunkStreamManager = null
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_show_build_stamp()
|
_show_build_stamp()
|
||||||
if Session.editing:
|
if Session.editing:
|
||||||
_open_editor_for_world()
|
_open_editor_for_world()
|
||||||
elif Session.active:
|
else:
|
||||||
|
if Session.active:
|
||||||
_setup_networked()
|
_setup_networked()
|
||||||
|
_setup_chunk_streaming()
|
||||||
|
|
||||||
|
|
||||||
|
func _setup_chunk_streaming() -> void:
|
||||||
|
if Session.world_id == null or int(Session.world_id) <= 0:
|
||||||
|
return
|
||||||
|
if _stream_manager != null and is_instance_valid(_stream_manager):
|
||||||
|
return
|
||||||
|
|
||||||
|
_stream_manager = ChunkStreamManager.new()
|
||||||
|
_stream_manager.name = "ChunkStreamManager"
|
||||||
|
_stream_manager.player = _player
|
||||||
|
_stream_manager.streaming_radius = 2
|
||||||
|
add_child(_stream_manager)
|
||||||
|
|
||||||
|
var chunk_source := HttpChunkSource.new()
|
||||||
|
chunk_source.name = "HttpChunkSource"
|
||||||
|
chunk_source.world_id = str(Session.world_id)
|
||||||
|
chunk_source.room_slug = "main"
|
||||||
|
chunk_source.stream_manager = _stream_manager
|
||||||
|
_stream_manager.add_child(chunk_source)
|
||||||
|
_stream_manager.set_server_rpc(chunk_source)
|
||||||
|
_stream_manager.is_active = true
|
||||||
|
|
||||||
|
_stream_manager.chunk_loaded.connect(_on_chunk_loaded)
|
||||||
|
_stream_manager._load_chunk(Vector3i.ZERO)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_chunk_loaded(_coord: Vector3i, chunk_scene: Node) -> void:
|
||||||
|
if chunk_scene is ChunkScene:
|
||||||
|
var cs := chunk_scene as ChunkScene
|
||||||
|
if not cs.world_objects.is_empty() or not cs.tile_layers.is_empty():
|
||||||
|
_hide_default_test_props()
|
||||||
|
|
||||||
|
|
||||||
|
func _hide_default_test_props() -> void:
|
||||||
|
var props := get_node_or_null("Level/Props")
|
||||||
|
if props != null and props is Node3D:
|
||||||
|
(props as Node3D).visible = false
|
||||||
|
for child in props.get_children():
|
||||||
|
if child is CollisionObject3D:
|
||||||
|
(child as CollisionObject3D).set_collision_layer_value(1, false)
|
||||||
|
(child as CollisionObject3D).set_collision_mask_value(1, false)
|
||||||
|
var chest := get_node_or_null("Level/ScriptedChest")
|
||||||
|
if chest != null and chest is Node3D:
|
||||||
|
(chest as Node3D).visible = false
|
||||||
|
if chest is CollisionObject3D:
|
||||||
|
(chest as CollisionObject3D).set_collision_layer_value(1, false)
|
||||||
|
(chest as CollisionObject3D).set_collision_mask_value(1, false)
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
@@ -89,7 +142,29 @@ func _open_build_mode() -> void:
|
|||||||
var room_slug := "main"
|
var room_slug := "main"
|
||||||
var coord := Vector3i.ZERO
|
var coord := Vector3i.ZERO
|
||||||
|
|
||||||
var model := ChunkEditorModel.create_empty(32, 32, world_id_str, room_slug, coord)
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
_player.set_physics_process(false)
|
||||||
|
_player.set_process_unhandled_input(false)
|
||||||
|
|
||||||
|
var save_service: ChunkSaveService = null
|
||||||
|
var model: ChunkEditorModel = null
|
||||||
|
var base_revision := 0
|
||||||
|
|
||||||
|
if int(world_id_str) > 0:
|
||||||
|
save_service = ChunkSaveService.new()
|
||||||
|
save_service.world_id = world_id_str
|
||||||
|
save_service.room_slug = room_slug
|
||||||
|
add_child(save_service)
|
||||||
|
|
||||||
|
var fetch_result: Dictionary = await save_service.fetch_chunk(coord)
|
||||||
|
if fetch_result.get("ok", false) and fetch_result.get("model") != null:
|
||||||
|
model = fetch_result["model"]
|
||||||
|
base_revision = int(fetch_result.get("revision", 0))
|
||||||
|
|
||||||
|
if model == null:
|
||||||
|
model = ChunkEditorModel.create_empty(32, 32, world_id_str, room_slug, coord)
|
||||||
|
base_revision = 0
|
||||||
|
|
||||||
var catalog := EditorObjectCatalog.new()
|
var catalog := EditorObjectCatalog.new()
|
||||||
var err := catalog.load_catalog()
|
var err := catalog.load_catalog()
|
||||||
if err != OK:
|
if err != OK:
|
||||||
@@ -97,20 +172,12 @@ func _open_build_mode() -> void:
|
|||||||
var core := EditorCore.new(model, catalog)
|
var core := EditorCore.new(model, catalog)
|
||||||
|
|
||||||
_build_editor = InGameEditor.new(core)
|
_build_editor = InGameEditor.new(core)
|
||||||
if int(world_id_str) > 0:
|
if save_service != null:
|
||||||
var save_service := ChunkSaveService.new()
|
_build_editor.setup_persistence(save_service, world_id_str, room_slug, coord, base_revision)
|
||||||
save_service.world_id = world_id_str
|
|
||||||
save_service.room_slug = room_slug
|
|
||||||
add_child(save_service)
|
|
||||||
_build_editor.setup_persistence(save_service, world_id_str, room_slug, coord, 0)
|
|
||||||
|
|
||||||
_build_editor.closed.connect(_close_build_mode)
|
_build_editor.closed.connect(_close_build_mode)
|
||||||
add_child(_build_editor)
|
add_child(_build_editor)
|
||||||
|
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
||||||
_player.set_physics_process(false)
|
|
||||||
_player.set_process_unhandled_input(false)
|
|
||||||
|
|
||||||
|
|
||||||
## Shared teardown for both close paths (F2 again, or the editor's own
|
## Shared teardown for both close paths (F2 again, or the editor's own
|
||||||
## Close button/`closed` signal) -- safe to call twice: `queue_free()` on an
|
## Close button/`closed` signal) -- safe to call twice: `queue_free()` on an
|
||||||
@@ -127,6 +194,14 @@ func _close_build_mode() -> void:
|
|||||||
_player.set_process_unhandled_input(true)
|
_player.set_process_unhandled_input(true)
|
||||||
_player._refresh_mouse_mode()
|
_player._refresh_mouse_mode()
|
||||||
|
|
||||||
|
if _stream_manager == null:
|
||||||
|
_setup_chunk_streaming()
|
||||||
|
elif is_instance_valid(_stream_manager):
|
||||||
|
var source := _stream_manager.get_node_or_null("HttpChunkSource") as HttpChunkSource
|
||||||
|
if source != null and Session.world_id != null and int(Session.world_id) > 0:
|
||||||
|
source.world_id = str(Session.world_id)
|
||||||
|
_stream_manager.force_reload_chunk(Vector3i.ZERO)
|
||||||
|
|
||||||
|
|
||||||
## "Edit world" entry point from the world browser
|
## "Edit world" entry point from the world browser
|
||||||
## (world_persistence_impl.md §6 D4 / §9 "Client/editor task"). Reached only
|
## (world_persistence_impl.md §6 D4 / §9 "Client/editor task"). Reached only
|
||||||
@@ -198,6 +273,10 @@ func _leave_world() -> void:
|
|||||||
|
|
||||||
## Close the ENet connection and stop it from calling us back mid-teardown.
|
## Close the ENet connection and stop it from calling us back mid-teardown.
|
||||||
func _teardown_net() -> void:
|
func _teardown_net() -> void:
|
||||||
|
if _stream_manager != null and is_instance_valid(_stream_manager):
|
||||||
|
_stream_manager.clear_all_chunks()
|
||||||
|
_stream_manager.queue_free()
|
||||||
|
_stream_manager = null
|
||||||
if _net == null or not is_instance_valid(_net):
|
if _net == null or not is_instance_valid(_net):
|
||||||
return
|
return
|
||||||
if _net.server_disconnected.is_connected(_on_server_lost):
|
if _net.server_disconnected.is_connected(_on_server_lost):
|
||||||
|
|||||||
+26
-7
@@ -263,9 +263,9 @@ func _build_chunk_collision(parent: Node3D, coord: Vector3i, data: ChunkDataDeco
|
|||||||
var body := StaticBody3D.new()
|
var body := StaticBody3D.new()
|
||||||
body.name = "chunk_%d_%d_%d" % [coord.x, coord.y, coord.z]
|
body.name = "chunk_%d_%d_%d" % [coord.x, coord.y, coord.z]
|
||||||
var chunk_origin := Vector3(
|
var chunk_origin := Vector3(
|
||||||
coord.x * Chunk.CHUNK_SIZE_X,
|
float(coord.x * Chunk.CHUNK_SIZE_X),
|
||||||
coord.y * Chunk.CHUNK_SIZE_Y,
|
float(coord.z * Chunk.CHUNK_SIZE_Z),
|
||||||
coord.z * Chunk.CHUNK_SIZE_Z
|
float(coord.y * Chunk.CHUNK_SIZE_Y)
|
||||||
)
|
)
|
||||||
|
|
||||||
for layer in data.tile_layers:
|
for layer in data.tile_layers:
|
||||||
@@ -282,20 +282,39 @@ func _build_chunk_collision(parent: Node3D, coord: Vector3i, data: ChunkDataDeco
|
|||||||
var box := BoxShape3D.new()
|
var box := BoxShape3D.new()
|
||||||
box.size = Vector3.ONE
|
box.size = Vector3.ONE
|
||||||
shape.shape = box
|
shape.shape = box
|
||||||
shape.position = chunk_origin + Vector3(x + 0.5, y + 0.5, z + 0.5)
|
shape.position = chunk_origin + Vector3(x + 0.5, z + 0.5, y + 0.5)
|
||||||
body.add_child(shape)
|
body.add_child(shape)
|
||||||
_collision_shapes_built += 1
|
_collision_shapes_built += 1
|
||||||
|
|
||||||
for obj in data.objects:
|
for obj in data.objects:
|
||||||
if obj.dynamic:
|
if obj.dynamic:
|
||||||
continue # dynamic templates are spawnable entities, not static geometry
|
continue # dynamic templates are spawnable entities, not static geometry
|
||||||
if bool(obj.properties.get("walkable", true)):
|
if obj.type == "spawn_point" or bool(obj.properties.get("walkable", false)):
|
||||||
continue
|
continue
|
||||||
var shape := CollisionShape3D.new()
|
var shape := CollisionShape3D.new()
|
||||||
var box := BoxShape3D.new()
|
var box := BoxShape3D.new()
|
||||||
box.size = obj.scale
|
var aid := str(obj.asset_id).to_lower()
|
||||||
|
var cat_id := str(obj.properties.get("catalog_id", "")).to_lower()
|
||||||
|
if "lamp" in aid or "lamp" in cat_id:
|
||||||
|
box.size = Vector3(0.5, 3.2, 0.5)
|
||||||
shape.shape = box
|
shape.shape = box
|
||||||
shape.position = obj.position
|
shape.position = chunk_origin + Vector3(obj.position.x, obj.position.z + 1.6, obj.position.y)
|
||||||
|
elif "tree" in aid or "tree" in cat_id:
|
||||||
|
box.size = Vector3(0.8, 4.0, 0.8)
|
||||||
|
shape.shape = box
|
||||||
|
shape.position = chunk_origin + Vector3(obj.position.x, obj.position.z + 2.0, obj.position.y)
|
||||||
|
elif "chest" in aid or "chest" in cat_id:
|
||||||
|
box.size = Vector3(1.0, 0.8, 0.6)
|
||||||
|
shape.shape = box
|
||||||
|
shape.position = chunk_origin + Vector3(obj.position.x, obj.position.z + 0.4, obj.position.y)
|
||||||
|
elif "rock" in aid or "rock" in cat_id:
|
||||||
|
box.size = Vector3(1.2, 1.0, 1.2)
|
||||||
|
shape.shape = box
|
||||||
|
shape.position = chunk_origin + Vector3(obj.position.x, obj.position.z + 0.5, obj.position.y)
|
||||||
|
else:
|
||||||
|
box.size = Vector3(obj.scale.x, obj.scale.z, obj.scale.y)
|
||||||
|
shape.shape = box
|
||||||
|
shape.position = chunk_origin + Vector3(obj.position.x, obj.position.z + obj.scale.y * 0.5, obj.position.y)
|
||||||
body.add_child(shape)
|
body.add_child(shape)
|
||||||
_collision_shapes_built += 1
|
_collision_shapes_built += 1
|
||||||
|
|
||||||
|
|||||||
@@ -29,16 +29,282 @@ func _ready() -> void:
|
|||||||
|
|
||||||
## Load and attach the object's mesh.
|
## Load and attach the object's mesh.
|
||||||
func _load_mesh() -> void:
|
func _load_mesh() -> void:
|
||||||
_mesh_instance = MeshInstance3D.new()
|
# 1. Try to load the asset directly if it exists on disk
|
||||||
_mesh_instance.name = "mesh"
|
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
|
||||||
|
|
||||||
# Try to load the asset by its id (path). Fall back to a placeholder.
|
# 2. Build a stylized, recognizable 3D representation based on catalog ID / asset ID
|
||||||
var mesh := _load_asset_mesh(world_object.asset_id)
|
var cat_id := _get_catalog_id()
|
||||||
if mesh == null:
|
_build_procedural_object(cat_id)
|
||||||
mesh = _make_placeholder_mesh()
|
|
||||||
|
|
||||||
_mesh_instance.mesh = mesh
|
|
||||||
add_child(_mesh_instance)
|
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.
|
## Attempt to load a mesh from the asset path.
|
||||||
@@ -49,7 +315,7 @@ func _load_asset_mesh(asset_id: String) -> Mesh:
|
|||||||
if ResourceLoader.exists(path):
|
if ResourceLoader.exists(path):
|
||||||
var res := load(path)
|
var res := load(path)
|
||||||
if res is Mesh:
|
if res is Mesh:
|
||||||
return res
|
return res as Mesh
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -74,17 +74,12 @@ func _create_mesh_for_tile(tile_def: TileDef) -> void:
|
|||||||
_mesh_instances[tile_def.id] = mesh_instance
|
_mesh_instances[tile_def.id] = mesh_instance
|
||||||
|
|
||||||
|
|
||||||
## Convert a tile grid coordinate to a world-space position (center of tile).
|
## Convert a tile grid coordinate to a chunk-local 3D position (center of tile).
|
||||||
func _tile_to_world(x: int, y: int, z: int) -> Vector3:
|
func _tile_to_world(x: int, y: int, z: int) -> Vector3:
|
||||||
var origin := Vector3(
|
return Vector3(
|
||||||
chunk_coord.x * tile_layer.width,
|
|
||||||
chunk_coord.y * tile_layer.height,
|
|
||||||
chunk_coord.z * tile_layer.depth
|
|
||||||
)
|
|
||||||
return origin + Vector3(
|
|
||||||
x * TILE_SIZE + TILE_SIZE * 0.5,
|
x * TILE_SIZE + TILE_SIZE * 0.5,
|
||||||
y * TILE_SIZE + TILE_SIZE * 0.5,
|
z * TILE_SIZE + TILE_SIZE * 0.5,
|
||||||
z * TILE_SIZE + TILE_SIZE * 0.5
|
y * TILE_SIZE + TILE_SIZE * 0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user