Files
5.24BuYu/Assets/MyGame/aa/Game/FLZY/TSY/ShanDian/ShanDian_DieEvent.cs
T
2026-09-13 20:59:05 +08:00

429 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Assets.MyGame.Scripts.Net.APIResponses.Enum;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 闪电类特殊鱼 死亡玩法 通用基类(HHWK 闪电鱼 SDY 与 FLZY 闪电鱼/电鳗 共用同一套代码)。
///
/// 流程:
/// 1. 以死亡位置为中心,先 circleRange 圈内找普通鱼,不够再从全屏补;
/// 2. 随机取 lightningCount 条,从中心向每条鱼画折线闪电(LineRenderer);
/// 3. 停 lightningWait 秒 → 清闪电 → 每个终点播爆炸特效(可选,没配不播但照常杀);
/// 4. 每个终点 explosionRadius 范围内的普通鱼全部击杀
/// (金币=炮倍×底分,Id_DieFish_FX 帧同步;金银按击杀者座位判定;金币飞向炮台起点);
/// 5. 结束 → CreateBaoJiangEffect(爆奖/BJEndUI)→ 回池。
///
/// 子类只需继承并配置 Inspector 参数(条数/范围/半径/特效预制体等),无需写逻辑。
/// </summary>
public class ShanDian_DieEvent : DieEffect_TSY
{
[Header("闪电表现")]
[Tooltip("激光预制体(配了则用 JiGuangControl 锁定鱼:激光跟随游动的鱼;不配则走 LineRenderer 折线闪电兜底)")]
public GameObject laserPrefab;
[Tooltip("激光长度校正值(JiGuangControl.JiGuangLength 用)")]
public float laserLengthCorrect = 1f;
[Tooltip("闪电材质(空则自动用 Additive 发光材质,效果更亮更明显)")]
public Material lightningMaterial;
[Tooltip("闪电颜色")]
public Color lightningColor = Color.white;
[Tooltip("闪电宽度(主线)")]
public float lightningWidth = 0.25f;
[Tooltip("闪电折线分段数")]
public int zigzagSegments = 8;
[Tooltip("折线抖动幅度")]
public float zigzagJitter = 0.6f;
[Tooltip("闪电持续期间抖动闪烁频率(次/秒,越大越闪)")]
public float flickerRate = 12f;
[Header("爆炸")]
[Tooltip("终点爆炸特效预制体(没配不播但照样范围杀鱼)")]
public GameObject explosionPrefab;
[Tooltip("爆炸范围伤害半径")]
public float explosionRadius = 2f;
[Tooltip("雷电命中点特效(闪电打到鱼身上时在每条命中点播,可配闪电爆闪图形,没配则跳过)")]
public GameObject hitFxPrefab;
[Header("参数")]
[Tooltip("闪电条数(= 终端爆炸次数)")]
public int lightningCount = 8;
[Tooltip("优先选择范围(世界单位):以鱼死亡位置为中心,先在这个圈内找普通鱼;不够再从全屏补")]
public float circleRange = 3f;
[Tooltip("全屏补鱼的检测范围(0=自动取屏幕对角线)。圈内不够时用它补足,默认全屏")]
public float fillRange = 0f;
[Tooltip("闪电显示时长(秒):LineRenderer 显示持续这一段时间后再消失")]
public float lightningWait = 2.5f;
[Tooltip("爆炸特效到杀鱼之间等待(秒)")]
public float killDelay = 0.3f;
[Tooltip("杀鱼的物理层(默认 Fish 层)")]
public LayerMask fishLayer;
// 激光锁定实例 / 折线闪电实例(收尾统一清理)
private readonly List<Transform> _laserFx = new List<Transform>();
private readonly List<GameObject> _lightningObjects = new List<GameObject>();
private Vector3 _laserFxBaseScale = Vector3.one;
// 运行时选鱼中心(供 Gizmos 画圈用)
private Vector3 _gizmoCenter;
public override void SetCoinCount()
{
}
public override void Init(long goldCount, int posID, long paobei, long fishScore, int beishu, System.Action<int> action, System.Action action1 = null)
{
base.Init(goldCount, posID, paobei, fishScore, beishu, action, action1);
StartCoroutine(LightningRoutine());
}
private IEnumerator LightningRoutine()
{
// ⭐ 闪电起点:优先用闪电鱼本体上配置的 shandianStart(ShanDianFishControl),
// 未配置或宿主不是闪电鱼控制脚本时,回退原来的死亡位置逻辑
Vector3 startPos;
var sdFish = monster as ShanDianFishControl;
if (sdFish != null && sdFish.shandianStart != null)
startPos = sdFish.shandianStart.position;
else
startPos = startPoint != Vector3.zero ? startPoint : transform.position;
startPos.z = 0;
_gizmoCenter = startPos;
if (fishLayer.value == 0) fishLayer = LayerMask.GetMask("Fish");
// ====== 0. 击杀者座位(posID 由 CreateFishManager 注入)======
SitPosData sit = _targetSit;
if (sit == null)
{
var sits = CreateFishManager.Instance?.currGameType?.list_Sits;
if (sits != null && posID >= 0 && posID < sits.Count) sit = sits[posID];
}
if (sit == null)
{
yield return new WaitForSeconds(0.3f);
CreateBaoJiangEffect();
EffectPool.Instance.Return(gameObject);
yield break;
}
// 炮倍兜底:本地路径未注入时取击杀者座位炮倍(旧版在 Awake 里取本地 currSitData,房主代人机时倍数会错)
if (paoBei <= 0 && sit.consumeGoldNum > 0) paoBei = (int)sit.consumeGoldNum;
// 金/银币:按击杀者座位判定(与 BJEventBase 一致)
bool isLocal = sit.user == GameManager.Instance.user.id;
// 金币飞向点:炮台子弹起点(世界坐标),与普通杀鱼一致
Vector3 flyTo = sit.currentTurret != null
? (Vector3)sit.currentTurret.GetBulletStartWorldPos()
: sit.turretWorldPos;
// ====== 1. 选鱼(只杀小鱼):先 circleRange 圈内找,不够再从 fillRange 补满 ======
var fishList = new List<MonsterContral>();
var fishPositions = new List<Vector3>();
var addedGuids = new HashSet<string>();
// nearestFirst=true:按离死亡位置的距离升序收集(身边的鱼优先遭殃);false:按物理检测原始顺序(近似随机,全屏补鱼分布更自然)
void Collect(float radius, bool nearestFirst, string tag)
{
var hits = Physics2D.OverlapCircleAll(startPos, radius, fishLayer);
var candidates = new List<KeyValuePair<float, MonsterContral>>();
int noCtrl = 0, skipType = 0, skipDup = 0;
foreach (var hit in hits)
{
var control = hit.GetComponentInParent<MonsterContral>();
if (control == null) { noCtrl++; continue; }
if (control.fishenum != FishTypeEnum.普通鱼) { skipType++; continue; } // ⭐ 只杀小鱼
if (addedGuids.Contains(control.guid)) { skipDup++; continue; }
candidates.Add(new KeyValuePair<float, MonsterContral>(
Vector2.Distance(startPos, control.transform.position), control));
}
// 圈内模式:距离排序后从最近的开始取,保证"身边的鱼先被电"
if (nearestFirst) candidates.Sort((a, b) => a.Key.CompareTo(b.Key));
int before = fishList.Count;
var picked = new System.Text.StringBuilder();
foreach (var kv in candidates)
{
if (fishList.Count >= lightningCount) break; // 收集到目标数量即停
var control = kv.Value;
addedGuids.Add(control.guid);
fishList.Add(control);
var pos = control.transform.position;
pos.z = 0;
fishPositions.Add(pos);
picked.Append($" {control.name}(d={kv.Key:F1})");
}
Debug.Log($"[闪电][{tag}] 中心={startPos} 半径={radius:F1} 命中={hits.Length} " +
$"候选={candidates.Count}(无脚本={noCtrl} 非小鱼={skipType} 重复={skipDup}) " +
$"选中={fishList.Count - before}{(fishList.Count > before ? ":" + picked : "(无)")}");
}
Debug.Log($"[闪电] 选鱼开始 startPoint={startPoint} 本体={transform.position} 中心={startPos} " +
$"circleRange={circleRange} fillRange={fillRange} 目标={lightningCount} 层=[{LayerMaskNames(fishLayer)}]");
// 第一段:圈内按距离取最近的(身边的先遭殃)
if (circleRange > 0f) Collect(circleRange, true, "圈内");
// 第二段:圈内不够 → 全屏补足(随机分布)
if (fishList.Count < lightningCount)
{
float fill = fillRange > 0f ? fillRange : ScreenDiagonal();
Collect(fill, false, "全屏补");
}
int count = Mathf.Min(lightningCount, fishList.Count);
if (count == 0)
{
yield return new WaitForSeconds(0.3f);
CreateBaoJiangEffect();
EffectPool.Instance.Return(gameObject);
yield break;
}
// ====== 2. 按选鱼结果锁定(圈内最近优先 / 全屏补足,激光锁定 / 折线闪电两种表现)======
var explodePoints = new List<Vector3>();
var lockedFish = new List<MonsterContral>();
var frozen = new List<bool>();
for (int i = 0; i < count; i++)
{
explodePoints.Add(fishPositions[i]);
lockedFish.Add(fishList[i]);
frozen.Add(false);
// ⭐ 电到的鱼定身:闪电期间停止移动
// (鱼被炸死后回池,重新生成时 MonsterContral.OnEnable 会恢复 isMove=baseMmove)
fishList[i].isMove = false;
// 雷电命中点特效:命中鱼身时播,强化打击感(没配跳过)
if (hitFxPrefab != null)
{
var hit = Instantiate(hitFxPrefab, fishPositions[i], Quaternion.identity, CreateFishManager.Instance.bjEffectRoot);
Destroy(hit, lightningWait);
}
}
bool useLaser = laserPrefab != null;
if (useLaser)
{
// 激光锁定形态:JiGuangControl 按目标数量创建激光实例,持续期间跟随游动的鱼
var laserTargets = new List<Transform>();
for (int i = 0; i < lockedFish.Count; i++) laserTargets.Add(lockedFish[i].transform);
_laserFxBaseScale = laserPrefab.transform.localScale;
JiGuangControl.JiGuangCreate(laserPrefab, startPos, laserTargets, _laserFx,
CreateFishManager.Instance.bjEffectRoot);
float elapsed = 0f;
while (elapsed < lightningWait)
{
elapsed += Time.deltaTime;
// 死鱼冻结爆炸点并隐藏激光;活鱼更新爆炸点 + 刷新激光朝向/长度(跟随移动)
for (int i = 0; i < lockedFish.Count; i++)
{
var ctrl = lockedFish[i];
if (!frozen[i] && (ctrl == null || ctrl.isDead || !ctrl.gameObject.activeInHierarchy))
{
frozen[i] = true;
if (i < _laserFx.Count && _laserFx[i] != null) _laserFx[i].gameObject.SetActive(false);
}
if (frozen[i]) continue;
Vector3 p = ctrl.transform.position;
p.z = 0;
explodePoints[i] = p;
}
JiGuangControl.JiGuangDirection(startPos, laserTargets, _laserFx);
JiGuangControl.JiGuangLength(startPos, laserTargets, _laserFx, _laserFxBaseScale, laserLengthCorrect);
yield return null;
}
}
else
{
// LineRenderer 折线闪电兜底
for (int i = 0; i < lockedFish.Count; i++)
{
var lrObj = CreateLightningBolt(startPos, explodePoints[i]);
if (lrObj != null) _lightningObjects.Add(lrObj);
}
yield return new WaitForSeconds(lightningWait);
}
// ====== 3. 清除闪电/激光 → 释放爆炸特效 ======
ClearLightningFx();
foreach (var ep in explodePoints)
{
if (explosionPrefab != null)
{
var fx = Instantiate(explosionPrefab, ep, Quaternion.identity, CreateFishManager.Instance.bjEffectRoot);
Destroy(fx, 1.5f);
}
}
yield return new WaitForSeconds(killDelay);
// ====== 4. 每个爆炸点范围杀鱼(去重,只杀普通鱼)======
var killedGuids = new HashSet<string>();
foreach (var ep in explodePoints)
{
var blastHits = Physics2D.OverlapCircleAll(ep, explosionRadius, fishLayer);
foreach (var hit in blastHits)
{
var control = hit.GetComponentInParent<MonsterContral>();
if (control == null) continue;
if (control.fishenum != FishTypeEnum.普通鱼) continue;
if (killedGuids.Contains(control.guid)) continue;
killedGuids.Add(control.guid);
CreateFishManager.Instance.Id_DieFish_FX(control.guid,
new RedPacketRecord { Amount = paoBei * control.monsterData.fishTypeConfig.fishScore },
flyTo,
isLocal: isLocal);
}
}
// ====== 5. 结束:爆奖/结算 → 回池 ======
yield return new WaitForSeconds(0.3f);
CreateBaoJiangEffect();
EffectPool.Instance.Return(gameObject);
}
/// <summary>
/// 清理激光/闪电表现(两种形态都清),在爆炸杀鱼前调用。
/// </summary>
private void ClearLightningFx()
{
// 激光实例:JiGuangCreate 克隆出来的销毁掉
for (int i = 0; i < _laserFx.Count; i++)
{
if (_laserFx[i] != null) Destroy(_laserFx[i].gameObject);
}
_laserFx.Clear();
// 折线闪电对象
for (int i = 0; i < _lightningObjects.Count; i++)
{
if (_lightningObjects[i] != null) Destroy(_lightningObjects[i]);
}
_lightningObjects.Clear();
}
/// <summary>
/// 创建一根闪电(主线折线 + 持续抖动闪烁)。
/// ⭐ 效果增强:Additive 发光材质、粗主线、细分段随机折线、持续期间高频闪烁。
/// </summary>
private GameObject CreateLightningBolt(Vector3 from, Vector3 to)
{
var go = new GameObject("LightningBolt");
go.transform.SetParent(transform);
// 材质:有配置用配置,否则自动用 Additive 发光材质(比 Sprites/Default 亮很多)
Material mat = lightningMaterial;
if (mat == null)
{
var shader = Shader.Find("Legacy Shaders/Particles/Additive")
?? Shader.Find("Particles/Additive")
?? Shader.Find("Sprites/Default");
mat = new Material(shader);
}
var lr = go.AddComponent<LineRenderer>();
lr.material = mat;
lr.startColor = lightningColor;
lr.endColor = lightningColor;
lr.startWidth = lightningWidth;
lr.endWidth = lightningWidth * 0.4f;
lr.sortingOrder = 50;
// 生成初始折线 + 启动闪烁协程(持续抖动重绘 → 更明显的"滋啦"效果)
int totalPoints = zigzagSegments + 2;
lr.positionCount = totalPoints;
DrawZigzag(lr, totalPoints, from, to);
StartCoroutine(Flicker(lr, totalPoints, from, to));
return go;
}
/// <summary>重绘折线点(每次调用随机抖动,产生闪烁/扭动效果)</summary>
private void DrawZigzag(LineRenderer lr, int totalPoints, Vector3 from, Vector3 to)
{
lr.SetPosition(0, from);
for (int i = 1; i <= zigzagSegments; i++)
{
float t = (float)i / (zigzagSegments + 1);
Vector3 straight = Vector3.Lerp(from, to, t);
Vector3 dir = (to - from).normalized;
Vector3 perp = new Vector3(-dir.y, dir.x, 0) * Random.Range(-zigzagJitter, zigzagJitter);
lr.SetPosition(i, straight + perp);
}
lr.SetPosition(totalPoints - 1, to);
}
/// <summary>闪烁协程:持续期间高频重绘折线 + 随机亮度 → 闪电"滋滋"闪动更明显</summary>
private IEnumerator Flicker(LineRenderer lr, int totalPoints, Vector3 from, Vector3 to)
{
while (lr != null)
{
DrawZigzag(lr, totalPoints, from, to);
// 随机整体亮度(0.6~1 之间跳动),加粗/提亮配合
float b = Random.Range(0.6f, 1f);
var col = new Color(lightningColor.r * b, lightningColor.g * b, lightningColor.b * b, 1f);
lr.startColor = col;
lr.endColor = col;
yield return new WaitForSeconds(1f / Mathf.Max(1f, flickerRate));
}
}
/// <summary>LayerMask 转层名串:日志用,一眼看出 fishLayer 配没配对</summary>
private static string LayerMaskNames(LayerMask mask)
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 32; i++)
{
if ((mask.value & (1 << i)) == 0) continue;
string n = LayerMask.LayerToName(i);
if (!string.IsNullOrEmpty(n)) sb.Append(n).Append(",");
}
return sb.ToString();
}
/// <summary>屏幕(z=0 平面)对角线长度:选鱼范围 0=全屏时用它,保证电鳗能劈到全场鱼。兼容透视相机</summary>
private float ScreenDiagonal()
{
Camera cam = Camera.main;
if (cam == null) return 50f;
float h;
if (cam.orthographic)
{
h = cam.orthographicSize * 2f;
}
else
{
// 透视相机:按相机到 z=0 平面的距离换算该平面的可视高度(鱼都在 z=0)
float dist = Mathf.Abs(cam.transform.position.z);
h = 2f * dist * Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
}
float w = h * cam.aspect;
return Mathf.Sqrt(h * h + w * w);
}
#if UNITY_EDITOR
// 改用 OnDrawGizmos:不用选中物体就常显(OnDrawGizmosSelected 只有选中才画,物体隐藏/回池时不调用)
// 黄圈=circleRange 圈内优先范围;青圈=fillRange 全屏补鱼范围;红圈=explosionRadius 爆炸伤害半径
private void OnDrawGizmos()
{
Vector3 center = Application.isPlaying && _gizmoCenter != Vector3.zero ? _gizmoCenter : transform.position;
Gizmos.color = new Color(1f, 1f, 0f, 0.35f);
Gizmos.DrawWireSphere(center, circleRange > 0f ? circleRange : 5f);
if (fillRange > 0f)
{
Gizmos.color = new Color(0f, 1f, 1f, 0.35f);
Gizmos.DrawWireSphere(center, fillRange);
}
Gizmos.color = new Color(1f, 0f, 0f, 0.35f);
Gizmos.DrawWireSphere(center, explosionRadius);
}
#endif
}