Files
5.24BuYu/Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/XingJiXunHangEvent.cs
T
2026-09-17 03:06:38 +08:00

1794 lines
76 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 Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 星际巡航 Boss 爆奖事件(继承 BJEventBase)。
///
/// ==================== 美术规格(4种形态) ====================
///
/// 形态一(富甲天下,无道具武器):
/// Settle: PW_Effect_Settle_FuJiaTianXia (Node子物体指认Source Image为tj_fcboss_0)
///
/// 形态二(铁拳 IronFist, weaponID=6):
/// 1. FullScreen(PW_Effect_FullScreen_Symmetra): 依次激活 tap_tip → knock_tip(Source Image=Prop_Poster_IronFist) → 隐藏Tip
/// 2. Prop(PW_Prop_IronFist): 激活Hole子对象 → Hole从死亡位置移动到屏幕左下角 → 隐藏Hole → 激活Hand
/// 3. TextCrit: PW_Effect_TextCrit_Other / PW_Effect_TextCrit_Self (满屏暴击)
/// 4. AwardFrame: PW_Effect_AwardFrame_IronFist
/// 5. Settle: PW_Effect_Settle_IronFist_Other
///
/// 形态三(光子 Symmetra, weaponID=8):
/// 1. FullScreen(PW_Effect_FullScreen_Symmetra): 依次激活 tap_tip → knock_tip(Source Image=Prop_Poster_Symmetra) → 隐藏Tip
/// 2. Prop(PW_Prop_Symmetra): 从死亡位置移动到屏幕中心 → 隐藏Lock → PropL和PropR分别移动到屏幕两边 → 激活laser子物体
/// 3. AwardFrame: PW_Effect_AwardFrame_Symmetra
/// 4. Settle: PW_Effect_Settle_Symmetra_Other / PW_Effect_Settle_Symmetra_Self
///
/// 形态四(伽马风暴 GammaStorm, weaponID=7):
/// 1. FullScreen(PW_Effect_FullScreen_GammaStorm): 依次激活 tap_tip → knock_tip(Source Image=Prop_Poster_GammaStorm) → 隐藏Tip
/// 2. Prop(PW_Prop_GammaStorm): 从死亡位置移动到屏幕中心 → 根据视频内容激活特效
/// 3. TextCrit: PW_Effect_TextCrit_Other / PW_Effect_TextCrit_Self (满屏暴击)
/// 4. Crit: PW_Effect_Crit_GammaStorm (暴击倍率)
/// 5. AwardFrame: PW_Effect_AwardFrame_GammaStorm
/// 6. Settle: PW_Effect_Settle_GammaStorm_Other
///
/// ⭐ 注意: 铁拳(形态二)的全屏 UI 框按美术规格用 PW_Effect_FullScreen_Symmetra,
/// 海报(Source Image) 仍设置 Prop_Poster_IronFist。
///
/// ==================== 完整流程 ====================
///
/// Init → 解析 weaponID → 道具出场 → 全屏特效(tap_tip/knock_tip序列) → 道具动画
/// → 奖励框 → CallBJ发红包 → 暴击文字 → 结算特效 → EffEnd
/// → DelayThenScore → StartAddScore → MoveToSit → OnScoreEnd → JiaFen → die
/// </summary>
public class XingJiXunHangEvent : BJRedPacketEvent
{
// ==================== 形态一:富甲天下(无道具武器) ====================
[Header("形态一 - 富甲天下")]
[Tooltip("富甲天下结算特效(PW_Effect_Settle_FuJiaTianXia_XingJiXunHang)")]
public GameObject settleFuJiaTianXia;
// ==================== 形态二:铁拳 (weaponID=6) ====================
[Header("形态二 - 铁拳 IronFist")]
[Tooltip("铁拳道具预制体(PW_Prop_IronFist Variant)")]
public GameObject propIronFist;
[Tooltip("铁拳全屏特效(PW_Effect_FullScreen_IronFist_XingJiXunHang)")]
public GameObject fullScreenIronFist;
[Tooltip("铁拳奖励框(PW_Effect_AwardFrame_IronFist_XingJiXunHang)")]
public GameObject awardFrameIronFist;
[Tooltip("铁拳结算特效-自己(PW_Effect_Settle_IronFist_Self_XingJiXunHang)")]
public GameObject settleIronFistSelf;
[Tooltip("铁拳结算特效-他人(PW_Effect_Settle_IronFist_Other_XingJiXunHang)")]
public GameObject settleIronFistOther;
[Tooltip("铁拳海报Sprite(Prop_Poster_IronFist),设置到FullScreen的knock_tip上")]
public Sprite posterIronFist;
// ==================== 形态三:光子 (weaponID=8) ====================
[Header("形态三 - 光子 Symmetra")]
[Tooltip("光子道具预制体(PW_Prop_Symmetra Variant)")]
public GameObject propSymmetra;
[Tooltip("光子全屏特效(PW_Effect_FullScreen_Symmetra_XingJiXunHang)")]
public GameObject fullScreenSymmetra;
[Tooltip("光子奖励框(PW_Effect_AwardFrame_Symmetra_XingJiXunHang)")]
public GameObject awardFrameSymmetra;
[Tooltip("光子结算特效-自己(PW_Effect_Settle_Symmetra_Self_XingJiXunHang)")]
public GameObject settleSymmetraSelf;
[Tooltip("光子结算特效-他人(PW_Effect_Settle_Symmetra_Other_XingJiXunHang)")]
public GameObject settleSymmetraOther;
[Tooltip("光子海报Sprite(Prop_Poster_Symmetra),设置到FullScreen的knock_tip上")]
public Sprite posterSymmetra;
// ==================== 形态四:伽马风暴 (weaponID=7) ====================
[Header("形态四 - 伽马风暴 GammaStorm")]
[Tooltip("伽马风暴道具预制体(PW_Prop_GammaStorm Variant)")]
public GameObject propGammaStorm;
[Tooltip("伽马风暴全屏特效(PW_Effect_FullScreen_GammaStorm_XingJiXunHang)")]
public GameObject fullScreenGammaStorm;
[Tooltip("伽马风暴奖励框(PW_Effect_AwardFrame_GammaStorm_XingJiXunHang)")]
public GameObject awardFrameGammaStorm;
[Tooltip("伽马风暴结算特效-自己(PW_Effect_Settle_GammaStorm_Self_XingJiXunHang)")]
public GameObject settleGammaStormSelf;
[Tooltip("伽马风暴结算特效-他人(PW_Effect_Settle_GammaStorm_Other_XingJiXunHang)")]
public GameObject settleGammaStormOther;
[Tooltip("伽马风暴暴击框(PW_Effect_Crit_GammaStorm_XingJiXunHang)")]
public GameObject critFrameGammaStorm;
[Tooltip("伽马风暴海报Sprite(Prop_Poster_GammaStorm),设置到FullScreen的knock_tip上")]
public Sprite posterGammaStorm;
// ==================== 满屏暴击文字(铁拳/伽马风暴共用) ====================
[Header("满屏暴击文字")]
[Tooltip("满屏暴击-自己(PW_Effect_TextCrit_Self_XingJiXunHang)")]
public GameObject textCritSelf;
[Tooltip("满屏暴击-他人(PW_Effect_TextCrit_Other_XingJiXunHang)")]
public GameObject textCritOther;
// ==================== 道具出场特效 ====================
[Header("道具出场")]
[Tooltip("道具出场特效(三种武器共用,PW_Effect_Appear_Prop)")]
public GameObject appearPropEffect;
// ==================== FullScreen 子物体名称配置 ====================
[Header("FullScreen 子物体名称")]
[Tooltip("tap_tip 子物体名(点击提示)")]
public string tapTipName = "tap_tip";
[Tooltip("knock_tip 子物体名(敲击提示,需设置海报Sprite)")]
public string knockTipName = "knock_tip";
[Tooltip("Tip 容器子物体名(tap_tip/knock_tip 的父级,序列结束后隐藏,所有形态通用)")]
public string tipContainerName = "Tip";
// ==================== Prop 子物体名称配置 ====================
[Header("铁拳 Prop 子物体名称")]
[Tooltip("铁拳-Hole子物体名(从死亡位置移到屏幕左下角)")]
public string ironFistHoleName = "Hole";
[Tooltip("铁拳-Hand子物体名(Hole隐藏后激活)")]
public string ironFistHandName = "Hand";
[Tooltip("铁拳-Hole终点偏移(世界单位,往屏幕内偏移,如 (1.5, 1, 0) 从左下角往右上挪一点)")]
public Vector3 ironFistCornerOffset = new Vector3(1.5f, 1f, 0f);
[Tooltip("铁拳-Hole 到位后停留时长(秒),让玩家看清再释放 Hand(默认0.5)")]
public float ironFistHoldDelay = 0.5f;
[Header("铁拳 - 击打杀鱼")]
[Tooltip("铁拳杀鱼检测半径(世界单位,Hand 击打范围)")]
public float ironFistKillRadius = 2.5f;
[Tooltip("铁拳杀鱼检测层(Fish)")]
public LayerMask ironFistKillLayer;
[Tooltip("铁拳是否杀鱼(被击中的飞机死亡,金币从左下角飞向炮台)")]
public bool ironFistKillFish = true;
[Tooltip("从近到远逐个杀鱼的总时长(秒,默认1秒)")]
public float ironFistKillTotalTime = 1f;
[Tooltip("Hand 激活后延迟(秒,默认2),延迟结束隐藏 Hand 并开始死飞机/爆金币")]
public float ironFistAwardDelay = 2f;
[Header("光子 Prop 子物体名称")]
[Tooltip("光子-Lock子物体名(到屏幕中心后隐藏)")]
public string symmetraLockName = "Lock";
[Tooltip("光子-PropL子物体名(移到屏幕左边)")]
public string symmetraPropLName = "PropL";
[Tooltip("光子-PropR子物体名(移到屏幕右边)")]
public string symmetraPropRName = "PropR";
[Tooltip("光子-laser子物体名(PropL/PropR到位后激活)")]
public string symmetraLaserName = "laser";
[Tooltip("光子-laser 激活后上下摆动幅度(世界单位)")]
public float laserSwingAmplitude = 0.5f;
[Tooltip("光子-laser 激活后上下摆动来回次数(0=不摆动)")]
public int laserSwingCycles = 0;
[Header("光子 - 4条激光伸展")]
[Tooltip("laser1 子物体名(向 X 正方向伸到屏幕最右)")]
public string laser1Name = "laser1";
[Tooltip("laser2 子物体名(向 -Y 方向伸到屏幕最下)")]
public string laser2Name = "laser2";
[Tooltip("laser3 子物体名(向 +Y 方向伸到屏幕最上)")]
public string laser3Name = "laser3";
[Tooltip("laser4 子物体名(向 -X 方向伸到屏幕最左)")]
public string laser4Name = "laser4";
[Tooltip("4条激光伸展到最长位置的耗时(秒)")]
public float laserExpandSpeed = 0.6f;
[Tooltip("光子激光结束后等待时长(秒),然后直接隐藏 Prop(默认1)")]
public float symmetraPropHideDelay = 1f;
[Header("光子 - 电网扫动杀鱼")]
[Tooltip("电网(Prop整体)上下扫动幅度(世界单位)")]
public float netSweepAmplitude = 5f;
[Tooltip("电网上下扫动来回次数(0=不扫动)")]
public int netSweepCycles = 3;
[Tooltip("电网每次扫动耗时(秒)")]
public float netSweepSpeed = 0.8f;
[Tooltip("电网杀鱼检测半径(世界单位,电网线两侧厚度)")]
public float netKillRadius = 1f;
[Tooltip("电网杀鱼检测层(Fish,Inspector 选 Fish 层)")]
public LayerMask netKillLayer;
[Tooltip("电网是否杀鱼(碰到飞机飞机爆炸)")]
public bool netKillFish = true;
[Header("伽马风暴 - 四射线旋转")]
[Tooltip("四根射线子物体名(伽马风暴激光,按顺序填;预制体内为小写 laser1~4)")]
public string[] gammaLaserNames = { "laser1", "laser2", "laser3", "laser4" };
[Tooltip("射线显示后到开始旋转的等待(秒,默认1)")]
public float gammaRotateDelay = 1f;
[Tooltip("总旋转角度(度,默认90°,转完为止)")]
public float gammaRotateAngle = 90f;
[Tooltip("转到该角度后激光开始一点一点缩短(度,默认45°)")]
public float gammaShrinkStartAngle = 45f;
[Tooltip("旋转速度(度/秒,正=逆时针)")]
public float gammaRotateSpeed = 45f;
[Tooltip("伽马风暴射线杀鱼检测层(Fish)")]
public LayerMask gammaKillLayer;
[Tooltip("伽马风暴射线是否杀鱼(碰到激光的飞机直接死亡)")]
public bool gammaKillFish = true;
// ==================== 爆奖配置 ====================
[Header("爆奖配置")]
[Tooltip("爆奖红包次数(CallBJ 调用次数)")]
public int bjCount = 1;
[Tooltip("每次 CallBJ 最小红包数")]
public int bjMinCount = 10;
[Tooltip("每次 CallBJ 最大红包数")]
public int bjMaxCount = 15;
[Header("时间配置")]
[Tooltip("道具出场特效持续时间")]
public float appearDuration = 1f;
[Tooltip("tap_tip 显示持续时间")]
public float tapTipDuration = 0.5f;
[Tooltip("knock_tip 显示持续时间")]
public float knockTipDuration = 0.8f;
[Tooltip("全屏特效总持续时间")]
public float fullScreenDuration = 2f;
[Tooltip("道具动画持续时间(Hole移动/Prop移动等)")]
public float propAnimDuration = 1.5f;
[Tooltip("结算特效持续时间")]
public float settleDuration = 3f;
// ==================== 运行时状态 ====================
/// <summary>Boss 引用(爆奖结束时通知 Boss 回收)</summary>
[System.NonSerialized]
public XingJiXunHang_BOSS bossCtrl;
/// <summary>Boss 死亡位置(世界坐标,由 Boss 传入)</summary>
[HideInInspector]
public Vector3 diePos = Vector3.zero;
/// <summary>服务端返回的武器 ID(6=IronFist, 7=GammaStorm, 8=Symmetra, 0=富甲天下)</summary>
private int _weaponId = 0;
/// <summary>武器标签(GammaStorm / IronFist / Symmetra / FuJiaTianXia)</summary>
private string _weaponTag = "";
/// <summary>当前选中的结算预制体</summary>
private GameObject _settlePrefab;
/// <summary>当前选中的奖励框预制体</summary>
private GameObject _awardFramePrefab;
/// <summary>当前选中的全屏特效预制体</summary>
private GameObject _fullScreenPrefab;
/// <summary>当前选中的道具预制体</summary>
private GameObject _propPrefab;
/// <summary>当前选中的海报Sprite</summary>
private Sprite _posterSprite;
/// <summary>本局电网已炸的鱼 guid(防止扫动中重复炸同一鱼)</summary>
private readonly HashSet<string> _netKilledFish = new HashSet<string>();
/// <summary>Boss 分数面板 TMP(挂在独立 UI 层,Boss 删除后仍有效)</summary>
private TMPro.TextMeshProUGUI _bossScoreTmp;
/// <summary>本局杀鱼累计加到 Boss 面板的分值</summary>
private long _bossPanelScore = 0;
/// <summary>爆奖过程中实例化的所有临时特效(die() 时统一销毁,防止残留)</summary>
private readonly System.Collections.Generic.List<GameObject> _spawnedEffects = new System.Collections.Generic.List<GameObject>();
/// <summary>UI 层父节点(UI 类特效:FullScreen/AwardFrame/Settle/Crit 挂这里)</summary>
private Transform _uiLayer;
/// <summary>UI Canvas(世界坐标 → UI 坐标转换用)</summary>
private Canvas _uiCanvas;
// ==================== 初始化 ====================
public override void Init(long goldCount, int posID, long paobei, long fishScore, int beishu,
Action<int> action, Action action1 = null)
{
base.Init(goldCount, posID, paobei, fishScore, beishu, action, action1);
// 恢复位置到 Boss 死亡位置
transform.position = diePos;
// 设置爆奖次数
scoreCount = bjCount;
totalBJCount = bjCount;
// ⭐ 星际巡航不需要 ScoreNumScroll(Clone) 加分数字滚动特效(杀鱼分已加 Boss 面板)
showAddScoreFX = false;
// ⭐ 爆奖只分包不关联场上鱼(已有完整的道具武器/全屏/结算动画,
// 避免全屏随机位置死飞机爆奖)
bigPrizeFishMode = BigPrizeFishMode.NoFish;
// 解析武器 ID
ParseWeaponId();
// 根据武器 ID 选择预制体
SelectWeaponPrefabs();
// 启动完整爆奖流程
StartCoroutine(PropWeaponFlow());
}
/// <summary>重写:不移动到屏幕中心,但缓存座位引用</summary>
protected override void ApplySeatOffset()
{
GetSeatPosition();
}
/// <summary>重置子类运行时状态</summary>
protected override void ResetState()
{
base.ResetState();
_weaponId = 0;
_weaponTag = "";
_settlePrefab = null;
_awardFramePrefab = null;
_fullScreenPrefab = null;
_propPrefab = null;
_posterSprite = null;
bossCtrl = null;
diePos = Vector3.zero;
_netKilledFish.Clear();
_bossScoreTmp = null;
_bossPanelScore = 0;
// ⭐ 对象池复用:清理上次残留的临时特效
for (int i = 0; i < _spawnedEffects.Count; i++)
{
if (_spawnedEffects[i] != null)
Destroy(_spawnedEffects[i]);
}
_spawnedEffects.Clear();
}
// ==================== 武器 ID 解析 ====================
private void ParseWeaponId()
{
if (obj == null)
{
Debug.LogWarning("[XingJiXunHang] obj 为 null,无法解析 weaponID,走富甲天下");
_weaponId = 0;
return;
}
try
{
JToken root = obj is JToken t ? t : JToken.Parse(obj.ToString());
_weaponId = root["weaponID"]?.Value<int>()
?? root["weaponId"]?.Value<int>()
?? root["weapon_id"]?.Value<int>()
?? 0;
// ⭐ 服务端现在用 animId 区分三种带死亡的爆奖(weaponID 可能不再下发):
// animId=2 → 形态二 铁拳 IronFist (weaponID=6)
// animId=3 → 形态三 光子 Symmetra (weaponID=8)
// animId=4 → 形态四 伽马风暴 GammaStorm (weaponID=7)
if (_weaponId == 0)
{
int animId = root["animId"]?.Value<int>() ?? 0;
switch (animId)
{
case 2: _weaponId = 6; break; // 铁拳
case 3: _weaponId = 8; break; // 光子
case 4: _weaponId = 7; break; // 伽马风暴
}
if (animId >= 2)
Debug.Log($"[XingJiXunHang] 无 weaponID,按 animId={animId} 映射为 weaponID={_weaponId}");
}
Debug.Log($"[XingJiXunHang] 解析武器 ID: weaponID={_weaponId}");
}
catch (Exception e)
{
Debug.LogError($"[XingJiXunHang] 解析 weaponID 失败: {e.Message}; 原始={obj}");
_weaponId = 0;
}
}
/// <summary>根据武器 ID 选择对应的预制体和标签</summary>
private void SelectWeaponPrefabs()
{
switch (_weaponId)
{
case 0:
// 形态一:富甲天下(无道具武器)
_weaponTag = "FuJiaTianXia";
_settlePrefab = settleFuJiaTianXia;
_awardFramePrefab = null;
_fullScreenPrefab = null;
_propPrefab = null;
_posterSprite = null;
break;
//铁拳
case 6:
_weaponTag = "IronFist";
_settlePrefab = isSelf ? settleIronFistSelf : settleIronFistOther;
_awardFramePrefab = awardFrameIronFist;
// ⭐ 美术规格:铁拳的全屏 UI 框用 PW_Effect_FullScreen_Symmetra(海报仍为 Prop_Poster_IronFist)
_fullScreenPrefab = fullScreenSymmetra;
_propPrefab = propIronFist;
_posterSprite = posterIronFist;
break;
case 7:
_weaponTag = "GammaStorm";
_settlePrefab = isSelf ? settleGammaStormSelf : settleGammaStormOther;
_awardFramePrefab = awardFrameGammaStorm;
_fullScreenPrefab = fullScreenGammaStorm;
_propPrefab = propGammaStorm;
_posterSprite = posterGammaStorm;
break;
//激光
case 8:
_weaponTag = "Symmetra";
_settlePrefab = isSelf ? settleSymmetraSelf : settleSymmetraOther;
_awardFramePrefab = awardFrameSymmetra;
_fullScreenPrefab = fullScreenSymmetra;
_propPrefab = propSymmetra;
_posterSprite = posterSymmetra;
break;
default:
Debug.LogWarning($"[XingJiXunHang] 未知 weaponID={_weaponId},走富甲天下");
goto case 0;
}
Debug.Log($"[XingJiXunHang] 选择武器: tag={_weaponTag} isSelf={isSelf}" +
$" settle={(_settlePrefab != null ? _settlePrefab.name : "null")}" +
$" awardFrame={(_awardFramePrefab != null ? _awardFramePrefab.name : "null")}" +
$" fullScreen={(_fullScreenPrefab != null ? _fullScreenPrefab.name : "null")}" +
$" prop={(_propPrefab != null ? _propPrefab.name : "null")}");
}
// ==================== 爆奖主流程 ====================
/// <summary>
/// 道具武器完整流程:
/// 1. 道具出场特效 (AppearProp)
/// 2. 全屏特效 (FullScreen: tap_tip → knock_tip → 隐藏Tip,仅 isSelf)
/// 3. 道具动画 (Prop: 各武器不同的子物体操作)
/// 4. 奖励框 (AwardFrame)
/// 5. CallBJ 发红包
/// 6. 满屏暴击文字 (TextCrit,仅 IronFist/GammaStorm)
/// 7. 结算特效 (Settle)
/// 8. EffEnd → 标准收尾
/// </summary>
private IEnumerator PropWeaponFlow()
{
Debug.Log($"[XingJiXunHang] 开始爆奖流程 tag={_weaponTag} isSelf={isSelf} diePos={diePos}");
// -------- 形态一(富甲天下):直接结算,无道具流程 --------
if (_weaponId == 0)
{
yield return StartCoroutine(FuJiaTianXiaFlow());
yield break;
}
// -------- Phase 1: 道具出场特效 --------
if (appearPropEffect != null)
{
var appearObj = SpawnWorldEffect(appearPropEffect, diePos);
Debug.Log("[XingJiXunHang] Phase1: 道具出场特效");
yield return new WaitForSeconds(appearDuration);
if (appearObj != null) Destroy(appearObj); // 出场特效播完即删(不等 die)
}
else
{
yield return new WaitForSeconds(appearDuration);
}
// -------- Phase 2: 全屏特效(仅 isSelf) --------
GameObject fullScreenObj = null;
if (isSelf && _fullScreenPrefab != null)
{
// ⭐ 全屏特效是 UI 预制体,锚点/anchoredPosition 已在预制体内写好,
// 只挂到父节点即可,不要传 position/rotation(否则会覆盖写好的值)
fullScreenObj = Instantiate(_fullScreenPrefab, CreateFishManager.Instance.bjEffectRoot);
_spawnedEffects.Add(fullScreenObj); // 加入统一销毁列表
Debug.Log("[XingJiXunHang] Phase2: 全屏特效(自己)");
// 执行 tap_tip → knock_tip 序列(序列内部已把 Tip 隐藏)
yield return StartCoroutine(PlayFullScreenTipSequence(fullScreenObj));
// ⭐ FullScreen 克隆对象本身不提前销毁(只隐藏 Tip),保留到爆奖结束,
// 由 die() 统一销毁/回收(_spawnedEffects 列表管理)
Debug.Log("[XingJiXunHang] FullScreen UI 框保留到爆奖结束(Tip 已隐藏)");
}
else
{
yield return new WaitForSeconds(0.1f);
}
// -------- Phase 3: 道具动画 --------
if (_propPrefab != null)
{
yield return StartCoroutine(PlayPropAnimation());
}
// -------- Phase 4: 奖励框(⭐ 不再额外 Spawn:奖励框即加分面板,已在杀鱼加分时由
// EnsureBossScoreTmp 以 _awardFramePrefab 创建并显示分数) --------
Debug.Log("[XingJiXunHang] Phase4: 奖励框即加分面板(EnsureBossScoreTmp 已按武器创建)");
// -------- Phase 5: CallBJ 发红包 --------
for (int i = 0; i < bjCount; i++)
{
Debug.Log($"[XingJiXunHang] Phase5: CallBJ {i + 1}/{bjCount}");
CallBJ(bjMinCount, bjMaxCount);
yield return new WaitForSeconds(0.5f);
}
// -------- Phase 6: 满屏暴击文字(仅 IronFist/GammaStorm) --------
if (_weaponId == 6 || _weaponId == 7)
{
PlayTextCrit();
yield return new WaitForSeconds(0.5f);
}
// -------- Phase 6.5: 伽马风暴暴击框 --------
if (_weaponId == 7 && critFrameGammaStorm != null)
{
PlayGammaStormCritFrame();
yield return new WaitForSeconds(0.5f);
}
// -------- Phase 7: 等待金币协程完成 --------
if (_activeCoinRoutines > 0)
{
yield return new WaitUntil(() => _activeCoinRoutines <= 0);
}
// -------- Phase 8: 结算特效(⭐ UI,isSelf 放 UI 中心,不再放世界 0,0) --------
if (_settlePrefab != null)
{
if (isSelf)
{
// ⭐ 结算特效是 UI 预制体 → 挂 UI 层放屏幕中心,加入统一销毁列表(die() 时删除)
var settleObj = SpawnUiEffect(_settlePrefab);
// ⭐ 中央结算面板上挂着 BJEndUI(美术已接好 tMP_beishu / tMP_score)→ 写入倍数 + 最终总分
InitSettleBjEndUI(settleObj);
Debug.Log("[XingJiXunHang] Phase8: 结算特效(自己,UI中心)");
}
else
{
// 他人视角:显示在击杀者座位上方(世界坐标)
Vector3 settlePos = GetSeatPosition() + Vector3.up * 2f;
var settleObj = SpawnWorldEffect(_settlePrefab, settlePos);
if (settleObj != null)
settleObj.transform.localScale = Vector3.one * 0.6f;
// ⭐ 他人视角结算面板同样写分数(银币精灵,isSelf=false)
InitSettleBjEndUI(settleObj);
Debug.Log($"[XingJiXunHang] Phase8: 结算特效(他人) pos={settlePos}");
}
}
else
{
Debug.LogWarning($"[XingJiXunHang] 结算预制体为空 tag={_weaponTag}");
}
yield return new WaitForSeconds(settleDuration);
// -------- Phase 10: 触发 EffEnd → 标准收尾 --------
if (!isEffEnd && !isScoreEnd)
{
Debug.Log("[XingJiXunHang] Phase10: 触发 EffEnd");
isGoldEffectStart = false;
EffEnd();
}
}
// ==================== 形态一:富甲天下流程 ====================
/// <summary>
/// 富甲天下流程:无道具武器,直接 CallBJ → 结算。
/// Settle: PW_Effect_Settle_FuJiaTianXia (Node子物体指认Source Image为tj_fcboss_0)
/// </summary>
private IEnumerator FuJiaTianXiaFlow()
{
Debug.Log("[XingJiXunHang] 富甲天下流程开始");
// CallBJ 发红包
for (int i = 0; i < bjCount; i++)
{
CallBJ(bjMinCount, bjMaxCount);
yield return new WaitForSeconds(0.5f);
}
// 等待金币协程完成
if (_activeCoinRoutines > 0)
{
yield return new WaitUntil(() => _activeCoinRoutines <= 0);
}
// 结算特效(⭐ UI,isSelf 放 UI 中心)
if (settleFuJiaTianXia != null)
{
// 音效:富甲天下结算——金币喷出 + 语音(PW_Settle_FuJiaTianXia1 / PW_Settle_FuJiaTianXia2)
AudioManager.Instance.PlayerFXByName("PW_Settle_FuJiaTianXia1");
AudioManager.Instance.PlayerFXByName("PW_Settle_FuJiaTianXia2");
if (isSelf)
{
var settleObj = SpawnUiEffect(settleFuJiaTianXia);
// ⭐ 中央结算面板 BJEndUI 写入倍数 + 最终总分
InitSettleBjEndUI(settleObj);
Debug.Log("[XingJiXunHang] 富甲天下结算(自己,UI中心)");
}
else
{
Vector3 settlePos = GetSeatPosition() + Vector3.up * 2f;
var settleObj = SpawnWorldEffect(settleFuJiaTianXia, settlePos);
if (settleObj != null)
settleObj.transform.localScale = Vector3.one * 0.6f;
// ⭐ 他人视角结算面板同样写分数
InitSettleBjEndUI(settleObj);
Debug.Log($"[XingJiXunHang] 富甲天下结算(他人) pos={settlePos}");
}
}
yield return new WaitForSeconds(settleDuration);
// 触发 EffEnd
if (!isEffEnd && !isScoreEnd)
{
isGoldEffectStart = false;
EffEnd();
}
}
// ==================== FullScreen tap_tip/knock_tip 序列 ====================
/// <summary>
/// 全屏特效的 tap_tip → knock_tip 序列(美术规格要求)。
/// 1. 激活 Tip 容器(tap_tip/knock_tip 的父级,保证子物体能显示)
/// 2. 激活 tap_tip
/// 3. 等待 tapTipDuration
/// 4. 激活 knock_tip(设置海报Sprite)
/// 5. 等待 knockTipDuration
/// 6. 隐藏 Tip 容器(连带 tap_tip/knock_tip 一起隐藏,所有形态通用)
/// </summary>
private IEnumerator PlayFullScreenTipSequence(GameObject fullScreenObj)
{
if (fullScreenObj == null) yield break;
// 查找子物体
Transform tapTip = FindChildRecursive(fullScreenObj.transform, tapTipName);
Transform knockTip = FindChildRecursive(fullScreenObj.transform, knockTipName);
Transform tipContainer = FindChildRecursive(fullScreenObj.transform, tipContainerName);
// ⭐ 序列开始前确保 Tip 容器激活(否则里面的 tap_tip/knock_tip 激活了也看不到)
if (tipContainer != null) tipContainer.gameObject.SetActive(true);
// 初始隐藏
if (tapTip != null) tapTip.gameObject.SetActive(false);
if (knockTip != null) knockTip.gameObject.SetActive(false);
// 1. 激活 tap_tip
if (tapTip != null)
{
tapTip.gameObject.SetActive(true);
Debug.Log($"[XingJiXunHang] FullScreen: 激活 {tapTipName}");
}
yield return new WaitForSeconds(tapTipDuration);
// 2. 激活 knock_tip(设置海报Sprite)
if (knockTip != null)
{
knockTip.gameObject.SetActive(true);
// 设置海报Sprite到 knock_tip 的 Image 组件
if (_posterSprite != null)
{
var img = knockTip.GetComponent<UnityEngine.UI.Image>();
if (img == null)
img = knockTip.GetComponentInChildren<UnityEngine.UI.Image>();
if (img != null)
{
img.sprite = _posterSprite;
Debug.Log($"[XingJiXunHang] FullScreen: 设置 {knockTipName} 海报={_posterSprite.name}");
}
}
Debug.Log($"[XingJiXunHang] FullScreen: 激活 {knockTipName}");
}
yield return new WaitForSeconds(knockTipDuration);
// 3. ⭐ 隐藏 Tip(隐藏 Tip 容器 + 显式隐藏 tap_tip/knock_tip,所有形态通用)
if (tapTip != null) tapTip.gameObject.SetActive(false);
if (knockTip != null) knockTip.gameObject.SetActive(false);
if (tipContainer != null)
{
tipContainer.gameObject.SetActive(false);
Debug.Log($"[XingJiXunHang] FullScreen: 隐藏 Tip 容器 {tipContainerName}");
}
Debug.Log("[XingJiXunHang] FullScreen: 隐藏 Tip");
}
// ==================== 道具动画(各武器不同) ====================
/// <summary>
/// 道具动画,根据武器类型执行不同的子物体操作。
/// </summary>
private IEnumerator PlayPropAnimation()
{
GameObject propObj = SpawnWorldEffect(_propPrefab, diePos);
Debug.Log($"[XingJiXunHang] Phase3: 道具动画 tag={_weaponTag} pos={diePos}");
switch (_weaponId)
{
case 6:
yield return StartCoroutine(IronFistPropAnim(propObj));
break;
case 7:
yield return StartCoroutine(GammaStormPropAnim(propObj));
break;
case 8:
yield return StartCoroutine(SymmetraPropAnim(propObj));
break;
}
// ⭐ 道具不再延迟销毁,统一由 die() 时清空 _spawnedEffects
}
/// <summary>
/// 铁拳道具动画(美术规格):
/// 1. 激活 Hole 子对象
/// 2. Hole 从死亡位置移动到屏幕左下角
/// 3. 隐藏 Hole
/// 4. 激活 Hand
/// </summary>
private IEnumerator IronFistPropAnim(GameObject propObj)
{
Transform hole = FindChildRecursive(propObj.transform, ironFistHoleName);
Transform hand = FindChildRecursive(propObj.transform, ironFistHandName);
// 初始状态
if (hole != null) hole.gameObject.SetActive(false);
if (hand != null) hand.gameObject.SetActive(false);
// 1. 激活 Hole
if (hole != null)
{
hole.gameObject.SetActive(true);
hole.position = diePos;
Debug.Log("[XingJiXunHang] 铁拳: 激活Hole");
// 音效:道具铁拳展示(PW_Prop_IronFist_Show)
AudioManager.Instance.PlayerFXByName("PW_Prop_IronFist_Show");
}
yield return new WaitForSeconds(0.2f);
// 2. Hole 移动到屏幕左下角(加偏移,往屏幕内挪一点)
Vector3 bottomLeft = GetScreenBottomLeft() + ironFistCornerOffset;
if (hole != null)
{
yield return StartCoroutine(MoveTo(hole, bottomLeft, propAnimDuration));
Debug.Log($"[XingJiXunHang] 铁拳: Hole移动到左下角+偏移 {bottomLeft}");
// ⭐ Hole 到位后停留 0.5 秒,让玩家看清再释放(之前太快直接释放 Hand)
yield return new WaitForSeconds(ironFistHoldDelay);
Debug.Log($"[XingJiXunHang] 铁拳: Hole 到位停留 {ironFistHoldDelay}s");
}
// 3. 隐藏 Hole
if (hole != null) hole.gameObject.SetActive(false);
// 4. 激活 Hand(击打)
Vector3 handPos = bottomLeft;
if (hand != null)
{
hand.gameObject.SetActive(true);
hand.position = handPos;
Debug.Log("[XingJiXunHang] 铁拳: 激活Hand");
// 音效:道具铁拳击打节拍(PW_Prop_IronFist_Knock_Beat)
AudioManager.Instance.PlayerFXByName("PW_Prop_IronFist_Knock_Beat");
}
// ⭐ Hand 激活后延迟 2 秒,然后直接把 Hand 隐藏
yield return new WaitForSeconds(ironFistAwardDelay);
Debug.Log($"[XingJiXunHang] 铁拳: Hand 激活后延迟 {ironFistAwardDelay}s,隐藏 Hand");
if (hand != null) hand.gameObject.SetActive(false);
// ⭐ 铁拳击打:范围内普通鱼从近到远逐个死亡(总时长约1秒),金币从左下角飞向炮台
if (ironFistKillFish)
{
yield return StartCoroutine(KillFishByIronFistRoutine(handPos));
}
yield return new WaitForSeconds(0.3f);
}
/// <summary>
/// 铁拳击打杀鱼:检测 Hand 半径 ironFistKillRadius 范围内的普通鱼,
/// 计算鱼与预制体(Hand)的距离,从近到远逐个死亡(总时长约 ironFistKillTotalTime 秒),
/// 金币从鱼的位置(左下角)飞向炮台座位(从下往上延伸)。
/// </summary>
private IEnumerator KillFishByIronFistRoutine(Vector3 center)
{
if (ironFistKillLayer.value == 0)
{
Debug.LogWarning("[XingJiXunHang] 铁拳杀鱼层 ironFistKillLayer 未设置(Inspector 选 Fish 层),跳过杀鱼");
ironFistKillFish = false; // 只提示一次
yield break;
}
Collider2D[] hits = Physics2D.OverlapCircleAll(center, Mathf.Max(0.1f, ironFistKillRadius), ironFistKillLayer);
if (hits == null || hits.Length == 0) yield break;
// 收集范围内未炸过的普通鱼
List<MonsterContral> fishes = new List<MonsterContral>();
foreach (var hit in hits)
{
if (hit == null || !hit.CompareTag("Fish")) continue;
var monster = hit.GetComponentInParent<MonsterContral>();
if (monster == null) continue;
if (monster.fishenum != FishTypeEnum.普通鱼) continue; // 只炸普通鱼
if (_netKilledFish.Contains(monster.guid)) continue; // 已炸过跳过
fishes.Add(monster);
}
if (fishes.Count == 0) yield break;
// ⭐ 按距离 Hand(预制体)从近到远排序
fishes.Sort((a, b) =>
Vector2.Distance(a.transform.position, center)
.CompareTo(Vector2.Distance(b.transform.position, center)));
// ⭐ 逐个杀:每只间隔 = 总时长/数量,整体约 1 秒
float interval = ironFistKillTotalTime / fishes.Count;
Vector3 turretPos = GetSeatPosition(); // 金币飞向炮台
int killed = 0;
foreach (var monster in fishes)
{
// ⭐ 分数加满(=maxScore)后停止杀鱼
if (BossScoreFull)
{
Debug.Log($"[XingJiXunHang] 铁拳: 面板分已达上限 {maxScore},停止杀鱼");
break;
}
if (monster == null) continue;
if (!_netKilledFish.Add(monster.guid)) continue;
var coinNum = new RedPacketRecord
{
Amount = paoBei * monster.monsterData.fishTypeConfig.fishScore
};
// ⭐ 飞机死亡掉金币(飞向炮台),分数同时加到 Boss 面板(封顶 maxScore)
CreateFishManager.Instance.Id_DieFish_FX(monster.guid, coinNum, turretPos, isLocal: isSelf);
AddBossPanelScore(coinNum.Amount);
killed++;
Debug.Log($"[XingJiXunHang] 铁拳: 从近到远杀鱼[{killed}/{fishes.Count}] 距离={Vector2.Distance(monster.transform.position, center):0.00}");
yield return new WaitForSeconds(interval);
}
Debug.Log($"[XingJiXunHang] 铁拳: 击打杀鱼完成 {killed} 条(总时长约{ironFistKillTotalTime}s)");
}
/// <summary>
/// 光子道具动画(美术规格):
/// 1. Prop 从死亡位置移动到屏幕中心
/// 2. 隐藏 Lock
/// 3. PropL 和 PropR 分别移动到屏幕两边
/// 4. 激活 laser 子物体
/// </summary>
private IEnumerator SymmetraPropAnim(GameObject propObj)
{
Transform lockObj = FindChildRecursive(propObj.transform, symmetraLockName);
Transform propL = FindChildRecursive(propObj.transform, symmetraPropLName);
Transform propR = FindChildRecursive(propObj.transform, symmetraPropRName);
Transform laser = FindChildRecursive(propObj.transform, symmetraLaserName);
// 初始状态
if (lockObj != null) lockObj.gameObject.SetActive(true);
if (propL != null) propL.gameObject.SetActive(false);
if (propR != null) propR.gameObject.SetActive(false);
if (laser != null) laser.gameObject.SetActive(false);
// 1. Prop 移动到屏幕中心
propObj.transform.position = diePos;
yield return StartCoroutine(MoveTo(propObj.transform, Vector3.zero, propAnimDuration));
Debug.Log("[XingJiXunHang] 光子: 移动到屏幕中心");
// 2. 隐藏 Lock
if (lockObj != null)
{
lockObj.gameObject.SetActive(false);
Debug.Log("[XingJiXunHang] 光子: 隐藏Lock");
}
// 3. PropL/PropR 移动到屏幕两边
Vector3 leftPos = GetScreenLeft();
Vector3 rightPos = GetScreenRight();
if (propL != null && propR != null)
{
propL.gameObject.SetActive(true);
propR.gameObject.SetActive(true);
propL.position = Vector3.zero;
propR.position = Vector3.zero;
// 音效:道具对称体到达边缘(PW_Prop_Symmetra_To_Edge)
AudioManager.Instance.PlayerFXByName("PW_Prop_Symmetra_To_Edge");
StartCoroutine(MoveTo(propL, leftPos, propAnimDuration * 0.5f));
yield return StartCoroutine(MoveTo(propR, rightPos, propAnimDuration * 0.5f));
Debug.Log("[XingJiXunHang] 光子: PropL/PropR移动到两边");
}
// 4. 激活 laser
if (laser != null)
{
laser.gameObject.SetActive(true);
// 音效:道具对称体接触(PW_Prop_Symmetra_Contact)
AudioManager.Instance.PlayerFXByName("PW_Prop_Symmetra_Contact");
// ⭐ laser 里的 LineRenderer:端点用 左右(PropL/PropR)里面各自的 laser 子对象位置
var lr = laser.GetComponent<LineRenderer>();
if (lr != null && propL != null && propR != null)
{
// 找左右里面的 laser 发射点对象(找不到则回退用 PropL/PropR 自身)
Transform laserL = FindChildRecursive(propL, symmetraLaserName);
Transform laserR = FindChildRecursive(propR, symmetraLaserName);
Vector3 endL = laserL != null ? laserL.position : propL.position;
Vector3 endR = laserR != null ? laserR.position : propR.position;
lr.positionCount = 2;
if (lr.useWorldSpace)
{
lr.SetPosition(0, endL);
lr.SetPosition(1, endR);
}
else
{
// 本地坐标空间:转成 laser 的本地坐标
lr.SetPosition(0, laser.InverseTransformPoint(endL));
lr.SetPosition(1, laser.InverseTransformPoint(endR));
}
Debug.Log($"[XingJiXunHang] 光子: laser LineRenderer 端点 L={endL} R={endR} useWorldSpace={lr.useWorldSpace}");
}
else
{
Debug.LogWarning($"[XingJiXunHang] 光子: laser 无 LineRenderer 或 PropL/PropR 为空(lr={lr != null} propL={propL != null} propR={propR != null})");
}
// ⭐ laser 设置好并激活后,上下摆动(让激光看起来在动)
if (laserSwingCycles > 0)
{
yield return StartCoroutine(LaserSwing(laser, laserSwingAmplitude, laserSwingCycles));
}
}
// ⭐ 4条激光沿各自方向伸展到最长位置(laser1:+X → laser2:-Y → laser3:+Y → laser4:-X)
yield return StartCoroutine(LaserExpand(propObj));
// ⭐ 电网:Prop 整体上下扫动 + 碰到飞机(鱼)爆炸
if (netSweepCycles > 0)
{
// 音效:道具对称体攻击(PW_Prop_Symmetra_Attack,电网扫动)
AudioManager.Instance.PlayerFXByName("PW_Prop_Symmetra_Attack");
yield return StartCoroutine(ElectricNetSweep(propObj, propL, propR));
}
// ⭐ 激光结束:等 1 秒后直接隐藏 Prop(不再一直显示到爆奖结束才回收)
yield return new WaitForSeconds(symmetraPropHideDelay);
if (propObj != null)
{
propObj.SetActive(false);
Debug.Log($"[XingJiXunHang] 光子: 激光结束等待 {symmetraPropHideDelay}s 后隐藏 Prop");
}
}
/// <summary>
/// 4条激光伸展:laser1 向 +X(屏幕最右)、laser2 向 -Y(屏幕最下)、
/// laser3 向 +Y(屏幕最上)、laser4 向 -X(屏幕最左),到达最长位置。
/// </summary>
private IEnumerator LaserExpand(GameObject propObj)
{
if (propObj == null) yield break;
Transform l1 = FindChildRecursive(propObj.transform, laser1Name);
Transform l2 = FindChildRecursive(propObj.transform, laser2Name);
Transform l3 = FindChildRecursive(propObj.transform, laser3Name);
Transform l4 = FindChildRecursive(propObj.transform, laser4Name);
int count = 0;
// laser1 → +X 正方向(保持当前 Y,X 到屏幕最右)
if (l1 != null)
{
Vector3 dest = new Vector3(GetScreenRight().x, l1.position.y, 0);
StartCoroutine(MoveTo(l1, dest, laserExpandSpeed));
count++;
}
// laser2 → -Y 方向(保持当前 X,Y 到屏幕最下)
if (l2 != null)
{
Vector3 dest = new Vector3(l2.position.x, GetScreenBottom().y, 0);
StartCoroutine(MoveTo(l2, dest, laserExpandSpeed));
count++;
}
// laser3 → +Y 方向(保持当前 X,Y 到屏幕最上)
if (l3 != null)
{
Vector3 dest = new Vector3(l3.position.x, GetScreenTop().y, 0);
StartCoroutine(MoveTo(l3, dest, laserExpandSpeed));
count++;
}
// laser4 → -X 方向(保持当前 Y,X 到屏幕最左)
if (l4 != null)
{
Vector3 dest = new Vector3(GetScreenLeft().x, l4.position.y, 0);
StartCoroutine(MoveTo(l4, dest, laserExpandSpeed));
count++;
}
yield return new WaitForSeconds(laserExpandSpeed + 0.2f);
Debug.Log($"[XingJiXunHang] 光子: 4条激光伸展完成 找到{count}条({laser1Name}/{laser2Name}/{laser3Name}/{laser4Name})");
}
/// <summary>
/// 电网扫动:Prop 整体上下移动(幅度 netSweepAmplitude,最多 netSweepCycles 次),
/// 扫动过程中每帧检测 PropL~PropR 之间的矩形区域,碰到普通鱼 → 爆炸(Id_DieFish_FX)。
/// ⭐ 不需要一直按设定的圈数移动:面板分加满(=maxScore)后提前停止扫动。
/// </summary>
private IEnumerator ElectricNetSweep(GameObject propObj, Transform propL, Transform propR)
{
if (propObj == null) yield break;
Vector3 basePos = propObj.transform.position;
for (int i = 0; i < netSweepCycles; i++)
{
// ⭐ 面板分加满:提前停止扫动(不需要一直按圈数移动)
if (BossScoreFull)
{
Debug.Log($"[XingJiXunHang] 光子: 面板分已达上限 {maxScore},提前停止电网扫动");
break;
}
// 上扫
yield return StartCoroutine(SweepSegment(propObj, propL, propR, basePos + Vector3.up * netSweepAmplitude));
if (BossScoreFull) break;
// 下扫
yield return StartCoroutine(SweepSegment(propObj, propL, propR, basePos - Vector3.up * netSweepAmplitude));
}
// 回原位
yield return StartCoroutine(SweepSegment(propObj, propL, propR, basePos));
Debug.Log($"[XingJiXunHang] 光子: 电网扫动结束(计划{netSweepCycles}圈)炸鱼数={_netKilledFish.Count}");
}
/// <summary>电网从当前位置移动到 dest(逐帧插值 + 途中杀鱼)</summary>
private IEnumerator SweepSegment(GameObject propObj, Transform propL, Transform propR, Vector3 dest)
{
if (propObj == null) yield break;
Vector3 start = propObj.transform.position;
float elapsed = 0f;
while (elapsed < netSweepSpeed)
{
elapsed += Time.deltaTime;
float t = netSweepSpeed > 0 ? elapsed / netSweepSpeed : 1f;
if (t > 1f) t = 1f;
propObj.transform.position = Vector3.Lerp(start, dest, t);
if (netKillFish)
KillFishInNet(propL, propR);
// ⭐ 分数加满:本段提前结束
if (BossScoreFull) break;
yield return null;
}
propObj.transform.position = dest;
if (netKillFish && !BossScoreFull)
KillFishInNet(propL, propR);
}
/// <summary>检测 PropL~PropR 之间的矩形电网区域,普通鱼直接爆炸</summary>
private void KillFishInNet(Transform propL, Transform propR)
{
if (propL == null || propR == null) return;
if (netKillLayer.value == 0)
{
Debug.LogWarning("[XingJiXunHang] 电网杀鱼层 netKillLayer 未设置(Inspector 选 Fish 层),跳过杀鱼");
netKillFish = false; // 只提示一次
return;
}
// 电网矩形区域:左右端 ± 半径
float radius = Mathf.Max(0.1f, netKillRadius);
Vector2 min = new Vector2(
Mathf.Min(propL.position.x, propR.position.x) - radius,
Mathf.Min(propL.position.y, propR.position.y) - radius);
Vector2 max = new Vector2(
Mathf.Max(propL.position.x, propR.position.x) + radius,
Mathf.Max(propL.position.y, propR.position.y) + radius);
Collider2D[] hits = Physics2D.OverlapAreaAll(min, max, netKillLayer);
if (hits == null || hits.Length == 0) return;
Vector3 turretPos = GetSeatPosition(); // 金币飞向炮台
int killed = 0;
foreach (var hit in hits)
{
// ⭐ 分数加满(=maxScore)后停止杀鱼
if (BossScoreFull)
{
Debug.Log($"[XingJiXunHang] 电网: 面板分已达上限 {maxScore},停止杀鱼");
break;
}
if (hit == null || !hit.CompareTag("Fish")) continue;
var monster = hit.GetComponentInParent<MonsterContral>();
if (monster == null) continue;
if (monster.fishenum != FishTypeEnum.普通鱼) continue; // 只炸普通鱼
if (!_netKilledFish.Add(monster.guid)) continue; // 已炸过跳过
var coinNum = new RedPacketRecord
{
Amount = paoBei * monster.monsterData.fishTypeConfig.fishScore
};
// ⭐ 飞机死亡掉金币(飞向炮台),分数同时加到 Boss 面板(封顶 maxScore)
CreateFishManager.Instance.Id_DieFish_FX(monster.guid, coinNum, turretPos, isLocal: isSelf);
AddBossPanelScore(coinNum.Amount);
killed++;
}
if (killed > 0)
Debug.Log($"[XingJiXunHang] 电网炸鱼 {killed} 条");
}
/// <summary>laser 上下摆动动画(幅度 amplitude,来回 cycles 次,结束后回到原位)</summary>
private IEnumerator LaserSwing(Transform target, float amplitude, int cycles)
{
if (target == null) yield break;
Vector3 basePos = target.position;
for (int i = 0; i < cycles; i++)
{
yield return StartCoroutine(MoveTo(target, basePos + Vector3.up * amplitude, 0.3f));
yield return StartCoroutine(MoveTo(target, basePos - Vector3.up * amplitude, 0.3f));
}
// 回到原位
yield return StartCoroutine(MoveTo(target, basePos, 0.2f));
Debug.Log($"[XingJiXunHang] 光子: laser 上下摆动完成 cycles={cycles}");
}
/// <summary>
/// 伽马风暴道具动画(美术规格):
/// 1. Prop 从死亡位置移动到屏幕中心
/// 2. 显示四根射线(激光)
/// 3. 等待 gammaRotateDelay(默认1秒)
/// 4. 开始旋转(Prop 整体绕 Z 轴),旋转中碰到激光的普通鱼直接死亡
/// </summary>
private IEnumerator GammaStormPropAnim(GameObject propObj)
{
// 1. 移动到屏幕中心
propObj.transform.position = diePos;
yield return StartCoroutine(MoveTo(propObj.transform, Vector3.zero, propAnimDuration));
Debug.Log("[XingJiXunHang] 伽马风暴: 移动到屏幕中心");
// 2. 显示四根射线(激光,默认隐藏则激活)
Transform[] lasers = FindGammaLasers(propObj);
int shown = 0;
foreach (var l in lasers)
{
if (l != null)
{
l.gameObject.SetActive(true);
shown++;
}
}
Debug.Log($"[XingJiXunHang] 伽马风暴: 显示激光 {shown}/{lasers.Length} 根");
// 音效:道具伽马风暴准备(PW_Prop_GammaStorm_Ready)
AudioManager.Instance.PlayerFXByName("PW_Prop_GammaStorm_Ready");
// ⭐ 射线长度 = 斜边(直角边 = 屏幕宽度一半 / 高度一半),延伸到屏幕角落
float diag = GetScreenHalfDiagonal();
UpdateGammaLaserEndpoints(lasers, propObj.transform.position, diag);
Debug.Log($"[XingJiXunHang] 伽马风暴: 射线长度设为斜边 {diag}");
// 3. 等待 1 秒(Tip 隐藏后停顿,让玩家看清激光)
yield return new WaitForSeconds(gammaRotateDelay);
Debug.Log($"[XingJiXunHang] 伽马风暴: 激光显示等待 {gammaRotateDelay}s 后开始旋转");
// 音效:道具伽马风暴攻击(PW_Prop_GammaStorm_Attack,开始旋转)
AudioManager.Instance.PlayerFXByName("PW_Prop_GammaStorm_Attack");
// 4. 旋转 + 射线杀鱼
yield return StartCoroutine(GammaRotateAndKill(propObj, lasers, diag));
}
/// <summary>按 gammaLaserNames 查找四根射线子物体</summary>
private Transform[] FindGammaLasers(GameObject propObj)
{
if (propObj == null || gammaLaserNames == null || gammaLaserNames.Length == 0)
return new Transform[0];
Transform[] lasers = new Transform[gammaLaserNames.Length];
for (int i = 0; i < gammaLaserNames.Length; i++)
{
// ⭐ 先忽略大小写找(预制体实际小写 laser1~4),找不到再精确匹配
lasers[i] = FindChildIgnoreCase(propObj.transform, gammaLaserNames[i]);
if (lasers[i] == null)
lasers[i] = FindChildRecursive(propObj.transform, gammaLaserNames[i]);
if (lasers[i] == null)
Debug.LogWarning($"[XingJiXunHang] 伽马风暴: 找不到射线子物体 {gammaLaserNames[i]}");
}
return lasers;
}
/// <summary>
/// 伽马风暴:Prop 整体旋转 gammaRotateAngle 度(默认80°)。
/// 转到 gammaShrinkStartAngle(45°)后激光开始慢慢缩短,转到终点时缩到看不见(长度 0)。
/// 旋转中射线扫到的普通鱼死亡。
/// </summary>
private IEnumerator GammaRotateAndKill(GameObject propObj, Transform[] lasers, float laserLength)
{
if (propObj == null) yield break;
float rotated = 0f;
int killCount = 0;
float shrinkSpan = Mathf.Max(0.01f, gammaRotateAngle - gammaShrinkStartAngle);
while (rotated < gammaRotateAngle)
{
float step = gammaRotateSpeed * Time.deltaTime;
propObj.transform.Rotate(0f, 0f, step);
rotated += step;
// ⭐ 激光长度:前 gammaShrinkStartAngle 度保持全斜边,之后线性缩短到 0(看不见)
float curLen = laserLength;
if (rotated > gammaShrinkStartAngle)
{
float t = (rotated - gammaShrinkStartAngle) / shrinkSpan;
curLen = laserLength * (1f - Mathf.Clamp01(t));
}
// 每帧更新射线端点(LineRenderer 世界坐标不随父旋转,必须跟随激光新朝向)
UpdateGammaLaserEndpoints(lasers, propObj.transform.position, curLen);
if (gammaKillFish)
KillFishByGammaLasers(propObj.transform, lasers, curLen, ref killCount);
yield return null;
}
// 终点:激光缩到 0 长度(隐藏)
UpdateGammaLaserEndpoints(lasers, propObj.transform.position, 0f);
Debug.Log($"[XingJiXunHang] 伽马风暴: 旋转完成 {rotated:0.#}°(45°后激光缩短至消失)炸鱼 {killCount} 条");
// ⭐ 分不够就直接补齐到 maxScore(旋转杀鱼没杀满时,面板补满)
if (!BossScoreFull)
{
long need = maxScore - _bossPanelScore;
if (need > 0)
{
AddBossPanelScore(need);
Debug.Log($"[XingJiXunHang] 伽马风暴: 分不够,直接补齐 {need} → {_bossPanelScore}");
}
}
}
/// <summary>
/// 设置四根射线长度为斜边:LineRenderer 端点 = 中心 → 中心 + 射线朝向×长度。
/// 射线朝向优先取"激光当前位置-中心"(随 Prop 旋转自动更新),零向量时用激光本地 Y 轴方向。
/// </summary>
private void UpdateGammaLaserEndpoints(Transform[] lasers, Vector3 center, float length)
{
if (lasers == null) return;
foreach (var laser in lasers)
{
if (laser == null) continue;
// 射线方向:激光相对中心的位置(旋转后自动更新);为 0 则用激光自身的朝向
Vector2 dir = (Vector2)laser.position - (Vector2)center;
if (dir.sqrMagnitude < 0.0001f)
dir = laser.up; // 本地 Y 轴世界方向(或 right,按预制体实际朝向)
if (dir.sqrMagnitude < 0.0001f) dir = Vector2.right;
dir.Normalize();
Vector3 endWorld = center + (Vector3)dir * length;
var lr = laser.GetComponent<LineRenderer>();
if (lr != null)
{
lr.positionCount = 2;
if (lr.useWorldSpace)
{
lr.SetPosition(0, center);
lr.SetPosition(1, endWorld);
}
else
{
lr.SetPosition(0, laser.InverseTransformPoint(center));
lr.SetPosition(1, laser.InverseTransformPoint(endWorld));
}
}
else
{
// 非 LineRenderer:把激光整体拉伸到目标长度(假定沿本地 Y 轴伸长)
float curLen = laser.localScale.y;
if (curLen < 0.001f) curLen = 1f;
laser.localScale = new Vector3(laser.localScale.x, length / curLen, laser.localScale.z);
}
}
}
/// <summary>屏幕半宽半高为直角边的斜边长度(世界单位)= 屏幕中心到角落的距离</summary>
private float GetScreenHalfDiagonal()
{
var cam = Camera.main;
if (cam == null) return 10f;
float z = cam.transform.position.z * -1f;
Vector3 center = cam.ScreenToWorldPoint(new Vector3(Screen.width * 0.5f, Screen.height * 0.5f, z));
Vector3 corner = cam.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, z));
float halfW = Mathf.Abs(corner.x - center.x);
float halfH = Mathf.Abs(corner.y - center.y);
return Mathf.Sqrt(halfW * halfW + halfH * halfH);
}
/// <summary>沿每根射线方向 Raycast 检测(长度=斜边),扫到的普通鱼直接爆炸(防重复)</summary>
private void KillFishByGammaLasers(Transform center, Transform[] lasers, float laserLength, ref int killCount)
{
if (center == null || lasers == null || lasers.Length == 0) return;
if (gammaKillLayer.value == 0)
{
Debug.LogWarning("[XingJiXunHang] 伽马风暴杀鱼层 gammaKillLayer 未设置(Inspector 选 Fish 层),跳过杀鱼");
gammaKillFish = false; // 只提示一次
return;
}
Vector3 turretPos = GetSeatPosition(); // 金币飞向炮台
foreach (var laser in lasers)
{
if (laser == null) continue;
// 射线:从 Prop 中心朝激光朝向,距离 = 斜边长度(整根激光覆盖范围)
Vector2 origin = center.position;
Vector2 toEnd = (Vector2)laser.position - origin;
Vector2 dir = toEnd.sqrMagnitude < 0.0001f ? (Vector2)laser.up : toEnd.normalized;
if (dir.sqrMagnitude < 0.0001f) dir = Vector2.right;
dir.Normalize();
RaycastHit2D[] hits = Physics2D.RaycastAll(origin, dir, laserLength, gammaKillLayer);
if (hits == null || hits.Length == 0) continue;
foreach (var hit in hits)
{
// ⭐ 分数加满(=maxScore)后停止杀鱼
if (BossScoreFull)
{
Debug.Log($"[XingJiXunHang] 伽马射线: 面板分已达上限 {maxScore},停止杀鱼");
return;
}
if (hit.collider == null || !hit.collider.CompareTag("Fish")) continue;
var monster = hit.collider.GetComponentInParent<MonsterContral>();
if (monster == null) continue;
if (monster.fishenum != FishTypeEnum.普通鱼) continue; // 只炸普通鱼
if (!_netKilledFish.Add(monster.guid)) continue; // 已炸过跳过
var coinNum = new RedPacketRecord
{
Amount = paoBei * monster.monsterData.fishTypeConfig.fishScore
};
// ⭐ 飞机死亡掉金币(飞向炮台),分数同时加到 Boss 面板(封顶 maxScore)
CreateFishManager.Instance.Id_DieFish_FX(monster.guid, coinNum, turretPos, isLocal: isSelf);
AddBossPanelScore(coinNum.Amount);
killCount++;
}
}
}
// ==================== 满屏暴击文字 ====================
/// <summary>
/// 播放满屏暴击文字(IronFist/GammaStorm 用)。
/// 自己看 TextCrit_Self,他人看 TextCrit_Other。
///
/// ⭐ 文字内容:预制体里写死的是占位 '123',必须由代码写入「爆奖小鱼死亡累加的面板分」
/// (<see cref="_bossPanelScore"/>,与奖励框上滚动的分同一个值,封顶 maxScore)。
/// ⭐ 层级:克隆体根上挂着嵌套 Canvas,美术里是无效层ID + order 5,
/// 会被 FullScreen 框(Default, order 12)压在下面 → 这里显式改成 Default + 更高 order。
/// </summary>
private void PlayTextCrit()
{
GameObject critPrefab = isSelf ? textCritSelf : textCritOther;
if (critPrefab == null)
{
Debug.LogWarning("[XingJiXunHang] 暴击文字预制体为空");
return;
}
GameObject critObj;
if (isSelf)
{
// ⭐ 满屏暴击文字是 UI → 放 UI 中心,加入统一销毁列表
critObj = SpawnUiEffect(critPrefab);
Debug.Log("[XingJiXunHang] 播放满屏暴击(自己,UI中心)");
}
else
{
Vector3 critPos = GetSeatPosition() + Vector3.up * 2f;
critObj = SpawnWorldEffect(critPrefab, critPos);
if (critObj != null)
critObj.transform.localScale = Vector3.one * 0.6f;
Debug.Log($"[XingJiXunHang] 播放满屏暴击(他人) pos={critPos}");
}
if (critObj == null)
{
Debug.LogWarning("[XingJiXunHang] 暴击文字生成失败,跳过分数写入");
return;
}
// ⭐ 写入分数:爆奖过程中小鱼死亡累加的面板分(_bossPanelScore)
// 该 TMP 的 spriteAsset 已配好「数字→图片」映射,直接赋数字即可
var critTmp = critObj.GetComponentInChildren<TMPro.TextMeshProUGUI>(true);
if (critTmp != null)
{
critTmp.text = _bossPanelScore.ToString();
Debug.Log($"[XingJiXunHang] 暴击文字分数 = {_bossPanelScore}(上限{maxScore})");
if (_bossPanelScore <= 0)
Debug.LogWarning("[XingJiXunHang] 暴击文字分数为 0:本次没有小鱼死亡累加(面板分仍为 0)");
}
else
{
Debug.LogWarning($"[XingJiXunHang] 暴击文字 {critObj.name} 上没找到 TMP,分数未写入");
}
// ⭐ 修层级:嵌套 Canvas → Default(0) + 高于 FullScreen(order 12),
// 否则暴击文字会被全屏特效框挡住(美术里的 -559336389 在本项目 TagManager 里不存在)
var critCanvas = critObj.GetComponent<Canvas>();
if (critCanvas == null)
critCanvas = critObj.GetComponentInChildren<Canvas>(true);
if (critCanvas != null)
{
critCanvas.overrideSorting = true;
critCanvas.sortingLayerID = 0; // Default(本项目只有 Default 一个排序层)
critCanvas.sortingOrder = 15; // 高于 FullScreen 的 12
Debug.Log($"[XingJiXunHang] 暴击文字 Canvas 层级修正: Default / order={critCanvas.sortingOrder}");
}
else
{
Debug.LogWarning($"[XingJiXunHang] 暴击文字 {critObj.name} 上没找到 Canvas,层级未修正");
}
}
// ==================== 伽马风暴暴击框 ====================
/// <summary>
/// 播放伽马风暴暴击框(PW_Effect_Crit_GammaStorm)。
/// 仅 weaponID=7 (GammaStorm) 有效。
/// </summary>
public void PlayGammaStormCritFrame()
{
if (_weaponTag != "GammaStorm" || critFrameGammaStorm == null) return;
// ⭐ 伽马暴击框是 UI → 放 UI 中心,加入统一销毁列表
SpawnUiEffect(critFrameGammaStorm);
Debug.Log("[XingJiXunHang] 播放伽马风暴暴击框(UI中心)");
}
// ==================== 辅助方法 ====================
/// <summary>每死亡一个飞机,就往 Boss 分数面板上加一个分(面板独立于 Boss 本体,删除后仍有效)</summary>
private void AddBossPanelScore(long amount)
{
if (amount <= 0) return;
// ⭐ 面板加分不能超过金币上限 maxScore(封顶)
long newScore = _bossPanelScore + amount;
if (newScore > maxScore)
newScore = maxScore;
if (newScore == _bossPanelScore) return; // 已封顶,无变化
_bossPanelScore = newScore;
EnsureBossScoreTmp();
if (_bossScoreTmp != null)
{
_bossScoreTmp.text = _bossPanelScore.ToString();
Debug.Log($"[XingJiXunHang] Boss分数面板 +{amount} = {_bossPanelScore}(上限{maxScore})");
}
}
/// <summary>面板分是否已加满(= maxScore 后停止杀鱼)</summary>
private bool BossScoreFull => _bossPanelScore >= maxScore;
/// <summary>确保 Boss 分数面板 TMP 可用(优先用 bossCtrl 已创建的,否则手动创建)</summary>
private void EnsureBossScoreTmp()
{
if (_bossScoreTmp != null) return;
// 1. 从 bossCtrl 拿(Boss 删除前已缓存的面板引用)
if (bossCtrl != null && bossCtrl.tMP_CurrBossScore != null)
{
_bossScoreTmp = bossCtrl.tMP_CurrBossScore;
return;
}
// 2. ⭐ 用当前武器对应的奖励框作为加分面板(详细指定出的分数预制体),
// 不再使用 Manager 默认的 BJEventBase 分数预制体(dieFXPrefabAwardTmp)
var prefab = _awardFramePrefab;
var parent = CreateFishManager.Instance != null && CreateFishManager.Instance.currGameType != null
? CreateFishManager.Instance.currGameType.subPanelParent : null;
if (prefab == null || parent == null) return;
var board = Instantiate(prefab, parent);
// ⭐ 加分面板位置放在座位上方才是正常的
board.transform.position = GetSeatPosition() + Vector3.up * 150f;
// 从 BJImage 拿加分 TMP(指向奖励框自身的 Num),并按 isSelf 应用金/银 sprite 样式
var bjImage = board.GetComponentInChildren<BJImage>();
if (bjImage != null && bjImage.tmp_CoinNum != null)
{
_bossScoreTmp = bjImage.tmp_CoinNum;
_bossScoreTmp.text = "0";
var spriteAsset = isSelf ? bjImage.goldSprite : bjImage.silverSprite;
if (spriteAsset != null)
_bossScoreTmp.spriteAsset = spriteAsset;
}
else
{
_bossScoreTmp = board.GetComponentInChildren<TMPro.TextMeshProUGUI>();
}
}
/// <summary>
/// ⭐ 给结算特效上的 BJEndUI 写入爆奖分数(倍数 + 最终总分)。
///
/// 结算预制体(PW_Effect_Settle_*_XingJiXunHang,Art/PW_Effect_Settle_* 的变体)
/// 根节点上挂了 BJEndUI,tMP_beishu / tMP_score 已由美术接好,代码只需 Init:
/// isSelf → 面板挂在 UI 层、屏幕中心(SpawnUiEffect);他人 → 击杀者座位上方(银币样式)。
/// 不写会一直显示预制体里的占位文本,所以爆奖结束前必须 Init 一次。
/// </summary>
/// <param name="settleObj">已实例化的结算特效对象(SpawnUiEffect / SpawnWorldEffect 的返回值)</param>
private void InitSettleBjEndUI(GameObject settleObj)
{
if (settleObj == null) return;
// 优先根节点(预制体挂在根上),回退子节点(防止后续挪到子物体)
var endUI = settleObj.GetComponent<BJEndUI>();
if (endUI == null)
endUI = settleObj.GetComponentInChildren<BJEndUI>(true);
if (endUI == null)
{
Debug.LogWarning($"[XingJiXunHang] 结算特效 {settleObj.name} 上未找到 BJEndUI,跳过分数写入");
return;
}
// ⭐ 空安全:TMP 引用全空(预制体没接)时不调 Init,避免空引用
if (endUI.tMP_beishu == null && endUI.tMP_score == null)
{
Debug.LogWarning($"[XingJiXunHang] 结算面板 BJEndUI 未接 tMP_beishu/tMP_score({settleObj.name}),跳过分数写入");
return;
}
// ⭐ 分数 = maxScore(服务端下发的爆奖最终总分),与基类 ShowBJEndUI 保持一致;
// 他人视角传 isSelf=false → 银币精灵
endUI.Init(bJbeishu, maxScore, isSelf);
Debug.Log($"[XingJiXunHang] 结算面板 BJEndUI 写入分数: 倍数={bJbeishu} 分数={maxScore} " +
$"isSelf={isSelf} obj={settleObj.name}");
}
/// <summary>
/// 缓存 UI 层父节点 + Canvas:
/// 父节点 = currGameType.subPanelParent.parent(与爆奖预制体同层,随 UI 卸载自动清理)
/// Canvas = UIManager.uiRoot 上的 Canvas
/// </summary>
private void EnsureUiLayer()
{
if (_uiLayer != null && _uiCanvas != null) return;
if (CreateFishManager.Instance != null && CreateFishManager.Instance.currGameType != null)
{
var subPanel = CreateFishManager.Instance.currGameType.subPanelParent;
if (subPanel != null)
_uiLayer = subPanel.parent;
}
if (UIManager.Instance != null && UIManager.Instance.uiRoot != null)
_uiCanvas = UIManager.Instance.uiRoot.GetComponent<Canvas>();
if (_uiCanvas == null && _uiLayer != null)
_uiCanvas = _uiLayer.GetComponentInParent<Canvas>();
}
/// <summary>
/// ⭐ 在 UI 层实例化 UI 类特效(FullScreen/AwardFrame/Settle/Crit 等),放到 UI 中心。
/// 不再放世界坐标 (0,0)。自动加入 _spawnedEffects,die() 时统一销毁。
/// </summary>
private GameObject SpawnUiEffect(GameObject prefab)
{
if (prefab == null) return null;
EnsureUiLayer();
if (_uiLayer == null || _uiCanvas == null)
{
// 兜底:UI 层拿不到才放世界层(原逻辑)
var fallback = Instantiate(prefab, Vector3.zero, Quaternion.identity,
CreateFishManager.Instance.bjEffectRoot);
_spawnedEffects.Add(fallback);
return fallback;
}
var obj = Instantiate(prefab, _uiLayer);
var rt = obj.GetComponent<RectTransform>();
if (rt != null)
{
rt.anchoredPosition = CoordinateTransformation.ScreenPoint_ToCanvas(
new Vector2(Screen.width / 2f, Screen.height / 2f), _uiCanvas);
}
_spawnedEffects.Add(obj);
return obj;
}
/// <summary>在世界层实例化 2D 类特效(道具出场/道具动画),保持世界坐标,加入统一销毁列表</summary>
private GameObject SpawnWorldEffect(GameObject prefab, Vector3 pos)
{
if (prefab == null) return null;
var obj = Instantiate(prefab, pos, Quaternion.identity,
CreateFishManager.Instance.bjEffectRoot);
_spawnedEffects.Add(obj);
return obj;
}
/// <summary>递归查找子物体(⭐ 忽略大小写,防预制体命名 laser1/Laser1 不一致)</summary>
private Transform FindChildRecursive(Transform parent, string name)
{
if (parent.name == name)
return parent;
for (int i = 0; i < parent.childCount; i++)
{
var result = FindChildRecursive(parent.GetChild(i), name);
if (result != null) return result;
}
return null;
}
/// <summary>递归查找子物体(忽略大小写版本,找不到再试精确)</summary>
private Transform FindChildIgnoreCase(Transform parent, string name)
{
if (string.Equals(parent.name, name, System.StringComparison.OrdinalIgnoreCase))
return parent;
for (int i = 0; i < parent.childCount; i++)
{
var result = FindChildIgnoreCase(parent.GetChild(i), name);
if (result != null) return result;
}
return null;
}
/// <summary>物体移动协程</summary>
private IEnumerator MoveTo(Transform target, Vector3 destination, float duration)
{
if (target == null) yield break;
Vector3 start = target.position;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
target.position = Vector3.Lerp(start, destination, t);
yield return null;
}
target.position = destination;
}
/// <summary>获取屏幕左下角世界坐标</summary>
private Vector3 GetScreenBottomLeft()
{
var cam = Camera.main;
if (cam != null)
return cam.ScreenToWorldPoint(new Vector3(0, 0, cam.transform.position.z * -1f));
return new Vector3(-5, -3, 0);
}
/// <summary>获取屏幕左边世界坐标</summary>
private Vector3 GetScreenLeft()
{
var cam = Camera.main;
if (cam != null)
return cam.ScreenToWorldPoint(new Vector3(0, Screen.height * 0.5f, cam.transform.position.z * -1f));
return new Vector3(-5, 0, 0);
}
/// <summary>获取屏幕右边世界坐标</summary>
private Vector3 GetScreenRight()
{
var cam = Camera.main;
if (cam != null)
return cam.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height * 0.5f, cam.transform.position.z * -1f));
return new Vector3(5, 0, 0);
}
/// <summary>获取屏幕顶部世界坐标</summary>
private Vector3 GetScreenTop()
{
var cam = Camera.main;
if (cam != null)
return cam.ScreenToWorldPoint(new Vector3(Screen.width * 0.5f, Screen.height, cam.transform.position.z * -1f));
return new Vector3(0, 5, 0);
}
/// <summary>获取屏幕底部世界坐标</summary>
private Vector3 GetScreenBottom()
{
var cam = Camera.main;
if (cam != null)
return cam.ScreenToWorldPoint(new Vector3(Screen.width * 0.5f, 0, cam.transform.position.z * -1f));
return new Vector3(0, -5, 0);
}
// ==================== 销毁 ====================
/// <summary>
/// 回收爆奖预制体 + 回收 Boss 本体 + 统一销毁所有临时特效。
/// </summary>
public override void die()
{
CleanupBeforeDie();
// ⭐ 爆奖结束:统一销毁所有临时特效(FullScreen/AwardFrame/Settle/Crit/道具动画等)
// 防止 PW_Effect_AwardFrame_XXX(Clone) / PW_Effect_Settle_XXX(Clone) 残留
for (int i = 0; i < _spawnedEffects.Count; i++)
{
if (_spawnedEffects[i] != null)
Destroy(_spawnedEffects[i]);
}
_spawnedEffects.Clear();
if (bossCtrl != null)
{
// ⭐ Boss 本体已在最终死亡(isKill=true)时由 XingJiXunHang_BOSS 直接删除,
// 这里不需要再调 Id_DieFish_NotFX(重复调用会报"找不到鱼"警告)
bossCtrl.NotifyBossRemoved();
bossCtrl = null;
}
EffectPool.Instance.Return(gameObject);
}
/// <summary>
/// 加分完成:先走基类(显示 BJEndUI + 加分 + 收尾),
/// 然后删除/回收手动创建的 Boss 分数面板(_bossScoreTmp)。
/// </summary>
protected override void OnScoreEnd()
{
base.OnScoreEnd(); // ⭐ 弹出 BJEndUI
// ⭐ 弹出 BJEndUI 时回收/删除手动创建的 Boss 分数面板
if (_bossScoreTmp != null)
{
var board = _bossScoreTmp.transform.parent;
if (board != null)
Destroy(board.gameObject);
_bossScoreTmp = null;
Debug.Log("[XingJiXunHang] 弹出 BJEndUI:Boss 分数面板已删除");
}
}
private void OnDestroy()
{
StopAllCoroutines();
}
}