This commit is contained in:
2026-08-31 16:40:04 +08:00
parent f9d08ef07d
commit 66385fc151
38 changed files with 2395 additions and 319 deletions
+268 -16
View File
@@ -58,6 +58,13 @@ public class SitPosData : MonoBehaviour
public bool isDrillActive;
[Tooltip("激光锁定:true=激光发射期间炮台锁定方向,SetRotation 不生效(原方向打到爆奖结束)")]
public bool isLaserLocked;
/// <summary>
/// 散射激光模式中(GameMainPanel 每帧刷新:三发+锁定+已锁目标)。
/// ⭐ 激活左右起点球父对象用这个标记,不依赖实际锁定点数量(目标不足2个时散射模式开着也要显示起点球)
/// </summary>
[System.NonSerialized]
public bool isScatterLaserOn;
// -------------------------
// 炮台管理(动态克隆)
// -------------------------
@@ -92,12 +99,14 @@ public class SitPosData : MonoBehaviour
// - laserPrefab 非空 → 预制体形态:JiGuangControl 工具类驱动(克隆激光预制体,方向/长度每帧刷新)
// - laserPrefab 为空 → LineRenderer 兜底形态:直接新建 GameObject + LineRenderer
// -------------------------
/// <summary>LineRenderer 形态激光列表(起点=子弹创建位置,终点=锁定鱼的位置)。三发锁定=3条</summary>
/// <summary>LineRenderer 形态激光列表。三发锁定=3条。起点:0=主激光(座位点+selfOffsetJGStart),1/2=散射左右(JGFX父对象位置)</summary>
private readonly List<LineRenderer> _zhanShenLasers = new List<LineRenderer>();
/// <summary>预制体形态激光实例列表(世界空间,由 JiGuangControl.JiGuangCreate 按目标数量重建)</summary>
private readonly List<Transform> _laserFx = new List<Transform>();
/// <summary>预制体形态激光的基准缩放(重建时取自 laserPrefab.transform.localScale)</summary>
private Vector3 _laserFxBaseScale = Vector3.one;
/// <summary>每条激光的起点缓存(索引与 _laserTargets 对齐,每帧复用避免 GC)</summary>
private readonly List<Vector3> _laserStarts = new List<Vector3>();
// -------------------------
// XJMH 炮台列表(星际迷航专用)
@@ -154,7 +163,9 @@ public class SitPosData : MonoBehaviour
public Button btn_TurretMinus;
[Header("激光效果父对象")]
[Tooltip("散射激光左起点球的父对象:出场/换战神时克隆 ZhanShenTurretData.JGQiuStartPrefab 到它下面,散射激光激活时显示。不拖也没关系:Awake 会自动创建兜底节点")]
public Transform JGFXLeftParent;
[Tooltip("散射激光右起点球的父对象:出场/换战神时克隆 ZhanShenTurretData.JGQiuStartPrefab 到它下面,散射激光激活时显示。不拖也没关系:Awake 会自动创建兜底节点")]
public Transform JGFXRightParent;
private void Awake()
@@ -164,6 +175,10 @@ public class SitPosData : MonoBehaviour
if (btn_TurretMinus != null)
btn_TurretMinus.onClick.AddListener(() => ChangeTurretMinusConsumeNum());
// ⭐ 散射起点球父对象兜底:预制体没拖引用就自动创建(挂 beij 下,炮台两侧),
// 保证 UpdateJGFXParentsActive 永远有对象可激活,不依赖 Inspector 配置
EnsureJGFXParents();
// XJMH 模式:隐藏钻石显示(只显示金币)
if (turretControlMode == TurretControlMode.XJMH && tMP_DiamondNum != null)
tMP_DiamondNum.gameObject.SetActive(false);
@@ -174,6 +189,39 @@ public class SitPosData : MonoBehaviour
InitXJMHTurret();
}
/// <summary>
/// 散射起点球父对象兜底:Inspector 没拖引用时自动创建两个 RectTransform
/// (挂在 turretRotate 的父节点 beij 下,炮台左右两侧 ±50),并写回字段。
/// 拖了引用(预制体配置的座位)则什么都不做。
/// </summary>
private void EnsureJGFXParents()
{
if (JGFXLeftParent != null && JGFXRightParent != null) return;
// 挂载点:turretRotate 的父节点(beij);拿不到就挂座位根
Transform attach = turretRotate != null && turretRotate.parent != null
? turretRotate.parent
: transform;
if (JGFXLeftParent == null)
JGFXLeftParent = CreateJGFXParent(attach, "JGFXLeftParent", -50f);
if (JGFXRightParent == null)
JGFXRightParent = CreateJGFXParent(attach, "JGFXRightParent", 50f);
}
/// <summary>创建一个散射起点球父节点(beij 下,x 偏移 ±50、y 100,默认隐藏)</summary>
private static RectTransform CreateJGFXParent(Transform parent, string name, float xOffset)
{
GameObject go = new GameObject(name, typeof(RectTransform));
RectTransform rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
rt.anchorMin = rt.anchorMax = new Vector2(0.5f, 0.5f);
rt.sizeDelta = new Vector2(44f, 37f);
rt.anchoredPosition = new Vector2(xOffset, 100f);
go.SetActive(false);
return rt;
}
/// <summary>
/// XJMH 模式:炮台是座位自带子对象,预制体里已摆好激活状态,不需要 LoadTurret 加载。
/// 此方法只做引用初始化:从 <see cref="xjmhTurretList"/> 取第一个激活的炮台赋给 currentTurret,
@@ -234,6 +282,11 @@ public class SitPosData : MonoBehaviour
}
// 座位禁用时清理战神激光(世界空间对象,不随座位隐藏)
DestroyZhanShenLaser();
// 散射起点父对象藏起来(子对象保留,下次出场/换战神时重建)
if (JGFXLeftParent != null)
JGFXLeftParent.gameObject.SetActive(false);
if (JGFXRightParent != null)
JGFXRightParent.gameObject.SetActive(false);
}
private void Update()
@@ -244,8 +297,12 @@ public class SitPosData : MonoBehaviour
// ==================== 战神激光(锁定时显示) ====================
/// <summary>
/// 战神锁定时显示激光:起点=创建子弹的位置(GetBulletStartPos),终点=锁定鱼的位置,Z 统一压到 -50 显示层。
/// 战神锁定时显示激光,Z 统一压到 -50 显示层。
/// ⭐ 一个锁定点一条激光:单发锁定=1条,三发锁定=3条(主目标+2个三发目标)。
/// ⭐ 每条激光独立起点:第0条(主锁定)=turretRotate 子对象偏移 selfOffsetJGStart(⭐跟随炮台旋转),
/// 第1/2条(散射左右)=JGFXLeftParent/JGFXRightParent 的位置(座位 UI 上的起点球父对象)。
/// ⭐ 使用散射激光时激活 JGFXLeftParent/JGFXRightParent 显示起点球,否则隐藏:
/// 本地座位=三发+锁定开着(isScatterLaserOn,面板每帧刷新);远程/bot座位=锁定点>1(三发目标凑满)。
/// ⭐ 两种形态:laserPrefab 非空 → 预制体形态(JiGuangControl 工具类);为空 → LineRenderer 兜底形态。
/// ⭐ 只是视觉表现:打中鱼仍按正常攻击频率由子弹完成(HandleAutoFire 以 attackTime 发射锁定子弹)。
/// 普通炮台没有此效果(currentZhanShenData 为 null 时不显示)。
@@ -274,6 +331,15 @@ public class SitPosData : MonoBehaviour
}
}
// ⭐ 散射激光使用中就激活左右起点父对象显示起点球:
// isScatterLaserOn=三发+锁定开着(面板每帧刷新,本地座位);锁定点>1=三发目标已凑满(远程/bot座位)
// 两个条件满足其一即激活——散射模式开着但目标不足时也要显示起点球
UpdateJGFXParentsActive(currentZhanShenData != null && (_laserTargets.Count > 1 || isScatterLaserOn));
#if UNITY_EDITOR
DebugJGFXState();
#endif
// 没有锁定点:隐藏所有激光
if (_laserTargets.Count == 0)
{
@@ -306,43 +372,42 @@ public class SitPosData : MonoBehaviour
/// <summary>
/// 预制体形态(laserPrefab 非空):JiGuangControl 工具类驱动。
/// 目标数量变化时全量重建(JiGuangCreate),每帧刷新位置/朝向/长度(JiGuangDirection/JiGuangLength,Z 强制 -50)。
/// ⭐ 每条激光独立起点(多起点重载):0=主锁定(座位点+偏移),1/2=散射左右(JGFX父对象位置)。
/// </summary>
private void UpdateZhanShenLaserFx()
{
var zsData = currentZhanShenData;
// 起点:创建子弹的位置(Z=-50,战神激光显示层,和 FireBullet 的 startPos 同源仅 Z 不同)
Vector3 startPos = GetBulletStartPos();
startPos.z = -50f;
// 收集每条激光的起点(与 _laserTargets 一一对应)
_laserStarts.Clear();
for (int i = 0; i < _laserTargets.Count; i++)
_laserStarts.Add(GetZhanShenLaserStart(i));
// 目标数量变化时全量重建(锁定建立/解除/三发目标增减时触发),并刷新基准缩放
if (_laserFx.Count != _laserTargets.Count)
{
_laserFxBaseScale = zsData.laserPrefab.transform.localScale;
JiGuangControl.JiGuangCreate(zsData.laserPrefab, startPos, _laserTargets, _laserFx, null, -50f);
JiGuangControl.JiGuangCreate(zsData.laserPrefab, _laserStarts, _laserTargets, _laserFx, null, -50f);
}
// 炮口随炮台旋转会移动,位置需每帧同步;隐藏中的重新激活
// 起点需每帧同步(座位布局/起点球位置变化);隐藏中的重新激活
for (int i = 0; i < _laserFx.Count; i++)
{
if (_laserFx[i] == null) continue;
if (!_laserFx[i].gameObject.activeSelf)
_laserFx[i].gameObject.SetActive(true);
_laserFx[i].position = startPos;
if (i < _laserStarts.Count)
_laserFx[i].position = _laserStarts[i];
}
// 朝向 + 长度(指定 Z=-50 的重载:起终点同层)
JiGuangControl.JiGuangDirection(startPos, _laserTargets, _laserFx, -50f);
JiGuangControl.JiGuangLength(startPos, _laserTargets, _laserFx, _laserFxBaseScale, zsData.laserLengthCorrect, -50f);
// 朝向 + 长度(多起点重载:起终点同层 Z=-50)
JiGuangControl.JiGuangDirection(_laserStarts, _laserTargets, _laserFx, -50f);
JiGuangControl.JiGuangLength(_laserStarts, _laserTargets, _laserFx, _laserFxBaseScale, zsData.laserLengthCorrect, -50f);
}
/// <summary>LineRenderer 兜底形态(laserPrefab 为空):按需创建、多余的隐藏</summary>
/// <summary>LineRenderer 兜底形态(laserPrefab 为空):按需创建、多余的隐藏。每条激光独立起点</summary>
private void UpdateZhanShenLaserLine()
{
// 起点:创建子弹的位置(Z=-50,战神激光显示层,和 FireBullet 的 startPos 同源仅 Z 不同)
Vector3 start = GetBulletStartPos();
start.z = -50f;
// 需要几条就创建几条(三发=3个 LineRenderer),多余的隐藏
for (int i = 0; i < _laserTargets.Count; i++)
{
@@ -351,6 +416,8 @@ public class SitPosData : MonoBehaviour
if (!line.gameObject.activeSelf)
line.gameObject.SetActive(true);
// 起点:第0条=主锁定(座位点+偏移),1/2=散射左右(JGFX父对象位置),Z=-50
Vector3 start = GetZhanShenLaserStart(i);
// 终点:该条锁定鱼的位置(Z=-50,与起点同层)
Vector3 end = _laserTargets[i].position;
end.z = -50f;
@@ -412,6 +479,186 @@ public class SitPosData : MonoBehaviour
_laserFx.Clear();
}
// ==================== 战神激光起点(每条激光独立起点) ====================
/// <summary>
/// 战神激光第 index 条的起点(鱼群世界坐标,Z=-50 显示层):
/// - 第 0 条(主锁定):turretRotate 子对象偏移(selfOffsetJGStart 是 turretRotate 的本地偏移,
/// ⭐ 跟随炮台旋转:炮口转向时起点绕炮台中心跟着转)
/// - 第 1 条(散射左):JGFXLeftParent 的位置
/// - 第 2 条(散射右):JGFXRightParent 的位置
/// - 越界/未配置:回退子弹炮口位置(旧版行为)
/// </summary>
private Vector3 GetZhanShenLaserStart(int index)
{
var zs = currentZhanShenData as ZhanShenTurretData;
// 主起点:turretRotate 的子对象偏移(炮台旋转时该点跟着转,TransformPoint 含旋转+缩放)
if (index == 0 && zs != null)
{
Vector3 offset = new Vector3(zs.selfOffsetJGStart.x, zs.selfOffsetJGStart.y, 0f);
Vector3 startPoint = turretRotate != null
? turretRotate.TransformPoint(offset)
: transform.position + offset;
return SeatUIToFishWorld(startPoint, -50f);
}
// 散射左右起点:座位上左右两个起点球父对象的位置
if (index == 1 && JGFXLeftParent != null)
return SeatUIToFishWorld(JGFXLeftParent.position, -50f);
if (index == 2 && JGFXRightParent != null)
return SeatUIToFishWorld(JGFXRightParent.position, -50f);
// 回退:子弹炮口(旧版行为,未配置左右父对象/未挂 ZhanShenTurretData 时)
Vector3 fallback = GetBulletStartPos();
fallback.z = -50f;
return fallback;
}
/// <summary>
/// 座位 UI 上的世界坐标(座位根节点/座位子节点位置)→ 鱼群世界坐标(子弹/激光所在空间)。
/// 与 TurretData.GetBulletStartWorldPos 同一套换算(UI→屏幕→世界),Z 压到指定显示层。
/// </summary>
private Vector3 SeatUIToFishWorld(Vector3 uiWorldPos, float z)
{
Canvas canvas = UIManager.Instance != null && UIManager.Instance.uiRoot != null
? UIManager.Instance.uiRoot.GetComponent<Canvas>()
: null;
Camera cam = Camera.main;
if (canvas == null || cam == null)
{
// 编辑器没跑起来时兜底:直接用 UI 坐标压 Z
Vector3 fallback = uiWorldPos;
fallback.z = z;
return fallback;
}
Vector3 screenPos = RectTransformUtility.WorldToScreenPoint(
canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera,
uiWorldPos);
float distance = Mathf.Abs(cam.transform.position.z - 0f);
Vector3 worldPos = cam.ScreenToWorldPoint(new Vector3(screenPos.x, screenPos.y, distance));
worldPos.z = z;
return worldPos;
}
/// <summary>
/// 散射激光激活/隐藏左右起点父对象(JGFXLeftParent/JGFXRightParent):
/// 使用散射激光(三发+锁定开着,或锁定点>1)时激活显示起点球,关闭/单发时隐藏。
/// </summary>
private void UpdateJGFXParentsActive(bool scatterActive)
{
if (JGFXLeftParent != null && JGFXLeftParent.gameObject.activeSelf != scatterActive)
JGFXLeftParent.gameObject.SetActive(scatterActive);
if (JGFXRightParent != null && JGFXRightParent.gameObject.activeSelf != scatterActive)
JGFXRightParent.gameObject.SetActive(scatterActive);
}
#if UNITY_EDITOR
/// <summary>散射起点诊断计时器(编辑器排查用,3秒一条,只在战神+锁定期间打印)</summary>
private float _jgfxLogTimer = -99f;
/// <summary>
/// 编辑器诊断:锁定期间每3秒打印散射起点球的判定条件。
/// 用来定位「三发+锁定开了但左右父对象不激活」断在哪个环节:
/// 三发目标数 / 激光条数 / 散射标记 / 左右父对象引用是否拖了 / 当前激活状态。
/// </summary>
private void DebugJGFXState()
{
if (currentZhanShenData == null || !isLock) return;
if (Time.unscaledTime - _jgfxLogTimer < 3f) return;
_jgfxLogTimer = Time.unscaledTime;
Debug.Log($"[SitPosData] 座位{posID} 散射起点诊断: " +
$"三发目标数={tripleLockTargets.Count} 激光条数={_laserTargets.Count} " +
$"散射标记={isScatterLaserOn} " +
$"左父对象={(JGFXLeftParent != null ? "已拖" : "NULL!")} " +
$"右父对象={(JGFXRightParent != null ? "已拖" : "NULL!")} " +
$"左激活={(JGFXLeftParent != null && JGFXLeftParent.gameObject.activeSelf)} " +
$"左子对象数={(JGFXLeftParent != null ? JGFXLeftParent.childCount : -1)}");
}
#endif
/// <summary>
/// 出场/换战神时刷新散射激光起点球:
/// 先清空 JGFXLeftParent/JGFXRightParent 的旧子对象,再各自克隆一份
/// ZhanShenTurretData.JGQiuStartPrefab(保持预制体原始位置/旋转/缩放)。
/// 没配置 JGQiuStartPrefab/组件就只清空并打警告;父对象默认隐藏,散射激光激活时由 UpdateZhanShenLaser 打开。
/// </summary>
private void RefreshJGQiuStartFX()
{
var zs = currentZhanShenData as ZhanShenTurretData;
ClearJGFXParents();
if (zs == null)
{
Debug.LogWarning($"[SitPosData] 战神 #{currentZhanShenNumber} 未挂 ZhanShenTurretData 组件,散射起点球无法创建(座位 {posID})");
return;
}
if (zs.JGQiuStartPrefab == null)
{
Debug.LogWarning($"[SitPosData] 战神 #{currentZhanShenNumber} 的 ZhanShenTurretData.JGQiuStartPrefab 未配置,散射起点球无法创建(座位 {posID})——请在战神预制体上拖入起点球预制体");
return;
}
// 未配置父对象就不克隆(避免起点球落到场景根下),并提示去座位预制体上拖引用
if (JGFXLeftParent == null || JGFXRightParent == null)
Debug.LogWarning($"[SitPosData] 座位{posID} 的 JGFXLeftParent/JGFXRightParent 未拖引用——散射起点球无处挂载,请在座位预制体上把左右起点球父对象拖进 SitPosData(座位 {posID} 战神 #{currentZhanShenNumber})");
if (JGFXLeftParent != null)
Instantiate(zs.JGQiuStartPrefab, JGFXLeftParent, false);
if (JGFXRightParent != null)
Instantiate(zs.JGQiuStartPrefab, JGFXRightParent, false);
}
/// <summary>清空左右起点父对象的子对象并隐藏(清战神/重装起点球前调用)</summary>
private void ClearJGFXParents()
{
ClearChildren(JGFXLeftParent);
ClearChildren(JGFXRightParent);
if (JGFXLeftParent != null)
JGFXLeftParent.gameObject.SetActive(false);
if (JGFXRightParent != null)
JGFXRightParent.gameObject.SetActive(false);
}
/// <summary>清空一个父节点下的所有子对象</summary>
private static void ClearChildren(Transform parent)
{
if (parent == null) return;
for (int i = parent.childCount - 1; i >= 0; i--)
Destroy(parent.GetChild(i).gameObject);
}
#if UNITY_EDITOR
/// <summary>
/// 编辑器可视化:战神主激光起点(turretRotate 子对象偏移 selfOffsetJGStart,⭐ 跟随炮台旋转)。
/// 运行中加载战神后在 Scene 视图可见(旋转炮台时球会绕炮台中心跟着转):
/// 黄色小球=turretRotate 位置(偏移基准),洋红线框球=主激光起点,连线=偏移方向。
/// </summary>
private void OnDrawGizmos()
{
var zs = currentZhanShenData as ZhanShenTurretData;
if (zs == null) return;
// 主起点:turretRotate 的本地偏移(跟随旋转,与 GetZhanShenLaserStart(0) 同一套计算)
Vector3 offset = new Vector3(zs.selfOffsetJGStart.x, zs.selfOffsetJGStart.y, 0f);
Vector3 basePoint = turretRotate != null ? turretRotate.position : transform.position;
Vector3 startPoint = turretRotate != null
? turretRotate.TransformPoint(offset)
: basePoint + offset;
// turretRotate 位置(偏移基准)
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(basePoint, 8f);
// 主激光起点 + 基准点连线(显示偏移关系)
Gizmos.color = Color.magenta;
Gizmos.DrawWireSphere(startPoint, 20f);
Gizmos.DrawLine(basePoint, startPoint);
UnityEditor.Handles.Label(startPoint + Vector3.up * 25f, "主激光起点(跟随炮台旋转)");
}
#endif
/// <summary>
/// 玩家加入 → 显示玩家 UI(child0),隐藏等待界面(child1)。
/// </summary>
@@ -848,6 +1095,9 @@ public class SitPosData : MonoBehaviour
OnStateChange = zsData.ChangeState;
}
// ⭐ 出场/换战神:清空左右起点父对象并重新克隆 JGQiuStartPrefab(当前战神配置的起点球)
RefreshJGQiuStartFX();
// ⭐ 炮台/战神显示二选一:显示战神时隐藏普通炮台
if (turretParent != null)
turretParent.gameObject.SetActive(false);
@@ -894,6 +1144,8 @@ public class SitPosData : MonoBehaviour
currentZhanShenData = null;
// 战神清了激光也没了
DestroyZhanShenLaser();
// 散射起点球也清掉(下次出场/换战神时重建)
ClearJGFXParents();
}
// -------------------------
// 切换状态(GameMainPanel 调用)