429 lines
19 KiB
C#
429 lines
19 KiB
C#
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
|
||
}
|