using AOT.Boot; using HotUpdate.Net; using HotUpdate.UI; using UnityEngine; namespace HotUpdate.Gameplay { /// /// M4 玩法骨架:挂 HUD、角色、网络状态;处理公告与断线重连提示。 /// public sealed class GameplayBootstrap : MonoBehaviour { BootContext _ctx; GameNet _net; MainHudView _hud; ConfirmDialogView _dialog; PrototypeFlow _prototype; UiPrefabLoader _musicLoader; GameObject _musicInstance; bool _maintenanceLock; string _maintenanceMessage; string _reconnectMessage; public void Bind(BootContext ctx, GameNet net) { _ctx = ctx; _net = net; } void Start() { WireNet(); StartCoroutine(EnsureUi()); var plaza = gameObject.AddComponent(); plaza.Bind(_ctx); StartCoroutine(SpawnWorld(plaza)); } System.Collections.IEnumerator SpawnWorld(TownSquareSpawner plaza) { yield return plaza.SpawnCo(); var spawner = gameObject.AddComponent(); spawner.Bind(_ctx); spawner.Spawn(); } System.Collections.IEnumerator EnsureUi() { var uiRoot = new GameObject("UiRoot"); DontDestroyOnLoad(uiRoot); var mgr = uiRoot.AddComponent(); mgr.Init(uiRoot.transform, new UiPrefabLoader(_ctx?.Package), _ctx?.UiTheme); GameObject hudGo = null; yield return mgr.OpenAsync(MainHudView.PrefabName, go => hudGo = go); _hud = hudGo != null ? hudGo.GetComponent() : null; if (_hud == null && hudGo != null) _hud = hudGo.AddComponent(); _hud?.Bind(); _hud?.SetStatus(_maintenanceLock ? "服务器维护中" : FriendlyStatus(_net?.Status)); _hud?.SetPid(_net?.Session?.Pid ?? 0); _hud?.ShowReconnect(!_maintenanceLock && _reconnectMessage != null, _reconnectMessage); if (_hud != null) { _hud.OnReconnectClicked += () => _net?.RequestReconnect(); _hud.OnHomeActionClicked += ShowHomeFeature; } GameObject dlgGo = null; yield return mgr.OpenAsync(ConfirmDialogView.PrefabName, go => dlgGo = go); if (dlgGo != null) { dlgGo.SetActive(false); _dialog = dlgGo.GetComponent(); if (_dialog == null) _dialog = dlgGo.AddComponent(); _dialog.Bind(); } _prototype = gameObject.AddComponent(); _prototype.Bind(_ctx, mgr, _hud, _dialog, () => _net?.Session, () => _net?.RenewSession()); _prototype.SetLocked(_maintenanceLock); StartCoroutine(EnsureMusic()); if (_maintenanceLock) _dialog?.Show("维护公告", _maintenanceMessage); else MaybeShowBootNotice(); } System.Collections.IEnumerator EnsureMusic() { if (_musicInstance != null || _musicLoader != null) yield break; _musicLoader = new UiPrefabLoader(_ctx?.Package, MainMenuMusic.PrefabFolder); GameObject prefab = null; yield return _musicLoader.LoadAsync(MainMenuMusic.PrefabName, loaded => prefab = loaded); if (prefab == null) { Debug.LogWarning("[MainMenuMusic] Music prefab unavailable in the current Art package."); yield break; } _musicInstance = Instantiate(prefab, transform, false); _musicInstance.name = MainMenuMusic.PrefabName; _prototype?.BindMusic(_musicInstance.GetComponent()); } void OnDestroy() { StopAllCoroutines(); if (_musicInstance != null) Destroy(_musicInstance); _musicLoader?.Dispose(); } static string FriendlyStatus(string status) { if (string.IsNullOrEmpty(status) || status == "idle") return "启动中…"; if (status == "auth…") return "正在登录…"; if (status.StartsWith("authed")) return "登录成功,正在连接…"; if (status == "ws connected" || status == "ws reconnected") return "已连接"; if (status.StartsWith("ws connecting")) return "正在连接游戏服务…"; if (status.StartsWith("ws reconnecting")) return "正在恢复连接…"; if (status.StartsWith("ws down") || status.StartsWith("ws fail")) return "游戏服务连接已断开"; return status; } void ShowHomeFeature(string key) { if (_maintenanceLock || _dialog == null) return; if (_prototype != null && _prototype.Handle(key == "Home" ? "Camp" : key)) return; if (key == "Home") { _dialog.Hide(); return; } string title; switch (key) { case "Profile": title = "探险家档案"; break; case "Coin": title = "金币"; break; case "Diamond": title = "钻石"; break; case "Energy": title = "冒险体力"; break; case "Tasks": title = "今日目标"; break; case "Settings": title = "设置"; break; case "Events": title = "小镇活动"; break; case "Daily": title = "每日签到"; break; case "Rank": title = "冒险排行"; break; case "Mail": title = "小镇邮件"; break; case "Rewards": title = "宝箱奖励"; break; case "Bag": title = "背包"; break; case "Adventure": case "Battle": title = "开始冒险"; break; case "Shop": title = "小镇商店"; break; case "Pets": title = "伙伴图鉴"; break; case "Pinball": title = "弹珠乐园"; break; default: return; } _dialog.Show(title, "这片新天地正在建设中,\n期待与你一起开启新的旅程!"); } void WireNet() { if (_net == null) return; _net.OnStatus += s => _hud?.SetStatus(FriendlyStatus(s)); _net.OnLoginFailed += error => { _reconnectMessage = "登录服务暂不可用,请重试。"; _hud?.ShowReconnect(true, _reconnectMessage); }; _net.OnAuthed += session => _hud?.SetPid(session != null ? session.Pid : 0); _net.OnWsReady += () => { if (_maintenanceLock) return; _reconnectMessage = null; _hud?.ShowReconnect(false); _hud?.SetStatus("已连接"); }; _net.OnWsDisconnected += reason => { if (_maintenanceLock) return; _reconnectMessage = $"网络已断开({reason}),正在重连…"; _hud?.ShowReconnect(true, _reconnectMessage); }; _net.OnReconnecting += attempt => { if (_maintenanceLock) return; _reconnectMessage = $"正在重连…(第 {attempt} 次)"; _hud?.ShowReconnect(true, _reconnectMessage); }; _net.OnMaintenance += msg => { _maintenanceLock = true; _prototype?.SetLocked(true); _maintenanceMessage = string.IsNullOrEmpty(msg) ? "服务器维护中,请稍后再试。" : msg; _hud?.ShowReconnect(false); _hud?.SetStatus("服务器维护中"); _dialog?.Show("维护公告", _maintenanceMessage); }; _net.OnWsMessage += TryHandleProtocol; } void MaybeShowBootNotice() { if (_ctx == null || string.IsNullOrEmpty(_ctx.Notice)) return; _dialog?.Show("公告", _ctx.Notice); } void TryHandleProtocol(string json) { if (string.IsNullOrEmpty(json)) return; // 轻量解析,避免引入完整 JSON 库依赖 if (json.IndexOf("\"op\":\"maintenance\"", System.StringComparison.Ordinal) >= 0 || json.IndexOf("\"op\": \"maintenance\"", System.StringComparison.Ordinal) >= 0) { var msg = ExtractJsonString(json, "message"); _net?.NotifyMaintenance(msg); } } static string ExtractJsonString(string json, string key) { var token = $"\"{key}\""; var i = json.IndexOf(token, System.StringComparison.Ordinal); if (i < 0) return null; i = json.IndexOf(':', i); if (i < 0) return null; i = json.IndexOf('"', i + 1); if (i < 0) return null; var j = json.IndexOf('"', i + 1); if (j < 0) return null; return json.Substring(i + 1, j - i - 1); } } }