From e709a4a7ab50a1c7129728ddcbd8c056128cb611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=AE2003?= <2384017988@qq.com> Date: Tue, 15 Sep 2026 22:38:11 +0800 Subject: [PATCH] 1 --- .../Scripts/Manager/CreateFishManager.cs | 51 +++++- .../Scripts/Net/WebSocketReponseList.cs | 148 ++++++++++++++++-- .../Boss/LieYanFengBao/LieYanFengBao_BOSS.cs | 3 + .../Boss/XingJiXunHang/XingJiXunHang_BOSS.cs | 4 + .../aa/UI/Game/GameMainPanel_FishBase.cs | 5 + .../aa/UI/Game/XJMH/GameMainPanelXJMH.prefab | 18 ++- 6 files changed, 197 insertions(+), 32 deletions(-) diff --git a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs index 8de472ca1..d07974d5d 100644 --- a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs +++ b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs @@ -415,6 +415,8 @@ public class CreateFishManager : MonoSingleton InitSpawnSystem(gameType); BindCurrentGamePanel(); EnsureBJEffectRoot(); + // ⭐ 回放进房加载期间收到的出鱼事件(服务端进房瞬间推的 init/sync 初始鱼) + FlushPendingFishSpawns(); } /// @@ -665,11 +667,21 @@ public class CreateFishManager : MonoSingleton { if (data == null || data.fish == null || data.fish.Count == 0) return; if (isFrozen) return; // 冰冻中不创建鱼 - if (spawnDic == null) return; // 场景卸载后忽略 + // ⭐ 修复:生成系统未就绪(进房加载中/退房清理后 spawnDic==null)时不再丢弃事件, + // 先入队,EnterRoom 完成后回放。服务端在进房瞬间就会推 OnFishSpawn(init/sync)初始鱼, + // 而此时客户端还在 LoadResources 加载(数秒),直接 return 会把初始鱼全部丢掉 → 进房不出鱼。 + if (spawnDic == null) + { + if (_pendingFishSpawns == null) _pendingFishSpawns = new List(); + _pendingFishSpawns.Add(data); + Debug.Log($"[CreateFishManager] 生成系统未就绪,出鱼事件入队等待回放: type={data.type}, count={data.fish.Count}, queued={_pendingFishSpawns.Count}"); + return; + } // ⭐ 分帧出鱼:普通鱼/黄金鱼分批生成(每帧5条),其他类型立即全出 // 避免一次出大量普通鱼导致单帧卡顿 var batchedQueue = new List(data.fish.Count); + bool isSync = data.type == "sync"; foreach (var item in data.fish) { @@ -685,19 +697,39 @@ public class CreateFishManager : MonoSingleton else { // Boss/特殊鱼/鱼阵等:立即生成 - TrySpawnSingleFish(item); + TrySpawnSingleFish(item, isSync); } } // 分帧组:启动协程逐帧生成 if (batchedQueue.Count > 0) { - StartCoroutine(SpawnFishBatchedCoroutine(batchedQueue)); + StartCoroutine(SpawnFishBatchedCoroutine(batchedQueue, isSync)); } NetLog.Log($"[CreateFishManager] OnFishSpawn type={data.type}, count={data.fish.Count}, batched={batchedQueue.Count}"); } + /// 生成系统未就绪期间收到的出鱼事件队列(EnterRoom 完成后回放) + private List _pendingFishSpawns; + + /// + /// 回放进房加载期间收到的出鱼事件(服务端在进房瞬间推的 init/sync 初始鱼)。 + /// 在 EnterRoom 末尾调用:此时配置/资源/面板都已就绪。 + /// + private void FlushPendingFishSpawns() + { + if (_pendingFishSpawns == null || _pendingFishSpawns.Count == 0) return; + var queue = _pendingFishSpawns; + _pendingFishSpawns = null; // 先置空,回放期间新到事件走正常路径 + Debug.Log($"[CreateFishManager] 回放加载期间出鱼事件: {queue.Count} 批"); + foreach (var data in queue) + { + try { OnFishSpawn(data); } + catch (System.Exception e) { Debug.LogWarning($"[CreateFishManager] 回放出鱼事件失败: {e.Message}"); } + } + } + /// /// 判断该鱼是否需要分帧生成。 /// 普通鱼(1)和黄金鱼(3)且非鱼阵 → 分帧;其他(Boss/特殊鱼/特殊Boss/鱼阵) → 立即出。 @@ -722,7 +754,7 @@ public class CreateFishManager : MonoSingleton /// /// 分帧出鱼协程:每帧生成 batchSize 条,避免单帧卡顿。 /// - private IEnumerator SpawnFishBatchedCoroutine(List queue) + private IEnumerator SpawnFishBatchedCoroutine(List queue, bool isSync) { const int batchSize = 5; // 每帧生成5条 int index = 0; @@ -734,7 +766,7 @@ public class CreateFishManager : MonoSingleton int end = Mathf.Min(index + batchSize, queue.Count); for (int i = index; i < end; i++) { - TrySpawnSingleFish(queue[i]); + TrySpawnSingleFish(queue[i], isSync); } index = end; yield return null; // 等待下一帧 @@ -744,7 +776,7 @@ public class CreateFishManager : MonoSingleton /// /// 尝试生成单条鱼(含去重检查)。返回是否成功生成。 /// - private bool TrySpawnSingleFish(OnFishSpawnFishItem item) + private bool TrySpawnSingleFish(OnFishSpawnFishItem item, bool isSync = false) { if (item == null) return false; @@ -791,9 +823,10 @@ public class CreateFishManager : MonoSingleton // ⭐ 根据 type 决定 moveTime: // - spawn/init:新鱼,传当前时间 → moveTime≈0,鱼从曲线起点开始游 - // (避免服务端/客户端时间差导致 moveTime>0,鱼瞬间出现在曲线中间) + // (避免服务端/客户端时间差或事件排队回放延迟导致 moveTime>0, + // 鱼凭空出现在曲线中间甚至已过路径末端) // - sync:同步已有鱼,传 createdAt → moveTime=时间差,鱼出现在路径正确位置 - long spawnTime = item.createdAt; + long spawnTime = isSync ? item.createdAt : BeijingTimeManager.Instance.CurrentTimestampMs; SpawnFishMainThread(createFishData, spawnTime); return true; @@ -1273,6 +1306,8 @@ public class CreateFishManager : MonoSingleton //await Task.Delay(2000); spawnList = null; spawnDic = null; + // ⭐ 退房清理排队中的出鱼事件,防止旧房间残留鱼泄进下一个房间 + if (_pendingFishSpawns != null) _pendingFishSpawns.Clear(); yz_FishConfig_Line_Dic = null; yz_FishConfig_Line = null; yz_FishConfig_Line_Dic1 = null; diff --git a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs index 6f6153df0..df34ddd10 100644 --- a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs +++ b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs @@ -57,18 +57,32 @@ namespace Assets.MyGame.Scripts.Net /// public static void RoomJoinNotice(string msg) { + // ⭐ 只反序列化一次复用(金币流水 / currRoomData.players 补齐 / 座位激活) + RoomJoinNotice notice = null; + try + { + notice = JsonConvert.DeserializeObject>(msg).arguments[0]; + } + catch { } + // ⭐ 记录金币流水:收到加钱信息(RoomJoinNotice 推送,尚未执行)—— 只记录自己 try { - var notice = JsonConvert.DeserializeObject>(msg).arguments[0]; string myId = GameManager.Instance.user?.id; - if (!string.IsNullOrEmpty(myId) && notice.UserId == myId) + if (notice != null && !string.IsNullOrEmpty(myId) && notice.UserId == myId) { GameManager.LogGoldFlowReceiveGain("RoomJoinNotice", $"推送金币={notice.Gold} 钻石={notice.Diamond} 炮台消耗={notice.CannonMultiple} 座位={notice.Position}"); } } catch { } + // ⭐ 修复:把加入玩家 upsert 进 currRoomData.players。 + // JoinGame 里 SetUserInfo 按 userId 从 currRoomData.players 查显示信息 + // (昵称/头像/VIP/短编号),而 currRoomData 只在本地进房收到 RoomInfo 时写一次, + // 后加入的玩家不在快照里 → 老客户端把他显示成匿名(无昵称/默认头像/原始32位长ID)。 + // RoomJoinNotice 自带完整玩家数据,这里补进快照后再走 JoinGame。 + UpsertPlayerToCurrRoom(notice); + string sceneName = ""; UIPanel uIPanel = UIPanel.None; switch (GameManager.Instance.gameData.currGameType) @@ -132,29 +146,65 @@ namespace Assets.MyGame.Scripts.Net { // 面板已存在 → 新玩家加入,转发给现有面板激活座位 var panel = UIManager.Instance.Get(uIPanel); - if (panel is GameMainPanel_FishBase fishPanel) + if (panel is GameMainPanel_FishBase fishPanel && notice != null) { - var v = JsonConvert.DeserializeObject>(msg); - if (v.arguments.Count > 0) + // 如果座位被bot占用则替换 + var targetSit = fishPanel.list_Sits.Find(s => s.posID == notice.Position); + if (targetSit != null && targetSit.isBot) { - var notice = v.arguments[0]; - // 如果座位被bot占用则替换 - var targetSit = fishPanel.list_Sits.Find(s => s.posID == notice.Position); - if (targetSit != null && targetSit.isBot) - { - targetSit.isBot = false; - targetSit.ShowWaiting(); - } - fishPanel.JoinGame(notice.Position, notice.Gold, notice.Diamond, notice.CannonMultiple, 0, notice.UserId, notice.CannonId, notice.WingsId, notice.WarGodId); - // 后端动态加入的机器人也要标记 - if (targetSit != null && !string.IsNullOrEmpty(notice.UserId) && notice.UserId.StartsWith("bot_")) - targetSit.isBot = true; + targetSit.isBot = false; + targetSit.ShowWaiting(); } + fishPanel.JoinGame(notice.Position, notice.Gold, notice.Diamond, notice.CannonMultiple, 0, notice.UserId, notice.CannonId, notice.WingsId, notice.WarGodId); + // 后端动态加入的机器人也要标记 + if (targetSit != null && !string.IsNullOrEmpty(notice.UserId) && notice.UserId.StartsWith("bot_")) + targetSit.isBot = true; } } } + /// + /// 把 RoomJoinNotice 玩家数据写入 currRoomData.players(存在则更新,不存在则添加)。 + /// JoinGame 里 SetUserInfo 按 userId 从 currRoomData.players 查显示信息 + /// (昵称/头像/VIP/短编号),后加入的玩家不在快照里会显示成匿名玩家。 + /// + private static void UpsertPlayerToCurrRoom(RoomJoinNotice notice) + { + if (notice == null || string.IsNullOrEmpty(notice.UserId)) return; + var roomData = GameManager.Instance.gameData?.currRoomData; + if (roomData == null) return; + if (roomData.players == null) roomData.players = new List(); + + var p = roomData.players.Find(x => x.userId == notice.UserId); + if (p == null) + { + p = new RoomPlayerInfo(); + roomData.players.Add(p); + } + + p.userId = notice.UserId; + p.userName = notice.UserName; + p.userNumber = notice.UserNumber; + p.avatar = notice.Avatar; + p.isRedName = notice.IsRedName; + p.level = notice.Level; + p.exp = notice.Exp; + p.vipLevel = notice.VipLevel; + p.cId = notice.CId; + p.gold = notice.Gold; + p.diamond = notice.Diamond; + p.position = notice.Position; + p.cannonId = notice.CannonId; + p.wingsId = notice.WingsId; + p.warGodId = notice.WarGodId; + p.cannonMultiple = notice.CannonMultiple; + p.beginCoin = notice.BeginCoin; + p.beginTime = notice.BeginTime; + p.state = notice.State; + p.isBot = notice.IsBot; + } + /// /// 房间列表 room /// @@ -992,6 +1042,14 @@ namespace Assets.MyGame.Scripts.Net { var roomData = JsonConvert.DeserializeObject>(msg).arguments[0]; if (roomData == null) return; + + // ⭐ 修复:游戏中收到本房间的 RoomChange(players 是全量最新数据)→ 同步座位。 + // room socket 进游戏后仍保持连接,玩家加入/离开时都会推 RoomChange; + // 若 game socket 侧没收到(或没处理)RoomJoinNotice/RoomLeaveNotice, + // 老客户端就识别不到新玩家。这里按 players 与 currRoomData.players 全量比对: + // 新加入的入座(JoinGame),离开的释放座位(ShowWaiting),幂等可重复。 + SyncSeatsFromRoomChange(roomData); + // 通知手动选座面板刷新(若开启) var homeMain = UIManager.Instance.Get(UIPanel.HomeMainPanel); if (homeMain == null) return; @@ -1007,6 +1065,62 @@ namespace Assets.MyGame.Scripts.Net } } + /// + /// 游戏中收到本房间 RoomChange 时同步座位:players 与 currRoomData.players 全量比对。 + /// 新加入(列表有、快照无)→ JoinGame 入座;离开(快照有、列表无)→ ShowWaiting 释放座位。 + /// 与 RoomJoinNotice/RoomLeaveNotice 处理幂等(快照里已有的玩家不会重复入座)。 + /// + private static void SyncSeatsFromRoomChange(RoomChangeResponse roomData) + { + var currRoom = GameManager.Instance.gameData?.currRoomData; + // 只处理当前所在房间(房间列表里其他房间的 RoomChange 不关心) + if (currRoom == null || roomData.roomId != currRoom.roomId) return; + if (roomData.players == null) return; + if (currRoom.players == null) currRoom.players = new List(); + + var panel = UIManager.Instance.Get(UIPanel.MainGamePanelDefault) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelFLZY) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelYYSC) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelXJMH) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelHHWK); + + // 1. 新加入:列表有、快照无 → 写入快照并入座 + foreach (var p in roomData.players) + { + if (string.IsNullOrEmpty(p.userId)) continue; + if (currRoom.players.Exists(x => x.userId == p.userId)) continue; + currRoom.players.Add(p); + + if (panel == null) continue; // 不在游戏中只补数据,进房流程自己会建座位 + + bool isBot = p.isBot || p.userId.StartsWith("bot_"); + var targetSit = panel.list_Sits.Find(s => s.posID == p.position); + // 真人顶掉 bot 座位时先替换(与 RoomJoinNotice 分支同逻辑) + if (targetSit != null && targetSit.isBot && !isBot) + { + targetSit.isBot = false; + targetSit.ShowWaiting(); + } + panel.JoinGame(p.position, p.gold, p.diamond, p.cannonMultiple, 0, p.userId, p.cannonId, p.wingsId, p.warGodId); + } + + // 2. 离开:快照有、列表无 → 释放座位并移出快照 + for (int i = currRoom.players.Count - 1; i >= 0; i--) + { + var p = currRoom.players[i]; + if (string.IsNullOrEmpty(p.userId)) continue; + if (roomData.players.Exists(x => x.userId == p.userId)) continue; + currRoom.players.RemoveAt(i); + + if (panel == null) continue; + foreach (var sit in panel.list_Sits) + { + if (sit.user == p.userId) + sit.ShowWaiting(); + } + } + } + /// /// LoginHub LevelChanged 数据(字段名对齐后端大写首字母) /// diff --git a/Assets/MyGame/aa/Game/XJMH/Boss/LieYanFengBao/LieYanFengBao_BOSS.cs b/Assets/MyGame/aa/Game/XJMH/Boss/LieYanFengBao/LieYanFengBao_BOSS.cs index 9d1eb5149..58cd2eca3 100644 --- a/Assets/MyGame/aa/Game/XJMH/Boss/LieYanFengBao/LieYanFengBao_BOSS.cs +++ b/Assets/MyGame/aa/Game/XJMH/Boss/LieYanFengBao/LieYanFengBao_BOSS.cs @@ -163,6 +163,9 @@ public class LieYanFengBao_BOSS : MonsterControl_BossBase { ChangeState(CharacterStateType.move); isMove = true; + // ⭐ 复位死亡标记(同 XingJiXunHang):非最终死亡 Boss 不回池, + // isDead 卡 true 会让锁定子弹被 BullitMove 判 target.isDead 立即 NotLock + isDead = false; Debug.Log("[LieYanFengBao] 未死亡爆奖结束,恢复游动"); } currEvent = null; diff --git a/Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/XingJiXunHang_BOSS.cs b/Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/XingJiXunHang_BOSS.cs index 3fe16ce46..c7abc8a68 100644 --- a/Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/XingJiXunHang_BOSS.cs +++ b/Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/XingJiXunHang_BOSS.cs @@ -246,6 +246,10 @@ public class XingJiXunHang_BOSS : MonsterControl_BossBase { ChangeState(CharacterStateType.move); isMove = true; + // ⭐ 复位死亡标记:Ws_DieFish 对每次击杀统一置 isDead=true,非最终死亡 Boss 不回池, + // OnEnable 不会重跑导致 isDead 卡 true → 重新锁定后子弹被 BullitMove 判 target.isDead + // 立即 NotLock(碰撞器恢复+直线飞行,被路径中间的小鱼挡住) + isDead = false; Debug.Log("[XingJiXunHang] 非最终死亡后恢复游动"); } diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index 9a207f197..a233ed022 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -3229,6 +3229,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // 处理选中的最佳命中 MonsterContral monster = bestHit.collider.GetComponentInParent(); + // ⭐ 死亡窗口期的鱼不可手动锁定(isDead=true:死亡/爆奖动画播放中;XJMH 多次死亡 Boss + // 非最终死亡恢复后曾卡 true,锁上后子弹被 BullitMove 判 target.isDead 立即 NotLock)。 + // ⚠ 不用 IsMonsterLockable:它额外要求 lockPoint/colliderList 配了激活锚点, + // 普通鱼预制体没配这些列表(手动锁定靠点击的碰撞器 hitCollider 本身),用它会把所有鱼都拒掉 + if (monster == null || !monster.gameObject.activeInHierarchy || monster.isDead) return; // ⭐ 记录被点击的碰撞器,用于 ResolveLockTransform 优先级判断 if (monster != null) monster.hitCollider = bestHit.collider.transform; diff --git a/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab b/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab index 80d8d62a4..2bddbdd32 100644 --- a/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab +++ b/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab @@ -8213,9 +8213,9 @@ RectTransform: m_Father: {fileID: 245637205031072731} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: -342.40753, y: -181.04694} - m_SizeDelta: {x: -811.868, y: -563.5797} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 342.51886, y: 59.886642} + m_SizeDelta: {x: 812.132, y: 186.42029} m_Pivot: {x: 0.4217527, y: 0.32124478} --- !u!114 &5812222518583518 MonoBehaviour: @@ -8249,6 +8249,8 @@ MonoBehaviour: user: tmp_UserNumber: {fileID: 0} tmp_UserName: {fileID: 0} + tmp_UserLevel: {fileID: 0} + slider_UserLevelEXP: {fileID: 0} img_Head: {fileID: 0} img_Vip: {fileID: 0} sprite_VipList: [] @@ -8440,10 +8442,10 @@ RectTransform: - {fileID: 4487808903415790459} m_Father: {fileID: 245637205031072731} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 482.52466, y: -187.01053} - m_SizeDelta: {x: -811.868, y: -563.5797} + m_AnchorMin: {x: 1, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: -458.54608, y: 61.85913} + m_SizeDelta: {x: 812.132, y: 186.42029} m_Pivot: {x: 0.42052296, y: 0.3318262} --- !u!114 &3631726351435843207 MonoBehaviour: @@ -8477,6 +8479,8 @@ MonoBehaviour: user: tmp_UserNumber: {fileID: 0} tmp_UserName: {fileID: 0} + tmp_UserLevel: {fileID: 0} + slider_UserLevelEXP: {fileID: 0} img_Head: {fileID: 0} img_Vip: {fileID: 0} sprite_VipList: []