diff --git a/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs b/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs index 4a1574d60..e984d1e00 100644 --- a/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs +++ b/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs @@ -141,18 +141,25 @@ public class MonsterControl_BossBase : MonsterContral CreateFishManager.Instance.currGameType.subUIManager.ShowBoardBoss(handle.Result, bossName); } - + if (isMove) { var bezierData = MonsterMoveLine.dic_BezierCurveDatas[bossName + pathId]; monsterData.moveLine.Init(bezierData, time); - + // ⭐ isMove=true 的 Boss 初始位置设为曲线起点, + // 避免在 moveTime 更新前卡在生成时的 (9999,9999,0) + transform.position = new Vector3(bezierData.start.x, bezierData.start.y, 0); } else { transform.position = new Vector3(0, 0, 0); } + + // ⭐ Boss 重写 Init 未调用 base.Init,需手动标记已初始化并切换到 move 状态, + // 否则 OnMoveUpdata 不会被调用(OnUpdate 里 isInitialized=false 会 return),moveTime 不自增 + isInitialized = true; + ChangeState(CharacterStateType.move); } /// diff --git a/Assets/MyGame/Scripts/Manager/GameManager.cs b/Assets/MyGame/Scripts/Manager/GameManager.cs index a7454473c..a881e4f5b 100644 --- a/Assets/MyGame/Scripts/Manager/GameManager.cs +++ b/Assets/MyGame/Scripts/Manager/GameManager.cs @@ -585,9 +585,11 @@ public class GameManager : MonoSingleton IEnumerator yanchi(LoadingPanel loadingPanel) { yield return new WaitForSeconds(5); - btn_Skip.gameObject.SetActive(true); + if (btn_Skip != null) btn_Skip.gameObject.SetActive(true); yield return new WaitForSeconds(23f); - loadingPanel.LoadBar(100); + // ⭐ 防御:loadingPanel 可能已被 UIManager.CloseAll 销毁(如 Logout 时) + if (loadingPanel != null) + loadingPanel.LoadBar(100); } void Start() { @@ -646,12 +648,16 @@ public class GameManager : MonoSingleton public void Logout(Action onDone = null) { - // 关闭所有 WebSocket 连接 + // ⭐ 标记登出中:阻止 WebSocket 关闭后触发自动重连(避免访问已 null 的 user) + WebSocketManager.Instance.isLoggingOut = true; + // 关闭所有 WebSocket 连接(内部会先 StopAllReconnects 停止所有重连协程) WebSocketManager.Instance.CloseAll(); // 清空用户数据 user = null; // 关闭所有 UI UIManager.Instance.CloseAll(); + // ⭐ 登出流程完成,立即恢复重连开关(不依赖 ShowAsync 回调,避免 UI 异常导致永不重置) + WebSocketManager.Instance.isLoggingOut = false; // 切换到登录界面 UIManager.Instance.ShowAsync(UIPanel.LoginPanel, null, (panel) => { diff --git a/Assets/MyGame/Scripts/Net/APIManager.cs b/Assets/MyGame/Scripts/Net/APIManager.cs index bb8965b6e..62b453386 100644 --- a/Assets/MyGame/Scripts/Net/APIManager.cs +++ b/Assets/MyGame/Scripts/Net/APIManager.cs @@ -249,7 +249,7 @@ namespace Assets.MyGame.Scripts.Net var json = req.downloadHandler.text; var res = JsonConvert.DeserializeObject>(json); #if UNITY_EDITOR - Debug.Log("url----" + url+"--"+ "Json-"+json+"res--"+JsonConvert.SerializeObject(res)); + Debug.Log("url----" + url + "--" + "Json-" + json + "res--" + JsonConvert.SerializeObject(res)); #endif Debug.Log("url----" + url + "--" + "Json-" + json + "res--" + JsonConvert.SerializeObject(res)); @@ -313,9 +313,9 @@ namespace Assets.MyGame.Scripts.Net var res = JsonConvert.DeserializeObject>(json); #if UNITY_EDITOR Debug.Log("url----" + url + "--" + "Json-" + json + "res--" + JsonConvert.SerializeObject(res)); - +#else +Debug.Log("url----" + url + "--" + "Json-" + json + "res--" + JsonConvert.SerializeObject(res)); #endif - Debug.Log("url----" + url + "--" + "Json-" + json + "res--" + JsonConvert.SerializeObject(res)); // 自动保存 token if (typeof(T) == typeof(UserToken)) @@ -383,8 +383,8 @@ namespace Assets.MyGame.Scripts.Net } public static void Log(string json) { - #if UNITY_EDITOR - Debug.Log(json); +#if UNITY_EDITOR + Debug.Log(json); #endif } diff --git a/Assets/MyGame/Scripts/Net/WebSocketManager.cs b/Assets/MyGame/Scripts/Net/WebSocketManager.cs index de26b0808..c9dcdb5b1 100644 --- a/Assets/MyGame/Scripts/Net/WebSocketManager.cs +++ b/Assets/MyGame/Scripts/Net/WebSocketManager.cs @@ -27,6 +27,11 @@ namespace Assets.MyGame.Scripts.Net private readonly HashSet handshakeCompleted = new(); private readonly HashSet reconnecting = new(); private readonly HashSet manualClosing = new(); + /// 登出标志:true 时所有 socket 关闭后都不触发自动重连。 + /// Logout 开始时设 true,切回登录界面后设 false,避免关连接时误触发重连访问已 null 的 user。 + public bool isLoggingOut = false; + /// 每个 socketType 正在运行的重连协程引用,用于主动 StopCoroutine + private readonly Dictionary reconnectCoroutines = new(); private float nextHeartbeatTime; private const float HeartbeatIntervalSeconds = 20f; #if ENABLE_LOG_FILE @@ -154,6 +159,11 @@ namespace Assets.MyGame.Scripts.Net sockets.Remove(type); if (manualClosing.Remove(type)) return; + // ⭐ 登出中或未登录:不触发自动重连。 + // isLoggingOut 可能因 ws.Close 异步导致 OnClosed 触发时已重置, + // 所以同时检查 user==null 做双重保险(登出后 user 必为 null) + if (isLoggingOut || GameManager.Instance.user == null) + return; // 自动重连 switch (type) @@ -256,7 +266,7 @@ namespace Assets.MyGame.Scripts.Net { if (sockets.TryGetValue(type, out var ws)) { - manualClosing.Add(type); + //manualClosing.Add(type); handshakeCompleted.Remove(type); ws.Close(WebSocketStatusCodes.NormalClosure, "正常关闭"); // 或者 CloseStatusCode.Normal //ws.Close(); @@ -269,6 +279,9 @@ namespace Assets.MyGame.Scripts.Net /// public void CloseAll() { + // ⭐ 先停止所有重连协程:socket 因 ClosedAbnormally 关闭时已从 sockets 移除, + // CloseAll 遍历 sockets.Keys 会漏掉它,导致重连协程继续运行。这里统一停止。 + StopAllReconnects(); foreach (var type in new List(sockets.Keys)) { Close(type); @@ -282,7 +295,23 @@ namespace Assets.MyGame.Scripts.Net { if (!reconnecting.Add(type)) return; - StartCoroutine(Reconnect(type, action)); + var co = StartCoroutine(Reconnect(type, action)); + reconnectCoroutines[type] = co; + } + + /// + /// 主动停止所有正在等待的重连协程并清空状态。 + /// 用于 Logout 时彻底取消所有待重连任务,不依赖 isLoggingOut 时序。 + /// + public void StopAllReconnects() + { + foreach (var kv in reconnectCoroutines) + { + if (kv.Value != null) + StopCoroutine(kv.Value); + } + reconnectCoroutines.Clear(); + reconnecting.Clear(); } private System.Collections.IEnumerator Reconnect(SocketType type, Action action = null) @@ -296,9 +325,18 @@ namespace Assets.MyGame.Scripts.Net if (manualClosing.Contains(type)) { reconnecting.Remove(type); + reconnectCoroutines.Remove(type); + yield break; + } + // ⭐ 登出中或未登录:取消已排队等待的重连 + if (isLoggingOut || GameManager.Instance.user == null) + { + reconnecting.Remove(type); + reconnectCoroutines.Remove(type); yield break; } + reconnectCoroutines.Remove(type); socketUrls.TryGetValue(type, out var url); Connect(type, url, action); } @@ -311,6 +349,12 @@ namespace Assets.MyGame.Scripts.Net switch (type) { case SocketType.login: + // ⭐ 防御:登出后 user 可能为 null,避免 NRE + if (GameManager.Instance.user == null || string.IsNullOrEmpty(GameManager.Instance.user.id)) + { + Debug.LogWarning("[WebSocket] login 握手完成但 user 为 null,跳过 Bind(可能登出中)"); + return; + } WebSocketSendList.Bind(GameManager.Instance.user.id); break; case SocketType.chess: @@ -330,6 +374,7 @@ namespace Assets.MyGame.Scripts.Net /// private void OnDestroy() { + StopAllReconnects(); foreach (var type in new List(sockets.Keys)) manualClosing.Add(type); foreach (var ws in new List(sockets.Values)) diff --git a/Assets/MyGame/aa/UI/Home/UIHomeMain.cs b/Assets/MyGame/aa/UI/Home/UIHomeMain.cs index a5abd62a8..ff48b7dc4 100644 --- a/Assets/MyGame/aa/UI/Home/UIHomeMain.cs +++ b/Assets/MyGame/aa/UI/Home/UIHomeMain.cs @@ -100,8 +100,8 @@ public class UIHomeMain : UIBase }); XJMHButton.onClick.AddListener(() => { - subUIManager.ShowAsync(UIPanel.SelectRoomLeavePenel, GameType.XJMH); - //UIManager.Instance.ShowBoardError("维护中..."); + //subUIManager.ShowAsync(UIPanel.SelectRoomLeavePenel, GameType.XJMH); + UIManager.Instance.ShowBoardError("维护中..."); }); HHWKButton.onClick.AddListener(() =>