Files
5.24BuYu/Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/XingJiXunHang_BOSS.cs
T
2026-10-11 14:05:54 +08:00

646 lines
27 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.WebResponses;
using Newtonsoft.Json.Linq;
using Spine.Unity;
using System;
using System.Collections;
using UnityEngine;
/// <summary>
/// 星际巡航 Boss(继承 MonsterControl_BossBase)。
///
/// 支持多次死亡(1~3次,服务端决定):
/// 非最终死亡 → 显示 BJEndUI(倍数+分数),Boss 不消失,等待下次击杀
/// 最终死亡 → isKill=true 直接删除 Boss + 触发完整爆奖流程 + 道具武器掉落
///
/// 死亡判定来源:
/// 方式1(优先):BossKillParamParser.ParseMultiDeath 解析 RoomKillNotice.param
/// { "isKill": bool, "beishu": int, "fishId": "原ID-总次数-当前次数" }
/// 方式2(兜底):param 缺失时按 maxDeathCount 简单判定死亡次数
///
/// ⭐ 非最终死亡时不从鱼列表删除 Boss;最终死亡时下一帧直接 Id_DieFish_NotFX 删除。
/// </summary>
public class XingJiXunHang_BOSS : MonsterControl_BossBase
{
// ==================== Inspector 字段 ====================
[Header("星际巡航 - 多次死亡")]
[Tooltip("非最终死亡时显示的 BJEndUI 预制体(只显示倍数+分数,Boss 不消失)")]
public BJEndUI nonFinalDeathBjEndUI;
[Tooltip("非最终死亡后,Boss 恢复游动的延迟(秒)")]
public float resumeMoveDelay = 2f;
[Header("星际巡航 - 非最终死亡动画(animId=1)")]
[Tooltip("不死时播放的 Spine 动画名(stand02 = 第一种不死亡爆奖动画)")]
public string nonFinalDeathAnimationName = "stand02";
[Tooltip("正常默认动画名(stand01,不死动画播放完恢复游动时切回)")]
public string normalAnimationName = "stand01";
[Tooltip("SkeletonAnimation 所在子对象名(留空=自动 GetComponentInChildren 查找)")]
public string skeletonChildName = "";
[Header("星际巡航 - Hitting 特效")]
[Tooltip("Hitting 特效子对象路径(相对 Boss 根,如 body/bone/hitting)")]
public string hittingPath = "body/bone/hitting";
[Tooltip("Hitting 特效激活持续时间(秒,默认2)")]
public float hittingDuration = 2f;
[Header("星际巡航 - 服务端参数解析")]
[Tooltip("是否从 param 解析 isKill(关闭则用 maxDeathCount 客户端兜底判定)")]
public bool useServerParam = true;
[Header("星际巡航 - 客户端兜底配置")]
[Tooltip("最大死亡次数(param 缺失时生效,默认3次)")]
public int maxDeathCount = 3;
// ==================== 运行时状态 ====================
/// <summary>爆奖事件引用(死亡时赋值,供外部检查爆奖是否结束)</summary>
[System.NonSerialized]
public XingJiXunHangEvent currEvent;
/// <summary>当前死亡次数(从 1 开始)</summary>
private int _deathCount = 0;
/// <summary>星际巡航专用 fishId(带次数后缀,由 GetResponse 返回)</summary>
private string _cruiseFishId = "";
/// <summary>缓存的击杀数据(最终死亡时使用)</summary>
private RoomKillNotice _cachedKillNotice;
/// <summary>是否已进入最终死亡流程</summary>
private bool _isFinalDeath = false;
/// <summary>Boss 是否已从鱼列表回收(防止重复回收)</summary>
private bool _removed = false;
/// <summary>死亡位置(删除 Boss 前缓存,供爆奖事件使用)</summary>
private Vector3 _diePos;
/// <summary>是否已缓存死亡位置</summary>
private bool _diePosCached = false;
/// <summary>爆奖事件是否已 Init(决定特效实例由谁回收)</summary>
private bool _awardInitCalled = false;
/// <summary>正在运行的 Hitting 特效协程(同一时间只允许一个,防连续触发冲突)</summary>
private Coroutine _hittingRoutine = null;
// ==================== Unity 生命周期 ====================
public override void OnEnable()
{
base.OnEnable();
_deathCount = 0;
_cruiseFishId = "";
_cachedKillNotice = null;
_isFinalDeath = false;
_removed = false;
_diePosCached = false;
_awardInitCalled = false;
_hittingRoutine = null;
currEvent = null;
// ⭐ 恢复被修改过的激活状态(对象池复用:Hitting 特效强制恢复初始关闭)
ResetHittingState();
// ⭐ 回池复用/重生:把前端 spine 复位回正常待机(stand01),
// 清除上次残留的"不死爆奖循环动画",避免复用后还在循环播死亡动画
ResetSpineToIdle();
// 音效:星舰出场(PW_StarShip_Show)
AudioManager.Instance.PlayerFXByName("PW_StarShip_Show");
}
/// <summary>复用/重生时把前端 spine 复位为正常待机(stand01,循环播放)</summary>
private void ResetSpineToIdle()
{
PlaySkeletonAnimation(normalAnimationName);
}
/// <summary>恢复 Hitting 特效的初始激活状态(关闭)</summary>
private void ResetHittingState()
{
// 停掉残留的 Hitting 协程
if (_hittingRoutine != null)
{
StopCoroutine(_hittingRoutine);
_hittingRoutine = null;
}
Transform hitting = null;
if (!string.IsNullOrEmpty(hittingPath))
{
hitting = transform.Find(hittingPath);
if (hitting == null)
hitting = FindChildPathIgnoreCase(transform, hittingPath);
}
if (hitting != null)
hitting.gameObject.SetActive(false);
}
// ==================== 死亡逻辑 ====================
public override void DieFish(RoomKillNotice obj = null)
{
_deathCount++;
// 解析 isKill 和 beishu
bool isKill;
int beishu;
if (useServerParam && obj?.param != null)
{
// 方式1:BossKillParamParser 解析服务端 param
Debug.Log($"[XingJiXunHang] DieFish 第{_deathCount}次 param原文: {obj.param.ToString(Newtonsoft.Json.Formatting.None)}");
var p = BossKillParamParser.ParseMultiDeath(obj.param);
if (p != null)
{
isKill = p.isKill;
beishu = p.beishu;
if (!string.IsNullOrEmpty(p.fishId))
_cruiseFishId = p.fishId;
// ⭐ animId 优先于 isKill(服务端实际以 animId 为准):
// animId==1 → 不死(只播 BJEndUI,Boss 保留)
// animId>=2 → 带死亡的爆奖(2/3/4 三种表现)
// animId==0 → 老消息无 animId,回退用 isKill
if (p.animId >= 1)
{
isKill = p.animId >= 2;
Debug.Log($"[XingJiXunHang] animId={p.animId} → isKill={isKill}");
}
}
else
{
Debug.LogWarning("[XingJiXunHang] param 解析失败,走客户端兜底");
(isKill, beishu) = GetClientFallback(obj);
}
Debug.Log($"[XingJiXunHang] DieFish 第{_deathCount}次(服务端) isKill={isKill} beishu={beishu} fishId={_cruiseFishId} guid={guid}");
}
else
{
// 方式2:客户端兜底(param 缺失时按 maxDeathCount 判定)
(isKill, beishu) = GetClientFallback(obj);
Debug.Log($"[XingJiXunHang] DieFish 第{_deathCount}次(客户端兜底) isKill={isKill} beishu={beishu}");
}
if (!isKill)
{
// ===== 非最终死亡:显示 BJEndUI,Boss 不消失 =====
NonFinalDeath(obj, beishu);
}
else
{
// ===== 最终死亡:完整爆奖流程 =====
FinalDeath(obj, beishu);
}
}
// ==================== 锁定 ====================
/// <summary>
/// ⭐ 爆奖不死(非最终死亡)时不影响各座位对该 Boss 的锁定:不清锁,玩家不用重新点一次。
/// 判定与 DieFish 保持一致:animId 优先于 isKill(animId>=2 才算最终死亡)。
/// </summary>
public override bool ShouldClearLockOnKill(RoomKillNotice killNotice)
{
if (!useServerParam || killNotice?.param == null || killNotice.param.Type == JTokenType.Null)
return true; // 没有 param → 按"会死"处理,走原来的清锁逻辑
var p = BossKillParamParser.ParseMultiDeath(killNotice.param);
if (p == null) return true;
return p.animId >= 1 ? p.animId >= 2 : p.isKill;
}
// ==================== 非最终死亡 ====================
/// <summary>非最终死亡:显示 BJEndUI + 播放不死动画,Boss 保留,等待下次击杀</summary>
private void NonFinalDeath(RoomKillNotice obj, int beishu)
{
_cachedKillNotice = obj;
Debug.Log($"[XingJiXunHang] 非最终死亡:显示 BJEndUI,Boss 保留");
// 音效:星舰受击(PW_StarShip_On_Hit)
AudioManager.Instance.PlayerFXByName("PW_StarShip_On_Hit");
// ⭐ 播放不死动画(SkeletonAnimation,从子对象获取)
PlayNonFinalDeathAnimation();
// ⭐ 播放 Hitting 特效:只激活一个协程(连续触发先停旧的,防止特效状态错乱)
if (_hittingRoutine != null)
StopCoroutine(_hittingRoutine);
_hittingRoutine = StartCoroutine(PlayHittingEffect());
// 显示 BJEndUI(倍数+分数)
ShowNonFinalDeathBjEndUI(obj, beishu);
// 跳过爆奖特效初始化(CreateFishManager 仍会创建 effect_DieAward)
action_effect_DieAward = (o) =>
{
Debug.Log("[XingJiXunHang] 非最终死亡:跳过爆奖特效");
// ⭐ o 是从 EffectPool 取的实例,必须用 Return 回池,不能 Destroy(会破坏对象池)
if (o != null) EffectPool.Instance.Return(o);
};
// ⭐ 不设置 isDieFish,不调用 base.DieFish,不从鱼列表删除
// 延迟后恢复游动状态
StartCoroutine(ResumeMoveAfterDelay());
}
/// <summary>非最终死亡后恢复游动</summary>
private IEnumerator ResumeMoveAfterDelay()
{
yield return new WaitForSeconds(resumeMoveDelay);
// 恢复游动状态(如果 Boss 需要继续移动)
if (!_isFinalDeath && isInitialized)
{
ChangeState(CharacterStateType.move);
isMove = true;
// ⭐ 复位死亡标记:Ws_DieFish 对每次击杀统一置 isDead=true,非最终死亡 Boss 不回池,
// OnEnable 不会重跑导致 isDead 卡 true → 重新锁定后子弹被 BullitMove 判 target.isDead
// 立即 NotLock(碰撞器恢复+直线飞行,被路径中间的小鱼挡住)
isDead = false;
Debug.Log("[XingJiXunHang] 非最终死亡后恢复游动");
}
// ⭐ 不死动画(stand02)播完,切回正常默认动画(stand01)
PlaySkeletonAnimation(normalAnimationName);
}
/// <summary>获取 SkeletonAnimation(指定子对象或递归查找)并播放指定动画;动画不存在时回退 fallbackName</summary>
private void PlaySkeletonAnimation(string animationName, string fallbackName = null, bool loop = true)
{
if (string.IsNullOrEmpty(animationName)) return;
SkeletonAnimation sa = null;
// 1. 指定子对象名 → 直接找该子对象上的组件
if (!string.IsNullOrEmpty(skeletonChildName))
{
Transform child = transform.Find(skeletonChildName);
if (child != null)
{
sa = child.GetComponent<SkeletonAnimation>();
if (sa == null)
sa = child.GetComponentInChildren<SkeletonAnimation>(true);
}
}
// 2. 递归查找:⭐ 优先选"包含目标动画"的 SkeletonAnimation(避免选错骨架)
if (sa == null)
{
var all = GetComponentsInChildren<SkeletonAnimation>(true);
Debug.Log($"[XingJiXunHang] 找到 {all.Length} 个 SkeletonAnimation,目标动画={animationName}");
foreach (var s in all)
{
// Skeleton.data 是 protected,改用 SkeletonDataAsset 公共 API
var sd = s.SkeletonDataAsset != null ? s.SkeletonDataAsset.GetSkeletonData(false) : null;
if (sd != null && sd.FindAnimation(animationName) != null)
{
sa = s;
Debug.Log($"[XingJiXunHang] 选择包含动画 {animationName} 的骨架: {s.gameObject.name}");
break;
}
}
if (sa == null && all.Length > 0)
{
sa = all[0];
Debug.LogWarning($"[XingJiXunHang] 没有骨架包含动画 {animationName},用第一个 {sa.gameObject.name}");
}
}
if (sa == null)
{
Debug.LogWarning($"[XingJiXunHang] 找不到 SkeletonAnimation(子对象={skeletonChildName}),跳过动画 {animationName}");
return;
}
// ⭐ 目标动画在骨架里不存在 → 回退 fallbackName(防预制体存旧动画名如 "hit")
var data = sa.SkeletonDataAsset != null ? sa.SkeletonDataAsset.GetSkeletonData(false) : null;
if (data != null && data.FindAnimation(animationName) == null)
{
if (!string.IsNullOrEmpty(fallbackName) && data.FindAnimation(fallbackName) != null)
{
Debug.LogWarning($"[XingJiXunHang] 动画 {animationName} 不存在,回退 {fallbackName}");
animationName = fallbackName;
}
else
{
var animNames = new System.Collections.Generic.List<string>();
foreach (var a in data.Animations) animNames.Add(a.Name);
Debug.LogWarning($"[XingJiXunHang] 骨架 {sa.gameObject.name} 可用动画: {string.Join(", ", animNames)}");
return; // 找不到则直接返回,避免设置不存在的动画
}
}
// ⭐ 用 SetAnimation 显式控制是否循环:不死爆奖(stand02)只播一次(loop=false),
// 待机(stand01)循环播放
sa.AnimationState.SetAnimation(0, animationName, loop);
sa.timeScale = 1f;
Debug.Log($"[XingJiXunHang] 播放 Spine 动画: {animationName} loop={loop}(SkeletonAnimation on {sa.gameObject.name})");
}
/// <summary>播放不死动画(stand02 = 第一种不死亡爆奖动画;只播一次,不循环)</summary>
private void PlayNonFinalDeathAnimation()
{
PlaySkeletonAnimation(nonFinalDeathAnimationName, "stand02", false);
}
/// <summary>
/// 播放 Hitting 特效:激活子对象(body/bone/hitting),等待 hittingDuration 秒后关闭。
/// ⭐ transform.Find 大小写敏感,找不到时用大小写不敏感路径查找兜底(Body/bone/hitting)。
/// </summary>
private IEnumerator PlayHittingEffect()
{
Transform hitting = null;
if (!string.IsNullOrEmpty(hittingPath))
{
hitting = transform.Find(hittingPath);
if (hitting == null)
hitting = FindChildPathIgnoreCase(transform, hittingPath);
}
if (hitting == null)
{
Debug.LogWarning($"[XingJiXunHang] 找不到 Hitting 特效子对象 {hittingPath},跳过");
yield break;
}
hitting.gameObject.SetActive(true);
Debug.Log($"[XingJiXunHang] Hitting 特效激活({hittingPath} → {hitting.gameObject.name})");
yield return new WaitForSeconds(hittingDuration);
if (hitting != null)
hitting.gameObject.SetActive(false);
_hittingRoutine = null; // 协程结束,允许下一次触发
Debug.Log($"[XingJiXunHang] Hitting 特效关闭({hittingDuration}s 后)");
}
/// <summary>按路径逐级查找子对象(忽略大小写)</summary>
private Transform FindChildPathIgnoreCase(Transform root, string path)
{
if (root == null || string.IsNullOrEmpty(path)) return null;
Transform cur = root;
string[] parts = path.Split('/');
foreach (var p in parts)
{
Transform found = null;
for (int i = 0; i < cur.childCount; i++)
{
if (string.Equals(cur.GetChild(i).name, p, StringComparison.OrdinalIgnoreCase))
{
found = cur.GetChild(i);
break;
}
}
if (found == null) return null;
cur = found;
}
return cur;
}
/// <summary>
/// 非最终死亡(不死爆奖):显示 BJEndUI(倍数+分数)。
///
/// ⭐ 爆奖面板是 UI 预制体 → 必须挂游戏主 UI 面板层(currGameType.subPanelParent.parent),
/// 并用 UI 画布坐标(RectTransform.anchoredPosition)定位;
/// 之前挂 bjEffectRoot(uiRoot 层)用 3D 世界坐标摆放,不在游戏 UI 面板里,
/// 会浮在 3D 上、渲染层级和位置都不对。
/// </summary>
private void ShowNonFinalDeathBjEndUI(RoomKillNotice obj, int beishu)
{
if (nonFinalDeathBjEndUI == null || obj == null)
{
Debug.LogWarning("[XingJiXunHang] nonFinalDeathBjEndUI 或 obj 为 null,跳过 BJEndUI 显示");
return;
}
var mgr = CreateFishManager.Instance;
if (mgr == null || mgr.currGameType == null)
{
Debug.LogWarning("[XingJiXunHang] CreateFishManager/currGameType 为空,跳过 BJEndUI 显示");
return;
}
// 查找击杀者座位:⭐ XJMH 座位/炮台都是 UI 元素,取到的位置已是 UI 世界坐标
int posID = 0;
Vector3 seatPos = transform.position;
foreach (var item in mgr.currGameType.list_Sits)
{
if (item.user == obj.userId)
{
posID = item.posID;
seatPos = item.currentTurret != null
? item.currentTurret.transform.position
: item.transform.position;
break;
}
}
// 座位上方偏移:0/1 号座位向上,2/3 号座位向下(沿用原逻辑)
float y = 150f; // 默认 bjEndUIOffsetY
if (posID == 2 || posID == 3) y *= -1f;
bool isMine = obj.userId == GameManager.Instance.user.id;
// ⭐ UI 父节点:游戏主 UI 面板层(与 CreateFishManager 生成 UI 爆奖特效、其它 Boss 面板一致),
// 取不到才回退 bjEffectRoot
Transform uiParent = mgr.currGameType.subPanelParent != null
? mgr.currGameType.subPanelParent.parent
: null;
if (uiParent == null)
uiParent = mgr.bjEffectRoot;
// ⭐ 字段类型就是 BJEndUI:Instantiate 返回的 endUI 本身就是 BJEndUI 脚本(预制体必带)
var endUI = uiParent != null
? Instantiate(nonFinalDeathBjEndUI, uiParent)
: Instantiate(nonFinalDeathBjEndUI);
// ⭐ 用 UI 画布坐标定位(anchoredPosition),不再用 3D 世界坐标
var rect = endUI.GetComponent<RectTransform>();
var uiCanvas = UIManager.Instance != null && UIManager.Instance.uiRoot != null
? UIManager.Instance.uiRoot.GetComponent<Canvas>()
: null;
if (rect != null && uiCanvas != null)
{
// UI 元素世界坐标 → 屏幕坐标:Overlay 画布下世界坐标即屏幕坐标,其它模式走画布摄像机
Vector2 screenPos = uiCanvas.renderMode == RenderMode.ScreenSpaceOverlay
? new Vector2(seatPos.x, seatPos.y)
: RectTransformUtility.WorldToScreenPoint(uiCanvas.worldCamera, seatPos);
Vector2 canvasPos = CoordinateTransformation.ScreenPoint_ToCanvas(screenPos, uiCanvas);
rect.anchoredPosition = new Vector2(canvasPos.x, canvasPos.y + y);
}
else
{
// 兜底:拿不到画布(或非 UI 预制体)时,按原世界坐标摆放
endUI.transform.position = seatPos + Vector3.up * y;
}
endUI.Init(beishu, (int)obj.gold, isMine);
Debug.Log($"[XingJiXunHang] 非最终死亡 BJEndUI(UI层): 倍数={beishu} 分数={obj.gold} " +
$"isMine={isMine} posID={posID} parent={endUI.transform.parent?.name}");
}
// ==================== 最终死亡 ====================
/// <summary>最终死亡:⭐ 直接删除 Boss + 初始化爆奖事件</summary>
private void FinalDeath(RoomKillNotice obj, int beishu)
{
_isFinalDeath = true;
_cachedKillNotice = obj;
isDieFish = true;
// ⭐ 先缓存死亡位置(删除后 transform 位置就不可靠了)
_diePos = transform.position;
_diePosCached = true;
if (obj != null)
poolNum = (int)obj.gold;
// 相机震动 + 死亡音效
GameManager.Instance.cameraShake.ShakePresets.Explosion2D(3f, 4f, 1f);
// 音效:星舰爆炸(PW_StarShip_Explosion)
AudioManager.Instance.PlayerFXByName("PW_StarShip_Explosion");
var res = FishResourcesData.fishResourcesDatasDic.ContainsKey(fishTypeID)
? FishResourcesData.fishResourcesDatasDic[fishTypeID]
: null;
if (res != null && res.dieAudio != null && res.dieAudio.Count > 0)
AudioManager.Instance.PlayerFX(res.dieAudio[UnityEngine.Random.Range(0, res.dieAudio.Count)]);
Debug.Log($"[XingJiXunHang] 最终死亡 isKill=true:直接删除 Boss beishu={beishu} gold={obj?.gold} guid={guid} pos={_diePos}");
// 设置爆奖特效初始化回调(爆奖事件用缓存的 _diePos)
action_effect_DieAward = (o) => InitAwardEffect(o, beishu);
// ⭐ isKill=true → 直接从鱼列表删除 Boss(等1帧让 Ws_DieFish 当帧在正确位置创建爆奖特效)
StartCoroutine(RemoveBossNextFrame());
}
/// <summary>
/// 下一帧删除 Boss:Ws_DieFish 在 DieFish 返回后同帧会实例化爆奖特效
/// (用 Boss 的 transform 定位)并触发 action_effect_DieAward 回调。
/// 等到下一帧:① Boss 一定被删除 ② 此时已知爆奖事件是否初始化成功,
/// 没初始化(座位没找到)则把特效实例回池,防止残留在屏幕上。
/// </summary>
private IEnumerator RemoveBossNextFrame()
{
yield return null;
if (!_removed)
{
_removed = true;
// ⭐ 兜底:爆奖事件没初始化成功(击杀者不在座位列表)→ 特效实例回池
// (回调里已 Init 过的不动,由 BJEventBase 自己的流程收尾回收)
if (!_awardInitCalled && currEffect_DieAward != null)
{
Debug.LogError($"[XingJiXunHang] 爆奖事件未初始化(座位未找到 userId={_cachedKillNotice?.userId}),回收特效实例");
var effect = currEffect_DieAward;
currEffect_DieAward = null;
EffectPool.Instance.Return(effect);
}
CreateFishManager.Instance.Id_DieFish_NotFX(guid);
Debug.Log($"[XingJiXunHang] Boss 已删除(isKill=true 直接回收)guid={guid}");
}
}
/// <summary>初始化爆奖事件(由 action_effect_DieAward 回调触发)</summary>
private void InitAwardEffect(GameObject o, int beishu)
{
// ⭐ 用缓存的死亡位置(Boss 下一帧就被删除回池,transform.position 不可靠)
Vector3 diePos = _diePosCached ? _diePos : transform.position;
Debug.Log($"[XingJiXunHang] action_effect_DieAward: 鱼位置={diePos} guid={guid}");
if (_cachedKillNotice == null)
{
Debug.LogWarning("[XingJiXunHang] _cachedKillNotice 为 null,跳过爆奖初始化");
return;
}
// 查找击杀者座位,初始化爆奖事件
bool seatFound = false;
foreach (var item in CreateFishManager.Instance.currGameType.list_Sits)
{
if (item.user == _cachedKillNotice.userId)
{
seatFound = true;
_awardInitCalled = true; // ⭐ 标记已 Init(特效实例交给 BJEventBase 流程自己回收)
var v = o.GetComponent<BJEventBase>();
var evt = v as XingJiXunHangEvent;
if (evt != null)
{
evt.bossCtrl = this;
evt.diePos = diePos;
currEvent = evt;
Debug.Log($"[XingJiXunHang] 设置爆奖事件 位置={diePos}");
}
else
{
Debug.LogError("[XingJiXunHang] 爆奖预制体上没有 XingJiXunHangEvent 组件!" +
"(effect_DieAward 拖的是默认预制体?只走基类 BJEventBase 流程)");
}
v.Init(
_cachedKillNotice.gold, // goldCount(最终回加金币)
item.posID, // posID(击杀者座位)
_cachedKillNotice.skillsGold, // paobei(炮倍)
_cachedKillNotice.fishScore, // fishScore(底分)
beishu, // beishu(总倍数,BJEndUI 显示)
JiaFen, // action<int>(加分回调)
() => { v.obj = _cachedKillNotice.param; } // 服务端爆奖参数
);
break;
}
}
if (!seatFound)
{
Debug.LogError($"[XingJiXunHang] 未找到击杀者座位 userId={_cachedKillNotice.userId},爆奖事件未初始化!" +
"(特效实例将由 RemoveBossNextFrame 回收)");
}
}
/// <summary>爆奖流程正常结束时调用(XingJiXunHangEvent.die())</summary>
public void NotifyBossRemoved()
{
_removed = true;
}
// ==================== 参数解析 ====================
/// <summary>
/// 客户端兜底判定(param 缺失或解析失败时使用):
/// 按 maxDeathCount 简单判定死亡次数(类似 LieYanFengBao 模式)。
/// </summary>
private (bool isKill, int beishu) GetClientFallback(RoomKillNotice obj)
{
bool kill = _deathCount >= maxDeathCount;
int bs = obj?.beishu > 0 ? (int)obj.beishu : 1;
Debug.Log($"[XingJiXunHang] 客户端兜底: 第{_deathCount}次/{maxDeathCount}次 isKill={kill} beishu={bs}");
return (kill, bs);
}
// ==================== 清理 ====================
private void OnDestroy()
{
StopAllCoroutines();
}
}