Files

243 lines
10 KiB
C#

using AOT.Boot;
using HotUpdate.Net;
using HotUpdate.UI;
using UnityEngine;
namespace HotUpdate.Gameplay
{
/// <summary>
/// M4 玩法骨架:挂 HUD、角色、网络状态;处理公告与断线重连提示。
/// </summary>
public sealed class GameplayBootstrap : MonoBehaviour
{
BootContext _ctx;
GameNet _net;
MainHudView _hud;
ConfirmDialogView _dialog;
PrototypeFlow _prototype;
UiPrefabLoader _musicLoader;
GameObject _musicInstance, _sfxInstance;
bool _maintenanceLock;
string _maintenanceMessage;
string _reconnectMessage;
public void Bind(BootContext ctx, GameNet net)
{
_ctx = ctx;
_net = net;
}
void Start()
{
WireNet();
StartCoroutine(EnsureUi());
}
System.Collections.IEnumerator EnsureUi()
{
var uiRoot = new GameObject("UiRoot");
DontDestroyOnLoad(uiRoot);
var mgr = uiRoot.AddComponent<UiManager>();
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<MainHudView>() : null;
if (_hud == null && hudGo != null)
_hud = hudGo.AddComponent<MainHudView>();
if (_hud == null)
{
if (_ctx != null) _ctx.MainUiError = "主界面资源加载失败,请检查 Art 资源包";
yield break;
}
_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;
}
// Reveal the HUD after its camp is ready, avoiding a brief illustrated fallback.
var camp = hudGo.GetComponentInChildren<HomeCampPresenter>(true);
float campDeadline = Time.realtimeSinceStartup + 90f;
while (camp != null && camp.Loading && Time.realtimeSinceStartup < campDeadline)
yield return null;
if (camp != null && camp.Loading)
{
if (_ctx != null) _ctx.MainUiError = "营地加载超时,请重新进入游戏";
yield break;
}
if (_ctx != null) _ctx.MainUiReady = true;
Debug.Log("[UI] Main HUD ready");
GameObject dlgGo = null;
yield return mgr.OpenAsync(ConfirmDialogView.PrefabName, go => dlgGo = go);
if (dlgGo != null)
{
dlgGo.SetActive(false);
_dialog = dlgGo.GetComponent<ConfirmDialogView>();
if (_dialog == null) _dialog = dlgGo.AddComponent<ConfirmDialogView>();
_dialog.Bind();
}
_prototype = gameObject.AddComponent<PrototypeFlow>();
_prototype.Bind(_ctx, mgr, _hud, _dialog, () => _net?.Session, () => _net?.RenewSession());
_prototype.SetLocked(_maintenanceLock);
StartCoroutine(EnsureAudio());
if (_maintenanceLock) _dialog?.Show("维护公告", _maintenanceMessage);
else MaybeShowBootNotice();
}
/// <summary>Music and UI SFX prefabs share the Sound/Prefab folder and one loader.</summary>
System.Collections.IEnumerator EnsureAudio()
{
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.");
else
{
_musicInstance = Instantiate(prefab, transform, false);
_musicInstance.name = MainMenuMusic.PrefabName;
_prototype?.BindMusic(_musicInstance.GetComponent<MainMenuMusic>());
}
prefab = null;
yield return _musicLoader.LoadAsync(UiSfx.PrefabName, loaded => prefab = loaded);
if (prefab == null)
{
Debug.LogWarning("[UiSfx] UI sound prefab unavailable in the current Art package.");
yield break;
}
_sfxInstance = Instantiate(prefab, transform, false);
_sfxInstance.name = UiSfx.PrefabName;
}
void OnDestroy()
{
StopAllCoroutines();
if (_musicInstance != null) Destroy(_musicInstance);
if (_sfxInstance != null) Destroy(_sfxInstance);
_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 "Friends": title = "好友"; break;
case "Mail": title = "小镇邮件"; break;
case "Fountain": case "Rewards": title = "宝箱奖励"; break;
case "MainHouse": case "Camp": title = "营地大厅"; break;
case "Stall": 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);
}
}
}