425 lines
16 KiB
C#
425 lines
16 KiB
C#
using DG.Tweening;
|
||
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using Random = UnityEngine.Random;
|
||
|
||
/// <summary>
|
||
/// Boss 爆奖事件基类
|
||
/// 完整流程:死亡特效 → (多次 CallBJ 发红包 → 协程模拟金币逐个到达累分)→ EffEnd →
|
||
/// DelayThenScore → StartAddScore → MoveToSit → OnScoreEnd → JiaFen → die
|
||
///
|
||
/// CallBJ 调用 BigPrize 发包后,不立即累加分数,而是启动实例级协程,
|
||
/// 按金币飞行动画的节奏(0.5s 飞行 + 0.1s 间隔)逐个累分到 tMP_Score。
|
||
/// 每个 BJEventBase 实例独立管理自己的协程,支持多个 Boss 同时爆奖。
|
||
/// </summary>
|
||
public class BJEventBase : MonoBehaviour
|
||
{
|
||
public BJEndUI bjEndUI;
|
||
|
||
// ==================== Inspector 字段 ====================
|
||
[Header("分数")]
|
||
public bool useScorePanel = true; // 是否启用分数面板效果(关闭则跳过创建/滚分/移动)
|
||
public TextMeshProUGUI tMP_Score; // 分数显示文本(空则自动从预制体创建)
|
||
public GameObject scorePanelPrefab; // 分数版预制体(优先;空则用 Manager 自带 dieFXPrefabAwardTmp)
|
||
public int currScore; // 当前显示分数
|
||
public int maxScore; // 最终总分
|
||
|
||
[Header("红包")]
|
||
public LayeredPool layeredPool; // 红包分层对象池
|
||
public int scoreCount; // 红包分包数量(每次 CallBJ 发几个红包)
|
||
|
||
[Header("玩家")]
|
||
public int posID; // 击杀者座位ID
|
||
public int paoBei; // 炮倍
|
||
public int fishScore; // 底分
|
||
public int bJbeishu;
|
||
|
||
[Header("移动")]
|
||
public float showDelay = 3f; // 报完奖后停留时间(让玩家看清爆奖效果)
|
||
public float moveDuration = 0.5f; // UI 移动到座位的时间
|
||
public Ease moveEase = Ease.OutQuad; // 移动缓动曲线
|
||
public Vector3 startOffset = new Vector3(0, 0, -100);
|
||
public float bjEndUIOffsetY = 150f; // BJEndUI 在座位上方偏移(useScorePanel=false 时生效)
|
||
|
||
/// <summary>金币飞行到达面板的模拟时间(秒),应匹配 coinEvent DOMove 时长</summary>
|
||
public float coinFlyDuration = 0.5f;
|
||
/// <summary>金币之间到达的间隔时间(秒)</summary>
|
||
public float coinArrivalInterval = 0.1f;
|
||
|
||
// ==================== 回调 ====================
|
||
public Action JiaFen; // 加分完成回调(回加到玩家金币)
|
||
|
||
// ==================== 归属 ====================
|
||
public bool isSelf; // 是否是自己打出的爆奖(非自己只播特效不加分)
|
||
|
||
// ==================== 运行时状态 ====================
|
||
public int totalBJCount; // 计划爆奖总次数(由 boss 设置)
|
||
public int currBJCount; // 已爆奖次数
|
||
public bool isEffEnd; // 特效阶段已结束
|
||
public bool isScoreStart; // 加分阶段已开始
|
||
public bool isScoreEnd; // 加分阶段已结束
|
||
public bool isGoldEffectStart; // 红包特效阶段已开始
|
||
|
||
private SitPosData _targetSit; // 目标座位引用
|
||
private Coroutine _scoreCoroutine; // 加分协程
|
||
private GameObject _scoreBoard; // 自动创建的分数版(用于移动)
|
||
private int _activeCoinRoutines; // 正在运行的 CoinArrival 协程数量
|
||
|
||
public object obj;
|
||
|
||
// ==================== 初始化 ====================
|
||
/// <summary>
|
||
/// 初始化爆奖参数。由 Boss 的 DieFish → action_effect_DieAward 回调中调用。
|
||
/// </summary>
|
||
/// <param name="goldCount">服务器返回的总金币</param>
|
||
/// <param name="posID">击杀者座位 ID</param>
|
||
/// <param name="paobei">炮倍</param>
|
||
/// <param name="action">最终加分回调(回加到玩家金币)</param>
|
||
/// <param name="action1">初始化完成后的附加回调(如设置龙珠值)</param>
|
||
public virtual void Init(int goldCount, int posID, int paobei,int fishScore, int beishu,Action<int> action, Action action1 = null)
|
||
{
|
||
JiaFen = () => { action?.Invoke(0); };
|
||
maxScore = goldCount;
|
||
this.posID = posID;
|
||
this.fishScore = fishScore;
|
||
paoBei = paobei;
|
||
bJbeishu = beishu;
|
||
|
||
// 自动检测:tMP_Score 为空则按优先级创建分数版
|
||
EnsureScoreBoard();
|
||
|
||
// 初始位置:座位坐标 + 手调偏移
|
||
ApplySeatOffset();
|
||
|
||
// 是否是本地玩家自己打的
|
||
isSelf = _targetSit != null && _targetSit.user == GameManager.Instance.user.id;
|
||
|
||
action1?.Invoke();
|
||
}
|
||
private void Start()
|
||
{
|
||
totalBJCount = scoreCount;
|
||
}
|
||
/// <summary>
|
||
/// 自动补充分数版:tMP_Score 为空 → 优先用 scorePanelPrefab → 否则用 Manager 的 dieFXPrefabAwardTmp
|
||
/// 父对象挂在 CreateFishManager.Instance.currGameType.subPanelParent 下
|
||
/// </summary>
|
||
protected virtual void EnsureScoreBoard()
|
||
{
|
||
if (tMP_Score != null) return;
|
||
if (!useScorePanel) return;
|
||
|
||
if (CreateFishManager.Instance == null || CreateFishManager.Instance.currGameType == null) return;
|
||
var parent = CreateFishManager.Instance.currGameType.subPanelParent;
|
||
if (parent == null) return;
|
||
|
||
// 1. 优先用 Inspector 设置的预制体
|
||
GameObject prefab = scorePanelPrefab;
|
||
|
||
// 2. 否则用 Manager 自带的 dieFXPrefabAwardTmp
|
||
if (prefab == null)
|
||
prefab = CreateFishManager.Instance.dieFXPrefabAwardTmp;
|
||
|
||
if (prefab == null) return;
|
||
|
||
var board = Instantiate(prefab, parent);
|
||
_scoreBoard = board;
|
||
|
||
var tmp = board.GetComponentInChildren<TextMeshProUGUI>();
|
||
if (tmp != null) tMP_Score = tmp;
|
||
}
|
||
|
||
// ==================== 特效相关 ====================
|
||
/// <summary>死亡特效阶段结束回调(由动画事件/子类调用)</summary>
|
||
public virtual void EffEnd()
|
||
{
|
||
if (isEffEnd) return;
|
||
isEffEnd = true;
|
||
|
||
// 停留 showDelay 秒让玩家看清爆奖效果 → 开始加分滚动
|
||
StartCoroutine(DelayThenScore());
|
||
}
|
||
|
||
private IEnumerator DelayThenScore()
|
||
{
|
||
yield return new WaitForSeconds(showDelay);
|
||
StartAddScore(); // → AddScoreRoutine → MoveToSit → OnScoreEnd
|
||
}
|
||
|
||
/// <summary>触发红包特效 + 开始滚分</summary>
|
||
public virtual void GoldEffect()
|
||
{
|
||
isGoldEffectStart = true;
|
||
BigPrize();
|
||
}
|
||
|
||
// ==================== 移动到座位 ====================
|
||
/// <summary>
|
||
/// 设置爆奖初始位置:RectTransform → UI 画布中间;普通 Transform → 场景原点 (0,0,0)
|
||
/// </summary>
|
||
private void ApplySeatOffset()
|
||
{
|
||
var rect = GetComponent<RectTransform>();
|
||
if (rect != null)
|
||
{
|
||
var canvas = GetComponentInParent<Canvas>();
|
||
if (canvas != null && canvas.renderMode != RenderMode.WorldSpace)
|
||
{
|
||
Vector2 screenCenter = new Vector2(Screen.width / 2f, Screen.height / 2f);
|
||
RectTransformUtility.ScreenPointToWorldPointInRectangle(
|
||
canvas.GetComponent<RectTransform>(), screenCenter, canvas.worldCamera, out Vector3 worldPos);
|
||
transform.position = worldPos + startOffset;
|
||
}
|
||
else
|
||
{
|
||
transform.position = Vector3.zero + startOffset;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
transform.SetParent(null);
|
||
transform.position = Vector3.zero + startOffset;
|
||
}
|
||
|
||
// 缓存座位引用
|
||
if (CreateFishManager.Instance == null || CreateFishManager.Instance.currGameType == null) return;
|
||
var sits = CreateFishManager.Instance.currGameType.list_Sits;
|
||
if (posID >= 0 && posID < sits.Count && sits[posID] != null)
|
||
_targetSit = sits[posID];
|
||
}
|
||
|
||
/// <summary>分数版飞回到座位上方,爆奖对象本身不动</summary>
|
||
public virtual void MoveToSit()
|
||
{
|
||
if (!useScorePanel)
|
||
{
|
||
OnScoreEnd();
|
||
return;
|
||
}
|
||
|
||
if (_scoreBoard == null && tMP_Score == null)
|
||
{
|
||
OnScoreEnd();
|
||
return;
|
||
}
|
||
|
||
float y = bjEndUIOffsetY;
|
||
if (posID == 2 || posID == 3) y *= -1f;
|
||
Vector3 seatPos = GetSeatPosition() + Vector3.up * y;
|
||
|
||
Transform moveTarget = _scoreBoard != null ? _scoreBoard.transform : tMP_Score.transform.parent;
|
||
moveTarget.DOMove(seatPos, moveDuration)
|
||
.SetEase(moveEase)
|
||
.OnComplete(() =>
|
||
{
|
||
DOVirtual.DelayedCall(0.5f, () => OnScoreEnd());
|
||
});
|
||
}
|
||
|
||
// ==================== 爆奖(发红包) ====================
|
||
/// <summary>
|
||
/// 执行一次爆奖(发红包)。可在动画事件中多次调用。
|
||
/// 每次调用通过 LayeredPool 分发红包数据,然后启动协程按金币飞行节奏逐个累加分数到 tMP_Score。
|
||
/// </summary>
|
||
/// <param name="minCount">每次最小红包数</param>
|
||
/// <param name="maxCount">每次最大红包数</param>
|
||
public virtual void CallBJ(int minCount = 10, int maxCount = 15)
|
||
{
|
||
if (isScoreEnd) return;
|
||
|
||
var records = BigPrize(minCount, maxCount);
|
||
if (records != null && records.Count > 0)
|
||
{
|
||
// 启动协程:模拟金币逐个飞到面板的节奏
|
||
_activeCoinRoutines++;
|
||
StartCoroutine(CoinArrivalRoutine(records));
|
||
}
|
||
|
||
currBJCount++;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 协程:模拟金币飞行到达后逐个累加分数。
|
||
/// 等待 coinFlyDuration(匹配 coinEvent DOMove 0.5s),
|
||
/// 然后按 coinArrivalInterval 间隔逐个累加。
|
||
/// </summary>
|
||
private IEnumerator CoinArrivalRoutine(List<RedPacketRecord> records)
|
||
{
|
||
// 等待金币飞行时间(匹配 DOMove)
|
||
yield return new WaitForSeconds(coinFlyDuration);
|
||
|
||
foreach (var r in records)
|
||
{
|
||
currScore += (int)r.Amount;
|
||
if (tMP_Score != null)
|
||
tMP_Score.text = currScore.ToString();
|
||
|
||
// 加分弹出特效 + 音效
|
||
Transform parent = tMP_Score != null ? tMP_Score.transform.parent : transform;
|
||
GameObject addScorePrefab = CreateFishManager.Instance.dieFXPrefabAwardTmp_AddScore;
|
||
if (addScorePrefab != null)
|
||
{
|
||
var inst = Instantiate(addScorePrefab, parent.position + Vector3.up * 60, Quaternion.identity, parent);
|
||
inst.transform.GetChild(0).GetComponent<TextMeshProUGUI>().text = CoinScript.NumAdd(r.Amount.ToString());
|
||
}
|
||
AudioManager.Instance.PlayerFX(61009);
|
||
|
||
// 金币之间间隔
|
||
yield return new WaitForSeconds(coinArrivalInterval);
|
||
}
|
||
|
||
_activeCoinRoutines--;
|
||
|
||
// 所有协程完成 + 轮次满足 → 进入收尾
|
||
if (_activeCoinRoutines <= 0 && totalBJCount > 0 && currBJCount >= totalBJCount)
|
||
{
|
||
isGoldEffectStart = false;
|
||
EffEnd(); // → DelayThenScore → StartAddScore → MoveToSit → OnScoreEnd → JiaFen → die
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 包一个红包分层
|
||
/// </summary>
|
||
public virtual List<RedPacketRecord> BigPrize(int min = 10, int max = 15)
|
||
{
|
||
if (layeredPool == null)
|
||
layeredPool = new LayeredPool(maxScore, scoreCount > 0 ? scoreCount : 1);
|
||
|
||
var v = layeredPool.BossGlodCoinRandrom(
|
||
tMP_Score != null ? tMP_Score.transform.position : transform.position,
|
||
min, max);
|
||
return v;
|
||
}
|
||
|
||
// ==================== 加分滚动 ====================
|
||
/// <summary>开始加分滚动动画(子类可 override 自定义)</summary>
|
||
public virtual void StartAddScore()
|
||
{
|
||
if (!useScorePanel)
|
||
{
|
||
OnScoreEnd();
|
||
return;
|
||
}
|
||
|
||
if (_scoreCoroutine != null)
|
||
StopCoroutine(_scoreCoroutine);
|
||
|
||
_scoreCoroutine = StartCoroutine(AddScoreRoutine());
|
||
}
|
||
|
||
/// <summary>默认加分滚动协程</summary>
|
||
protected virtual IEnumerator AddScoreRoutine()
|
||
{
|
||
if (currBJCount > 0)
|
||
{
|
||
// CallBJ 模式:等所有金币协程跑完
|
||
while (_activeCoinRoutines > 0)
|
||
yield return null;
|
||
|
||
maxScore = currScore;
|
||
if (tMP_Score != null)
|
||
tMP_Score.text = maxScore.ToString();
|
||
}
|
||
else
|
||
{
|
||
// 非 CallBJ 模式(如雷龙): 滚动加分
|
||
while (currScore < maxScore - paoBei)
|
||
{
|
||
int step = Random.Range(paoBei , paoBei * 2);
|
||
currScore += step;
|
||
if (tMP_Score != null)
|
||
tMP_Score.text = currScore.ToString();
|
||
yield return null;
|
||
}
|
||
if (tMP_Score != null)
|
||
tMP_Score.text = maxScore.ToString();
|
||
yield return new WaitForSeconds(0.5f);
|
||
}
|
||
|
||
// 加分完毕 → 分数版飞回座位 → 结束后 JiaFen → die
|
||
MoveToSit();
|
||
}
|
||
|
||
/// <summary>加分完成——加金币、显示结束UI、删面板、销毁自身</summary>
|
||
protected virtual void OnScoreEnd()
|
||
{
|
||
if (isScoreEnd) return;
|
||
isScoreEnd = true;
|
||
|
||
// 回加金币到玩家
|
||
JiaFen?.Invoke();
|
||
|
||
// BJEndUI:无论是否开启分数面板都显示
|
||
if (bjEndUI != null)
|
||
{
|
||
Vector3 uiPos;
|
||
if (useScorePanel)
|
||
{
|
||
// 开启分数面板:定位在分数面板位置
|
||
if (_scoreBoard != null)
|
||
uiPos = _scoreBoard.transform.position;
|
||
else if (tMP_Score != null)
|
||
uiPos = tMP_Score.transform.position;
|
||
else
|
||
uiPos = transform.position;
|
||
}
|
||
else
|
||
{
|
||
float y = bjEndUIOffsetY;
|
||
if (posID == 2 || posID == 3) y *= -1f;
|
||
uiPos = GetSeatPosition() + Vector3.up * y;
|
||
}
|
||
|
||
var endUI = Instantiate(bjEndUI, uiPos, Quaternion.identity, UIManager.Instance.uiRoot);
|
||
endUI.Init(paoBei, maxScore);
|
||
}
|
||
|
||
// 删除分数面板
|
||
if (_scoreBoard != null)
|
||
Destroy(_scoreBoard);
|
||
|
||
// 销毁爆奖对象自身
|
||
die();
|
||
}
|
||
|
||
/// <summary>获取座位坐标</summary>
|
||
public Vector3 GetSeatPosition()
|
||
{
|
||
if (_targetSit != null)
|
||
{
|
||
if (_targetSit.currentTurret != null)
|
||
return _targetSit.currentTurret.transform.position;
|
||
return _targetSit.transform.position;
|
||
}
|
||
if (CreateFishManager.Instance != null && CreateFishManager.Instance.currGameType != null)
|
||
{
|
||
var sits = CreateFishManager.Instance.currGameType.list_Sits;
|
||
if (posID >= 0 && posID < sits.Count && sits[posID] != null)
|
||
{
|
||
_targetSit = sits[posID];
|
||
if (_targetSit.currentTurret != null)
|
||
return _targetSit.currentTurret.transform.position;
|
||
return _targetSit.transform.position;
|
||
}
|
||
}
|
||
return transform.position;
|
||
}
|
||
|
||
// ==================== 销毁 ====================
|
||
/// <summary>缩小隐藏爆奖本体(不影响分数面板)</summary>
|
||
public virtual void ShrinkEffect()
|
||
{
|
||
transform.DOScale(Vector3.zero, 0.5f);
|
||
}
|
||
|
||
/// <summary>销毁自身 GameObject</summary>
|
||
public virtual void die()
|
||
{
|
||
Destroy(gameObject);
|
||
}
|
||
}
|