751 lines
28 KiB
C#
751 lines
28 KiB
C#
//header
|
||
// Disco Party 活动
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using asap.core;
|
||
using DataCenter;
|
||
using game;
|
||
using GameCore;
|
||
using Spine.Unity;
|
||
using TMPro;
|
||
using UniRx;
|
||
using UnityEngine;
|
||
using UnityEngine.AddressableAssets;
|
||
using UnityEngine.Playables;
|
||
using UnityEngine.UI;
|
||
|
||
public class OfferDiscoPartyRefreshEvent { }
|
||
namespace UI.OfferDiscoParty
|
||
{
|
||
internal struct DiscoPartyReward
|
||
{
|
||
public RewardItemNew RewardItem;
|
||
public Transform RewardTrans;
|
||
// public Transform ChainTrans;
|
||
// public Transform Chest;
|
||
//
|
||
public int Status;
|
||
}
|
||
public class OfferDiscoPartyPanel : BasePanel
|
||
{
|
||
// 管理
|
||
private OfferDiscoPartyManager _manager;
|
||
|
||
private readonly PlayerItemData _playerItemData = GContext.container.Resolve<PlayerItemData>();
|
||
//
|
||
private Timer _timer;
|
||
private List<DiscoPartyReward> _rewardEntities;
|
||
private List<int> _validIds;
|
||
private int _purchasedId;
|
||
private int _purchasedIndex;
|
||
private bool _isRunning = false;
|
||
private int _currentIndex = -1; // 当前高亮的索引
|
||
private List<ItemData> _buyItems;
|
||
private TaskCompletionSource<bool> _rewardGetTask;
|
||
|
||
// 谷歌动画
|
||
private SkeletonGraphic _djSkeletonGraphic;
|
||
|
||
// skeletonGraphic.AnimationState.SetAnimation(0, "Idle01", true);
|
||
#region UIElement
|
||
private TMP_Text _textTime;
|
||
//
|
||
private Button _btnDance;
|
||
private Transform _btnDanceTrans;
|
||
|
||
private Transform _btnDanceGrayTrans;
|
||
private Button _btnDanceGray;
|
||
|
||
private Button _btnBuyGreen;
|
||
private Transform _btnBuyTrans;
|
||
private CanvasGroup _canvasGroup;
|
||
// private TMP_Text _btnBuyTextInfo;
|
||
private TMP_Text _btnBuyGreenPText;
|
||
private Transform _done;
|
||
private Button _btnProbability;
|
||
|
||
|
||
// 特效
|
||
private Transform _fxUIDiscoBgLight; // DONOthing
|
||
private Transform _fxUIDiscoDjPlay;
|
||
private Transform _fxUIDiscoDjSelected;
|
||
private Transform _fxUIDiscoDjPlayKeyboard;
|
||
private Transform _fxUIDiscoBallLight;
|
||
|
||
|
||
#endregion
|
||
|
||
#region 运动参数
|
||
[Space]
|
||
[Header("奖励组件")]
|
||
public Transform[] rewardTransGroups;
|
||
|
||
|
||
[Header("—— 基础时长(秒) ——")]
|
||
[Tooltip("总时长基础项:T = baseTime + perItemTime * n + 随机抖动")]
|
||
public float baseTime = 0.80f; // 可调:0.72~0.90
|
||
[Tooltip("每个剩余格带来的时长增量")]
|
||
public float perItemTime = 0.1f; // 可调:0.055~0.08
|
||
[Tooltip("时长随机抖动幅度(±)")]
|
||
public float timeJitter = 0.12f;
|
||
|
||
[Header("—— 步数估计 ——")]
|
||
[Tooltip("期望步数:S_desired ≈ stepK * n + stepB ± stepRand")]
|
||
public float stepK = 1.5f; // 可调:1.5~1.6
|
||
public int stepB = 8; // 可调:8~10
|
||
public int stepRandNeg = -2; // 随机下限
|
||
public int stepRandPos = +2; // 随机上限
|
||
|
||
[Header("—— 步间隔系数(相对 T/S)——")]
|
||
[Tooltip("最短步间隔系数(dt_min = minFactor * (T/S))")]
|
||
public float minFactor = 0.35f; // 原建议 0.35(更快);保守 0.5
|
||
[Tooltip("最长步间隔系数(dt_max = maxFactor * (T/S))")]
|
||
public float maxFactor = 3.0f; // 原建议 2.10;保守 2.50
|
||
|
||
[Header("—— 安全下限(避免卡慢/频闪)——")]
|
||
[Tooltip("大池子(n≥8)最短步间隔下限(秒)")]
|
||
public float hardMinLargePool = 0.035f; // 35ms,保守 0.040
|
||
[Tooltip("双格(n=2)最短步间隔下限(秒)")]
|
||
public float hardMinTwoPool = 0.075f; // 75ms,可 0.070~0.090 之间微调
|
||
|
||
[Header("—— 仪式感的最小整圈数 ——")]
|
||
[Tooltip("n≥6 时的最小整圈数 L_min")]
|
||
public int minLapsLarge = 2; // 2 圈
|
||
[Tooltip("3≤n≤5 时的最小整圈数 L_min")]
|
||
public int minLapsMid = 1; // 1 圈
|
||
[Tooltip("n=2 时的最小整圈数 L_min(建议更高以打散可预测)")]
|
||
public int minLapsTwo = 3; // 3 圈
|
||
|
||
[Header("—— 起步爆发与快段占比 ——")]
|
||
[Tooltip("起步爆发的步数(前几步更快),0 关闭")]
|
||
public int burstSteps = 2;
|
||
[Tooltip("快段占比(0~1),剩余为减速段")]
|
||
[Range(0.4f, 0.9f)] public float fastPhaseRatio = 0.70f;
|
||
|
||
[Header("—— n=2 特化 ——")]
|
||
|
||
[Tooltip("n=2:落点前是否微悬停(只加时间,不加步)")]
|
||
public bool microPauseForTwo = true;
|
||
[Tooltip("n=2:微悬停(秒)范围最小值")]
|
||
public float microPauseTwoMin = 0.1f;
|
||
[Tooltip("n=2:微悬停(秒)范围最大值")]
|
||
public float microPauseTwoMax = 0.15f;
|
||
|
||
private System.Random rng = new();
|
||
#endregion
|
||
|
||
#region 生命周期
|
||
private void Awake()
|
||
{
|
||
_manager = GContext.container.Resolve<OfferDiscoPartyManager>();
|
||
_canvasGroup = transform.Find("root").GetComponent<CanvasGroup>();
|
||
_textTime = transform.Find("root/text_time").GetComponent<TMP_Text>();
|
||
_btnBuyTrans = transform.Find("root/btn_buy");
|
||
_done = transform.Find("root/done");
|
||
_btnBuyGreen = transform.Find("root/btn_buy/btn_green").GetComponent<Button>();
|
||
_btnBuyGreenPText = transform.Find("root/btn_buy/btn_green/Ani_Container/p_text").GetComponent<TMP_Text>();
|
||
// _btnBuyTextInfo = transform.Find("root/btn_buy/text_info").GetComponent<TMP_Text>();
|
||
|
||
_btnDanceTrans = transform.Find("root/btn_dance");
|
||
_btnDance = transform.Find("root/btn_dance/btn_green").GetComponent<Button>();
|
||
_btnDanceGrayTrans = transform.Find("root/btn_dance_gray");
|
||
_btnDanceGray = transform.Find("root/btn_dance_gray/btn_green").GetComponent<Button>();
|
||
_btnProbability = transform.Find("root/btn_probability").GetComponent<Button>();
|
||
|
||
|
||
GContext.OnEvent<RewardPanelClose>().Subscribe(RewardPanelClose).AddTo(disposables);
|
||
|
||
// _btnBuyTextInfo.text = "";
|
||
//
|
||
_djSkeletonGraphic = transform.Find("root/spine_root/npc").GetComponent<SkeletonGraphic>();
|
||
|
||
_fxUIDiscoBgLight = transform.Find("root/spine_root/fx_ui_disco_bg_light");
|
||
_fxUIDiscoDjPlay = transform.Find("root/spine_root/fx_ui_disco_dj_play");
|
||
_fxUIDiscoDjSelected = transform.Find("root/spine_root/fx_ui_disco_dj_selected");
|
||
_fxUIDiscoBallLight = transform.Find("root/disco_ball/fx_ui_disco_ball_light");
|
||
_fxUIDiscoDjPlayKeyboard = transform.Find("root/spine_root/npc/fx_ui_disco_dj_play_keyboard");
|
||
}
|
||
|
||
protected override void Start()
|
||
{
|
||
Log("Start-> ");
|
||
base.Start();
|
||
|
||
AddEvents();
|
||
var rdNumber = rewardTransGroups.Length;
|
||
_rewardEntities = new List<DiscoPartyReward>();
|
||
for (var i = 0; i < rdNumber; ++i)
|
||
{
|
||
var rewardTrans = rewardTransGroups[i];
|
||
var rewardItem = rewardTrans.Find("reward").GetComponent<RewardItemNew>();
|
||
_rewardEntities.Add(new DiscoPartyReward
|
||
{
|
||
RewardTrans = rewardTransGroups[i],
|
||
RewardItem = rewardItem,
|
||
});
|
||
}
|
||
UpdateItemUI();
|
||
RunNormalFx();
|
||
SetDjStateIdle();
|
||
StartCountDown();
|
||
_manager.IsFirstOpen = false;
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
// throw new NotImplementedException();
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
// throw new NotImplementedException();
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
#if UNITY_EDITOR
|
||
if (Input.GetKeyDown(KeyCode.F))
|
||
{
|
||
// _ = RunFinalRewardLogic();
|
||
}
|
||
if (Input.GetKeyDown(KeyCode.D))
|
||
{
|
||
// _manager.ClearOfferChainsData();
|
||
// _manager.Init();
|
||
_manager.ClearOfferDiscoPartyData();
|
||
_manager.Init();
|
||
}
|
||
#endif
|
||
}
|
||
|
||
protected override void OnDestroy()
|
||
{
|
||
_timer?.Cancel();
|
||
_timer = null;
|
||
base.OnDestroy();
|
||
}
|
||
#endregion
|
||
|
||
#region 实现函数
|
||
|
||
// 设置DJ 状态
|
||
private void SetDjStateIdle()
|
||
{
|
||
_djSkeletonGraphic.AnimationState.SetAnimation(0, "dj_idle", true);
|
||
}
|
||
|
||
private void SetDjStateHype()
|
||
{
|
||
_djSkeletonGraphic.AnimationState.SetAnimation(0, "dj_hype", false);
|
||
_djSkeletonGraphic.AnimationState.AddAnimation(0, "dj_idle", true, 0);
|
||
}
|
||
|
||
private void SetDjStateHit()
|
||
{
|
||
_djSkeletonGraphic.AnimationState.SetAnimation(0, "dj_hit", false);
|
||
_djSkeletonGraphic.AnimationState.AddAnimation(0, "dj_idle", true, 0);
|
||
}
|
||
|
||
private void AddEvents()
|
||
{
|
||
_btnBuyGreen.onClick.AddListener(OnClickBuy);
|
||
_btnDance.onClick.AddListener(OnClickDance);
|
||
_btnProbability.onClick.AddListener(OnProbability);
|
||
}
|
||
|
||
private void StartCountDown()
|
||
{
|
||
//
|
||
var timeRemain = _manager.GetTimeRemain();
|
||
var seconds = timeRemain.TotalSeconds;
|
||
_timer = this.AttachTimer((float)seconds, null,
|
||
async (elapsed) =>
|
||
{
|
||
var now = _manager.GetTimeRemain();
|
||
_textTime.text = ConvertTools.ConvertTime2(now.Days, now.Hours, now.Minutes, now.Seconds);
|
||
if (now.TotalMilliseconds < 0)
|
||
{
|
||
if (_canvasGroup.blocksRaycasts)
|
||
{
|
||
_canvasGroup.blocksRaycasts = false;
|
||
Settle(); // 活动结束了
|
||
_canvasGroup.blocksRaycasts = true;
|
||
}
|
||
}
|
||
}, useRealTime: true);
|
||
}
|
||
|
||
private void UpdateItemUI()
|
||
{
|
||
var items = _manager.OfferDiscoPartyData.Items;
|
||
// _manager.OfferDiscoParty.RewardList
|
||
for (var i = 0; i < _rewardEntities.Count; ++i)
|
||
{
|
||
var itemData = new ItemData
|
||
{
|
||
id = items[i].Item,
|
||
count = items[i].Number,
|
||
};
|
||
if (itemData.id == 1001)
|
||
{
|
||
_playerItemData.ItemInflation(itemData, null);
|
||
}
|
||
_rewardEntities[i].RewardItem.SetData(itemData);
|
||
|
||
if (itemData.id == 90803001 || itemData.id == 90803002)
|
||
{
|
||
var btnQuestionMark = _rewardEntities[i].RewardTrans.Find("reward/btn_questionmark").GetComponent<Button>();
|
||
btnQuestionMark.gameObject.SetActive(true);
|
||
btnQuestionMark.enabled = false; // 避免阻挡
|
||
// btnQuestionMark.onClick.AddListener( OnProbability);
|
||
}
|
||
UpdateItemUIByStatus(_rewardEntities[i], items[i].Status);
|
||
}
|
||
UpdateBuyStatus();
|
||
}
|
||
private void UpdateBuyStatus()
|
||
{
|
||
//
|
||
var itemId = _manager.OfferDiscoPartyData.ItemId;
|
||
if (itemId == -1)
|
||
{
|
||
_btnBuyGreen.enabled = false;
|
||
_btnDance.enabled = false;
|
||
_btnDanceGray.enabled = false;
|
||
// _canvasGroup.blocksRaycasts = false;
|
||
_btnDanceTrans.gameObject.SetActive(false);
|
||
_btnBuyTrans.gameObject.SetActive(false);
|
||
_btnDanceGrayTrans.gameObject.SetActive(false);
|
||
_done.gameObject.SetActive(true);
|
||
}
|
||
else
|
||
{
|
||
_done.gameObject.SetActive(false);
|
||
// 第一次免费
|
||
if (_manager.OfferDiscoPartyData.LotteryNum < _manager.FreeTime)
|
||
{
|
||
_btnDanceTrans.gameObject.SetActive(true);
|
||
_btnDance.enabled = true;
|
||
_btnBuyTrans.gameObject.SetActive(false);
|
||
_btnDanceGrayTrans.gameObject.SetActive(false);
|
||
}
|
||
else
|
||
{
|
||
_btnBuyTrans.gameObject.SetActive(true);
|
||
_btnBuyGreen.enabled = true;
|
||
_btnDanceTrans.gameObject.SetActive(false);
|
||
_btnDanceGrayTrans.gameObject.SetActive(false);
|
||
|
||
var iAPItemList = _manager.GetIapItemListThisTime();
|
||
var skuDetailDataEvent = new SKUDetailDataEvent(iAPItemList);
|
||
GContext.Publish(skuDetailDataEvent);
|
||
_btnBuyGreenPText.text = LocalizationMgr.GetFormatTextValue("UI_OfferDiscoPartyPanel_2", skuDetailDataEvent.price);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void UpdateValidItemUIByStatusAndIndex(int validIdx, TItemStatues status)
|
||
{
|
||
var idx = _validIds[validIdx] - 1;
|
||
UpdateItemUIByStatus(_rewardEntities[idx], status);
|
||
}
|
||
|
||
private void UpdateItemUIByStatus(DiscoPartyReward rewardEntity, TItemStatues status)
|
||
{
|
||
var select = rewardEntity.RewardTrans.Find("select");
|
||
var done = rewardEntity.RewardTrans.Find("done");
|
||
var bg = rewardEntity.RewardTrans.Find("bg");
|
||
|
||
if (status == TItemStatues.Valid)
|
||
{
|
||
bg.gameObject.SetActive(true);
|
||
select.gameObject.SetActive(false);
|
||
done.gameObject.SetActive(false);
|
||
// rewardEntity.RewardTrans.Find("bg_locked").gameObject.SetActive(true);
|
||
// rewardEntity.RewardTrans.Find("selected").gameObject.SetActive(false);
|
||
// rewardEntity.RewardTrans.Find("bg_done").gameObject.SetActive(false);
|
||
// rewardEntity.RewardTrans.Find("mask_done").gameObject.SetActive(false);
|
||
// rewardEntity.ChainTrans.gameObject.SetActive(true);
|
||
// rewardEntity.Chest.GetComponent<Animation>().Play(aniChestNml);
|
||
}
|
||
else if (status == TItemStatues.HighLight)
|
||
{
|
||
bg.gameObject.SetActive(false);
|
||
select.gameObject.SetActive(true);
|
||
done.gameObject.SetActive(false);
|
||
}
|
||
else if (status == TItemStatues.Done)
|
||
{
|
||
bg.gameObject.SetActive(true);
|
||
select.gameObject.SetActive(false);
|
||
done.gameObject.SetActive(true);
|
||
|
||
// rewardEntity.RewardTrans.Find("bg_locked").gameObject.SetActive(false);
|
||
// rewardEntity.RewardTrans.Find("selected").gameObject.SetActive(false);
|
||
// rewardEntity.RewardTrans.Find("bg_done").gameObject.SetActive(true);
|
||
// rewardEntity.RewardTrans.Find("mask_done").gameObject.SetActive(true);
|
||
// rewardEntity.ChainTrans.gameObject.SetActive(false);
|
||
// rewardEntity.Chest.GetComponent<Animation>().Play(aniChestLocked);
|
||
}
|
||
}
|
||
|
||
private void SaveLotteryContext()
|
||
{
|
||
_validIds = _manager.ValidIds;
|
||
_purchasedId = _manager.OfferDiscoPartyData.ItemId;
|
||
_purchasedIndex = _validIds.IndexOf(_purchasedId);
|
||
}
|
||
|
||
// 刷新
|
||
private void OnRefreshByEvent(OfferDiscoPartyRefreshEvent evt)
|
||
{
|
||
Log("OnRefreshByEvent -> ");
|
||
}
|
||
|
||
public async void OnProbability()
|
||
{ // 设置
|
||
|
||
_btnProbability.enabled = false;
|
||
await UIManager.Instance.ShowUI(UITypes.OfferDiscoRewardsProbabilityPopupPanel);
|
||
_btnProbability.enabled = true;
|
||
}
|
||
|
||
public async void OnClickDance()
|
||
{
|
||
Log("OnClickBuyDance()-> ");
|
||
if (_manager.OfferDiscoPartyData.LotteryNum >= _manager.FreeTime)
|
||
return;
|
||
var item = _manager.GetTItemById(_manager.OfferDiscoPartyData.ItemId);
|
||
if (item == null)
|
||
return;
|
||
|
||
_btnDance.enabled = false;
|
||
|
||
var itemDatas = new List<ItemData>
|
||
{
|
||
new() { count = item.Number, id = item.Item,curCount = (ulong)_playerItemData.GetItemCount(item.Item) }
|
||
};
|
||
//膨胀体力
|
||
_playerItemData.LureInflation(itemDatas, null);
|
||
_buyItems = itemDatas;
|
||
SaveLotteryContext();
|
||
_manager.UIRunning = true;
|
||
if (_buyItems is { Count: > 0 })
|
||
{
|
||
if (item.Item != 90803001 && item.Item != 90803002)
|
||
{
|
||
GContext.Publish(new ShowData(_buyItems, RewardType.NormalOne));
|
||
}
|
||
_playerItemData.AddItem(_buyItems, null);
|
||
_manager.OnBuySuccess();
|
||
}
|
||
|
||
|
||
await StartLotteryAsync(_purchasedIndex);
|
||
|
||
_btnDance.enabled = true;
|
||
}
|
||
|
||
public async void OnClickBuy()
|
||
{
|
||
_btnBuyGreen.enabled = false;
|
||
var item = _manager.GetTItemById(_manager.OfferDiscoPartyData.ItemId);
|
||
PlayerItemData playerItemData = GContext.container.Resolve<PlayerItemData>();
|
||
var itemDatas = new List<ItemData>
|
||
{
|
||
new() { count = item.Number, id = item.Item,curCount = (ulong)playerItemData.GetItemCount(item.Item) }
|
||
};
|
||
//膨胀体力
|
||
playerItemData.LureInflation(itemDatas, null);
|
||
_buyItems = itemDatas;
|
||
|
||
|
||
|
||
var iapItemList = _manager.GetIapItemListThisTime();
|
||
//.GetIapItemListById(_manager.OfferChainsData.ItemId);
|
||
var dropId = _manager.GetDropIdById(_manager.OfferDiscoPartyData.ItemId);
|
||
var shopBuyTypeData = new ShopBuyTypeData
|
||
{
|
||
type = ShopBuyType.EventPack,
|
||
ID = _manager.OfferDiscoPartyData.EventId,
|
||
NoShow = true,
|
||
//IsHide = true,
|
||
};
|
||
SaveLotteryContext();
|
||
_manager.UIRunning = true;
|
||
//注意: 购买完成之后,会保存数据到管理器,从Manager 获取数据可能会变化
|
||
var rdType = RewardType.NormalOne;
|
||
if (item.Item is 90803001 or 90803002)
|
||
{
|
||
rdType = RewardType.Normal;
|
||
shopBuyTypeData.IsHide = true;
|
||
}
|
||
var result = await GContext.container.Resolve<PlayerShopData>().OnBuy(dropId, shopBuyTypeData, iapItemList, itemDatas, rewardShowType: rdType);
|
||
if (result)
|
||
{
|
||
await StartLotteryAsync(_purchasedIndex);
|
||
}
|
||
_btnBuyGreen.enabled = true;
|
||
}
|
||
|
||
private async Task StartLotteryAsync(int targetIndex)
|
||
{
|
||
if (_isRunning)
|
||
return;
|
||
targetIndex = Mathf.Clamp(targetIndex, 0, _validIds.Count - 1);
|
||
|
||
SetDjStateHype();
|
||
|
||
_fxUIDiscoDjPlay.gameObject.SetActive(true);
|
||
_fxUIDiscoDjPlayKeyboard.gameObject.SetActive(true);
|
||
_fxUIDiscoDjSelected.gameObject.SetActive(false);
|
||
await LotteryAsync(targetIndex);
|
||
}
|
||
|
||
|
||
private async Task LotteryAsync(int targetIndex)
|
||
{
|
||
if (_validIds == null || _validIds.Count == 0)
|
||
{
|
||
_manager.UIRunning = false;
|
||
return;
|
||
}
|
||
|
||
_isRunning = true;
|
||
_canvasGroup.blocksRaycasts = false;
|
||
var n = _validIds.Count;
|
||
_currentIndex = UnityEngine.Random.Range(0, n);
|
||
if (n > 1)
|
||
{
|
||
await RunLotteryLogic(targetIndex);
|
||
}
|
||
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
UpdateValidItemUIByStatusAndIndex(i, i == targetIndex ? TItemStatues.HighLight : TItemStatues.Valid);
|
||
}
|
||
|
||
Log($"LotteryAsync: {targetIndex} {_currentIndex} {_validIds[_currentIndex] - 1}");
|
||
await RunGetLotteryAni(targetIndex);
|
||
|
||
if (_rewardGetTask != null)
|
||
{
|
||
await _rewardGetTask.Task;
|
||
}
|
||
|
||
RunNormalFx();
|
||
Log($"LotteryAsync: 状态复原");
|
||
_manager.UIRunning = false;
|
||
_canvasGroup.blocksRaycasts = true;
|
||
_isRunning = false;
|
||
}
|
||
private void AdvanceStep(int step, int maxStep)
|
||
{
|
||
UpdateValidItemUIByStatusAndIndex(_currentIndex, TItemStatues.Valid);
|
||
_currentIndex = (_currentIndex + step) % maxStep;
|
||
UpdateValidItemUIByStatusAndIndex(_currentIndex, TItemStatues.HighLight);
|
||
}
|
||
|
||
private async Task RunLotteryLogic(int targetIndex)
|
||
{
|
||
var n = _validIds.Count;
|
||
var currIndex = _currentIndex;
|
||
// —— 1) 计算期望步数/总时长(仍按你的既有公式/参数)——
|
||
int sDesired = Mathf.RoundToInt(stepK * n + stepB) + RandInt(stepRandNeg, stepRandPos);
|
||
float totalTime = baseTime + perItemTime * n + RandFloat(-timeJitter, +timeJitter);
|
||
totalTime = Mathf.Max(0.2f, totalTime); // 安全夹紧
|
||
|
||
// —— 2) 目标对齐:S = d + L*n,必定落在目标 ——
|
||
int d = (targetIndex - currIndex + n) % n;
|
||
if (d == 0) d = n; // 避免原地落点(至少走一圈)
|
||
|
||
int minLaps = (n >= 6) ? minLapsLarge : (n >= 3 ? minLapsMid : minLapsTwo);
|
||
int L = Mathf.Max(minLaps, Mathf.CeilToInt((sDesired - d) / (float)n));
|
||
int S = d + L * n; // ★ 之后都以 S 为准
|
||
|
||
// —— 3) 计算步间隔范围 ——
|
||
float dtAvg = totalTime / Mathf.Max(1, S);
|
||
float dtMin = minFactor * dtAvg;
|
||
float dtMax = maxFactor * dtAvg;
|
||
|
||
// 安全下限:避免过慢/频闪
|
||
if (n >= 8) dtMin = Mathf.Max(dtMin, hardMinLargePool);
|
||
if (n == 2) dtMin = Mathf.Max(dtMin, hardMinTwoPool);
|
||
|
||
// —— 4) 主循环:前段快、尾段 easeOutQuad 减速 ——
|
||
int burst = Mathf.Clamp(burstSteps, 0, Mathf.Max(0, S - 1));
|
||
int fastSteps = Mathf.Clamp(Mathf.RoundToInt(fastPhaseRatio * S), 0, S);
|
||
|
||
for (int step = 1; step <= S; step++)
|
||
{
|
||
AdvanceStep(1, n);
|
||
float waitSec;
|
||
if (step <= burst && burst > 0)
|
||
{
|
||
// 起步爆发:更快
|
||
waitSec = dtMin * 0.70f;
|
||
}
|
||
else if (step <= fastSteps)
|
||
{
|
||
// 快段:近似匀速,可加入极轻微抖动
|
||
waitSec = dtMin;
|
||
}
|
||
else
|
||
{
|
||
// 减速段:easeOutQuad 从 dtMin -> dtMax
|
||
float t = (step - fastSteps) / Mathf.Max(1f, (S - fastSteps));
|
||
t = Mathf.Clamp01(t);
|
||
waitSec = Lerp(dtMin, dtMax, EaseOutQuad(t));
|
||
}
|
||
|
||
// n=2:最后一步前微悬停(只加时间,不加步)
|
||
bool doMicroPause = microPauseForTwo && (n == 2 || step == S - 1);
|
||
if (doMicroPause)
|
||
{
|
||
float extra = UnityEngine.Random.Range(microPauseTwoMin, microPauseTwoMax);
|
||
waitSec += Mathf.Max(0f, extra);
|
||
}
|
||
await Awaiters.Seconds(waitSec);
|
||
}
|
||
}
|
||
|
||
private async Task RunGetLotteryAni(int targetIndex)
|
||
{
|
||
Log("RunGetLotteryAni-> ");
|
||
var targetId = _validIds[targetIndex];
|
||
|
||
SetDjStateHit();
|
||
|
||
RunHitFx(targetIndex);
|
||
await Awaiters.Seconds(1f);
|
||
|
||
var itemData = _buyItems.FirstOrDefault();
|
||
if (itemData?.id is 90803001 or 90803002)
|
||
{
|
||
var boxOpenTask = new TaskCompletionSource<bool>();
|
||
var openGo = await UIManager.Instance.ShowUI(UITypes.OfferDiscoPartyBoxOpen);
|
||
var offerDiscoPartyBoxOpen = openGo.GetComponent<OfferDiscoPartyBoxOpen>().AddEndCallback(_ =>
|
||
{
|
||
Destroy(openGo);
|
||
RunGetLotteryCallback(targetIndex, targetId);
|
||
var bRet = boxOpenTask.TrySetResult(true);
|
||
if (bRet)
|
||
{
|
||
Log("EndCallback");
|
||
}
|
||
});
|
||
if (itemData.id == 90803001)
|
||
{
|
||
offerDiscoPartyBoxOpen.PlayOpenBox1TimeLine();
|
||
}
|
||
else if (itemData.id == 90803002)
|
||
{
|
||
offerDiscoPartyBoxOpen.PlayOpenBox2TimeLine();
|
||
}
|
||
await boxOpenTask.Task;
|
||
}
|
||
else
|
||
{
|
||
RunGetLotteryCallback(targetIndex, targetId);
|
||
// await Awaiters.Seconds(unlockToFinalDelay);
|
||
}
|
||
}
|
||
|
||
private void RunGetLotteryCallback(int targetIndex, int targetId)
|
||
{
|
||
UpdateValidItemUIByStatusAndIndex(targetIndex, TItemStatues.Done);
|
||
if (_buyItems is { Count: > 0 })
|
||
{
|
||
GContext.Publish(new ShowData());
|
||
_rewardGetTask = new TaskCompletionSource<bool>();
|
||
}
|
||
|
||
_manager.FinishLottery(targetId);
|
||
_manager.Settle();
|
||
UpdateBuyStatus();
|
||
Log($"RunGetLotteryAni {targetIndex},{targetId}");
|
||
}
|
||
|
||
//
|
||
private void RunHitFx(int targetIndex)
|
||
{
|
||
_fxUIDiscoDjPlay.gameObject.SetActive(false);
|
||
_fxUIDiscoDjPlayKeyboard.gameObject.SetActive(false);
|
||
_fxUIDiscoDjSelected.gameObject.SetActive(true);
|
||
|
||
var idx = _validIds[targetIndex] - 1;
|
||
Log($"RunHitFx-> {targetIndex} {idx}");
|
||
var iCount = rewardTransGroups.Length;
|
||
for (var i = 0; i < iCount; ++i)
|
||
{
|
||
var fxUIDiscoRewardLoop = rewardTransGroups[i].Find("fx_ui_disco_reward_loop");
|
||
fxUIDiscoRewardLoop?.gameObject?.SetActive(false);
|
||
}
|
||
|
||
var fxUIDiscoRewardSelected = rewardTransGroups[idx].Find("fx_ui_disco_reward_selected");
|
||
if (fxUIDiscoRewardSelected)
|
||
{
|
||
fxUIDiscoRewardSelected.gameObject.SetActive(true);
|
||
}
|
||
}
|
||
|
||
private void RunNormalFx()
|
||
{
|
||
Log("RunNormalFx-> ");
|
||
var items = _manager.OfferDiscoPartyData.Items;
|
||
for (var i = 0; i < rewardTransGroups.Length; ++i)
|
||
{
|
||
var fxUIDiscoRewardLoop = rewardTransGroups[i].Find("fx_ui_disco_reward_loop");
|
||
var fxUIDiscoRewardSelected = rewardTransGroups[i].Find("fx_ui_disco_reward_selected");
|
||
fxUIDiscoRewardSelected?.gameObject.SetActive(false);
|
||
var bLoopVisible = i < items.Count && items[i].Status == 0;
|
||
fxUIDiscoRewardLoop?.gameObject.SetActive(bLoopVisible);
|
||
}
|
||
_fxUIDiscoDjSelected.gameObject.SetActive(false);
|
||
_fxUIDiscoDjPlay.gameObject.SetActive(false);
|
||
_fxUIDiscoDjPlayKeyboard.gameObject.SetActive(false);
|
||
}
|
||
private void RewardPanelClose(RewardPanelClose rewardPanelClose)
|
||
{
|
||
Log("RewardPanelClose -> ");
|
||
_rewardGetTask.SetResult(true);
|
||
}
|
||
|
||
private void Settle()
|
||
{
|
||
UIManager.Instance.DestroyUI(gameObject.name);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 工具函数
|
||
private static float EaseOutQuad(float t) => 1f - (1f - t) * (1f - t);
|
||
private static float Lerp(float a, float b, float t) => a + (b - a) * t;
|
||
// RandInt
|
||
private int RandInt(int aInclusive, int bInclusive)
|
||
{
|
||
int lo = Mathf.Min(aInclusive, bInclusive);
|
||
int hi = Mathf.Max(aInclusive, bInclusive);
|
||
return rng.Next(lo, hi + 1);
|
||
}
|
||
private float RandFloat(float a, float b)
|
||
{
|
||
float lo = Mathf.Min(a, b);
|
||
float hi = Mathf.Max(a, b);
|
||
double r = rng.NextDouble(); // [0,1)
|
||
return (float)(lo + (hi - lo) * r);
|
||
}
|
||
#endregion
|
||
|
||
#region LOG
|
||
private static void Log(string message)
|
||
{
|
||
Debug.Log($"<color=yellow>[{nameof(OfferDiscoPartyPanel)}] {DateTime.Now}: {message}</color>");
|
||
}
|
||
#endregion
|
||
|
||
}
|
||
} |