487 lines
16 KiB
C#
487 lines
16 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
public class SitPosData : MonoBehaviour
|
||
{
|
||
/// <summary>本地玩家的座位引用(由游戏面板初始化时设置)</summary>
|
||
public static SitPosData localPlayerSit;
|
||
/// <summary>当前房间房主座位(人机请求由房主发送,房主退出后自动继承)</summary>
|
||
public static SitPosData roomOwnerSit;
|
||
|
||
// -------------------------
|
||
// 玩家基础信息
|
||
// -------------------------
|
||
[Header("玩家信息")]
|
||
public string user;
|
||
[Tooltip("座位编号 0~3。0,1=下方 2,3=上方")]
|
||
public int posID;
|
||
[Tooltip("是否为人机")]
|
||
public bool isBot = false;
|
||
|
||
/// <summary>posID>=2 = 上方,需要翻转(0,1=下方;2,3=上方)</summary>
|
||
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;
|
||
|
||
/// <summary>当前运行中的炮台实例</summary>
|
||
public TurretData currentTurret;
|
||
|
||
// 状态机入口(GameMainPanel 调用)
|
||
public Action<TurretState> OnStateChange;
|
||
|
||
// -------------------------
|
||
// 炮台旋转
|
||
// -------------------------
|
||
[Header("炮台旋转")]
|
||
public Transform tra_CurrTurret;
|
||
public float currentAngle;
|
||
|
||
/// <summary>
|
||
/// 炮台在世界坐标(Z=0平面)的位置缓存。
|
||
/// 因为炮台是 UGUI 元素,tra_CurrTurret.position 是 Canvas 坐标,不能直接和鱼的世界坐标比较距离。
|
||
/// 这里用和子弹起点(GetBulletStartWorldPos)相同的转换方式,把 UI 坐标转成世界坐标。
|
||
/// 由 LoadTurret 加载完炮台后计算并缓存,bot AI/锁定检测距离时用此字段。
|
||
/// </summary>
|
||
[System.NonSerialized]
|
||
public Vector3 turretWorldPos;
|
||
|
||
// -------------------------
|
||
// 锁定系统
|
||
// -------------------------
|
||
[Header("锁定系统")]
|
||
public MonsterContral monCotl_Lock;
|
||
public Transform monCotl_Lock_Position;
|
||
public bool isLock = false;
|
||
public GameObject lockFX; // 主锁定图标(单发/双倍用)
|
||
public List<GameObject> lockFX_Triple = new List<GameObject>(); // 三发锁定额外2个图标
|
||
public List<MonsterContral> tripleLockTargets = new List<MonsterContral>();
|
||
public List<Transform> tripleLockPositions = new List<Transform>();
|
||
|
||
// -------------------------
|
||
// 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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 玩家加入 → 显示玩家 UI(child0),隐藏等待界面(child1)。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 玩家退出/等待 → 显示等待界面(child1),隐藏玩家 UI(child0)。
|
||
/// </summary>
|
||
public void ShowWaiting()
|
||
{
|
||
if (transform.childCount > 0)
|
||
transform.GetChild(0).gameObject.SetActive(false);
|
||
if (transform.childCount > 1)
|
||
transform.GetChild(1).gameObject.SetActive(true);
|
||
}
|
||
|
||
/// <summary>房主退出后,将房主身份转移给第一个活跃的非人机玩家</summary>
|
||
public static void TransferRoomOwner()
|
||
{
|
||
var list = FindObjectsOfType<SitPosData>();
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>分配房主(首个非人机玩家)</summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==================== 炮台加载(动态克隆) ====================
|
||
|
||
/// <summary>
|
||
/// 根据炮台编号,从 Addressables 加载预制体并克隆到 turretParent。
|
||
/// 位置/旋转/缩放保持预制体原始值,不动。
|
||
/// </summary>
|
||
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<RectTransform>();
|
||
if (rt != null)
|
||
rt.anchoredPosition = turretAnchoredPos;
|
||
|
||
currentTurret = instance.GetComponent<TurretData>();
|
||
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}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 把炮台 UI 坐标转换成世界坐标(Z=0平面)并缓存到 turretWorldPos。
|
||
/// 转换方式和 TurretData.GetBulletStartWorldPos 一致。
|
||
/// </summary>
|
||
public void UpdateTurretWorldPos()
|
||
{
|
||
if (tra_CurrTurret == null)
|
||
{
|
||
turretWorldPos = Vector3.zero;
|
||
return;
|
||
}
|
||
|
||
RectTransform rect = tra_CurrTurret.GetComponent<RectTransform>();
|
||
if (rect != null && UIManager.Instance != null && UIManager.Instance.uiRoot != null)
|
||
{
|
||
Canvas canvas = UIManager.Instance.uiRoot.GetComponent<Canvas>();
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>根据炮台编号字符串加载。</summary>
|
||
public void LoadTurret(string turretNumberStr)
|
||
{
|
||
if (int.TryParse(turretNumberStr, out int num))
|
||
LoadTurret(num);
|
||
}
|
||
// ==================== 翅膀加载(动态克隆) ====================
|
||
|
||
/// <summary>
|
||
/// 根据炮台编号,从 Addressables 加载预制体并克隆到 turretParent。
|
||
/// 位置/旋转/缩放保持预制体原始值,不动。
|
||
/// </summary>
|
||
public void LoadWing(int turretNumber)
|
||
{
|
||
// 先清理已有翅膀(保留预设的 Camera,它是用来渲染翅膀到 RawImage 的)
|
||
for (int i = wingParent.childCount - 1; i >= 0; i--)
|
||
{
|
||
var child = wingParent.GetChild(i);
|
||
if (child.GetComponent<Camera>() != 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}");
|
||
}
|
||
|
||
/// <summary>根据炮台编号字符串加载。</summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置三发锁定目标(3个目标分别追踪)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|