Files
ft/Client/Assets/Scripts/Capsule/Pack/UIPanel/CapsulePackPanel.cs
2026-06-29 21:18:33 +08:00

538 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Activity;
using asap.core;
using Capsule.Pack.Manager;
using Capsule.Reward;
using DG.Tweening;
using game;
using Game;
using GameCore;
using Newtonsoft.Json.Linq;
using TimeManager;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.UI;
using Random = UnityEngine.Random;
namespace Capsule.Pack.UIPanel
{
public class CapsulePackPanel : BasePanel
{
#region public
private CapsulePackManager _manager;
private CapsulePackManager Manager => _manager ??= ActivityResolver.Resolve<CapsulePackManager>();
private CapsulePackData Data => Manager?.LoadData();
public void SetData(CapsulePackManager manager)
{
_manager = manager;
}
#endregion
#region UI_ELEMENTS
private CapsuleRewardCell[] _rewardItems;
private TextMeshProUGUI _textTime;
private Button btn_buy;
private Button btn_probability;
private Button btn_close;
private TextMeshProUGUI p_text;
private RewardFlyBatchController RewardFlyBatchCtrl;
private PlayableDirector _panelDirector;
private Transform _knobAni;
private CapsuleRewardCell _dropReward;
#endregion UI_ELEMENTS
#region SerializeField
[Header("UI")][SerializeField] private PlayableAsset showFirstTimeline;
[SerializeField] private PlayableAsset showTimeline;
[SerializeField] private PlayableAsset outTimeline;
[SerializeField][Min(0)] private float knobAniTime = 0.3f;
[SerializeField]
[Min(0)]
[Tooltip("等待多久播放小球进场动画")]
private float showFirstDelayTime = 0.3f;
[SerializeField]
[Min(0)]
[Tooltip("小球入场的间隔时间")]
private float capsuleShowTime = 0.05f;
[SerializeField]
[Min(0)]
[Tooltip("奖励界面显示时间")]
private float showRewardPanelTime = 0.05f;
[SerializeField]
[Min(0)]
[Tooltip("等待小球掉落然后播回正动画")]
private float delayRevertDropTime = 0.5f;
[SerializeField]
[Min(0)]
[Tooltip("等待多长时间开始回正旋钮(由于奖励界面打开比较慢)")]
private float delayRevertKnobTime = 0.5f;
[Header("capsule rotation effect ")]
[SerializeField]
private float force;
[SerializeField] private Vector2 forceCenter;
[SerializeField] private ForceMode2D forceMode = ForceMode2D.Impulse;
[SerializeField] private float rotationAngle = 0.5f;
[SerializeField] private float gravityInRotation = 10f;
[SerializeField] private float gravityOnEnd = 100f;
[SerializeField] private Vector2 sizeInRotation = new Vector2(150f, 150f);
[SerializeField] private Vector2 sizeOnEnd = new Vector2(175f, 175f);
[SerializeField]
[Tooltip("小球回正花费的时间")]
private float revertZeroMinTime = 0.1f;
[SerializeField]
[Tooltip("小球回正花费的时间")]
private float revertZeroMaxTime = 0.1f;
[SerializeField]
[Tooltip("小球速度小于多少播回正动画")]
private float speedThreshold = 0.1f;
[Header("capsule timeline effect ")]
[SerializeField]
private PlayableAsset rewardTimeline;
[SerializeField] private PlayableAsset rewardLastTimeline;
[SerializeField]
[Tooltip("delay Play reward Timeline Time <color=red>rewardDelayTime+rotationTime的时间是总的旋转时间</color>")]
private float rewardDelayTime = 0.2f;
[SerializeField]
[Tooltip("remain rotation time <color=red>rewardDelayTime+rotationTime的时间是总的旋转时间</color>")]
private float rotationTime = 0.2f;
[SerializeField]
[Tooltip("delay Play reward Timeline Time On last <color=red>rewardDelayTime+rotationTime的时间是总的旋转时间</color>")]
private float lastRewardDelayTime = 0.2f;
[SerializeField]
[Tooltip("remain rotation time On last <color=red>rewardDelayTime+rotationTime的时间是总的旋转时间</color>")]
private float lastRotationTime = 0.2f;
#endregion
private bool _isRotating = false;
private bool _isEnterAniPlaying = false;
private bool _isRoundCompleted = false;
private CancellationTokenSource _enterAniCancellationTokenSource;
private HashSet<CapsuleRewardCell> _rotationCells = new HashSet<CapsuleRewardCell>();
private ITimeTickService _tickService;
private void Awake()
{
_tickService = GContext.container.Resolve<ITimeTickService>();
_textTime = gameObject.FindChildGameObject("text_time").GetComponent<TextMeshProUGUI>();
_rewardItems = gameObject.FindChildGameObject("rewardContainer")
.GetComponentsInChildren<CapsuleRewardCell>();
btn_buy = gameObject.FindChildGameObject(nameof(btn_buy)).GetComponentInChildren<Button>();
btn_buy.onClick.AddListener(OnClickBuy);
btn_probability = gameObject.FindChildGameObject(nameof(btn_probability)).GetComponent<Button>();
btn_probability.onClick.AddListener(OnClickProbability);
btn_close = gameObject.FindChildGameObject(nameof(btn_close)).GetComponent<Button>();
btn_close.onClick.AddListener(OnClickClose);
p_text = btn_buy.gameObject.FindChildGameObject(nameof(p_text)).GetComponentInChildren<TextMeshProUGUI>();
_panelDirector = GetComponent<PlayableDirector>();
_knobAni = gameObject.FindChildGameObject("xuanniu").transform;
_dropReward = gameObject.FindChildGameObject("mask_reward").GetComponentInChildren<CapsuleRewardCell>();
_tickService?.SecondTick?.Subscribe(UpdateTimer).AddTo(disposables);
_enterAniCancellationTokenSource = new CancellationTokenSource();
PlayEnterAni(_enterAniCancellationTokenSource.Token);
}
protected override void OnDestroy()
{
// 取消正在进行的动画
if (_enterAniCancellationTokenSource != null)
{
_enterAniCancellationTokenSource.Cancel();
_enterAniCancellationTokenSource.Dispose();
_enterAniCancellationTokenSource = null;
}
btn_buy.onClick.RemoveAllListeners();
btn_probability.onClick.RemoveAllListeners();
btn_close.onClick.RemoveAllListeners();
_rotationCells.Clear();
_rotationCells = null;
base.OnDestroy();
}
private async void OnClickProbability()
{
try
{
UITypes.CapsulePackProbabilityPanel.SetType("OfferCapsuleRewardsProbabilityPopupPanel");
var panelGo = await UIManager.Instance.ShowUILoad(UITypes.CapsulePackProbabilityPanel);
panelGo.GetComponent<GeneralProbabilityPanel>().Init(Data.GetProbabilityData());
}
catch (Exception e)
{
Debug.LogError(e);
}
}
protected override void Start()
{
base.Start();
GContext.OnEvent<RewardPanelClose>().Subscribe(OnRewardPanelClose).AddTo(disposables);
InitUI();
ShowUI();
btn_close.enabled = false;
}
private async void PlayEnterAni(CancellationToken cancellationToken)
{
try
{
_rotationCells.Clear();
_isEnterAniPlaying = true;
if (Data.CurrentProgressCount == 0)
{
foreach (var cell in _rewardItems)
cell.Init();
_panelDirector.Play(showFirstTimeline);
cancellationToken.ThrowIfCancellationRequested();
await Awaiters.Seconds(showFirstDelayTime);
cancellationToken.ThrowIfCancellationRequested();
foreach (var cell in _rewardItems)
{
cancellationToken.ThrowIfCancellationRequested();
await cell.PlayEnterAni(capsuleShowTime);
}
}
else
{
_panelDirector.Play(showTimeline);
foreach (var cell in _rewardItems)
cell.PlayEnterAni2();
cancellationToken.ThrowIfCancellationRequested();
await Awaiters.Seconds((float)_panelDirector.duration);
}
}
catch (OperationCanceledException)
{
}
catch (Exception e)
{
Debug.LogError(e);
}
finally
{
_isEnterAniPlaying = false;
SetButtonState(true);
}
}
private void FixedUpdate()
{
if (_isEnterAniPlaying) return;
if (_rewardItems == null) return;
foreach (var cell in _rewardItems)
{
if (!cell.isActiveAndEnabled) continue;
if (_isRotating)
{
var ballPos = cell.RectTransform.anchoredPosition;
var dir = (ballPos - forceCenter).normalized;
dir = Quaternion.Euler(0, 0, rotationAngle) * -dir;
if (cell.Rigidbody2D)
cell.Rigidbody2D.AddForce(dir * force, forceMode);
}
if (_rotationCells.Contains(cell)) continue;
if (cell.transform.rotation == Quaternion.Euler(Vector3.zero)) continue;
if (cell.Rigidbody2D.velocity.magnitude <= speedThreshold)
{
_rotationCells.Add(cell);
cell.Rigidbody2D.freezeRotation = true;
cell.RectTransform.localEulerAngles = new Vector3(0, 0, cell.RectTransform.localEulerAngles.z);
var currentAngle = cell.RectTransform.localEulerAngles.z;
// 将角度转换到 -180~180 范围
if (currentAngle > 180f) currentAngle -= 360f;
var revertZeroTime = Random.Range(revertZeroMinTime, revertZeroMaxTime);
// 根据角度正负选择旋转方向
if (currentAngle < 0)
cell.transform.DORotate(new Vector3(0, 0, 360f), revertZeroTime, RotateMode.FastBeyond360);
else
cell.transform.DORotate(Vector3.zero, revertZeroTime, RotateMode.FastBeyond360);
}
}
}
private void StopBallRotation()
{
_isRotating = false;
if (!_isRoundCompleted)
ShowUI();
else
foreach (var rewardCell in _rewardItems)
rewardCell.gameObject.SetActive(false);
}
private void UpdateTimer(long obj)
{
_textTime.text = ConvertTools.ConvertTime2(Data.RemainingTime);
}
private void InitUI()
{
_textTime.text = string.Empty;
p_text.text = string.Empty;
_dropReward.gameObject.SetActive(false);
}
private void ShowUI()
{
if (Data == null) return;
ShowBtn();
_textTime.text = ConvertTools.ConvertTime2(Data.RemainingTime);
var rewards = Data.GetRemainRewards();
for (int i = 0; i < _rewardItems.Length; i++)
{
var rewardItem = _rewardItems[i];
if (i < rewards.Count)
{
rewardItem.SetData(rewards[i]);
rewardItem.ShowUI();
rewardItem.gameObject.SetActive(true);
}
else
rewardItem.gameObject.SetActive(false);
}
}
private void ShowBtn()
{
if (!Data.IsActive)
{
this.gameObject.SetActive(false);
return;
}
var packConfig = Data.GetPackConfig();
var iapItem = _manager.Tables.TbIAPItemList?.GetOrDefault(packConfig.IAPID);
var sKUDetailDataEvent = new SKUDetailDataEvent(iapItem);
GContext.Publish(sKUDetailDataEvent);
p_text.text = sKUDetailDataEvent.price;
}
[ContextMenu("OnClickBuy")]
private async void OnClickBuy()
{
try
{
var packConfig = Data.GetPackConfig();
var iapItem = Data.Tables.TbIAPItemList?.GetOrDefault(packConfig.IAPID);
var reward = Data.GetRandomRewardIndex();
var shopBuyTypeData = new ShopBuyTypeData
{
NoShow = true,
extraPurcheData = new JObject
{
{nameof(CapsulePackData), reward.Index },
{"EventID",Data.EventID},
{"Progress",Data.CurrentProgressCount },
{"DropID",reward.DropID}
}
};
var itemDatas = GContext.container.Resolve<PlayerItemData>()
.GetItemDataByDropIdLureInflation(Data.InflationRate, reward.DropID);
if (itemDatas == null)
{
Debug.LogError($"itemDatas is null, dropID:{reward.DropID}");
return;
}
if (iapItem == null)
{
GContext.Publish(new ShowData(itemDatas, RewardType.NormalOne));
GContext.container.Resolve<PlayerItemData>().AddItem(itemDatas, null);
await OnBuySuccess(reward);
return;
}
var result = await GContext.container.Resolve<PlayerShopData>()
.OnBuy(packConfig.DropID, shopBuyTypeData, iapItem, null, RewardType.NormalOne);
if (result)
{
GContext.Publish(new ShowData(itemDatas, RewardType.NormalOne));
GContext.container.Resolve<PlayerItemData>().AddItem(itemDatas, null);
await OnBuySuccess(reward);
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
private async Task OnBuySuccess(ICapsuleReward reward)
{
_dropReward.SetData(reward);
_dropReward.ShowUI();
_rotationCells.Clear();
_manager.AddProgress(reward.Index, Data, ref _isRoundCompleted);
SetButtonState(false);
await PlayKnobAni();
await PlayRotationAni();
await Awaiters.Seconds(showRewardPanelTime);
GContext.Publish(new ShowData());
await Awaiters.Seconds(delayRevertKnobTime);
PlayKnobRevertAni();
SetButtonState(true);
}
private void SetButtonState(bool isActive)
{
btn_buy.transform.parent.gameObject.SetActive(isActive);
btn_close.enabled = isActive;
}
private async Task PlayKnobAni()
{
GContext.Publish(new EventUISound("audio_ui_offercapsuleicebound_knob_turnon"));
await _knobAni.transform.DORotate(new Vector3(0, 0, -180), knobAniTime, RotateMode.LocalAxisAdd)
.AsyncWaitForCompletion();
}
private void PlayKnobRevertAni()
{
_knobAni.transform.DORotate(new Vector3(0, 0, -180), knobAniTime, RotateMode.LocalAxisAdd);
}
private async Task PlayRotationAni()
{
try
{
if (_rewardItems == null) return;
foreach (var cell in _rewardItems)
{
if (cell.Rigidbody2D)
{
cell.Rigidbody2D.gravityScale = gravityInRotation;
cell.Rigidbody2D.freezeRotation = false;
}
if (cell.Collider)
{
cell.Collider.size = sizeInRotation;
}
}
_isRotating = true;
float remainTime = 0;
if (_isRoundCompleted)
{
await Awaiters.Seconds(lastRewardDelayTime);
_panelDirector.Play(rewardLastTimeline);
remainTime = lastRotationTime;
}
else
{
await Awaiters.Seconds(rewardDelayTime);
_panelDirector.Play(rewardTimeline);
remainTime = rotationTime;
}
_dropReward.gameObject.SetActive(true);
await Awaiters.Seconds(remainTime);
_isRotating = false;
StopBallRotation();
foreach (var cell in _rewardItems)
{
if (!cell.Rigidbody2D) continue;
cell.Rigidbody2D.gravityScale = gravityOnEnd;
await Awaiters.NextFrame;
if (cell.Collider) cell.Collider.size = sizeOnEnd;
}
var timelineRemainTime = (float)_panelDirector.duration - remainTime;
await Awaiters.Seconds(timelineRemainTime < 0 ? 0 : timelineRemainTime);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public void OnRewardPanelClose(RewardPanelClose e)
{
try
{
if (_dropReward) _dropReward?.gameObject?.SetActive(false);
if (!_isRoundCompleted) return;
_isRoundCompleted = false;
// if(Data.IsActive)
// RefreshUI();
// else
OnClickClose();
}
catch (Exception exception)
{
Debug.LogError($"jsd {exception}");
}
}
private void RefreshUI()
{
_panelDirector.Play(outTimeline);
_panelDirector.stopped -= ShowNewRound;
_panelDirector.stopped += ShowNewRound;
}
private void ShowNewRound(PlayableDirector director)
{
ShowUI();
// 取消正在进行的动画
if (_enterAniCancellationTokenSource != null)
{
_enterAniCancellationTokenSource.Cancel();
_enterAniCancellationTokenSource.Dispose();
_enterAniCancellationTokenSource = null;
}
_enterAniCancellationTokenSource = new CancellationTokenSource();
PlayEnterAni(_enterAniCancellationTokenSource.Token);
_panelDirector.stopped -= ShowNewRound;
}
private async void OnClickClose()
{
try
{
SetButtonState(false);
await PlayOutAni();
UIManager.Instance.DestroyUI(UITypes.CapsulePackPanel);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
private async Task PlayOutAni()
{
_panelDirector.Play(outTimeline);
await Awaiters.Seconds((float)outTimeline.duration);
}
}
}