116 lines
5.3 KiB
C#
116 lines
5.3 KiB
C#
// ============================================================
|
||
// 文件:SeatMirror.cs
|
||
// 职责:4人捕鱼桌座位镜像转换(玩家永远看自己在下方)
|
||
//
|
||
// 座位定义(服务端与场景一致):
|
||
// 0 = 左下 1 = 右下
|
||
// 2 = 左上 3 = 右上
|
||
//
|
||
// 镜像规则:
|
||
// 本地玩家分到上方座位(2/3)时,整个桌面上下翻转显示:
|
||
// 显示座位 = 服务端座位 XOR 2(0↔2、1↔3 对调),本地玩家落回下方。
|
||
// 本地玩家在下方座位(0/1)时不镜像,显示 = 服务端。
|
||
//
|
||
// 两个坐标空间(必须严格区分,跨界必经本类转换):
|
||
// - 服务端空间(server):网络协议字段 SyncGameData.sitPos / RoomJoinNotice.Position
|
||
// / players[].position,服务端只认这个空间。
|
||
// - 显示空间(display):场景座位 SitPosData.posID 及一切本地表现
|
||
// (炮台朝向 IsFlipped、锁定圈、金币飞、bot AI 等)。
|
||
// SitPosData.posID 永远是显示座位,场景预制体不用动。
|
||
//
|
||
// 收发转换口诀:
|
||
// 收到网络数据(含 sitPos/position)→ 先 ToDisplay 再找场景座位;
|
||
// 向服务端发数据(含 sitPos)→ 用 ToServer 把显示座位转回服务端座位。
|
||
// ============================================================
|
||
public static class SeatMirror
|
||
{
|
||
/// <summary>本地玩家在服务端的座位号(OnShow 收到 RoomJoinNotice 时 Setup;-1=未初始化/已离房)</summary>
|
||
public static int LocalServerPos { get; private set; } = -1;
|
||
|
||
/// <summary>是否镜像生效(本地分到上方座位 2/3)</summary>
|
||
public static bool IsMirrored => LocalServerPos >= 2;
|
||
|
||
/// <summary>
|
||
/// 进房时设置本地服务端座位号(决定是否镜像)。
|
||
/// GameMainPanel_FishBase.OnShow 解析出 RoomJoinNotice 后第一时间调用;
|
||
/// 切换房间会重新调用(新房间座位可能变化)。
|
||
/// </summary>
|
||
public static void Setup(int localServerPos)
|
||
{
|
||
LocalServerPos = localServerPos;
|
||
UnityEngine.Debug.Log($"[SeatMirror] 本地服务端座位={localServerPos} " +
|
||
(IsMirrored ? "→ 在上方,启用全桌上下镜像(0↔2,1↔3)" : "→ 在下方,不镜像"));
|
||
}
|
||
|
||
/// <summary>离房时清空(OnHide 调用),防止跨房间残留旧镜像状态</summary>
|
||
public static void Clear()
|
||
{
|
||
LocalServerPos = -1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 服务端座位 → 显示座位(本地视角)。
|
||
/// 用于:接收网络数据后定位场景座位、给玩家分配 SitPosData、RoomJoinNotice/RoomChange 的 position。
|
||
/// 越界值原样返回(防御:后端异常数据不误转换)。
|
||
/// </summary>
|
||
public static int ToDisplay(int serverPos)
|
||
{
|
||
if (serverPos < 0 || serverPos > 3 || !IsMirrored) return serverPos;
|
||
return serverPos ^ 2; // 0↔2、1↔3 上下对调
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示座位 → 服务端座位(发送网络数据时用)。
|
||
/// XOR 2 自反,与 ToDisplay 互逆。
|
||
/// </summary>
|
||
public static int ToServer(int displayPos)
|
||
{
|
||
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 由房主代算,先转成源角度再广播。
|
||
// 接收端按该座位在自己场景里的显示位置还原成世界角度。
|
||
// ================================================================
|
||
|
||
/// <summary>角度归一化到 [0,360)</summary>
|
||
public static float NormalizeAngle(float angle)
|
||
{
|
||
angle %= 360f;
|
||
if (angle < 0f) angle += 360f;
|
||
return angle;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 源角度(网络帧,下方座位坐标系)→ 本地世界角度。
|
||
/// seatFlipped = 该座位在本端场景里的显示位置在上方(SitPosData.IsFlipped)。
|
||
/// </summary>
|
||
public static float SourceAngleToWorld(float sourceAngle, bool seatFlipped)
|
||
{
|
||
return seatFlipped ? NormalizeAngle(180f - sourceAngle) : sourceAngle;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 本地世界角度 → 源角度(发送网络帧时用)。
|
||
/// seatFlipped = 计算该角度的座位显示位置在上方(bot 由房主代算时可能为 true)。
|
||
/// 180−angle 自反,与 SourceAngleToWorld 互逆。
|
||
/// </summary>
|
||
public static float WorldAngleToSource(float worldAngle, bool seatFlipped)
|
||
{
|
||
return seatFlipped ? NormalizeAngle(180f - worldAngle) : worldAngle;
|
||
}
|
||
}
|