using System; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using UnityEngine.UI; using TMPro; using cfg; using GameCore; using asap.core; using EventSmash.Manager; using DG.Tweening; namespace EventSmash.UIPanel { /// /// 收集进度展示Cell /// public class EventSmashProgressCell : MonoBehaviour { #region 组件 private RectTransform _progressRect; private TMP_Text _progressText; private Image _progressIcon; private RewardItemNew _reward; private Animation _potRefreshAnimation; #endregion [Header("进度(按 Y 映射,不再使用 fillAmount)")] [Tooltip("不勾选:起点 y=0,终点 y=当前进度 Rect 的 rect.height;勾选:使用下方两个自定义值")] [SerializeField] private bool _useCustomProgressAnchoredY; [Tooltip("仅勾选「自定义 Y」时生效:进度 0 时的 anchoredPosition.y")] [SerializeField] private float _progressAnchoredYWhenEmpty; [Tooltip("仅勾选「自定义 Y」时生效:进度满时的 anchoredPosition.y")] [SerializeField] private float _progressAnchoredYWhenFull; [Header("收集进度上涨(与 img_content 移动同时)")] [Tooltip("拖特效根节点(如 fx_ui_eventsmashscrap_collect_add_01);与进度条补间同启同停,未赋值则仅补间")] [SerializeField] private GameObject _progressIncreaseFxRoot; [Header("进度节点显隐(按本档 current/target)")] [SerializeField][Range(0,1)]private float NearFullShowRatio = 0.8f; [Tooltip("未赋值则忽略。收集进度 ≥90% 且未满本档时显示;达到本档 100%(可领档)时隐藏。")] [SerializeField] private GameObject _visibleFrom90PercentUntilFullObject; [Header("收集达标:满条 → [可选]庆祝动效根 → 等待 → 勾选 → 领罐上奖励 → 罐子刷新 → 有溢出再涨条")] [Tooltip("满条后先 SetActive(true),子节点可用 Animation/Timeline 自控显隐;不赋值则跳过")] [SerializeField] private GameObject _fullBarCelebrationRoot; [Tooltip("开始勾选(ImageCheck)前等待的秒数;有庆祝根时在其 SetActive(true) 之后计时。0 表示不额外等待")] [SerializeField] private float _fullBarWaitBeforeCheckSeconds; [Tooltip("罐子/进度区域刷新动效,空则跳过")] [SerializeField] private string _potRefreshAnimClipName = "EventSmashScrapPiplineCheckRefresh"; [Tooltip("刷新动画开始播放后,等待该秒数即继续进度条归零/溢出补间,不必等动画播完。负数=仍按片段全长等待(旧行为)")] [SerializeField] private float _potRefreshProceedAfterSeconds = 0.3f; [Header("进度条移动速度")] [SerializeField] private float _overflowFillDuration = 0.35f; private int _collectionId; private EventSmashManager _manager; private EventSmashData _data; /// 进度条 anchoredPosition.y 补间。 private Tween _progressRectYTween; /// 领奖动效进行中: 只刷新文案/奖励图标,不 Kill、不强行写条,避免打断「当前→满→领奖→归零→剩余」。 private bool _claimSequenceInProgress; private void Awake() { _potRefreshAnimation = gameObject.GetComponent(); _progressRect = gameObject.FindChildGameObject("img_content").GetComponent(); //_progressText = gameObject.FindChildGameObject("progress_text").GetComponent(); _progressIcon = gameObject.FindChildGameObject("img_icon").GetComponent(); _reward = gameObject.FindChildGameObject("reward").GetComponent(); } private void GetProgressYBounds(out float yEmpty, out float yFull) { if (_useCustomProgressAnchoredY) { yEmpty = _progressAnchoredYWhenEmpty; yFull = _progressAnchoredYWhenFull; return; } yEmpty = 0f; yFull = _progressRect != null ? _progressRect.rect.height : 0f; } /// 按 0~1 进度设置锚点 Y(线性映射起点/终点) private void SetProgressVisualRatio(float ratioClamped01) { if (_progressRect == null) return; ratioClamped01 = Mathf.Clamp01(ratioClamped01); GetProgressYBounds(out var y0, out var y1); var ap = _progressRect.anchoredPosition; ap.y = Mathf.Lerp(y0, y1, ratioClamped01); _progressRect.anchoredPosition = ap; } private float GetProgressRatio(int current, int target) => target > 0 ? Mathf.Clamp01((float)current / target) : 0f; /// ≥90% 且未满本档显示,本档已满(100%)隐藏。 private void ApplyVisibleFrom90UntilFullObject(int current, int target) { var o = _visibleFrom90PercentUntilFullObject; if (o == null) return; if (target <= 0) { o.SetActive(false); return; } float ratio = GetProgressRatio(current, target); bool show = ratio >= NearFullShowRatio && current < target; o.SetActive(show); } private bool TryGetProgressVisualRatio(out float ratio01) { ratio01 = 0f; if (_progressRect == null) return false; GetProgressYBounds(out var y0, out var y1); float denom = y1 - y0; if (Mathf.Abs(denom) < 1e-4f) return true; ratio01 = Mathf.Clamp01((_progressRect.anchoredPosition.y - y0) / denom); return true; } /// /// 未达领奖档时:Grant 已加数据但条仍停在旧位置,下一帧 Refresh 会直接对齐;此处补间到当前数据比例。 /// public async Task PlayCollectionProgressVisualCatchUpAsync(EventSmashManager manager, EventSmashData data, int collectionId) { if (manager == null || data == null || _progressRect == null) return; var (current, target) = manager.GetCollectionProgress(data, collectionId, out var collectionConfig); if (target <= 0) return; RefreshRewardTextAndIconOnly(collectionConfig, current, target); if (!TryGetProgressVisualRatio(out var ratioNow)) return; float ratioTarget = GetProgressRatio(current, target); if (Mathf.Abs(ratioNow - ratioTarget) < 0.008f) return; GetProgressYBounds(out var yEmpty, out var yFull); float lo = Mathf.Min(yEmpty, yFull); float hi = Mathf.Max(yEmpty, yFull); _claimSequenceInProgress = true; try { float fromY = Mathf.Clamp(_progressRect.anchoredPosition.y, lo, hi); float toY = Mathf.Lerp(yEmpty, yFull, ratioTarget); await TweenProgressAnchoredYAsync(fromY, toY, playIncreaseLeadIn: true); } finally { _claimSequenceInProgress = false; _progressRectYTween = null; } } /// 收集物飞向进度条时的落点(与收集配置 ID 对应) public RectTransform GetProgressIconRect() => _progressIcon != null ? _progressIcon.rectTransform : null; /// /// 初始化进度Cell /// public void Initialize(int collectionId, EventSmashManager manager, EventSmashData data) { _collectionId = collectionId; _manager = manager; _data = data; UpdateDisplay(); } private void KillProgressRectTween() { if (_progressRectYTween != null && _progressRectYTween.IsActive()) _progressRectYTween.Kill(); _progressRectYTween = null; } /// 只更新数字/奖励/图标,不改进度条位置(用于领奖序列里补间前先同步文案)。 private void RefreshRewardTextAndIconOnly(EventSmashCollection collectionConfig, int current, int target) { if (_progressText != null) _progressText.text = $"{current}/{target}"; UpdateRewardDisplay(collectionConfig); var iconName = _data.MainConfig.GetCollectionIcon(_manager.Tables.TbEventSmashCollection, _collectionId); SetProgressIcon(iconName); ApplyVisibleFrom90UntilFullObject(current, target); } /// /// 更新显示 /// public void UpdateDisplay() { if (_manager == null || _data == null) return; var (current, target) = _manager.GetCollectionProgress(_data, _collectionId, out var collectionConfig); if (_claimSequenceInProgress) { RefreshRewardTextAndIconOnly(collectionConfig, current, target); return; } KillProgressRectTween(); SetProgressVisualRatio(GetProgressRatio(current, target)); RefreshRewardTextAndIconOnly(collectionConfig, current, target); } private void SetProgressAnchoredY(float y) { if (_progressRect == null) return; var p = _progressRect.anchoredPosition; p.y = y; _progressRect.anchoredPosition = p; } private async Task TweenProgressAnchoredYAsync(float fromY, float toY, bool playIncreaseLeadIn = false) { if (_progressRect == null) return; KillProgressRectTween(); SetProgressAnchoredY(fromY); if (Mathf.Abs(fromY - toY) < 0.5f) return; var fx = _progressIncreaseFxRoot; bool syncFx = playIncreaseLeadIn && fx != null; if (syncFx) fx.SetActive(true); void EndProgressTweenAndFx() { if (syncFx && fx != null) fx.SetActive(false); _progressRectYTween = null; } _progressRectYTween = DOTween .To(() => _progressRect.anchoredPosition.y, y => { var p = _progressRect.anchoredPosition; p.y = y; _progressRect.anchoredPosition = p; }, toY, _overflowFillDuration) .SetEase(Ease.OutQuad) .OnKill(EndProgressTweenAndFx) .OnComplete(EndProgressTweenAndFx); await _progressRectYTween.AsyncWaitForCompletion(); } /// 批量领奖表现开始:锁住进度条刷新,避免虚拟多档与真实数据不一致时 抢写。 public void EnterClaimPresentationMode() { _claimSequenceInProgress = true; } /// 批量领奖表现结束:恢复刷新并同步最终数据到 UI。 public void ExitClaimPresentationMode() { _claimSequenceInProgress = false; _progressRectYTween = null; UpdateDisplay(); } /// /// 第一档:条补间到满 → 勾选(数据尚未扣档)。单砸飞入回溯逻辑与原先整段领奖一致。 /// public async Task PlayCollectionFillAndCheckFirstTierAsync(int flyGrantCountForVisualRewind, bool applySingleSmashFlyVisualRewind) { if (_manager == null || _data == null) return; if (!_manager.CanClaimCollectionTier(_data, _collectionId)) return; GetProgressYBounds(out var yEmpty, out var yFull); float lo = Mathf.Min(yEmpty, yFull); float hi = Mathf.Max(yEmpty, yFull); try { if (_progressRect != null && applySingleSmashFlyVisualRewind && flyGrantCountForVisualRewind > 0) { var (cur, tgt) = _manager.GetCollectionProgress(_data, _collectionId, out var cfg); RefreshRewardTextAndIconOnly(cfg, cur, tgt); if (tgt > 0 && cur >= flyGrantCountForVisualRewind) { float preRatio = Mathf.Clamp01((float)(cur - flyGrantCountForVisualRewind) / tgt); SetProgressVisualRatio(preRatio); } } if (_progressRect != null) { float startY = Mathf.Clamp(_progressRect.anchoredPosition.y, lo, hi); await TweenProgressAnchoredYAsync(startY, yFull, playIncreaseLeadIn: true); } await RunFullBarCelebrationWaitBeforeCheckAsync(); } finally { if (_fullBarCelebrationRoot != null) _fullBarCelebrationRoot.SetActive(false); } } /// /// 满条后可选显示庆祝根(子物体动效自控显隐),再按配置等待,之后由调用方播勾选;根节点在整段勾选 finally 里统一关。 /// private async Task RunFullBarCelebrationWaitBeforeCheckAsync() { float wait = Mathf.Max(0f, _fullBarWaitBeforeCheckSeconds); if (_fullBarCelebrationRoot != null) _fullBarCelebrationRoot.SetActive(true); if (wait > 0f) await Task.Delay(TimeSpan.FromSeconds(wait)); } /// /// 后续档(数据仍未扣):按虚拟 current/target 对齐文案与条起点,再涨到满并播勾选。 /// public async Task PlayCollectionFillAndCheckSubsequentTierAsync(int displayCurrent, int displayRequirement, EventSmashCollection collectionConfig) { if (_manager == null || _data == null || collectionConfig == null) return; if (displayRequirement <= 0) return; GetProgressYBounds(out var yEmpty, out var yFull); float lo = Mathf.Min(yEmpty, yFull); float hi = Mathf.Max(yEmpty, yFull); try { RefreshRewardTextAndIconOnly(collectionConfig, displayCurrent, displayRequirement); SetProgressVisualRatio(GetProgressRatio(displayCurrent, displayRequirement)); if (_progressRect != null) { float startY = Mathf.Clamp(_progressRect.anchoredPosition.y, lo, hi); await TweenProgressAnchoredYAsync(startY, yFull, playIncreaseLeadIn: true); } await RunFullBarCelebrationWaitBeforeCheckAsync(); } finally { if (_fullBarCelebrationRoot != null) _fullBarCelebrationRoot.SetActive(false); } } /// /// 所有档扣档并入暂存之后:罐子刷新 → 条归零 → 按真实进度补间溢出。 /// public async Task PlayCollectionPostGrantVisualAsync() { if (_manager == null || _data == null) return; if (_potRefreshAnimation != null && !string.IsNullOrEmpty(_potRefreshAnimClipName)) { var potClip = _potRefreshAnimation[_potRefreshAnimClipName]; if (potClip == null) { Debug.LogWarning( $"Animation clip '{_potRefreshAnimClipName}' not found on {_potRefreshAnimation.gameObject.name}"); } else { _potRefreshAnimation.Play(_potRefreshAnimClipName); if (_potRefreshProceedAfterSeconds < 0f) await Task.Delay(TimeSpan.FromSeconds(potClip.length)); else await Task.Delay(TimeSpan.FromSeconds(Mathf.Max(0f, _potRefreshProceedAfterSeconds))); } } if (_progressRect == null) return; GetProgressYBounds(out var yEmpty, out var yFull); var (currentAfter, targetAfter) = _manager.GetCollectionProgress(_data, _collectionId, out var collectionConfigAfter); RefreshRewardTextAndIconOnly(collectionConfigAfter, currentAfter, targetAfter); float remainderRatio = GetProgressRatio(currentAfter, targetAfter); SetProgressAnchoredY(yEmpty); if (remainderRatio > 0.001f) { float endY = Mathf.Lerp(yEmpty, yFull, remainderRatio); await TweenProgressAnchoredYAsync(yEmpty, endY, playIncreaseLeadIn: true); } } /// /// 当前档位收集奖励预览(道具图标)。须与掉落解析结果一致,否则仍显示上一态易被误认为「飞入没播」。 /// private void UpdateRewardDisplay(EventSmashCollection collectionConfig) { if (_reward == null) return; if (collectionConfig == null || collectionConfig.CollectingRewardList == null || collectionConfig.CollectingRewardList.Count == 0) { _reward.gameObject.SetActive(false); return; } var (round, _) = _data.CollectionProgressPairs.TryGetValue(_collectionId, out var pair) ? pair : (0, 0); int configCount = collectionConfig.CollectingRewardList.Count; int rewardDropId = collectionConfig.CollectingRewardList[round % configCount]; if (rewardDropId <= 0) { _reward.gameObject.SetActive(false); return; } var playerItem = GContext.container.Resolve(); List itemDatas = playerItem.GetItemDataByDropIdLureInflation(_data.InflationRate, rewardDropId); if (itemDatas == null || itemDatas.Count == 0) itemDatas = playerItem.GetItemDataByDropIdLureInflation(null, rewardDropId); if (itemDatas == null || itemDatas.Count == 0) itemDatas = playerItem.GetItemDataByDropId(rewardDropId); if (itemDatas != null && itemDatas.Count > 0) { _reward.InflationRate = _data.InflationRate; _reward.SetData(itemDatas[0]); _reward.gameObject.SetActive(true); return; } _reward.gameObject.SetActive(false); } /// /// 设置进度图标 /// public void SetProgressIcon(string icon) { if (_progressIcon != null && icon != null) { GContext.container.Resolve().SetImageSprite(_progressIcon, icon); _progressIcon.gameObject.SetActive(true); } } } }