extends Node # Подгружает карту отдельным пакетом при первом входе в портал: в вебе её не качают при старте игры. # Пакет (например backrooms.pck) лежит рядом с index.html. Если сцена уже есть в проекте # (редактор, десктоп-запуск) — качать нечего, переходим сразу. signal enter_failed const PACK_URL_ARG := "--pack-url=" # Для тестов на десктопе: --pack-url=http://127.0.0.1:8000/ var _loaded := {} # имя пакета -> true, чтобы не качать и не монтировать дважды var _busy := false var _layer: CanvasLayer var _label: Label var _bar: ProgressBar func _ready() -> void: process_mode = Node.PROCESS_MODE_ALWAYS func enter(map: String, scene_path: String, pack_name: String, title: String) -> void: if _busy: return if pack_name != "" and not _loaded.has(pack_name) and not ResourceLoader.exists(scene_path): _busy = true var ok := await _download_and_mount(pack_name, title) _busy = false if not ok: enter_failed.emit() return Net.go_to_map(map, scene_path) func _download_and_mount(pack_name: String, title: String) -> bool: _show("Загрузка: %s…" % title) get_tree().paused = true # Пока качаем, мир стоит (камень не должен убить игрока на экране загрузки) var http := HTTPRequest.new() http.process_mode = Node.PROCESS_MODE_ALWAYS # Если сервер сжимает пакет gzip, браузер распаковывает его сам; вторая распаковка в движке ломает загрузку http.accept_gzip = false add_child(http) # Тело берём в память и пишем в user:// сами: в вебе download_file файл не создаёт var state := {"done": false, "result": -1, "code": 0, "body": PackedByteArray()} http.request_completed.connect(func(result: int, code: int, _h, body: PackedByteArray): state.result = result state.code = code state.body = body state.done = true) var ok := false var detail := "запрос не отправлен" # no-cache: браузер сверяет файл с сервером (после обновления пакета не подсунет старый из кэша) if http.request(_pack_url(pack_name), ["Cache-Control: no-cache"]) == OK: while not state.done: _update_progress(http, title) await get_tree().process_frame if state.result == HTTPRequest.RESULT_SUCCESS and state.code == 200: var path := "user://" + pack_name var file := FileAccess.open(path, FileAccess.WRITE) if file == null: detail = "не удалось сохранить пакет, код %d" % FileAccess.get_open_error() else: file.store_buffer(state.body) file.close() state.body = PackedByteArray() # Освобождаем память: пакет уже на диске ok = ProjectSettings.load_resource_pack(path, false) # false: файлы основной игры в приоритете detail = "пакет скачан, но не подключился (%s)" % _describe_file(path) else: detail = "HTTP %d, код загрузки %d" % [state.code, state.result] http.queue_free() if ok: _loaded[pack_name] = true _hide() else: printerr("MapLoader: не удалось загрузить %s: %s" % [pack_name, detail]) _label.text = "Не удалось загрузить карту\n%s" % detail _bar.visible = false await get_tree().create_timer(2.5).timeout _hide() get_tree().paused = false return ok # Для сообщения об ошибке: размер и первые байты скачанного файла func _describe_file(path: String) -> String: var f := FileAccess.open(path, FileAccess.READ) if f == null: return "файл не открывается, код %d" % FileAccess.get_open_error() var head := f.get_buffer(4) return "%d байт, начало %s" % [f.get_length(), head.hex_encode()] func _pack_url(pack_name: String) -> String: for arg in OS.get_cmdline_user_args(): if arg.begins_with(PACK_URL_ARG): return arg.substr(PACK_URL_ARG.length()) + pack_name if OS.has_feature("web"): return str(JavaScriptBridge.eval("new URL('%s', window.location.href).href" % pack_name)) return "http://127.0.0.1:8000/" + pack_name # ---------- Экран загрузки ---------- func _show(text: String) -> void: _layer = CanvasLayer.new() _layer.layer = 100 add_child(_layer) var bg := ColorRect.new() bg.color = Color(0, 0, 0, 0.88) bg.mouse_filter = Control.MOUSE_FILTER_IGNORE _layer.add_child(bg) bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _label = Label.new() _label.text = text _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _label.add_theme_font_size_override("font_size", 40) bg.add_child(_label) _label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _bar = ProgressBar.new() _bar.custom_minimum_size = Vector2(420, 14) _bar.show_percentage = false _bar.indeterminate = true var track := StyleBoxFlat.new() track.bg_color = Color(1, 1, 1, 0.15) var fill := StyleBoxFlat.new() fill.bg_color = Color(1.0, 0.85, 0.25) _bar.add_theme_stylebox_override("background", track) _bar.add_theme_stylebox_override("fill", fill) bg.add_child(_bar) _bar.set_anchors_and_offsets_preset(Control.PRESET_CENTER_BOTTOM, Control.PRESET_MODE_KEEP_SIZE) _bar.position.y -= 120 func _update_progress(http: HTTPRequest, title: String) -> void: var got := http.get_downloaded_bytes() var total := http.get_body_size() # -1, если сервер не прислал Content-Length (например, gzip) if total > 0: _bar.indeterminate = false _bar.max_value = total _bar.value = got _label.text = "Загрузка: %s\n%.1f / %.1f МБ" % [title, got / 1048576.0, total / 1048576.0] else: _label.text = "Загрузка: %s\n%.1f МБ" % [title, got / 1048576.0] func _hide() -> void: if _layer: _layer.queue_free() _layer = null