using System; using System.Collections; using AOT.Boot; using UnityEngine; namespace HotUpdate.Net { /// /// 登录 → WS 连接编排。 /// public sealed class GameNet : MonoBehaviour { public string HttpBase = AuthApi.DefaultHttpBase; public string WsUrlTemplate = "wss://game.xworld.ren/ws?pid={pid}&token={token}"; public SessionData Session { get; private set; } public WsClient Socket { get; private set; } public string Status { get; private set; } = "idle"; public event Action OnAuthed; public event Action OnWsReady; public event Action OnStatus; public event Action OnWsMessage; BootContext _boot; bool _started; public void Bind(BootContext boot) { _boot = boot; if (_boot == null) return; if (!string.IsNullOrEmpty(_boot.HttpBase)) HttpBase = _boot.HttpBase; if (!string.IsNullOrEmpty(_boot.WsUrlTemplate)) { // 旧模板只有 pid 时补上 token var tpl = _boot.WsUrlTemplate; if (!tpl.Contains("{token}")) { tpl = tpl.Contains("?") ? tpl + (tpl.EndsWith("?") || tpl.EndsWith("&") ? "token={token}" : "&token={token}") : tpl + "?pid={pid}&token={token}"; } WsUrlTemplate = tpl; } } public void StartGuestSession() { if (_started) return; _started = true; StartCoroutine(RunGuestFlow()); } IEnumerator RunGuestFlow() { SetStatus("auth…"); var deviceId = DeviceId.GetOrCreate(); // 服务器要求 username/password;编辑器自动账号 var username = $"guest_{deviceId}"; var password = $"pw_{deviceId}"; AuthResponse auth = null; string err = null; yield return AuthApi.EnsureAccount(HttpBase, username, password, (r, e) => { auth = r; err = e; }); if (auth == null || string.IsNullOrEmpty(auth.token)) { SetStatus($"auth fail: {err}"); Debug.LogError($"[GameNet] auth failed: {err}"); yield break; } Session = new SessionData { Username = username, Token = auth.token, Pid = auth.pid, DeviceId = deviceId, }; Debug.Log($"[GameNet] authed pid={Session.Pid} user={username}"); OnAuthed?.Invoke(Session); SetStatus($"authed pid={Session.Pid}"); yield return ConnectWs(); } IEnumerator ConnectWs() { if (Session == null || !Session.IsValid) { SetStatus("no session"); yield break; } SetStatus("ws connecting…"); Socket?.Dispose(); Socket = new WsClient(); Socket.AutoReconnect = true; Socket.OnConnected += () => { SetStatus("ws connected"); OnWsReady?.Invoke(); // 应用层握手(服务端当前可无响应,保留扩展) Socket.SendText($"{{\"op\":\"hello\",\"proto\":1,\"app\":\"{_boot?.AppVersion}\",\"res\":\"{_boot?.ResVersion}\",\"pid\":{Session.Pid}}}"); }; Socket.OnDisconnected += reason => SetStatus($"ws down: {reason}"); Socket.OnMessage += msg => { Debug.Log($"[GameNet] << {msg}"); OnWsMessage?.Invoke(msg); }; var url = Session.BuildWsUrl(WsUrlTemplate); Socket.Connect(url); // 等待最多 15s float t = 0f; while (t < 15f && Socket.State != WsState.Connected && Socket.State != WsState.Disconnected) { t += Time.unscaledDeltaTime; yield return null; } if (Socket.State == WsState.Connected) SetStatus("ws connected"); else if (Socket.State == WsState.Disconnected && !string.IsNullOrEmpty(Socket.LastError)) SetStatus($"ws fail: {Socket.LastError}"); else if (Socket.State != WsState.Connected) SetStatus($"ws state={Socket.State}"); } void Update() { Socket?.Tick(); } void OnDestroy() { Socket?.Dispose(); Socket = null; } void SetStatus(string s) { Status = s; Debug.Log($"[GameNet] {s}"); OnStatus?.Invoke(s); } } }