478 lines
19 KiB
C#
478 lines
19 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 收集进度展示Cell
|
||
/// </summary>
|
||
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;
|
||
|
||
/// <summary>进度条 anchoredPosition.y 补间。</summary>
|
||
private Tween _progressRectYTween;
|
||
|
||
/// <summary>领奖动效进行中:<see cref="UpdateDisplay"/> 只刷新文案/奖励图标,不 Kill、不强行写条,避免打断「当前→满→领奖→归零→剩余」。</summary>
|
||
private bool _claimSequenceInProgress;
|
||
|
||
private void Awake()
|
||
{
|
||
_potRefreshAnimation = gameObject.GetComponent<Animation>();
|
||
_progressRect = gameObject.FindChildGameObject("img_content").GetComponent<RectTransform>();
|
||
//_progressText = gameObject.FindChildGameObject("progress_text").GetComponent<TMP_Text>();
|
||
_progressIcon = gameObject.FindChildGameObject("img_icon").GetComponent<Image>();
|
||
_reward = gameObject.FindChildGameObject("reward").GetComponent<RewardItemNew>();
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>按 0~1 进度设置锚点 Y(线性映射起点/终点)</summary>
|
||
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;
|
||
|
||
|
||
/// <summary>≥90% 且未满本档显示,本档已满(100%)隐藏。</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 未达领奖档时:Grant 已加数据但条仍停在旧位置,下一帧 Refresh 会直接对齐;此处补间到当前数据比例。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>收集物飞向进度条时的落点(与收集配置 ID 对应)</summary>
|
||
public RectTransform GetProgressIconRect() =>
|
||
_progressIcon != null ? _progressIcon.rectTransform : null;
|
||
/// <summary>
|
||
/// 初始化进度Cell
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>只更新数字/奖励/图标,不改进度条位置(用于领奖序列里补间前先同步文案)。</summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新显示
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>批量领奖表现开始:锁住进度条刷新,避免虚拟多档与真实数据不一致时 <see cref="UpdateDisplay"/> 抢写。</summary>
|
||
public void EnterClaimPresentationMode()
|
||
{
|
||
_claimSequenceInProgress = true;
|
||
}
|
||
|
||
/// <summary>批量领奖表现结束:恢复刷新并同步最终数据到 UI。</summary>
|
||
public void ExitClaimPresentationMode()
|
||
{
|
||
_claimSequenceInProgress = false;
|
||
_progressRectYTween = null;
|
||
UpdateDisplay();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 第一档:条补间到满 → 勾选(数据尚未扣档)。单砸飞入回溯逻辑与原先整段领奖一致。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 满条后可选显示庆祝根(子物体动效自控显隐),再按配置等待,之后由调用方播勾选;根节点在整段勾选 finally 里统一关。
|
||
/// </summary>
|
||
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));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 后续档(数据仍未扣):按虚拟 current/target 对齐文案与条起点,再涨到满并播勾选。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 所有档扣档并入暂存之后:罐子刷新 → 条归零 → 按真实进度补间溢出。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前档位收集奖励预览(道具图标)。须与掉落解析结果一致,否则仍显示上一态易被误认为「飞入没播」。
|
||
/// </summary>
|
||
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<PlayerItemData>();
|
||
List<ItemData> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置进度图标
|
||
/// </summary>
|
||
public void SetProgressIcon(string icon)
|
||
{
|
||
if (_progressIcon != null && icon != null)
|
||
{
|
||
GContext.container.Resolve<IUIService>().SetImageSprite(_progressIcon, icon);
|
||
_progressIcon.gameObject.SetActive(true);
|
||
}
|
||
}
|
||
}
|
||
}
|