2026-02-03 11:35:19 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using TMPro;
|
|
|
|
|
|
using UnityEngine;
|
2026-03-09 09:25:01 +08:00
|
|
|
|
using UnityEngine.UI;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
2026-07-28 18:29:49 +08:00
|
|
|
|
/// <summary>炮台控制模式</summary>
|
|
|
|
|
|
public enum TurretControlMode
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>普通模式:动态克隆炮台,显示金币+钻石,按钮打开面板切换</summary>
|
|
|
|
|
|
Normal = 0,
|
|
|
|
|
|
/// <summary>星际迷航模式:座位子对象激活/停用,只显示金币,长按炮台弹出扇形切换</summary>
|
|
|
|
|
|
XJMH = 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public class SitPosData : MonoBehaviour
|
|
|
|
|
|
{
|
2026-07-30 14:18:17 +08:00
|
|
|
|
public bool IsBaoJiangingActive;
|
2026-07-30 22:09:55 +08:00
|
|
|
|
public Transform obj_BaoJiangZhongPanelPosition;
|
2026-07-30 14:18:17 +08:00
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
/// <summary>本地玩家的座位引用(由游戏面板初始化时设置)</summary>
|
|
|
|
|
|
public static SitPosData localPlayerSit;
|
2026-06-16 19:12:37 +08:00
|
|
|
|
/// <summary>当前房间房主座位(人机请求由房主发送,房主退出后自动继承)</summary>
|
|
|
|
|
|
public static SitPosData roomOwnerSit;
|
2026-06-08 21:23:40 +08:00
|
|
|
|
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
2026-05-04 17:21:52 +08:00
|
|
|
|
// 玩家基础信息
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
2026-05-04 17:21:52 +08:00
|
|
|
|
[Header("玩家信息")]
|
2026-03-18 17:26:14 +08:00
|
|
|
|
public string user;
|
2026-06-13 01:25:41 +08:00
|
|
|
|
[Tooltip("座位编号 0~3。0,1=下方 2,3=上方")]
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public int posID;
|
2026-06-16 19:12:37 +08:00
|
|
|
|
[Tooltip("是否为人机")]
|
|
|
|
|
|
public bool isBot = false;
|
2026-05-02 23:52:48 +08:00
|
|
|
|
|
2026-06-13 01:25:41 +08:00
|
|
|
|
/// <summary>posID>=2 = 上方,需要翻转(0,1=下方;2,3=上方)</summary>
|
|
|
|
|
|
public bool IsFlipped => posID >= 2;
|
|
|
|
|
|
|
2026-03-27 15:18:35 +08:00
|
|
|
|
[Header("金币")]
|
2026-05-02 23:52:48 +08:00
|
|
|
|
public long goldNum;
|
|
|
|
|
|
public long consumeGoldNum;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public TextMeshProUGUI tMP_GoldNum;
|
2026-06-19 18:18:04 +08:00
|
|
|
|
public TextMeshProUGUI tMP_DiamondNum;
|
2026-05-04 17:21:52 +08:00
|
|
|
|
public TextMeshProUGUI tMP_TurretConsumeNum;
|
2026-06-29 20:40:30 +08:00
|
|
|
|
[Header("翅膀")]
|
|
|
|
|
|
[Tooltip("翅膀实例化的父节点")]
|
|
|
|
|
|
public Transform wingParent;
|
2026-08-27 19:06:35 +08:00
|
|
|
|
[Header("战神")]
|
|
|
|
|
|
[Tooltip("战神实例化的父节点")]
|
|
|
|
|
|
public Transform zhanShenParent;
|
2026-08-28 22:33:35 +08:00
|
|
|
|
[Tooltip("当前装备的战神编号(0=无战神)。炮台/战神显示二选一:有战神时 turretParent 隐藏")]
|
|
|
|
|
|
public int currentZhanShenNumber;
|
2026-08-27 19:06:35 +08:00
|
|
|
|
[Header("特殊鱼炮台")]
|
|
|
|
|
|
[Tooltip("特殊鱼炮台实例化的父节点")]
|
|
|
|
|
|
public Transform tSYPTParent;
|
2026-08-28 18:57:36 +08:00
|
|
|
|
[Tooltip("特殊鱼炮台(钻头)是否正在使用:true=期间禁用普通开火,点击只发射钻头")]
|
|
|
|
|
|
public bool isDrillActive;
|
2026-08-29 17:28:00 +08:00
|
|
|
|
[Tooltip("激光锁定:true=激光发射期间炮台锁定方向,SetRotation 不生效(原方向打到爆奖结束)")]
|
|
|
|
|
|
public bool isLaserLocked;
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
2026-06-08 21:23:40 +08:00
|
|
|
|
// 炮台管理(动态克隆)
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
2026-05-04 17:21:52 +08:00
|
|
|
|
[Header("炮台")]
|
2026-06-08 21:23:40 +08:00
|
|
|
|
[Tooltip("炮台实例化的父节点")]
|
|
|
|
|
|
public Transform turretParent;
|
2026-08-27 17:28:19 +08:00
|
|
|
|
public Transform turretRotate;
|
2026-05-04 17:21:52 +08:00
|
|
|
|
|
2026-07-28 18:29:49 +08:00
|
|
|
|
[Header("炮台控制模式")]
|
|
|
|
|
|
[Tooltip("Normal=普通模式(动态克隆炮台,显示金币+钻石,按钮切换); XJMH=星际迷航模式(座位子对象激活/停用,只显示金币,长按扇形切换)")]
|
|
|
|
|
|
public TurretControlMode turretControlMode = TurretControlMode.Normal;
|
|
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
[Tooltip("炮台锚点位置(相对 turretParent)")]
|
|
|
|
|
|
public Vector2 turretAnchoredPos = Vector2.zero;
|
|
|
|
|
|
|
|
|
|
|
|
[Tooltip("当前装备的炮台编号")]
|
|
|
|
|
|
public int currentTurretNumber;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>当前运行中的炮台实例</summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public TurretData currentTurret;
|
2026-05-02 23:52:48 +08:00
|
|
|
|
|
2026-08-28 22:33:35 +08:00
|
|
|
|
/// <summary>当前运行中的战神实例(null=未显示)。炮台/战神显示二选一:有战神时 turretParent 隐藏</summary>
|
|
|
|
|
|
[System.NonSerialized]
|
|
|
|
|
|
public GameObject currentZhanShen;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>战神实例上的 TurretData/ZhanShenTurretData(开火时优先取战神的子弹预制体,null=无战神或未挂组件)</summary>
|
|
|
|
|
|
[System.NonSerialized]
|
|
|
|
|
|
public TurretData currentZhanShenData;
|
|
|
|
|
|
|
2026-08-29 01:02:09 +08:00
|
|
|
|
// -------------------------
|
|
|
|
|
|
// 战神激光(锁定时显示)
|
|
|
|
|
|
// -------------------------
|
|
|
|
|
|
/// <summary>战神激光对象列表(世界空间 LineRenderer,起点=子弹创建位置,终点=锁定鱼的位置)。三发锁定=3条</summary>
|
|
|
|
|
|
private readonly List<LineRenderer> _zhanShenLasers = new List<LineRenderer>();
|
|
|
|
|
|
|
2026-07-28 18:29:49 +08:00
|
|
|
|
// -------------------------
|
|
|
|
|
|
// XJMH 炮台列表(星际迷航专用)
|
|
|
|
|
|
// -------------------------
|
|
|
|
|
|
[Header("XJMH 炮台列表")]
|
|
|
|
|
|
[Tooltip("XJMH 模式专用:在 Inspector 中拖入座位下的所有炮台子对象(带 TurretData 组件)。" +
|
|
|
|
|
|
"数量不多,建议按炮台编号升序排列,便于扇形切换按索引选择。")]
|
|
|
|
|
|
public List<TurretData> xjmhTurretList = new List<TurretData>();
|
|
|
|
|
|
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// 状态机入口(GameMainPanel 调用)
|
|
|
|
|
|
public Action<TurretState> OnStateChange;
|
|
|
|
|
|
|
|
|
|
|
|
// -------------------------
|
|
|
|
|
|
// 炮台旋转
|
|
|
|
|
|
// -------------------------
|
2026-05-04 17:21:52 +08:00
|
|
|
|
[Header("炮台旋转")]
|
2026-05-02 23:52:48 +08:00
|
|
|
|
public Transform tra_CurrTurret;
|
|
|
|
|
|
public float currentAngle;
|
|
|
|
|
|
|
2026-07-16 20:16:05 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 炮台在世界坐标(Z=0平面)的位置缓存。
|
|
|
|
|
|
/// 因为炮台是 UGUI 元素,tra_CurrTurret.position 是 Canvas 坐标,不能直接和鱼的世界坐标比较距离。
|
|
|
|
|
|
/// 这里用和子弹起点(GetBulletStartWorldPos)相同的转换方式,把 UI 坐标转成世界坐标。
|
|
|
|
|
|
/// 由 LoadTurret 加载完炮台后计算并缓存,bot AI/锁定检测距离时用此字段。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[System.NonSerialized]
|
|
|
|
|
|
public Vector3 turretWorldPos;
|
|
|
|
|
|
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
|
|
|
|
|
// 锁定系统
|
|
|
|
|
|
// -------------------------
|
2026-05-04 17:21:52 +08:00
|
|
|
|
[Header("锁定系统")]
|
2026-03-09 09:25:01 +08:00
|
|
|
|
public MonsterContral monCotl_Lock;
|
2026-05-01 06:26:50 +08:00
|
|
|
|
public Transform monCotl_Lock_Position;
|
2026-05-04 17:21:52 +08:00
|
|
|
|
public bool isLock = false;
|
2026-07-31 17:57:24 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// ⭐ 当前锁定是否为玩家手动锁定。
|
|
|
|
|
|
/// - true:玩家点击锁定的鱼 → 不受路径进度(0.1~0.9)限制,鱼离场前一直锁定
|
|
|
|
|
|
/// - false:自动锁定/三发查找/bot → 受路径进度限制
|
|
|
|
|
|
/// 由 GameMainPanel_FishBase 在 SetLockTarget 前设置
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[System.NonSerialized]
|
|
|
|
|
|
public bool isManualLock = false;
|
2026-06-21 00:28:59 +08:00
|
|
|
|
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>();
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
|
|
|
|
|
// UI
|
|
|
|
|
|
// -------------------------
|
2026-05-04 17:21:52 +08:00
|
|
|
|
[Header("UI")]
|
2026-05-02 23:52:48 +08:00
|
|
|
|
public Button btn_TurretAdd;
|
|
|
|
|
|
public Button btn_TurretMinus;
|
2026-03-27 15:18:35 +08:00
|
|
|
|
|
2026-05-02 23:52:48 +08:00
|
|
|
|
private void Awake()
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-07-28 18:29:49 +08:00
|
|
|
|
if (btn_TurretAdd != null)
|
|
|
|
|
|
btn_TurretAdd.onClick.AddListener(() => ChangeTurretAddConsumeNum());
|
|
|
|
|
|
if (btn_TurretMinus != null)
|
|
|
|
|
|
btn_TurretMinus.onClick.AddListener(() => ChangeTurretMinusConsumeNum());
|
|
|
|
|
|
|
|
|
|
|
|
// XJMH 模式:隐藏钻石显示(只显示金币)
|
|
|
|
|
|
if (turretControlMode == TurretControlMode.XJMH && tMP_DiamondNum != null)
|
|
|
|
|
|
tMP_DiamondNum.gameObject.SetActive(false);
|
|
|
|
|
|
|
|
|
|
|
|
// XJMH 模式:炮台是座位自带子对象,预制体里已摆好,不需要加载。
|
|
|
|
|
|
// 这里只做引用初始化,不做激活/停用。
|
|
|
|
|
|
if (turretControlMode == TurretControlMode.XJMH)
|
|
|
|
|
|
InitXJMHTurret();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// XJMH 模式:炮台是座位自带子对象,预制体里已摆好激活状态,不需要 LoadTurret 加载。
|
|
|
|
|
|
/// 此方法只做引用初始化:从 <see cref="xjmhTurretList"/> 取第一个激活的炮台赋给 currentTurret,
|
|
|
|
|
|
/// 并绑定旋转节点、状态机入口、世界坐标缓存。不做任何激活/停用操作。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void InitXJMHTurret()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (xjmhTurretList == null || xjmhTurretList.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[SitPosData] XJMH 炮台列表未配置,座位 {posID} 无炮台可用");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 优先取列表中第一个激活的炮台;若全部未激活,取 list[0]
|
|
|
|
|
|
TurretData found = null;
|
|
|
|
|
|
foreach (var td in xjmhTurretList)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (td == null) continue;
|
|
|
|
|
|
if (td.gameObject.activeSelf)
|
|
|
|
|
|
{
|
|
|
|
|
|
found = td;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (found == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
found = xjmhTurretList[0];
|
|
|
|
|
|
found.gameObject.SetActive(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
currentTurret = found;
|
|
|
|
|
|
currentTurretNumber = found.turretNumber;
|
|
|
|
|
|
|
2026-07-28 21:11:57 +08:00
|
|
|
|
// 缓存旋转节点:优先用 TurretData.tra_RotationPivot,否则回退父节点
|
|
|
|
|
|
tra_CurrTurret = GetTurretRotationPivot(currentTurret);
|
2026-07-28 18:29:49 +08:00
|
|
|
|
|
|
|
|
|
|
OnStateChange = currentTurret.ChangeState;
|
|
|
|
|
|
UpdateTurretWorldPos();
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[SitPosData] XJMH 炮台引用已初始化: #{currentTurretNumber}");
|
2026-05-27 19:47:06 +08:00
|
|
|
|
}
|
2026-06-08 21:23:40 +08:00
|
|
|
|
|
|
|
|
|
|
private void OnEnable()
|
|
|
|
|
|
{
|
2026-07-02 18:28:50 +08:00
|
|
|
|
// 默认显示等待界面
|
|
|
|
|
|
ShowWaiting();
|
2026-06-08 21:23:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDisable()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (localPlayerSit == this)
|
|
|
|
|
|
localPlayerSit = null;
|
2026-06-16 19:12:37 +08:00
|
|
|
|
// 房主退出 → 自动继承给第一个非人机玩家
|
|
|
|
|
|
if (roomOwnerSit == this)
|
|
|
|
|
|
{
|
|
|
|
|
|
roomOwnerSit = null;
|
|
|
|
|
|
TransferRoomOwner();
|
|
|
|
|
|
}
|
2026-08-29 01:02:09 +08:00
|
|
|
|
// 座位禁用时清理战神激光(世界空间对象,不随座位隐藏)
|
|
|
|
|
|
DestroyZhanShenLaser();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateZhanShenLaser();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 战神激光(锁定时显示) ====================
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 战神锁定时显示激光(LineRenderer):起点=创建子弹的位置(GetBulletStartPos),终点=锁定鱼的位置。
|
|
|
|
|
|
/// ⭐ 一个锁定点一条线:单发锁定=1条,三发锁定=3条(主目标+2个三发目标)。
|
|
|
|
|
|
/// ⭐ 只是视觉表现:打中鱼仍按正常攻击频率由子弹完成(HandleAutoFire 以 attackTime 发射锁定子弹)。
|
|
|
|
|
|
/// 普通炮台没有此效果(currentZhanShenData 为 null 时不显示)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void UpdateZhanShenLaser()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 收集所有有效锁定点:主目标 + 三发锁定的额外目标
|
|
|
|
|
|
_laserTargets.Clear();
|
|
|
|
|
|
if (currentZhanShenData != null && isLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (monCotl_Lock_Position != null && monCotl_Lock_Position.gameObject.activeInHierarchy)
|
|
|
|
|
|
_laserTargets.Add(monCotl_Lock_Position);
|
|
|
|
|
|
|
|
|
|
|
|
// 三发锁定:每个有效目标一条线(主目标已在上面,这里只补额外的)
|
|
|
|
|
|
if (tripleLockTargets.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < tripleLockTargets.Count && i < tripleLockPositions.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var pos = tripleLockPositions[i];
|
|
|
|
|
|
var mon = tripleLockTargets[i];
|
|
|
|
|
|
// 主目标(monCotl_Lock)已在上面加过,跳过避免重复
|
|
|
|
|
|
if (mon == monCotl_Lock) continue;
|
|
|
|
|
|
if (mon == null || mon.isDead || pos == null || !pos.gameObject.activeInHierarchy) continue;
|
|
|
|
|
|
_laserTargets.Add(pos);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 没有锁定点:隐藏所有激光
|
|
|
|
|
|
if (_laserTargets.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < _zhanShenLasers.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_zhanShenLasers[i] != null && _zhanShenLasers[i].gameObject.activeSelf)
|
|
|
|
|
|
_zhanShenLasers[i].gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-29 17:28:00 +08:00
|
|
|
|
// 起点:创建子弹的位置(Z=-50,战神激光显示层,和 FireBullet 的 startPos 同源仅 Z 不同)
|
2026-08-29 01:02:09 +08:00
|
|
|
|
Vector3 start = GetBulletStartPos();
|
2026-08-29 17:28:00 +08:00
|
|
|
|
start.z = -50f;
|
2026-08-29 01:02:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 需要几条就创建几条(三发=3个 LineRenderer),多余的隐藏
|
|
|
|
|
|
for (int i = 0; i < _laserTargets.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var line = i < _zhanShenLasers.Count ? _zhanShenLasers[i] : CreateZhanShenLaser(i);
|
|
|
|
|
|
if (line == null) continue;
|
|
|
|
|
|
if (!line.gameObject.activeSelf)
|
|
|
|
|
|
line.gameObject.SetActive(true);
|
|
|
|
|
|
|
2026-08-29 17:28:00 +08:00
|
|
|
|
// 终点:该条锁定鱼的位置(Z=-50,与起点同层)
|
2026-08-29 01:02:09 +08:00
|
|
|
|
Vector3 end = _laserTargets[i].position;
|
2026-08-29 17:28:00 +08:00
|
|
|
|
end.z = -50f;
|
2026-08-29 01:02:09 +08:00
|
|
|
|
line.SetPosition(0, start);
|
|
|
|
|
|
line.SetPosition(1, end);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int i = _laserTargets.Count; i < _zhanShenLasers.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_zhanShenLasers[i] != null && _zhanShenLasers[i].gameObject.activeSelf)
|
|
|
|
|
|
_zhanShenLasers[i].gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>激光终点缓存列表(每帧复用,避免 GC)</summary>
|
|
|
|
|
|
private readonly List<Transform> _laserTargets = new List<Transform>();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建一条战神激光(第 index 条):
|
|
|
|
|
|
/// - laserPrefab 非空 → 克隆预制体(LineRenderer 配置以预制体为准);
|
|
|
|
|
|
/// - laserPrefab 为空 → 直接创建一个新 GameObject 并加上 LineRenderer(用 TurretData 上的宽度/颜色)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private LineRenderer CreateZhanShenLaser(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
var zsData = currentZhanShenData;
|
|
|
|
|
|
GameObject laserGo;
|
|
|
|
|
|
LineRenderer line;
|
|
|
|
|
|
|
|
|
|
|
|
if (zsData.laserPrefab != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
laserGo = Instantiate(zsData.laserPrefab);
|
|
|
|
|
|
laserGo.name = $"ZhanShenLaser_{index}";
|
|
|
|
|
|
line = laserGo.GetComponent<LineRenderer>();
|
|
|
|
|
|
if (line == null)
|
|
|
|
|
|
line = laserGo.AddComponent<LineRenderer>();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// ⭐ prefab 为空:直接新建对象 + LineRenderer
|
|
|
|
|
|
laserGo = new GameObject($"ZhanShenLaser_{index}");
|
|
|
|
|
|
line = laserGo.AddComponent<LineRenderer>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 公共配置:两个端点
|
|
|
|
|
|
line.positionCount = Mathf.Max(line.positionCount, 2);
|
|
|
|
|
|
laserGo.transform.SetParent(null); // 世界空间,不随座位/炮台旋转
|
|
|
|
|
|
|
|
|
|
|
|
if (zsData.laserPrefab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 自动创建时应用 TurretData 上的宽度/颜色/材质
|
|
|
|
|
|
line.startWidth = zsData.laserWidth;
|
|
|
|
|
|
line.endWidth = zsData.laserWidth;
|
|
|
|
|
|
line.startColor = zsData.laserColor;
|
|
|
|
|
|
line.endColor = zsData.laserColor;
|
|
|
|
|
|
var shader = Shader.Find("Sprites/Default");
|
|
|
|
|
|
if (shader != null)
|
|
|
|
|
|
line.material = new Material(shader) { color = Color.white };
|
|
|
|
|
|
line.useWorldSpace = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_zhanShenLasers.Add(line);
|
|
|
|
|
|
return line;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>销毁所有战神激光对象(清战神/座位禁用时调用)</summary>
|
|
|
|
|
|
private void DestroyZhanShenLaser()
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < _zhanShenLasers.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_zhanShenLasers[i] != null)
|
|
|
|
|
|
Destroy(_zhanShenLasers[i].gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
_zhanShenLasers.Clear();
|
2026-06-16 19:12:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-02 18:28:50 +08:00
|
|
|
|
/// <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;
|
|
|
|
|
|
|
2026-07-28 18:29:49 +08:00
|
|
|
|
// XJMH 模式:炮台是座位自带子对象,Awake 已初始化引用,不需要加载
|
|
|
|
|
|
if (turretControlMode != TurretControlMode.XJMH)
|
|
|
|
|
|
{
|
|
|
|
|
|
var cannonUsing = GameManager.Instance.user.cannonUsing;
|
|
|
|
|
|
if (cannonUsing != null && int.TryParse(cannonUsing.number, out int num))
|
|
|
|
|
|
LoadTurret(num);
|
2026-07-02 18:28:50 +08:00
|
|
|
|
|
2026-07-28 18:29:49 +08:00
|
|
|
|
var wingUsing = GameManager.Instance.user.wingUsing;
|
|
|
|
|
|
if (wingUsing != null && int.TryParse(wingUsing.number, out int wingNum))
|
|
|
|
|
|
LoadWing(wingNum);
|
2026-08-28 22:33:35 +08:00
|
|
|
|
|
|
|
|
|
|
var godWarUsing = GameManager.Instance.user.godWarUsing;
|
|
|
|
|
|
if (godWarUsing != null && int.TryParse(godWarUsing.number, out int zsNum))
|
|
|
|
|
|
LoadZhanShen(zsNum);
|
2026-07-28 18:29:49 +08:00
|
|
|
|
}
|
2026-07-02 18:28:50 +08:00
|
|
|
|
}
|
2026-07-02 19:09:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 非人机玩家加入时,自动分配为首个房主
|
|
|
|
|
|
if (!isBot)
|
|
|
|
|
|
AssignRoomOwner(this);
|
2026-07-02 18:28:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-16 19:12:37 +08:00
|
|
|
|
/// <summary>房主退出后,将房主身份转移给第一个活跃的非人机玩家</summary>
|
2026-07-25 23:05:24 +08:00
|
|
|
|
/// <remarks>鱼创建已由服务端 OnFishSpawn 统一下发,此处仅维护 roomOwnerSit 标识(用于 bot 控制/AFK 等场景)。</remarks>
|
2026-06-16 19:12:37 +08:00
|
|
|
|
public static void TransferRoomOwner()
|
|
|
|
|
|
{
|
|
|
|
|
|
var list = FindObjectsOfType<SitPosData>();
|
|
|
|
|
|
foreach (var sit in list)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!sit.isBot && sit.gameObject.activeInHierarchy && sit != roomOwnerSit)
|
|
|
|
|
|
{
|
|
|
|
|
|
roomOwnerSit = sit;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>分配房主(首个非人机玩家)</summary>
|
2026-07-25 23:05:24 +08:00
|
|
|
|
/// <remarks>鱼创建已由服务端 OnFishSpawn 统一下发,此处仅维护 roomOwnerSit 标识(用于 bot 控制/AFK 等场景)。</remarks>
|
2026-06-16 19:12:37 +08:00
|
|
|
|
public static void AssignRoomOwner(SitPosData sit)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (roomOwnerSit == null && !sit.isBot)
|
2026-07-14 02:48:54 +08:00
|
|
|
|
{
|
2026-06-16 19:12:37 +08:00
|
|
|
|
roomOwnerSit = sit;
|
2026-07-14 02:48:54 +08:00
|
|
|
|
}
|
2026-06-08 21:23:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 18:29:49 +08:00
|
|
|
|
// ==================== 炮台加载 ====================
|
2026-06-08 21:23:40 +08:00
|
|
|
|
|
2026-07-28 21:11:57 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取炮台的旋转节点。
|
|
|
|
|
|
/// - 若 TurretData.tra_RotationPivot 已赋值 → 用它(精确指定旋转中心)
|
|
|
|
|
|
/// - 否则回退旧逻辑:炮台父节点(Normal 克隆的中间容器 / XJMH 子对象的父节点)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private Transform GetTurretRotationPivot(TurretData turret)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (turret == null) return null;
|
|
|
|
|
|
|
2026-08-27 17:28:19 +08:00
|
|
|
|
// ⭐ 已分离旋转(用 turretRotate 旋转)时:tra_CurrTurret 只当【瞄准/位置锚点】,
|
|
|
|
|
|
// 必须指向固定节点(turretRotate 的父节点),让瞄准用的 tra_CurrTurret.transform.parent
|
|
|
|
|
|
// 不随旋转而变,否则瞄准参考系跟着转就会抖动。旋转由 turretRotate 负责。
|
|
|
|
|
|
if (turretRotate != null)
|
|
|
|
|
|
return turretRotate.parent != null ? turretRotate.parent : turretRotate;
|
|
|
|
|
|
|
2026-07-28 21:11:57 +08:00
|
|
|
|
// 优先用 TurretData 上配置的旋转节点
|
|
|
|
|
|
if (turret.tra_RotationPivot != null)
|
|
|
|
|
|
return turret.tra_RotationPivot;
|
|
|
|
|
|
|
|
|
|
|
|
// 回退:用父节点(兼容旧逻辑)
|
|
|
|
|
|
Transform parent = turret.transform.parent;
|
|
|
|
|
|
return parent != null ? parent : turret.transform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
/// <summary>
|
2026-07-28 18:29:49 +08:00
|
|
|
|
/// 根据炮台编号加载炮台。
|
|
|
|
|
|
/// Normal 模式:从 Addressables 加载预制体并克隆到 turretParent。
|
|
|
|
|
|
/// XJMH 模式:炮台是座位子对象,激活对应编号子对象、停用其他。
|
2026-06-08 21:23:40 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LoadTurret(int turretNumber)
|
2026-07-28 18:29:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (turretControlMode == TurretControlMode.XJMH)
|
|
|
|
|
|
{
|
|
|
|
|
|
SwitchTurretChild(turretNumber);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
LoadTurretNormal(turretNumber);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 普通模式:从 Addressables 加载预制体并克隆到 turretParent。
|
|
|
|
|
|
/// 位置/旋转/缩放保持预制体原始值,不动。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void LoadTurretNormal(int turretNumber)
|
2026-02-09 10:44:38 +08:00
|
|
|
|
{
|
2026-08-28 22:33:35 +08:00
|
|
|
|
// ⭐ 炮台/战神显示二选一:切回普通炮台时清除战神并恢复 turretParent 显示
|
|
|
|
|
|
ClearZhanShen();
|
|
|
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
|
// ⭐ 换炮台时只清活跃子弹(清屏),保留池缓存(同 prefab 能复用)
|
|
|
|
|
|
// 用 ClearActive 而非 ClearAll:ClearAll 会清池导致永远不复用
|
|
|
|
|
|
BulletPool.Instance.ClearActive();
|
|
|
|
|
|
|
2026-02-03 11:35:19 +08:00
|
|
|
|
if (currentTurret != null)
|
2026-06-08 21:23:40 +08:00
|
|
|
|
{
|
|
|
|
|
|
Destroy(currentTurret.gameObject);
|
|
|
|
|
|
currentTurret = null;
|
|
|
|
|
|
}
|
2026-05-02 23:52:48 +08:00
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
currentTurretNumber = turretNumber;
|
2026-05-04 17:21:52 +08:00
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
// 从 TurretResourcesData 拿预制体
|
|
|
|
|
|
GameObject prefab = TurretResourcesData.GetTurretPrefab(turretNumber);
|
|
|
|
|
|
if (prefab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[SitPosData] 炮台预制体未加载: #{turretNumber}");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
// 克隆到父节点
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-07-28 21:11:57 +08:00
|
|
|
|
// 缓存旋转节点:优先用 TurretData.tra_RotationPivot,否则回退父节点
|
|
|
|
|
|
tra_CurrTurret = GetTurretRotationPivot(currentTurret);
|
2026-06-08 21:23:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 绑定状态机入口
|
2026-05-02 23:52:48 +08:00
|
|
|
|
OnStateChange = currentTurret.ChangeState;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
2026-07-16 20:16:05 +08:00
|
|
|
|
// ⭐ 缓存炮台在世界坐标(Z=0平面)的位置,bot AI/锁定检测距离时用
|
|
|
|
|
|
UpdateTurretWorldPos();
|
|
|
|
|
|
|
2026-07-28 18:29:49 +08:00
|
|
|
|
Debug.Log($"[SitPosData] 炮台已加载(Normal): #{turretNumber}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// XJMH 模式:炮台都是座位的子对象,激活对应编号的炮台、停用其他。
|
|
|
|
|
|
/// 不需要 Instantiate/Destroy,只做激活/停用,无需额外处理。
|
|
|
|
|
|
/// 优先从 <see cref="xjmhTurretList"/> 查找,列表为空时回退遍历子对象。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void SwitchTurretChild(int turretNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
BulletPool.Instance.ClearActive();
|
|
|
|
|
|
|
|
|
|
|
|
// 炮台挂载根:turretParent 优先,否则用座位自身
|
|
|
|
|
|
Transform parent = turretParent != null ? turretParent : transform;
|
|
|
|
|
|
|
|
|
|
|
|
// 优先用 Inspector 配置的炮台列表
|
|
|
|
|
|
TurretData found = null;
|
|
|
|
|
|
if (xjmhTurretList != null && xjmhTurretList.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var td in xjmhTurretList)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (td == null) continue;
|
|
|
|
|
|
bool active = td.turretNumber == turretNumber;
|
|
|
|
|
|
td.gameObject.SetActive(active);
|
|
|
|
|
|
if (active) found = td;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 回退:列表未配置时遍历子对象
|
|
|
|
|
|
for (int i = 0; i < parent.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var child = parent.GetChild(i);
|
|
|
|
|
|
var td = child.GetComponent<TurretData>();
|
|
|
|
|
|
if (td == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (td.turretNumber == turretNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
child.gameObject.SetActive(true);
|
|
|
|
|
|
found = td;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
child.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (found == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[SitPosData] XJMH 子对象中未找到炮台 #{turretNumber}");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
currentTurretNumber = turretNumber;
|
|
|
|
|
|
currentTurret = found;
|
2026-07-28 21:11:57 +08:00
|
|
|
|
// 缓存旋转节点:优先用 TurretData.tra_RotationPivot,否则回退父节点
|
|
|
|
|
|
tra_CurrTurret = GetTurretRotationPivot(currentTurret);
|
2026-07-28 18:29:49 +08:00
|
|
|
|
|
|
|
|
|
|
OnStateChange = currentTurret.ChangeState;
|
|
|
|
|
|
UpdateTurretWorldPos();
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[SitPosData] 炮台已切换(XJMH): #{turretNumber}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// XJMH 模式:按列表索引直接切换炮台。
|
|
|
|
|
|
/// 用于扇形切换 UI 中按位置选择炮台,避免再做编号匹配。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="index">xjmhTurretList 中的索引</param>
|
|
|
|
|
|
/// <returns>切换成功返回 true;索引越界或列表为空返回 false</returns>
|
|
|
|
|
|
public bool SwitchTurretByIndex(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (turretControlMode != TurretControlMode.XJMH) return false;
|
|
|
|
|
|
if (xjmhTurretList == null || index < 0 || index >= xjmhTurretList.Count) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var td = xjmhTurretList[index];
|
|
|
|
|
|
if (td == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 命中当前炮台则无需切换
|
|
|
|
|
|
if (td.turretNumber == currentTurretNumber) return true;
|
|
|
|
|
|
|
|
|
|
|
|
SwitchTurretChild(td.turretNumber);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// XJMH 模式:根据当前炮台在 xjmhTurretList 中的索引返回下一个炮台编号。
|
|
|
|
|
|
/// 用于"切换到下一把"的快捷操作。循环切换,列表为空返回 -1。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public int GetNextTurretNumber()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (turretControlMode != TurretControlMode.XJMH) return -1;
|
|
|
|
|
|
if (xjmhTurretList == null || xjmhTurretList.Count == 0) return -1;
|
|
|
|
|
|
|
|
|
|
|
|
int currIdx = xjmhTurretList.FindIndex(td => td != null && td.turretNumber == currentTurretNumber);
|
|
|
|
|
|
if (currIdx < 0) return xjmhTurretList[0].turretNumber;
|
|
|
|
|
|
|
|
|
|
|
|
int nextIdx = (currIdx + 1) % xjmhTurretList.Count;
|
|
|
|
|
|
return xjmhTurretList[nextIdx].turretNumber;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取当前可用的炮台编号列表。
|
|
|
|
|
|
/// Normal 模式:从资源表按类型查询。
|
|
|
|
|
|
/// XJMH 模式:优先从 <see cref="xjmhTurretList"/> 读取,列表为空时回退扫描座位子对象。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public List<int> GetAvailableTurretNumbers()
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = new List<int>();
|
|
|
|
|
|
if (turretControlMode == TurretControlMode.XJMH)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 优先用配置的炮台列表
|
|
|
|
|
|
if (xjmhTurretList != null && xjmhTurretList.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var td in xjmhTurretList)
|
|
|
|
|
|
if (td != null) result.Add(td.turretNumber);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 回退:扫描座位子对象
|
|
|
|
|
|
Transform parent = turretParent != null ? turretParent : transform;
|
|
|
|
|
|
for (int i = 0; i < parent.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var td = parent.GetChild(i).GetComponent<TurretData>();
|
|
|
|
|
|
if (td != null)
|
|
|
|
|
|
result.Add(td.turretNumber);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var list = TurretResourcesData.GetTurretDataByType(1);
|
|
|
|
|
|
if (list != null)
|
|
|
|
|
|
result = list.ConvertAll(d => d.turretNumber);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
2026-06-08 21:23:40 +08:00
|
|
|
|
}
|
2026-05-18 18:39:11 +08:00
|
|
|
|
|
2026-07-16 20:16:05 +08:00
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
/// <summary>根据炮台编号字符串加载。</summary>
|
|
|
|
|
|
public void LoadTurret(string turretNumberStr)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (int.TryParse(turretNumberStr, out int num))
|
|
|
|
|
|
LoadTurret(num);
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
2026-06-29 20:40:30 +08:00
|
|
|
|
// ==================== 翅膀加载(动态克隆) ====================
|
2026-05-02 23:52:48 +08:00
|
|
|
|
|
2026-06-29 20:40:30 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据炮台编号,从 Addressables 加载预制体并克隆到 turretParent。
|
|
|
|
|
|
/// 位置/旋转/缩放保持预制体原始值,不动。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LoadWing(int turretNumber)
|
|
|
|
|
|
{
|
2026-07-28 18:29:49 +08:00
|
|
|
|
// XJMH 模式没有翅膀概念,直接跳过
|
|
|
|
|
|
if (turretControlMode == TurretControlMode.XJMH) return;
|
|
|
|
|
|
|
2026-06-30 01:31:53 +08:00
|
|
|
|
// 先清理已有翅膀(保留预设的 Camera,它是用来渲染翅膀到 RawImage 的)
|
2026-06-29 21:06:36 +08:00
|
|
|
|
for (int i = wingParent.childCount - 1; i >= 0; i--)
|
|
|
|
|
|
{
|
2026-06-30 01:31:53 +08:00
|
|
|
|
var child = wingParent.GetChild(i);
|
|
|
|
|
|
if (child.GetComponent<Camera>() != null)
|
|
|
|
|
|
continue; // 保留 Camera
|
|
|
|
|
|
Destroy(child.gameObject);
|
2026-06-29 21:06:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从 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}");
|
2026-06-29 20:40:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>根据炮台编号字符串加载。</summary>
|
|
|
|
|
|
public void LoadWing(string turretNumberStr)
|
|
|
|
|
|
{
|
2026-06-29 21:06:36 +08:00
|
|
|
|
if (int.TryParse(turretNumberStr, out int num))
|
|
|
|
|
|
LoadWing(num);
|
2026-06-29 20:40:30 +08:00
|
|
|
|
}
|
2026-08-28 22:33:35 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 战神加载(动态克隆,同翅膀) ====================
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据战神编号,加载游戏内使用的战神模型(ZSYX 标签的那个)。
|
|
|
|
|
|
/// 处理方式和翅膀一样:直接克隆到 zhanShenParent、保持预制体原始位置/旋转/缩放,不再管其他的
|
|
|
|
|
|
/// (渲染由座位预设的 Camera + RawImage 完成,显示节点挂在 turretRotate 下会跟随瞄准旋转)。
|
|
|
|
|
|
/// ⭐ 炮台/战神显示二选一:加载战神后隐藏 turretParent(仅游戏内,切换面板 UI 不受影响)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LoadZhanShen(int zhanShenNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
// XJMH 模式没有战神概念,直接跳过(和翅膀一致)
|
|
|
|
|
|
if (turretControlMode == TurretControlMode.XJMH) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 先清理已有战神(保留预设的 Camera,它是用来渲染战神到 RawImage 的)
|
|
|
|
|
|
ClearZhanShenInstance();
|
|
|
|
|
|
|
|
|
|
|
|
// 从 TurretResourcesData 拿游戏内使用的战神预制体
|
|
|
|
|
|
GameObject prefab = TurretResourcesData.GetZhanShenPrefab(zhanShenNumber, ZhanShenUsage.GamePlay);
|
|
|
|
|
|
if (prefab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[SitPosData] 战神使用预制体未加载: #{zhanShenNumber}(检查 ZSYX 标签配置)");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 克隆到 zhanShenParent,保持预制体原始位置/旋转/缩放
|
|
|
|
|
|
GameObject instance = Instantiate(prefab, zhanShenParent, false);
|
|
|
|
|
|
instance.SetActive(true);
|
|
|
|
|
|
currentZhanShen = instance;
|
|
|
|
|
|
currentZhanShenNumber = zhanShenNumber;
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定状态机入口:战神接管开火/待机动画(PlayFire 约定,见 TurretData.PlayWarGodAnimator)
|
|
|
|
|
|
// 同时缓存 TurretData:开火时优先用战神的子弹预制体(bulletPrefab/bulletPrefab_Two)
|
|
|
|
|
|
var zsData = instance.GetComponent<TurretData>();
|
|
|
|
|
|
currentZhanShenData = zsData;
|
|
|
|
|
|
if (zsData != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
zsData.turretNumber = zhanShenNumber;
|
2026-08-29 01:02:09 +08:00
|
|
|
|
zsData.setPosData = this; // 注入座位引用:FireIndex 动态切换(锁定=1激光/非锁定=0子弹)需要查锁定状态
|
2026-08-28 22:33:35 +08:00
|
|
|
|
OnStateChange = zsData.ChangeState;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 炮台/战神显示二选一:显示战神时隐藏普通炮台
|
|
|
|
|
|
if (turretParent != null)
|
|
|
|
|
|
turretParent.gameObject.SetActive(false);
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[SitPosData] 战神已加载: #{zhanShenNumber}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>根据战神编号字符串加载。</summary>
|
|
|
|
|
|
public void LoadZhanShen(string zhanShenNumberStr)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (int.TryParse(zhanShenNumberStr, out int num))
|
|
|
|
|
|
LoadZhanShen(num);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>清除战神并恢复普通炮台显示(炮台/战神显示二选一的另一半)</summary>
|
|
|
|
|
|
public void ClearZhanShen()
|
|
|
|
|
|
{
|
|
|
|
|
|
ClearZhanShenInstance();
|
|
|
|
|
|
currentZhanShenNumber = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复普通炮台显示
|
|
|
|
|
|
if (turretParent != null)
|
|
|
|
|
|
turretParent.gameObject.SetActive(true);
|
|
|
|
|
|
|
|
|
|
|
|
// 状态机入口还给普通炮台(若已加载)
|
|
|
|
|
|
if (currentTurret != null)
|
|
|
|
|
|
OnStateChange = currentTurret.ChangeState;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>只清 zhanShenParent 下的战神实例(保留 Camera),不动炮台显隐</summary>
|
|
|
|
|
|
private void ClearZhanShenInstance()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (zhanShenParent != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = zhanShenParent.childCount - 1; i >= 0; i--)
|
|
|
|
|
|
{
|
|
|
|
|
|
var child = zhanShenParent.GetChild(i);
|
|
|
|
|
|
if (child.GetComponent<Camera>() != null)
|
|
|
|
|
|
continue; // 保留 Camera
|
|
|
|
|
|
Destroy(child.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
currentZhanShen = null;
|
|
|
|
|
|
currentZhanShenData = null;
|
2026-08-29 01:02:09 +08:00
|
|
|
|
// 战神清了激光也没了
|
|
|
|
|
|
DestroyZhanShenLaser();
|
2026-08-28 22:33:35 +08:00
|
|
|
|
}
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
|
|
|
|
|
// 切换状态(GameMainPanel 调用)
|
|
|
|
|
|
// -------------------------
|
|
|
|
|
|
public void ChangeState(TurretState state)
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-05-02 23:52:48 +08:00
|
|
|
|
OnStateChange?.Invoke(state);
|
2026-05-01 06:26:50 +08:00
|
|
|
|
}
|
2026-05-02 23:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
// -------------------------
|
2026-05-04 17:21:52 +08:00
|
|
|
|
// 炮台旋转(GameMainPanel 调用)
|
2026-05-02 23:52:48 +08:00
|
|
|
|
// -------------------------
|
|
|
|
|
|
public void SetRotation(float angle)
|
|
|
|
|
|
{
|
2026-08-29 17:28:00 +08:00
|
|
|
|
// ⭐ 激光发射期间锁定方向:忽略旋转请求(激光沿原方向打到爆奖结束)
|
|
|
|
|
|
if (isLaserLocked) return;
|
|
|
|
|
|
|
2026-05-02 23:52:48 +08:00
|
|
|
|
currentAngle = angle;
|
2026-08-27 17:28:19 +08:00
|
|
|
|
// ⭐ 炮台旋转只作用于 turretRotate(所有需要旋转的父对象),其余不改
|
|
|
|
|
|
if (turretRotate != null)
|
|
|
|
|
|
turretRotate.rotation = Quaternion.Euler(0, 0, angle);
|
2026-05-02 23:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// -------------------------
|
|
|
|
|
|
// 获取发射点(给 GameMainPanel)
|
|
|
|
|
|
// -------------------------
|
|
|
|
|
|
public Vector2 GetBulletStartPos()
|
|
|
|
|
|
{
|
2026-06-08 21:23:40 +08:00
|
|
|
|
return currentTurret != null
|
|
|
|
|
|
? currentTurret.GetBulletStartWorldPos()
|
|
|
|
|
|
: Vector2.zero;
|
2026-05-02 23:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
// ==================== 金币消耗 ====================
|
|
|
|
|
|
|
2026-07-30 23:54:35 +08:00
|
|
|
|
public bool ConsumeGold(int mul = 1, long fireConsumeGold = 0)
|
2026-05-02 23:52:48 +08:00
|
|
|
|
{
|
2026-07-30 23:54:35 +08:00
|
|
|
|
// fireConsumeGold>0: 用服务端返回的该发子弹权威扣费金额
|
|
|
|
|
|
// fireConsumeGold=0: 回退到本地 consumeGoldNum * mul
|
|
|
|
|
|
long useConsume = fireConsumeGold > 0 ? fireConsumeGold : consumeGoldNum * mul;
|
|
|
|
|
|
long temp = goldNum - useConsume;
|
2026-05-02 23:52:48 +08:00
|
|
|
|
if (temp < 0) return false;
|
2026-07-29 17:17:14 +08:00
|
|
|
|
long oldGold = goldNum;
|
2026-05-02 23:52:48 +08:00
|
|
|
|
goldNum = temp;
|
|
|
|
|
|
tMP_GoldNum.text = goldNum.ToString();
|
2026-07-29 22:59:26 +08:00
|
|
|
|
// ⭐ 记录金币流水:已扣钱(开火消耗,本地已执行)
|
2026-07-30 23:54:35 +08:00
|
|
|
|
GameManager.LogGoldFlowAppliedCost("Fire", $"开火消耗 原有金币={oldGold} 消耗金币=-{useConsume} 剩余金币={goldNum} 座位={posID} 房间={GameManager.Instance.gameData?.currRoomData?.roomId}");
|
2026-05-02 23:52:48 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-06-08 21:23:40 +08:00
|
|
|
|
|
|
|
|
|
|
int indexConsume = 0;
|
|
|
|
|
|
|
2026-07-19 18:24:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据后端下发的炮倍值同步 indexConsume 到 paoBeiList 对应档位,
|
|
|
|
|
|
/// 避免加减炮倍时从 0 档开始导致跳变。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void SyncIndexByConsume(long consumeGold)
|
|
|
|
|
|
{
|
|
|
|
|
|
var list = CreateFishManager.Instance?.currGameType?.paoBeiList;
|
|
|
|
|
|
if (list == null || list.Count == 0) return;
|
|
|
|
|
|
int idx = list.IndexOf((int)consumeGold);
|
|
|
|
|
|
indexConsume = idx >= 0 ? idx : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 19:47:06 +08:00
|
|
|
|
public void ChangeTurretAddConsumeNum()
|
2026-05-01 06:26:50 +08:00
|
|
|
|
{
|
2026-06-08 21:23:40 +08:00
|
|
|
|
if (CreateFishManager.Instance.currGameType.paoBeiList.Count - 1 > indexConsume)
|
2026-05-27 19:47:06 +08:00
|
|
|
|
indexConsume++;
|
|
|
|
|
|
else
|
|
|
|
|
|
indexConsume = 0;
|
2026-06-16 02:28:58 +08:00
|
|
|
|
AudioManager.Instance.PlayerFX(62011);
|
2026-06-08 21:23:40 +08:00
|
|
|
|
|
2026-07-30 23:54:35 +08:00
|
|
|
|
// ⭐ 不立即修改 consumeGoldNum 和 UI,等服务端 ChangeGunMultipleNotice 确认后再应用
|
|
|
|
|
|
// 避免:客户端先切换炮倍 → 开火按新炮倍扣费 → 服务端还没确认按旧炮倍扣费 → 差值
|
|
|
|
|
|
int newConsume = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume];
|
|
|
|
|
|
string roomId = GameManager.Instance.gameData?.currRoomData?.roomId;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(roomId))
|
|
|
|
|
|
WebSocketSendList.ChangeGunMultiple(roomId, user, newConsume);
|
2026-05-01 06:26:50 +08:00
|
|
|
|
}
|
2026-05-02 23:52:48 +08:00
|
|
|
|
|
2026-05-27 19:47:06 +08:00
|
|
|
|
public void ChangeTurretMinusConsumeNum()
|
2026-05-01 06:26:50 +08:00
|
|
|
|
{
|
2026-06-08 21:23:40 +08:00
|
|
|
|
if (indexConsume > 0)
|
2026-05-27 19:47:06 +08:00
|
|
|
|
indexConsume--;
|
|
|
|
|
|
else
|
|
|
|
|
|
indexConsume = CreateFishManager.Instance.currGameType.paoBeiList.Count - 1;
|
2026-06-16 02:28:58 +08:00
|
|
|
|
AudioManager.Instance.PlayerFX(62010);
|
2026-06-08 21:23:40 +08:00
|
|
|
|
|
2026-07-30 23:54:35 +08:00
|
|
|
|
// ⭐ 不立即修改 consumeGoldNum 和 UI,等服务端 ChangeGunMultipleNotice 确认后再应用
|
|
|
|
|
|
// 避免:客户端先切换炮倍 → 开火按新炮倍扣费 → 服务端还没确认按旧炮倍扣费 → 差值
|
|
|
|
|
|
int newConsume = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume];
|
|
|
|
|
|
string roomId = GameManager.Instance.gameData?.currRoomData?.roomId;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(roomId))
|
|
|
|
|
|
WebSocketSendList.ChangeGunMultiple(roomId, user, newConsume);
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
2026-05-04 17:21:52 +08:00
|
|
|
|
|
2026-06-08 21:23:40 +08:00
|
|
|
|
// ==================== 锁定系统 ====================
|
|
|
|
|
|
|
2026-05-04 17:21:52 +08:00
|
|
|
|
public void SetLockTarget(MonsterContral monster, Transform hitPos)
|
|
|
|
|
|
{
|
|
|
|
|
|
monCotl_Lock = monster;
|
|
|
|
|
|
monCotl_Lock_Position = hitPos;
|
|
|
|
|
|
isLock = monster != null;
|
|
|
|
|
|
lockFX.SetActive(isLock);
|
2026-06-21 00:28:59 +08:00
|
|
|
|
|
|
|
|
|
|
// 非三发锁定 → 隐藏额外图标
|
|
|
|
|
|
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();
|
2026-05-04 17:21:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ClearLock()
|
|
|
|
|
|
{
|
|
|
|
|
|
monCotl_Lock = null;
|
|
|
|
|
|
monCotl_Lock_Position = null;
|
|
|
|
|
|
isLock = false;
|
2026-07-31 17:57:24 +08:00
|
|
|
|
// ⭐ 清除锁定时重置手动锁定标记
|
|
|
|
|
|
isManualLock = false;
|
2026-05-04 17:21:52 +08:00
|
|
|
|
lockFX.SetActive(false);
|
2026-06-21 00:28:59 +08:00
|
|
|
|
HideTripleLockIcons();
|
2026-05-27 19:47:06 +08:00
|
|
|
|
ChangeState(TurretState.Idle);
|
2026-05-04 17:21:52 +08:00
|
|
|
|
}
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|