fix: stabilize web registration and asset loading

This commit is contained in:
2026-08-01 20:18:47 +08:00
parent bcdaa737eb
commit 7a07f52e2c
17 changed files with 1085 additions and 20 deletions

View File

@@ -30,6 +30,8 @@ signal scene_changed(scene_name: String)
# 场景切换开始信号
# 参数: scene_name - 即将切换到的场景名称
signal scene_change_started(scene_name: String)
signal scene_pack_progress(scene_name: String, downloaded_bytes: int, total_bytes: int)
signal scene_pack_failed(scene_name: String, message: String)
# ============ 成员变量 ============
@@ -38,6 +40,25 @@ var current_scene_name: String = "" # 当前场景名称
var is_changing_scene: bool = false # 是否正在切换场景
var _next_scene_position: Variant = null # 下一个场景的初始位置 (Vector2 or null)
var _next_spawn_name: String = "" # 下一个场景的出生点名称 (String)
var _pack_manifest: Dictionary = {}
var _loaded_scene_packs: Dictionary = {}
var _active_pack_request: HTTPRequest
var _active_pack_scene_name: String = ""
var _pack_overlay: CanvasLayer
var _pack_status_label: Label
var _pack_progress_bar: ProgressBar
const PACK_MANIFEST_PATH: String = "/packs/manifest.json"
const PACK_CACHE_DIR: String = "user://scene-packs"
const SCENE_PACK_KEYS: Dictionary = {
"main": "auth",
"auth": "auth",
"square": "square",
"work_zone": "work_zone",
"cafe_interior": "cafe_interior",
"room": "personal_space",
"personal_space": "personal_space",
}
# 场景路径映射表
# 将场景名称映射到实际的文件路径
@@ -101,6 +122,14 @@ func change_scene(scene_name: String, use_transition: bool = true):
if use_transition:
await show_transition()
if OS.get_name() == "Web" and SCENE_PACK_KEYS.has(scene_name):
var packLoaded := await _ensure_scene_pack(scene_name)
if not packLoaded:
is_changing_scene = false
if use_transition:
await hide_transition()
return false
# 执行场景切换
var error = get_tree().change_scene_to_file(scene_path)
if error != OK:
@@ -119,6 +148,162 @@ func change_scene(scene_name: String, use_transition: bool = true):
return true
func _process(_delta: float) -> void:
if not is_instance_valid(_active_pack_request):
return
var downloaded := _active_pack_request.get_downloaded_bytes()
var total := _active_pack_request.get_body_size()
if is_instance_valid(_pack_progress_bar):
_pack_progress_bar.indeterminate = total <= 0
if total > 0:
_pack_progress_bar.max_value = total
_pack_progress_bar.value = downloaded
if is_instance_valid(_pack_status_label):
_pack_status_label.text = "正在加载场景 %s" % _format_download_progress(downloaded, total)
scene_pack_progress.emit(_active_pack_scene_name, downloaded, total)
func _ensure_scene_pack(scene_name: String) -> bool:
var packKey := str(SCENE_PACK_KEYS.get(scene_name, ""))
if packKey.is_empty() or bool(_loaded_scene_packs.get(packKey, false)):
return true
_show_pack_overlay("正在准备场景...")
if _pack_manifest.is_empty():
_pack_manifest = await _download_pack_manifest()
if _pack_manifest.is_empty():
_show_pack_error(scene_name, "场景清单加载失败,请检查网络后重试")
return false
var entryVariant: Variant = _pack_manifest.get(packKey, {})
if not (entryVariant is Dictionary):
_show_pack_error(scene_name, "服务器缺少场景资源:%s" % packKey)
return false
var entry := entryVariant as Dictionary
var fileName := str(entry.get("file", "")).get_file()
if fileName.is_empty() or not fileName.ends_with(".pck"):
_show_pack_error(scene_name, "场景资源清单格式错误")
return false
var cacheDirAbsolute := ProjectSettings.globalize_path(PACK_CACHE_DIR)
DirAccess.make_dir_recursive_absolute(cacheDirAbsolute)
var localPath := "%s/%s" % [PACK_CACHE_DIR, fileName]
if FileAccess.file_exists(localPath) and ProjectSettings.load_resource_pack(localPath, true):
_loaded_scene_packs[packKey] = true
_hide_pack_overlay()
return true
if FileAccess.file_exists(localPath):
DirAccess.remove_absolute(ProjectSettings.globalize_path(localPath))
var packUrl := _resolve_web_url("/packs/%s" % fileName)
if packUrl.is_empty() or not await _download_pack(scene_name, packUrl, localPath):
_show_pack_error(scene_name, "场景下载失败,请检查网络后重试")
return false
if not ProjectSettings.load_resource_pack(localPath, true):
DirAccess.remove_absolute(ProjectSettings.globalize_path(localPath))
_show_pack_error(scene_name, "场景资源损坏,请重新进入")
return false
_loaded_scene_packs[packKey] = true
_hide_pack_overlay()
return true
func _download_pack_manifest() -> Dictionary:
var request := HTTPRequest.new()
request.timeout = 12.0
add_child(request)
var url := _resolve_web_url(PACK_MANIFEST_PATH)
if url.is_empty() or request.request(url, ["Cache-Control: no-cache"]) != OK:
request.queue_free()
return {}
var response: Array = await request.request_completed
request.queue_free()
if response.size() < 4 or int(response[0]) != HTTPRequest.RESULT_SUCCESS:
return {}
var responseCode := int(response[1])
if responseCode < 200 or responseCode >= 300:
return {}
var json := JSON.new()
if json.parse((response[3] as PackedByteArray).get_string_from_utf8()) != OK:
return {}
if not (json.data is Dictionary):
return {}
var root := json.data as Dictionary
var packsVariant: Variant = root.get("packs", {})
return packsVariant as Dictionary if packsVariant is Dictionary else {}
func _download_pack(scene_name: String, url: String, local_path: String) -> bool:
var request := HTTPRequest.new()
request.timeout = 180.0
request.download_file = local_path
add_child(request)
_active_pack_request = request
_active_pack_scene_name = scene_name
if request.request(url) != OK:
_active_pack_request = null
_active_pack_scene_name = ""
request.queue_free()
return false
var response: Array = await request.request_completed
_active_pack_request = null
_active_pack_scene_name = ""
request.queue_free()
if response.size() < 2 or int(response[0]) != HTTPRequest.RESULT_SUCCESS:
return false
var responseCode := int(response[1])
return responseCode >= 200 and responseCode < 300
func _resolve_web_url(path: String) -> String:
if not Engine.has_singleton("JavaScriptBridge"):
return ""
var jsBridge: Object = Engine.get_singleton("JavaScriptBridge")
if jsBridge == null:
return ""
return str(jsBridge.eval("new URL('%s', window.location.href).href" % path, true)).strip_edges()
func _show_pack_overlay(message: String) -> void:
if not is_instance_valid(_pack_overlay):
_pack_overlay = CanvasLayer.new()
_pack_overlay.layer = 1000
add_child(_pack_overlay)
var backdrop := ColorRect.new()
backdrop.color = Color(0.025, 0.09, 0.15, 0.92)
backdrop.set_anchors_preset(Control.PRESET_FULL_RECT)
_pack_overlay.add_child(backdrop)
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
_pack_overlay.add_child(center)
var box := VBoxContainer.new()
box.custom_minimum_size = Vector2(420, 100)
box.add_theme_constant_override("separation", 18)
center.add_child(box)
_pack_status_label = Label.new()
_pack_status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_pack_status_label.add_theme_font_size_override("font_size", 22)
box.add_child(_pack_status_label)
_pack_progress_bar = ProgressBar.new()
_pack_progress_bar.custom_minimum_size = Vector2(420, 14)
_pack_progress_bar.show_percentage = false
_pack_progress_bar.indeterminate = true
box.add_child(_pack_progress_bar)
_pack_overlay.show()
_pack_status_label.text = message
_pack_progress_bar.indeterminate = true
func _hide_pack_overlay() -> void:
if is_instance_valid(_pack_overlay):
_pack_overlay.hide()
func _show_pack_error(scene_name: String, message: String) -> void:
if not is_instance_valid(_pack_overlay):
_show_pack_overlay(message)
_pack_status_label.text = message
_pack_progress_bar.hide()
scene_pack_failed.emit(scene_name, message)
await get_tree().create_timer(3.0).timeout
_pack_progress_bar.show()
_hide_pack_overlay()
func _format_download_progress(downloaded: int, total: int) -> String:
var downloadedMb := float(downloaded) / 1048576.0
if total <= 0:
return "%.1f MB" % downloadedMb
return "%.1f / %.1f MB" % [downloadedMb, float(total) / 1048576.0]
# ============ 查询方法 ============
# 获取当前场景名称