Files
5.24BuYu/Assets/MyGame/aa/Game/FLZY/TSY/JiGuang/SpecialLaserMove.cs
T
2026-08-28 18:57:36 +08:00

182 lines
7.9 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 激光控制脚本(激光蟹专用,与钻头蟹的 SpecialBulletMove 同级对应——控制"发射物")。
///
/// ⭐ 激光特效是【炮台预制体里的子节点】(随炮台摆好位置/朝向,跟随 turretRotate 旋转):
/// 不克隆特效,直接在生成的炮台实例里找到节点 SetActive 激活/关闭。
///
/// 两种形态的激活时序(Inspector 拖炮台里的 4 个特效节点,或用默认名自动找):
/// 蓝:LaserBlue_hit 激活 0.5s → 关 → LaserBlue 激活持续 N 秒(期间扫鱼)
/// 紫:Laserpurple_hit 激活 0.5s → 关 → Laserpurple 激活持续 N 秒(期间扫鱼)
/// 两种形态跑完 → 销毁自身(控制器检测到走 onFinished 结算)。
///
/// 扫鱼:每帧沿炮台瞄准方向(长×宽 OverlapBox),扫到的普通鱼全部死亡
/// (帧同步由创建方回调注入),同一条鱼只杀一次。
/// </summary>
public class SpecialLaserMove : MonoBehaviour
{
[Header("激光节点(从炮台预制体里拖;留空则按默认名在炮台实例里自动找)")]
[Tooltip("蓝色形态 hit 节点(先激活 hitTime 秒)")]
public GameObject laserBlueHit;
[Tooltip("蓝色形态主激光(hit 播完激活,持续 laserDuration 秒)")]
public GameObject laserBlue;
[Tooltip("紫色形态 hit 节点(蓝色结束后先激活 hitTime 秒)")]
public GameObject laserPurpleHit;
[Tooltip("紫色形态主激光(hit 播完激活,持续 laserDuration 秒)")]
public GameObject laserPurple;
[Header("激光参数")]
[Tooltip("hit 节点播放时长(秒)")]
public float hitTime = 0.5f;
[Tooltip("每种形态主激光持续时长(秒),期间扫到就杀")]
public float laserDuration = 3f;
[Tooltip("激光扫鱼判定宽度(世界单位)")]
public float laserWidth = 1.5f;
[Tooltip("杀鱼的物理层(默认取 Fish 层)")]
public LayerMask fishLayer;
/// <summary>杀鱼回调(创建方设置:Id_DieFish_FX 帧同步加分)</summary>
public System.Action<MonsterContral> onHitFish;
/// <summary>激光全部跑完回调(蓝+紫两形态结束):由创建方接(销毁等待用的 holder 等)</summary>
public System.Action onFinished;
private readonly HashSet<string> _killed = new HashSet<string>();
private SitPosData _sit; // 击杀者座位
private Transform _turret; // 生成的激光炮台实例(激光节点的父物体)
/// <summary>
/// 初始化并开始激光。
/// </summary>
/// <param name="sit">击杀者座位(扫射方向=turretRotate 朝向,起点=炮台子弹起点)</param>
/// <param name="turret">生成的激光炮台实例(激光特效节点在其中)</param>
/// <param name="hitFish">杀鱼回调(帧同步逻辑由创建方注入)</param>
public void Init(SitPosData sit, Transform turret, System.Action<MonsterContral> hitFish)
{
_sit = sit;
_turret = turret;
onHitFish = hitFish;
_killed.Clear();
if (fishLayer.value == 0) fishLayer = LayerMask.GetMask("Fish");
// Inspector 拖的引用是预制体资源上的节点 → 运行时要在【炮台实例】里找同名节点激活
laserBlueHit = ResolveNode(laserBlueHit, _turret);
laserBlue = ResolveNode(laserBlue, _turret);
laserPurpleHit = ResolveNode(laserPurpleHit, _turret);
laserPurple = ResolveNode(laserPurple, _turret);
StartCoroutine(LaserRoutine());
}
/// <summary>
/// 把预制体资源引用解析成炮台实例里的同名节点:
/// 拖的引用非炮台实例子物体时,按节点名在实例里深度查找(含未激活节点)。
/// </summary>
private GameObject ResolveNode(GameObject dragged, Transform turret)
{
if (dragged == null || turret == null) return dragged;
if (dragged.transform.IsChildOf(turret)) return dragged; // 已经是实例里的节点
// 拖的是预制体资源节点 → 在炮台实例里深度找同名节点(含未激活)
foreach (var t in turret.GetComponentsInChildren<Transform>(true))
{
if (t.name == dragged.name) return t.gameObject;
}
Debug.LogWarning($"[SpecialLaserMove] 炮台实例里未找到节点 {dragged.name},将直接激活拖入的引用");
return dragged;
}
private IEnumerator LaserRoutine()
{
// ===== 形态1:蓝色(hit → 主激光)=====
yield return PlayForm(laserBlueHit, laserBlue);
// ===== 形态2:紫色(hit → 主激光)=====
yield return PlayForm(laserPurpleHit, laserPurple);
// ===== 两种形态跑完 → 回调(创建方销毁等待 holder / 走结算)=====
// ⭐ 不销毁自身:本组件通常挂在炮台预制体上,炮台由事件流程统一销毁
onFinished?.Invoke();
}
/// <summary>播放一种形态:hit 节点激活 hitTime 秒 → 关 → 主激光激活 laserDuration 秒(期间扫鱼)</summary>
private IEnumerator PlayForm(GameObject hitObj, GameObject laserObj)
{
// 1. hit 节点:激活播 hitTime 秒
SetActive(hitObj, true);
yield return new WaitForSeconds(hitTime);
SetActive(hitObj, false);
// 2. 主激光:激活持续 laserDuration 秒,期间每帧扫鱼(节点在炮台里,自动跟随瞄准旋转)
SetActive(laserObj, true);
float fireEnd = Time.time + laserDuration;
while (Time.time < fireEnd)
{
SweepKillFish();
yield return null;
}
SetActive(laserObj, false);
}
/// <summary>沿炮台当前瞄准方向扫一条宽激光,范围内普通鱼全部击杀(同一条只杀一次)</summary>
private void SweepKillFish()
{
if (_sit == null || onHitFish == null) return;
// 起点:炮台子弹起点(3D 世界坐标)
Vector3 origin = _sit.currentTurret != null
? (Vector3)_sit.currentTurret.GetBulletStartWorldPos()
: (Vector3)_sit.GetBulletStartPos();
origin.z = 0;
// 朝向:turretRotate 的"上"(激光节点挂在炮台里,跟随一起旋转)
Vector2 dir = _sit.turretRotate != null
? (Vector2)(_sit.turretRotate.rotation * Vector3.up)
: Quaternion.Euler(0, 0, _sit.currentAngle) * Vector2.up;
if (dir.sqrMagnitude < 0.0001f) return;
dir.Normalize();
// 扫射长度=屏幕对角线(3D 不放特效,只有碰撞判定;特效在炮台里显示)
float len = ScreenDiagonal();
Vector2 center = origin + (Vector3)(dir * (len * 0.5f));
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
var hits = Physics2D.OverlapBoxAll(center, new Vector2(len, laserWidth), angle, fishLayer);
foreach (var h in hits)
{
if (!h.CompareTag("Fish")) continue;
var fish = h.GetComponentInParent<MonsterContral>();
if (fish == null || fish.isDead) continue;
if (!_killed.Add(fish.guid)) continue; // 同一条鱼只杀一次
onHitFish(fish);
}
}
/// <summary>屏幕对角线长度(激光默认长度,保证横穿屏幕)</summary>
private float ScreenDiagonal()
{
Camera cam = Camera.main;
if (cam == null || !cam.orthographic) return 40f;
float h = cam.orthographicSize * 2f;
float w = h * cam.aspect;
return Mathf.Sqrt(h * h + w * w);
}
private static void SetActive(GameObject obj, bool active)
{
if (obj != null) obj.SetActive(active);
}
private void OnDestroy()
{
StopAllCoroutines();
// 兜底:意外销毁时把节点全部复位
SetActive(laserBlueHit, false);
SetActive(laserBlue, false);
SetActive(laserPurpleHit, false);
SetActive(laserPurple, false);
}
}