# ReconnectUI (P10) —— 断线遮罩 + 自动重连倒计时 + 手动重连。 # # var rc := preload("res://ui/reconnect_ui.gd").new() # add_child(rc) # rc.setup(m2client) # # `disconnected(reason)` → 显示遮罩 + "N 秒后自动重连";到点调 client.reconnect()。 # `entered_game` 或任何非 offline 的 phase_changed → 隐藏。 # 按钮:立即重连 / 停止(不再自动)。 extends CanvasLayer const MobileWindowHost := preload("res://ui/mobile/mobile_window_host.gd") const AUTO_DELAY := 5.0 const MAX_TRIES := 5 var client: Node var _panel: ColorRect var _msg: Label var _count: Label var _left := 0.0 var _tries := 0 var _auto := true var _armed := false var _mobile_mode := false var _box: VBoxContainer var _last_viewport_size := Vector2.ZERO func setup(m2client: Node) -> void: client = m2client layer = 70 _build() if client.has_signal("disconnected"): client.disconnected.connect(_on_disconnected) if client.has_signal("entered_game"): client.entered_game.connect(func(): _dismiss()) if client.has_signal("phase_changed"): client.phase_changed.connect(func(p): # Keep the reconnect surface visible while auth/game handshaking is in # progress. Hiding it on the first "login" phase left players with a # dead-looking world and no retry feedback on mobile. if not _armed and String(p).to_lower() not in ["offline", "", "close", "failed"]: _dismiss()) MobileWindowHost.wire_buttons(self) set_process(true) func set_mobile_mode(enabled: bool) -> void: _mobile_mode = enabled _apply_layout() func dismiss() -> void: _dismiss() func set_error(reason: String) -> void: if _msg: _msg.text = "重连失败:%s" % reason if _count: _count.text = "将自动重试" func is_showing() -> bool: return _panel.visible func _on_disconnected(reason: String) -> void: _msg.text = "连接断开:%s" % reason _panel.visible = true _armed = true _auto = true _tries = 0 _left = AUTO_DELAY _apply_layout() func _dismiss() -> void: _panel.visible = false _armed = false func _retry_now() -> void: if client == null or not client.has_method("reconnect"): return _tries += 1 _count.text = "重连中…(第 %d 次)" % _tries _left = AUTO_DELAY var ok: bool = client.reconnect() if not ok: _count.text = "重连失败,%0.0f 秒后再试" % AUTO_DELAY func _process(dt: float) -> void: _apply_layout() if not _panel.visible or not _armed: return if not _auto: _count.text = "自动重连已停止" return if _tries >= MAX_TRIES: _count.text = "已重试 %d 次,仍失败" % _tries return _left -= dt _count.text = "%0.0f 秒后自动重连(%d/%d)" % [maxf(_left, 0.0), _tries, MAX_TRIES] if _left <= 0.0: _retry_now() func _build() -> void: _panel = ColorRect.new() _panel.color = Color(0.05, 0.02, 0.02, 0.92) _panel.set_anchors_preset(Control.PRESET_FULL_RECT) _panel.visible = false add_child(_panel) var box := VBoxContainer.new() _box = box box.set_anchors_preset(Control.PRESET_CENTER) box.position = Vector2(-160, -70) box.custom_minimum_size = Vector2(320, 0) box.add_theme_constant_override("separation", 12) _panel.add_child(box) _msg = Label.new() _msg.text = "连接断开" _msg.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _msg.add_theme_font_size_override("font_size", 16) box.add_child(_msg) _count = Label.new() _count.text = "" _count.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(_count) var row := HBoxContainer.new() row.alignment = BoxContainer.ALIGNMENT_CENTER box.add_child(row) var now_btn := Button.new() now_btn.text = "立即重连" now_btn.custom_minimum_size = Vector2(150, 46) now_btn.pressed.connect(_retry_now) row.add_child(now_btn) var stop_btn := Button.new() stop_btn.text = "停止" stop_btn.custom_minimum_size = Vector2(120, 46) stop_btn.pressed.connect(func(): _auto = false) row.add_child(stop_btn) func _apply_layout() -> void: if _panel == null or get_viewport() == null: return var viewport_size := get_viewport().get_visible_rect().size if viewport_size.is_equal_approx(_last_viewport_size) and not _mobile_mode: return _last_viewport_size = viewport_size if not _mobile_mode: return var area := MobileWindowHost.safe_rect(get_viewport()) var width := minf(520.0, maxf(300.0, area.size.x - 32.0)) _box.position = area.position + Vector2((area.size.x - width) * 0.5, (area.size.y - 190.0) * 0.5) _box.size = Vector2(width, 190.0) _msg.add_theme_font_size_override("font_size", 18) _count.add_theme_font_size_override("font_size", 15)