feat: complete mobile UI implementation

This commit is contained in:
shenlei
2026-09-04 19:54:33 +09:00
parent 9a4a044da5
commit b19d5b5dd3
132 changed files with 6643 additions and 180 deletions
+46 -5
View File
@@ -6,7 +6,8 @@
# af.start(assets_root) # assets_root 传给 game_scene
#
# 状态机:LOGIN → SELECT → GAME。M2Client 信号驱动切屏。
# 断线:任何阶段回到登录阶段,和 ClientVS22 的 SetLoginPhase 一致。
# 断线:登录/选人阶段回到登录;游戏阶段保留 GameScene,显示重连遮罩,
# 重连成功后自动重新进入上次角色,避免短暂闪回选人界面。
extends Node
const ServerInfoRes = preload("res://net/serverinfo.gd")
@@ -15,6 +16,8 @@ const LoadingScreen = preload("res://ui/loading_screen.gd")
const CharSelectScreen = preload("res://ui/char_select_screen.gd")
const GameScene = preload("res://game_scene.gd")
const AppLifecycle = preload("res://app_lifecycle.gd")
const ReconnectUI = preload("res://ui/reconnect_ui.gd")
const UiProfile = preload("res://ui/ui_profile.gd")
enum { LOGIN, SELECT, GAME }
@@ -26,12 +29,15 @@ var _ui: CanvasLayer # 登录 / 选人的临时界面层
var _loading: CanvasLayer
var _game: Node
var _lifecycle: Node # 全流程唯一生命周期协调者
var _reconnect: Node
var build_game_scene := true # 测试里置 false,只跑状态机不建重场景
var _sel_server := 0
var _sel_channel := 1
var _id := OS.get_environment("MT_ACCOUNT")
var _pw := OS.get_environment("MT_PASSWORD")
var _chars: Array = []
var _selected_char_slot := -1
var _reconnecting_game := false
var _mark_redl_after := 0 # guild-mark re-download cooldown (ticks_msec)
# MT_AUTOLOGIN=1:进 LOGIN 自动连接,收到角色列表自动选第一个(冒烟 / CI 用)。
var auto_login := OS.get_environment("MT_AUTOLOGIN") == "1" and not _id.is_empty() and not _pw.is_empty()
@@ -60,6 +66,11 @@ func start(assets_root: String = "", injected_client: Node = null) -> void:
_lifecycle.name = "AppLifecycle"
add_child(_lifecycle)
_lifecycle.bind(client)
_reconnect = ReconnectUI.new()
_reconnect.name = "ReconnectUI"
add_child(_reconnect)
_reconnect.setup(client)
_reconnect.set_mobile_mode(UiProfile.resolve() == UiProfile.Mode.MOBILE)
_goto_login()
if auto_login:
call_deferred("_do_connect")
@@ -80,6 +91,16 @@ func _wire_client() -> void:
func _on_char_list(list: Array) -> void:
_chars = list
if _reconnecting_game:
# A full reconnect replays the character list before it can enter the
# previous character. Do not briefly replace the live game with SELECT;
# re-enter the remembered slot and keep the reconnect mask on screen.
var slot := _selected_char_slot
if slot < 0 and not _chars.is_empty():
slot = int(_chars[0].get("index", 0))
if slot >= 0:
_enter_character(slot)
return
# 已在选人页(建号 / 删号后 M2Client 会重发 char_list)——只刷数据,别整屏重建
if _state == SELECT and _ui:
var sc: Node = _ui.get_node_or_null("CharSelect")
@@ -100,6 +121,9 @@ func _on_char_name_changed(pid: int, name: String) -> void:
sc.on_char_name_changed(pid, name)
func _on_entered_game() -> void:
_reconnecting_game = false
if _reconnect and _reconnect.has_method("dismiss"):
_reconnect.dismiss()
_goto_game()
_start_guild_mark_download()
@@ -125,12 +149,24 @@ func _on_guild_mark_updated(_guild_id: int, _img_idx: int) -> void:
_start_guild_mark_download()
func _on_login_failed(reason: String) -> void:
if _reconnecting_game:
if _reconnect and _reconnect.has_method("set_error"):
_reconnect.set_error(reason)
return
if _state != GAME:
_goto_login()
_set_status("登录失败:%s" % reason)
func _on_disconnected(reason: String) -> void:
if _state == GAME and _game != null and is_instance_valid(_game):
_reconnecting_game = true
if _reconnect and _reconnect.has_method("set_error"):
_reconnect.set_error(reason)
return
_reconnecting_game = false
_goto_login()
if _reconnect and _reconnect.has_method("dismiss"):
_reconnect.dismiss()
_set_status("连接断开:%s" % reason)
# --- 切屏 ------------------------------------------------------------------
@@ -142,6 +178,7 @@ func _clear_ui() -> void:
func _goto_login() -> void:
_state = LOGIN
_reconnecting_game = false
if _lifecycle:
# GameScene 即将释放,不能让生命周期协调者保留悬空 Audio 引用。
_lifecycle.bind(client)
@@ -334,10 +371,7 @@ func _build_char_list() -> void:
_ui.add_child(screen)
screen.setup(client, _assets, _chars)
screen.select_requested.connect(func(idx: int):
if client.has_method("enter_game"):
client.enter_game(idx)
elif client.has_method("select_character"):
client.select_character(idx))
_enter_character(idx))
screen.back_requested.connect(func():
if client.has_method("disconnect_from_server"):
client.disconnect_from_server()
@@ -376,3 +410,10 @@ func _on_marks_ready_for_select() -> void:
var sc: Node = _ui.get_node_or_null("CharSelect") if _ui else null
if sc and sc.has_method("refresh_crest"):
sc.refresh_crest()
func _enter_character(idx: int) -> void:
_selected_char_slot = idx
if client.has_method("enter_game"):
client.enter_game(idx)
elif client.has_method("select_character"):
client.select_character(idx)