diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index a5635fe31..868b8ea44 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -4036,7 +4036,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base sitPos = SeatMirror.ToServer(currSitData.posID), frameType = FrameDataType.rotateTurrect, time = BeijingTimeManager.Instance.CurrentTimestampMs, - frameData = new FrameData_Rotate { angle = angle } + // ⭐ 座位镜像:网络角度统一用"源角度"(下方座位系,0=朝桌面中心)。 + // 本地玩家永远显示在下方座位(IsFlipped=false),这里实际是原值; + // 保留转换以兼容未来本地座位可能在上方的情况 + frameData = new FrameData_Rotate { angle = SeatMirror.WorldAngleToSource(angle, currSitData.IsFlipped) } }; WebSocketSendList.SyncGameData(sgd); } @@ -4045,7 +4048,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base { var fireData = new FrameData_FireBullet { - angle = angle, + // ⭐ 座位镜像:网络角度统一用"源角度"(下方座位系),接收端按显示座位还原 + angle = SeatMirror.WorldAngleToSource(angle, currSitData.IsFlipped), gold = currSitData.goldNum, isSanShe = isSanShe, twoRTruuect = isDouble, @@ -4191,7 +4195,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base if (sit != null && sit.isBot && currSitData == SitPosData.roomOwnerSit) return; var data = FrameDataParser.Parse(sync.frameData); - SmoothRotate(sit, data.angle); + // ⭐ 座位镜像:网络角度是"源角度"(发送方下方座位系,0=朝桌面中心), + // 按该座位在本端场景的显示位置还原成世界角度: + // 显示座位在下方 → 原样;在上方 → 180−源角度(上下翻转,左右不反) + SmoothRotate(sit, SeatMirror.SourceAngleToWorld(data.angle, sit.IsFlipped)); } public void SyncGameData_FireBullit(FireCannonSendData sync) @@ -4225,7 +4232,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base SitPosData sit = list_Sits.Find(s => s.user == sync.userId); if (sit == null) return; - float angle = botLastAngle.TryGetValue(sync.userId, out float a) ? a : sit.currentAngle; + // ⭐ 座位镜像:botLastAngle/botLastFireData 缓存的是"源角度"(下方座位系); + // 真人兜底用 sit.currentAngle(本端世界角度,无需转换) + bool angleFromBotMemory = botLastAngle.TryGetValue(sync.userId, out float a); + float angle = angleFromBotMemory ? a : sit.currentAngle; if (sync.bulletIds == null || sync.bulletIds.Count == 0) return; // ⭐ 优先用服务器广播的 sync.isBot 标记判断是否机器人 @@ -4233,9 +4243,17 @@ public class GameMainPanel_FishBase : GameMainPanel_Base bool isBot = sync.isBot || sit.isBot; if (isBot && botLastFireData.TryGetValue(sync.userId, out var fireData)) { - FireBullet(sit, angle, fireData.isSanShe, fireData.twoRTruuect, + // ⭐ 座位镜像:源角度按该 bot 显示座位还原成世界角度再创建子弹 + FireBullet(sit, SeatMirror.SourceAngleToWorld(fireData.angle, sit.IsFlipped), + fireData.isSanShe, fireData.twoRTruuect, fireData.isSuoDing, sync.bulletIds, fireData.monsterIDs, bulletGolds); } + else if (angleFromBotMemory) + { + // bot 有角度缓存但无完整开火数据 → 同样按源角度转换 + FireBullet(sit, SeatMirror.SourceAngleToWorld(angle, sit.IsFlipped), + false, false, false, sync.bulletIds, null, bulletGolds); + } else { FireBullet(sit, angle, false, false, false, sync.bulletIds, null, bulletGolds); @@ -4253,7 +4271,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base return; } var targetSit = list_Sits[dispPos]; - FireBullet(targetSit, data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs, bulletGolds); + // ⭐ 座位镜像:网络角度是"源角度"(发送方下方座位系),按该座位显示位置还原成世界角度 + FireBullet(targetSit, SeatMirror.SourceAngleToWorld(data.angle, targetSit.IsFlipped), + data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs, bulletGolds); } /// @@ -5076,7 +5096,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base botIdleTimer[sit.posID] = 0f; float angle = sit.currentAngle; - botLastAngle[sit.user] = angle; + // ⭐ 座位镜像:bot 角度缓存统一存"源角度"(下方座位系),与网络帧格式一致 + botLastAngle[sit.user] = SeatMirror.WorldAngleToSource(angle, sit.IsFlipped); bool isSanShe = (skill != null && skill.tripleActive) || mode == BotBehaviorMode.ScatterMode || mode == BotBehaviorMode.ScatterRandom; bool isDouble = skill != null && skill.doubleActive; @@ -5098,7 +5119,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base botLastFireData[sit.user] = new FrameData_FireBullet { - angle = angle, + // ⭐ 座位镜像:存"源角度"(下方座位系),房主自己收到广播回放时同样按源角度转换 + angle = SeatMirror.WorldAngleToSource(angle, sit.IsFlipped), gold = gold, isSanShe = isSanShe, twoRTruuect = isDouble, @@ -5378,7 +5400,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base sitPos = SeatMirror.ToServer(botSit.posID), frameType = FrameDataType.rotateTurrect, time = BeijingTimeManager.Instance.CurrentTimestampMs, - frameData = new FrameData_Rotate { angle = angle } + // ⭐ 座位镜像:房主代算的 bot 角度是世界角度(按 bot 显示座位算的), + // 先转成统一的"源角度"(下方座位系)再广播,接收端按各自显示座位还原 + frameData = new FrameData_Rotate { angle = SeatMirror.WorldAngleToSource(angle, botSit.IsFlipped) } }; WebSocketSendList.SyncGameData(sgd); } @@ -5447,7 +5471,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base { var fireData = new FrameData_FireBullet { - angle = angle, + // ⭐ 座位镜像:网络角度统一用"源角度"(下方座位系),接收端按显示座位还原 + angle = SeatMirror.WorldAngleToSource(angle, botSit.IsFlipped), gold = gold, isSanShe = isSanShe, twoRTruuect = isDouble, diff --git a/Assets/MyGame/aa/UI/Game/SeatMirror.cs b/Assets/MyGame/aa/UI/Game/SeatMirror.cs index 09c46d2df..d97273175 100644 --- a/Assets/MyGame/aa/UI/Game/SeatMirror.cs +++ b/Assets/MyGame/aa/UI/Game/SeatMirror.cs @@ -67,4 +67,49 @@ public static class SeatMirror { return ToDisplay(displayPos); } + + // ================================================================ + // 炮台角度镜像 + // ================================================================ + // 角度约定(与 SitPosData.SetRotation 一致的 aim-90 世界角): + // 0=朝上, 90=朝左, 180=朝下, 270=朝右(Unity z 轴逆时针为正)。 + // + // 座位镜像是"上下翻转"(0↔2、1↔3 同侧对调),不是旋转 180°, + // 所以方向向量的变换是 Y 轴翻转:(dx,dy) → (dx,−dy), + // 对应角度变换:angle' = 180 − angle(自反,与座位 XOR 2 一样互逆): + // - 垂直瞄准时(0↔180)恰好等于 +180; + // - 斜向/横向瞄准时左右侧不反(若用 +180 会把左右瞄反)。 + // + // 网络帧里的炮台角度统一用"源角度":发送方永远以"自己在下方座位" + // 的坐标系发送(0 = 朝桌面中心)。每个玩家进房后自己永远显示在下方 + // 座位,本地算出的就是源角度;bot 由房主代算,先转成源角度再广播。 + // 接收端按该座位在自己场景里的显示位置还原成世界角度。 + // ================================================================ + + /// 角度归一化到 [0,360) + public static float NormalizeAngle(float angle) + { + angle %= 360f; + if (angle < 0f) angle += 360f; + return angle; + } + + /// + /// 源角度(网络帧,下方座位坐标系)→ 本地世界角度。 + /// seatFlipped = 该座位在本端场景里的显示位置在上方(SitPosData.IsFlipped)。 + /// + public static float SourceAngleToWorld(float sourceAngle, bool seatFlipped) + { + return seatFlipped ? NormalizeAngle(180f - sourceAngle) : sourceAngle; + } + + /// + /// 本地世界角度 → 源角度(发送网络帧时用)。 + /// seatFlipped = 计算该角度的座位显示位置在上方(bot 由房主代算时可能为 true)。 + /// 180−angle 自反,与 SourceAngleToWorld 互逆。 + /// + public static float WorldAngleToSource(float worldAngle, bool seatFlipped) + { + return seatFlipped ? NormalizeAngle(180f - worldAngle) : worldAngle; + } } diff --git a/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs b/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs index 9a2d32492..371824b1a 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs +++ b/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs @@ -112,6 +112,15 @@ public class ChouJiangPanel : SubUIBase { if (this == null) return; config = cfg; + + // 活动未开启:提示后关闭面板 + if (config != null && !config.isOpen) + { + UIManager.Instance.ShowTiShi("活动未开启"); + uIManager.Hide(this); + return; + } + UpdateActivityUI(); InitToggles(); RefreshUserStatus(); diff --git a/Assets/MyGame/aa/UI/Home/UIHomeMain.cs b/Assets/MyGame/aa/UI/Home/UIHomeMain.cs index d0c4bb039..1df45806f 100644 --- a/Assets/MyGame/aa/UI/Home/UIHomeMain.cs +++ b/Assets/MyGame/aa/UI/Home/UIHomeMain.cs @@ -140,7 +140,16 @@ public class UIHomeMain : UIBase }); btn_ChouJiang.onClick.AddListener(() => { - subUIManager.ShowAsync(UIPanel.ChouJiangPanel); + // 接收配置信息时直接判断:未开启不打开面板,避免面板先弹出又关掉的闪烁 + APIRequestList.GetLotteryActivityConfig(cfg => + { + if (cfg != null && !cfg.isOpen) + { + UIManager.Instance.ShowTiShi("活动未开启"); + return; + } + subUIManager.ShowAsync(UIPanel.ChouJiangPanel); + }); }); #endregion