using System; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; public class SitPosData : MonoBehaviour { /// 本地玩家的座位引用(由游戏面板初始化时设置) public static SitPosData localPlayerSit; /// 当前房间房主座位(人机请求由房主发送,房主退出后自动继承) public static SitPosData roomOwnerSit; // ------------------------- // 玩家基础信息 // ------------------------- [Header("玩家信息")] public string user; [Tooltip("座位编号 0~3。0,1=下方 2,3=上方")] public int posID; [Tooltip("是否为人机")] public bool isBot = false; /// posID>=2 = 上方,需要翻转(0,1=下方;2,3=上方) public bool IsFlipped => posID >= 2; [Header("金币")] public long goldNum; public long consumeGoldNum; public TextMeshProUGUI tMP_GoldNum; public TextMeshProUGUI tMP_DiamondNum; public TextMeshProUGUI tMP_TurretConsumeNum; [Header("翅膀")] [Tooltip("翅膀实例化的父节点")] public Transform wingParent; // ------------------------- // 炮台管理(动态克隆) // ------------------------- [Header("炮台")] [Tooltip("炮台实例化的父节点")] public Transform turretParent; [Tooltip("炮台锚点位置(相对 turretParent)")] public Vector2 turretAnchoredPos = Vector2.zero; [Tooltip("当前装备的炮台编号")] public int currentTurretNumber; /// 当前运行中的炮台实例 public TurretData currentTurret; // 状态机入口(GameMainPanel 调用) public Action OnStateChange; // ------------------------- // 炮台旋转 // ------------------------- [Header("炮台旋转")] public Transform tra_CurrTurret; public float currentAngle; /// /// 炮台在世界坐标(Z=0平面)的位置缓存。 /// 因为炮台是 UGUI 元素,tra_CurrTurret.position 是 Canvas 坐标,不能直接和鱼的世界坐标比较距离。 /// 这里用和子弹起点(GetBulletStartWorldPos)相同的转换方式,把 UI 坐标转成世界坐标。 /// 由 LoadTurret 加载完炮台后计算并缓存,bot AI/锁定检测距离时用此字段。 /// [System.NonSerialized] public Vector3 turretWorldPos; // ------------------------- // 锁定系统 // ------------------------- [Header("锁定系统")] public MonsterContral monCotl_Lock; public Transform monCotl_Lock_Position; public bool isLock = false; public GameObject lockFX; // 主锁定图标(单发/双倍用) public List lockFX_Triple = new List(); // 三发锁定额外2个图标 public List tripleLockTargets = new List(); public List tripleLockPositions = new List(); // ------------------------- // UI // ------------------------- [Header("UI")] public Button btn_TurretAdd; public Button btn_TurretMinus; private void Awake() { btn_TurretAdd.onClick.AddListener(() => ChangeTurretAddConsumeNum()); btn_TurretMinus.onClick.AddListener(() => ChangeTurretMinusConsumeNum()); } private void OnEnable() { // 默认显示等待界面 ShowWaiting(); } private void OnDisable() { if (localPlayerSit == this) localPlayerSit = null; // 房主退出 → 自动继承给第一个非人机玩家 if (roomOwnerSit == this) { roomOwnerSit = null; TransferRoomOwner(); } } /// /// 玩家加入 → 显示玩家 UI(child0),隐藏等待界面(child1)。 /// public void ShowPlayer() { if (transform.childCount > 0) transform.GetChild(0).gameObject.SetActive(true); if (transform.childCount > 1) transform.GetChild(1).gameObject.SetActive(false); // 如果是本地玩家的座位,自动注册并加载装备 if (user == GameManager.Instance?.user?.id) { localPlayerSit = this; var cannonUsing = GameManager.Instance.user.cannonUsing; if (cannonUsing != null && int.TryParse(cannonUsing.number, out int num)) LoadTurret(num); var wingUsing = GameManager.Instance.user.wingUsing; if (wingUsing != null && int.TryParse(wingUsing.number, out int wingNum)) LoadWing(wingNum); } // 非人机玩家加入时,自动分配为首个房主 if (!isBot) AssignRoomOwner(this); } /// /// 玩家退出/等待 → 显示等待界面(child1),隐藏玩家 UI(child0)。 /// public void ShowWaiting() { if (transform.childCount > 0) transform.GetChild(0).gameObject.SetActive(false); if (transform.childCount > 1) transform.GetChild(1).gameObject.SetActive(true); } /// 房主退出后,将房主身份转移给第一个活跃的非人机玩家 public static void TransferRoomOwner() { var list = FindObjectsOfType(); foreach (var sit in list) { if (!sit.isBot && sit.gameObject.activeInHierarchy && sit != roomOwnerSit) { roomOwnerSit = sit; // 同步出鱼权限:继承者是本地玩家时接管出鱼 if (CreateFishManager.Instance != null) { CreateFishManager.Instance.fangzhu = sit.user == GameManager.Instance?.user?.id; } break; } } } /// 分配房主(首个非人机玩家) public static void AssignRoomOwner(SitPosData sit) { if (roomOwnerSit == null && !sit.isBot) { roomOwnerSit = sit; // 同步出鱼权限:本地玩家作为首个非人机玩家时接管出鱼 if (CreateFishManager.Instance != null) { CreateFishManager.Instance.fangzhu = sit.user == GameManager.Instance?.user?.id; } } } // ==================== 炮台加载(动态克隆) ==================== /// /// 根据炮台编号,从 Addressables 加载预制体并克隆到 turretParent。 /// 位置/旋转/缩放保持预制体原始值,不动。 /// public void LoadTurret(int turretNumber) { // ⭐ 换炮台时只清活跃子弹(清屏),保留池缓存(同 prefab 能复用) // 用 ClearActive 而非 ClearAll:ClearAll 会清池导致永远不复用 BulletPool.Instance.ClearActive(); if (currentTurret != null) { Destroy(currentTurret.gameObject); currentTurret = null; } currentTurretNumber = turretNumber; // 从 TurretResourcesData 拿预制体 GameObject prefab = TurretResourcesData.GetTurretPrefab(turretNumber); if (prefab == null) { Debug.LogWarning($"[SitPosData] 炮台预制体未加载: #{turretNumber}"); return; } // 克隆到父节点 GameObject instance = Instantiate(prefab, turretParent, false); instance.SetActive(true); // 固定位置归零,由外部父节点定位 var rt = instance.GetComponent(); if (rt != null) rt.anchoredPosition = turretAnchoredPos; currentTurret = instance.GetComponent(); if (currentTurret == null) { Debug.LogError($"[SitPosData] 预制体上未找到 TurretData 组件: #{turretNumber}"); Destroy(instance); return; } currentTurret.turretNumber = turretNumber; // 缓存旋转节点 //tra_CurrTurret = currentTurret.transform.GetChild(0); tra_CurrTurret = currentTurret.transform.parent; // 绑定状态机入口 OnStateChange = currentTurret.ChangeState; // ⭐ 缓存炮台在世界坐标(Z=0平面)的位置,bot AI/锁定检测距离时用 // 和子弹起点(GetBulletStartWorldPos)用相同的 UI→世界 转换方式 UpdateTurretWorldPos(); Debug.Log($"[SitPosData] 炮台已加载: #{turretNumber}"); } /// /// 把炮台 UI 坐标转换成世界坐标(Z=0平面)并缓存到 turretWorldPos。 /// 转换方式和 TurretData.GetBulletStartWorldPos 一致。 /// public void UpdateTurretWorldPos() { if (tra_CurrTurret == null) { turretWorldPos = Vector3.zero; return; } RectTransform rect = tra_CurrTurret.GetComponent(); if (rect != null && UIManager.Instance != null && UIManager.Instance.uiRoot != null) { Canvas canvas = UIManager.Instance.uiRoot.GetComponent(); Camera cam = Camera.main; Vector3 screenPos = RectTransformUtility.WorldToScreenPoint( canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera, rect.position ); float distance = Mathf.Abs(cam.transform.position.z - 0f); Vector3 worldPos = cam.ScreenToWorldPoint(new Vector3(screenPos.x, screenPos.y, distance)); worldPos.z = 0f; turretWorldPos = worldPos; } else { // 非 UI(3D 物体)直接用 position turretWorldPos = tra_CurrTurret.position; } } /// 根据炮台编号字符串加载。 public void LoadTurret(string turretNumberStr) { if (int.TryParse(turretNumberStr, out int num)) LoadTurret(num); } // ==================== 翅膀加载(动态克隆) ==================== /// /// 根据炮台编号,从 Addressables 加载预制体并克隆到 turretParent。 /// 位置/旋转/缩放保持预制体原始值,不动。 /// public void LoadWing(int turretNumber) { // 先清理已有翅膀(保留预设的 Camera,它是用来渲染翅膀到 RawImage 的) for (int i = wingParent.childCount - 1; i >= 0; i--) { var child = wingParent.GetChild(i); if (child.GetComponent() != null) continue; // 保留 Camera Destroy(child.gameObject); } // 从 TurretResourcesData 拿预制体 GameObject prefab = TurretResourcesData.GetTurretPrefab(turretNumber); if (prefab == null) { Debug.LogWarning($"[SitPosData] 翅膀预制体未加载: #{turretNumber}"); return; } // 克隆到 wingParent,保持预制体原始位置/旋转/缩放 GameObject instance = Instantiate(prefab, wingParent, false); instance.SetActive(true); Debug.Log($"[SitPosData] 翅膀已加载: #{turretNumber}"); } /// 根据炮台编号字符串加载。 public void LoadWing(string turretNumberStr) { if (int.TryParse(turretNumberStr, out int num)) LoadWing(num); } // ------------------------- // 切换状态(GameMainPanel 调用) // ------------------------- public void ChangeState(TurretState state) { OnStateChange?.Invoke(state); } // ------------------------- // 炮台旋转(GameMainPanel 调用) // ------------------------- public void SetRotation(float angle) { currentAngle = angle; if (tra_CurrTurret != null) tra_CurrTurret.rotation = Quaternion.Euler(0, 0, angle); } // ------------------------- // 获取发射点(给 GameMainPanel) // ------------------------- public Vector2 GetBulletStartPos() { return currentTurret != null ? currentTurret.GetBulletStartWorldPos() : Vector2.zero; } // ==================== 金币消耗 ==================== public bool ConsumeGold(int mul = 1) { long temp = goldNum - consumeGoldNum * mul; if (temp < 0) return false; goldNum = temp; tMP_GoldNum.text = goldNum.ToString(); return true; } int indexConsume = 0; public void ChangeTurretAddConsumeNum() { if (CreateFishManager.Instance.currGameType.paoBeiList.Count - 1 > indexConsume) indexConsume++; else indexConsume = 0; AudioManager.Instance.PlayerFX(62011); consumeGoldNum = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume]; tMP_TurretConsumeNum.text = consumeGoldNum.ToString(); CreateFishManager.Instance.currGameType?.SyncCannonMultiple((int)consumeGoldNum); } public void ChangeTurretMinusConsumeNum() { if (indexConsume > 0) indexConsume--; else indexConsume = CreateFishManager.Instance.currGameType.paoBeiList.Count - 1; AudioManager.Instance.PlayerFX(62010); consumeGoldNum = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume]; tMP_TurretConsumeNum.text = consumeGoldNum.ToString(); CreateFishManager.Instance.currGameType?.SyncCannonMultiple((int)consumeGoldNum); } // ==================== 锁定系统 ==================== public void SetLockTarget(MonsterContral monster, Transform hitPos) { monCotl_Lock = monster; monCotl_Lock_Position = hitPos; isLock = monster != null; lockFX.SetActive(isLock); // 非三发锁定 → 隐藏额外图标 if (!isLock) HideTripleLockIcons(); } /// /// 设置三发锁定目标(3个目标分别追踪) /// public void SetTripleLockTargets(List<(MonsterContral monster, Transform hitPos)> targets) { if (targets == null || targets.Count == 0) { HideTripleLockIcons(); return; } // 确保 lockFX_Triple 有2个克隆的图标(+主图标=3个) EnsureTripleLockIcons(); tripleLockTargets.Clear(); tripleLockPositions.Clear(); // 第一个目标用主 lockFX var primary = targets[0]; monCotl_Lock = primary.monster; monCotl_Lock_Position = primary.hitPos; isLock = true; lockFX.SetActive(true); tripleLockTargets.Add(primary.monster); tripleLockPositions.Add(primary.hitPos); // 第二个和第三个目标用 lockFX_Triple for (int i = 1; i < targets.Count && i < 3; i++) { int idx = i - 1; if (idx < lockFX_Triple.Count && lockFX_Triple[idx] != null) { lockFX_Triple[idx].SetActive(true); tripleLockTargets.Add(targets[i].monster); tripleLockPositions.Add(targets[i].hitPos); } } // 隐藏多余的 for (int i = targets.Count - 1; i < lockFX_Triple.Count; i++) { if (lockFX_Triple[i] != null) lockFX_Triple[i].SetActive(false); } } private void EnsureTripleLockIcons() { while (lockFX_Triple.Count < 2 && lockFX != null) { var clone = GameObject.Instantiate(lockFX, lockFX.transform.parent); clone.name = lockFX.name + "_Triple" + (lockFX_Triple.Count + 1); clone.transform.localScale = lockFX.transform.localScale * 0.5f; lockFX_Triple.Add(clone); } } public void HideTripleLockIcons() { foreach (var icon in lockFX_Triple) { if (icon != null) icon.SetActive(false); } tripleLockTargets.Clear(); tripleLockPositions.Clear(); } public void ClearLock() { monCotl_Lock = null; monCotl_Lock_Position = null; isLock = false; lockFX.SetActive(false); HideTripleLockIcons(); ChangeState(TurretState.Idle); } }