@@ -19,6 +19,10 @@ using UnityEngine;
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("闪电颜色")]
@@ -54,6 +58,13 @@ public class ShanDian_DieEvent : DieEffect_TSY
[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 ( )
{
}
@@ -67,6 +78,7 @@ public class ShanDian_DieEvent : DieEffect_TSY
{
Vector3 startPos = startPoint ! = Vector3 . zero ? startPoint : transform . position ;
startPos . z = 0 ;
_gizmoCenter = startPos ;
if ( fishLayer . value = = 0 ) fishLayer = LayerMask . GetMask ( "Fish" ) ;
@@ -100,32 +112,54 @@ public class ShanDian_DieEvent : DieEffect_TSY
var fishPositions = new List < Vector3 > ( ) ;
var addedGuids = new HashSet < string > ( ) ;
void Collect ( float radius )
// 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 )
{
if ( fishList . Count > = lightningCount ) break ; // 收集到目标数量即停
var control = hit . GetComponentInParent < MonsterContral > ( ) ;
if ( control = = null ) continue ;
if ( control . fishenum ! = FishTypeEnum . 普 通 鱼 ) continue ; // ⭐ 只杀小鱼
if ( addedGuids . Contains ( control . guid ) ) continue ;
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 : " ( 无 ) ")}" ) ;
}
// 第一段:圈内优先(身边的先遭殃)
if ( circleRange > 0f ) Collect ( circleRange ) ;
// 第二段:圈内不够 → 全屏补足
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 ) ;
Collect ( fill , false , "全屏补" ) ;
}
int count = Mathf . Min ( lightningCount , fishList . Count ) ;
@@ -137,41 +171,71 @@ public class ShanDian_DieEvent : DieEffect_TSY
yield break ;
}
// ====== 2. 随机选 count 条鱼,画闪电 ======
var lightningObjects = new List < GameObject > ( ) ;
// ====== 2. 按选鱼结果锁定(圈内最近优先 / 全屏补足,激光锁定 / 折线闪电两种表现) ======
var explodePoints = new List < Vector3 > ( ) ;
var indices = new List < int > ( fishList . Count ) ;
for ( int i = 0 ; i < fishList . Count ; i + + ) indices . Add ( i ) ;
for ( int i = 0 ; i < count ; i + + )
{
int r = Random . Range ( i , indices . Count ) ;
( indices [ i ] , indices [ r ] ) = ( indices [ r ] , indices [ i ] ) ;
}
var lockedFish = new List < MonsterContral > ( ) ;
var frozen = new List < bool > ( ) ;
for ( int i = 0 ; i < count ; i + + )
{
Vector3 endPos = fishPositions [ indices [ i ] ] ;
explodePoints . Add ( endPos ) ;
explodePoints . Add ( fishPositions [ i ] ) ;
lockedFish . Add ( fishList [ i ] ) ;
frozen . Add ( false ) ;
var lrObj = CreateLightningBolt ( startPos , endPos ) ;
if ( lrObj ! = null ) lightningObjects . Add ( lrObj ) ;
// 雷电命中点特效:闪电打到鱼身上时在命中点播,强化打击感(没配跳过)
// 雷电命中点特效:命中鱼身时播,强化打击感(没配跳过)
if ( hitFxPrefab ! = null )
{
var hit = Instantiate ( hitFxPrefab , endPos , Quaternion . identity , CreateFishManager . Instance . bjEffectRoot ) ;
var hit = Instantiate ( hitFxPrefab , fishPositions [ i ] , Quaternion . identity , CreateFishManager . Instance . bjEffectRoot ) ;
Destroy ( hit , lightningWait ) ;
}
}
// ====== 3. 等待 → 清除闪电 → 释放爆炸特效 ======
yield return new WaitForSeconds ( lightningWait ) ;
foreach ( var obj in lightningObjects )
bool useLaser = laserPrefab ! = null ;
if ( useLaser )
{
if ( obj ! = null ) Destroy ( obj ) ;
// 激光锁定形态: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 )
{
@@ -210,6 +274,26 @@ public class ShanDian_DieEvent : DieEffect_TSY
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 发光材质、粗主线、细分段随机折线、持续期间高频闪烁。
@@ -276,21 +360,58 @@ public class ShanDian_DieEvent : DieEffect_TSY
}
}
/// <summary>屏幕对角线长度:选鱼范围 0=全屏时用它,保证电鳗能劈到全场鱼 </summary>
/// <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 | | ! cam . orthographic ) return 50f ;
float h = cam . orthographicSize * 2f ;
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
private void OnDrawGizmosSelected ( )
// 改用 OnDrawGizmos:不用选中物体就常显(OnDrawGizmosSelected 只有选中才画,物体隐藏/回池时不调用)
// 黄圈=circleRange 圈内优先范围;青圈=fillRange 全屏补鱼范围;红圈=explosionRadius 爆炸伤害半径
private void OnDrawGizmos ( )
{
Gizmos . color = new Color ( 1 , 1 , 0 , 0.3f ) ;
Gizmos . DrawWireSphere ( transform . position , circleRange > 0f ? circleRange : 5f ) ;
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
}