# 登录界面 —— 服务器/账号/密码输入 + 连接,接 M2Client。 # 成功(char_list)后打印槽位;实际项目里在此切到 charselect.gd。 # 用法:godot --path project --script login.gd (--auto 用默认值直接连) extends SceneTree const UiKit = preload("res://ui_kit.gd") const Audio = preload("res://audio.gd") const Locale = preload("res://locale.gd") const AppLifecycle = preload("res://app_lifecycle.gd") const GameScene = preload("res://game_scene.gd") var _assets: String var _client: Node var _audio: Node var _life: Node var _layer: CanvasLayer var _game: Node3D var _auto_select := false var _loc: RefCounted var _host: LineEdit var _id: LineEdit var _pw: LineEdit var _status: Label var _t := 0.0 func _init() -> void: _assets = AssetRoot.path() _loc = Locale.new() _loc.setup(_assets, "en") _audio = Audio.new() get_root().add_child(_audio) _audio.setup(_assets) # F5:登录界面没有游戏逻辑要暂停,但后台时仍停 BGM / 挂起连接 _life = AppLifecycle.new() _life.pause_tree_on_background = false get_root().add_child(_life) _life.bind(null, _audio) _layer = CanvasLayer.new() var layer := _layer get_root().add_child(layer) var bg := ColorRect.new() bg.color = Color(0.08, 0.09, 0.12) bg.set_anchors_preset(Control.PRESET_FULL_RECT) layer.add_child(bg) var panel := UiKit.board(_assets, "board", 32, 128) panel.set_anchors_preset(Control.PRESET_CENTER) panel.position = Vector2(-220, -150) panel.size = Vector2(440, 300) layer.add_child(panel) var v := VBoxContainer.new() v.position = Vector2(40, 36) v.custom_minimum_size = Vector2(360, 0) v.add_theme_constant_override("separation", 12) panel.add_child(v) var title := Label.new() title.text = " " + _loc.t("LOGIN_CONNECT") title.add_theme_font_size_override("font_size", 20) v.add_child(title) _host = _field(v, _loc.t("LOGIN_DEFAULT_SERVERADDR").split(",")[0], "192.168.21.203") _id = _field(v, _loc.t("LOGIN_ID"), OS.get_environment("MT_ACCOUNT")) _pw = _field(v, _loc.t("LOGIN_PASSWORD"), OS.get_environment("MT_PASSWORD")) _pw.secret = true var btn := Button.new() btn.text = _loc.t("LOGIN_CONNECT") btn.custom_minimum_size = Vector2(0, 40) btn.pressed.connect(_connect) v.add_child(btn) _status = Label.new() _status.text = _loc.t("LOGIN_CONNECTING") v.add_child(_status) if "--auto" in OS.get_cmdline_user_args() or "--auto" in OS.get_cmdline_args(): _connect() func _field(parent: Control, label: String, val: String) -> LineEdit: var row := HBoxContainer.new() row.add_theme_constant_override("separation", 10) var l := Label.new() l.text = label l.custom_minimum_size = Vector2(64, 0) row.add_child(l) var le := LineEdit.new() le.text = val le.custom_minimum_size = Vector2(260, 0) row.add_child(le) parent.add_child(row) return le func _connect() -> void: if not ClassDB.class_exists("M2Client"): _status.text = "M2Client 未注册(扩展没编?)" return if _client: _client.call("disconnect_from_server") _client.queue_free() _client = ClassDB.instantiate("M2Client") get_root().add_child(_client) _client.connect("stage_changed", func(s): _msg("阶段: " + s)) _client.connect("auth_ok", func(k): _msg("认证成功 login_key=0x%08X" % k) _audio.play_ui("loginok")) _client.connect("login_failed", func(r): _msg("失败: " + r) _audio.play_ui("loginfail")) _client.connect("phase_changed", func(p): _msg("游戏相位: " + p)) _client.connect("char_list", _on_chars) _client.connect("entered_game", _on_entered_game) _client.connect("disconnected", func(r): _msg("断开: " + r)) _client.connect("resumed", func(): # 回前台:若后台期间 OS 掐了 TCP,下一次 pump 会发 disconnected; # 曾在游戏里就自动重连。 if _client.call("is_in_game"): _msg("回前台,尝试重连…") _client.call("reconnect")) _life.bind(_client, _audio) var h := _host.text _client.call("connect_to_server", h, 11000, h, 13002, _id.text, _pw.text) _msg("连接 %s ..." % h) func _on_chars(list: Array) -> void: _msg("角色列表: %d 个" % list.size()) for d in list: print("[login] slot %d: %s (job %d, Lv.%d) @ (%d,%d)" % [ d.get("index", -1), d.get("name", "?"), d.get("job", 0), d.get("level", 0), d.get("x", 0), d.get("y", 0)]) # 简版:自动选第一个非空槽进游戏。正式版切到 charselect.gd。 if list.size() > 0 and (_auto_select or "--auto" in OS.get_cmdline_args()): var idx: int = int(list[0].get("index", 0)) _msg("自动选择槽位 %d ..." % idx) if _client.has_method("enter_game"): _client.call("enter_game", idx) else: _client.call("select_character", idx) func _on_entered_game() -> void: _msg("已进入游戏 —— 加载场景") if _layer: _layer.visible = false # 兼容启动器没有 AppFlow,因此由这里创建的生命周期协调者贯穿 # 登录→游戏;GameScene 不再创建第二个协调者,也不能在切屏时销毁它。 if _life: _life.bind(_client, _audio) _game = GameScene.new() get_root().add_child(_game) _game.setup(_client, _assets) func _msg(t: String) -> void: if _status: _status.text = t print("[login] ", t) func _process(_dt: float) -> bool: return false