备份CatanBuilding瘦身独立工程

This commit is contained in:
JSD\13999
2026-05-26 16:15:54 +08:00
commit 2d0e6a61b7
12001 changed files with 2431925 additions and 0 deletions

View File

@@ -0,0 +1,543 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using asap.core;
using cfg;
using DataCenter;
using game;
using GameCore;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Events;
using UnityEngine.Playables;
using UnityEngine.Serialization;
using UnityEngine.UI;
public class OfferChainsRefreshEvent {}
namespace OfferChainsChest
{
public class OfferChainsChestPanel : MonoBehaviour
{
private OfferChainsChestManager _offerChainsChestManager;
private Timer _timer;
private CompositeDisposable _disposable = new();
// 获得商品结束逻辑
private TaskCompletionSource<bool> _rewardGetTask;
//
private struct MyReward
{
public RewardItemNew RewardItem;
public Transform RewardTrans;
public Transform ChainTrans;
public Transform Chest;
//
public int Status;
}
private List<MyReward> _rewardEntities;
// 有效的数据,随机的时候从这里选
private List<int> _validIds;
private int _purchasedId;
private int _purchasedIndex;
// UI
private TMP_Text _textTime;
private TMP_Text _textTitle;
private Button _btnBuyGreen;
private TMP_Text _textBuyGreen;
// private Button _btnClose;
private Button _btnProbability;
private Transform _reward;
private CanvasGroup _canvasGroup;
private Transform _rewardMain;
private RewardItemNew _mainRewardItemNew;
[Space]
[Header("奖励组件")]
public Transform[] rewardTransGroups;
[Header("播放动画")]
public string aniChestNml;
public string aniChestLocked;
public string aniChestUnlock;
[Header("中间播放解锁动画")]
public string aniChestChanged;
[Header("解锁动画时长")]
public float lotteryUnlockTime = 1.5f;
[Header("抽将到最终奖的间隔")]
public float unlockToFinalDelay = 1.0f;
[Header("最终奖到弹出结算(播放中间箱子解锁的动画)")]
public float finalTimeToPopup = 1.0f;
[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 antiParityForTwo = true;
[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 ();
//是否再抽奖
private bool _isRunning = false;
private int _currentIndex = -1; // 当前高亮的索引
private List<ItemData> _buyItems;
private void Awake()
{
_offerChainsChestManager = GContext.container.Resolve<OfferChainsChestManager>();
//
_canvasGroup = transform.Find("root").GetComponent<CanvasGroup>();
// _btnClose = transform.Find("root/btn_close").GetComponent<Button>();
_btnProbability = transform.Find("root/btn_probability").GetComponent<Button>();
_rewardMain = transform.Find("root/reward_main");
_mainRewardItemNew = _rewardMain.Find("reward").GetComponent<RewardItemNew>();
_textTime = transform.Find("root/text_time").GetComponent<TMP_Text>();
_textTitle = transform.Find("root/text_title").GetComponent<TMP_Text>();
_btnBuyGreen = transform.Find("root/btn_buy/btn_green").GetComponent<Button>();
_textBuyGreen = transform.Find("root/btn_buy/btn_green/Ani_Container/p_text").GetComponent<TMP_Text>();
//reward_2 = transform.Find("root/reward_2").GetComponent<RewardItemNew>();
// #region Resolve
// _tables = GContext.container.Resolve<Tables>();
// #endregion
// GContext.OnEvent
GContext.OnEvent<OfferChainsRefreshEvent>().Subscribe(OnRefreshByEvent).AddTo(_disposable);
GContext.OnEvent<RewardPanelClose>().Subscribe(RewardPanelClose).AddTo(_disposable);
}
private void Start()
{
_offerChainsChestManager.IsFirstOpen = false;
_rewardEntities = new List<MyReward>();
for (var i = 0; i < 8; ++i)
{
var kIdx = (i + 1);
var chest = transform.Find($"root/chests/chest{kIdx}");
var trans = //transform.Find($"root/chests/chest{kIdx}/OfferChainsChest_VaultReward");
rewardTransGroups[i];
var chains = transform.Find($"root/chests/chest{kIdx}/chain");
var rewardItem = trans.Find("reward").GetComponent<RewardItemNew>();
_rewardEntities.Add(new MyReward
{
RewardTrans = trans,
ChainTrans = chains,
RewardItem = rewardItem,
Chest = chest,
});
}
AddEvents();
// 填充游戏内容
UpdateItemUI();
//
StartCountDown();
}
private void StartCountDown()
{
//
var timeRemain = _offerChainsChestManager.GetTimeRemain();
var seconds = timeRemain.TotalSeconds;
_timer = this.AttachTimer((float)seconds, null,
async (elapsed) =>
{
var now = _offerChainsChestManager.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 AddEvents()
{
// _btnClose.onClick.AddListener(OnClickClose);
_btnBuyGreen.onClick.AddListener(OnClickBuy);
_btnProbability.onClick.AddListener(OnClickProbability);
}
private void Update()
{
#if UNITY_EDITOR
if (Input.GetKeyDown(KeyCode.F))
{
_ = RunFinalRewardLogic();
}
if (Input.GetKeyDown(KeyCode.D))
{
_offerChainsChestManager.ClearOfferChainsData();
_offerChainsChestManager.Init();
}
#endif
}
private void UpdateItemUI()
{
var items = _offerChainsChestManager.OfferChainsData.Items;
for(var i = 0;i < _rewardEntities.Count;++i)
{
var itemData = new ItemData
{
id = items[i].Item,
count = items[i].Number,
};
_rewardEntities[i].RewardItem.SetData(itemData);
UpdateItemUIByStatus(_rewardEntities[i],items[i].Status);
}
_mainRewardItemNew.SetData( new ItemData
{
id = _offerChainsChestManager.OfferChainsData.ChestReward,
count = 1,
});
;
UpdateBuyStatus();
}
private void UpdateBuyStatus()
{
//
var itemId = _offerChainsChestManager.OfferChainsData.ItemId;
if (itemId == -1)
{
_btnBuyGreen.enabled = false;
}
else
{
var iAPItemList = _offerChainsChestManager.GetIapItemListThisTime();
var skuDetailDataEvent = new SKUDetailDataEvent(iAPItemList);
GContext.Publish(skuDetailDataEvent);
_textBuyGreen.text = LocalizationMgr.GetFormatTextValue("OfferChainsChest_VinePanel_1",skuDetailDataEvent.price);
}
}
private void UpdateValidItemUIByStatusAndIndex(int validIdx, int status)
{
var idx = _validIds[validIdx] - 1;
UpdateItemUIByStatus(_rewardEntities[idx],status);
}
private void UpdateItemUIByStatus(MyReward rewardEntity, int status)
{
if (status == 0)
{
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 == 1)
{
rewardEntity.RewardTrans.Find("bg_locked").gameObject.SetActive(false);
rewardEntity.RewardTrans.Find("selected").gameObject.SetActive(true);
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 == 2)
{
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 OnDestroy()
{
// _btnClose.onClick.RemoveAllListeners();
_disposable?.Dispose();
_disposable = null;
_timer?.Cancel();
_timer = null;
}
private static void Log(object t)
{
Debug.Log($"<color=purple>OfferChainsChestPanel ->{t}</color>");
}
private async Task StartLotteryAsync(int targetIndex)
{
if (_isRunning)
return;
targetIndex = Mathf.Clamp(targetIndex, 0, _validIds.Count - 1);
await LotteryAsync(targetIndex);
}
// —— 工具函数 ——
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);
}
private void AdvanceStep(int step ,int maxStep)
{
UpdateValidItemUIByStatusAndIndex(_currentIndex, 0);
_currentIndex = (_currentIndex + step) % maxStep;
UpdateValidItemUIByStatusAndIndex(_currentIndex, 1);
}
private async Task LotteryAsync(int targetIndex)
{
if (_validIds == null || _validIds.Count == 0)
{
_offerChainsChestManager.UIRunning = false;
return;
}
// onAnimationStart?.Invoke();
_isRunning = true;
_canvasGroup.blocksRaycasts = false;
// var n = 8;
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 ? 1 : 0);
}
Log($"LotteryAsync: {targetIndex} {_currentIndex} {_validIds[_currentIndex] -1}");
await RunGetLotteryAni(targetIndex);
await _rewardGetTask.Task;
if (_offerChainsChestManager.IsCanGetFinalReward())
{
await RunFinalRewardLogic();
}
Log($"LotteryAsync: 状态复原");
_offerChainsChestManager.UIRunning = false;
_canvasGroup.blocksRaycasts = true;
_isRunning = false;
}
private async Task RunLotteryLogic(int targetIndex)
{
var n = _validIds.Count;
// _currentIndex = UnityEngine.Random.Range(0, n);
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++)
{
// UI 层:推进 1 格
// advanceStep?.Invoke();
// UpdateValidItemUIByStatusAndIndex(_currentIndex, 0);
// _currentIndex = (_currentIndex + 1) % n;
// UpdateValidItemUIByStatusAndIndex(_currentIndex, 1);
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 RunFinalRewardLogic()
{
Log("RunFinalRewardLogic-> ");
_rewardMain.GetComponent<Animation>().Play(aniChestChanged);
await Awaiters.Seconds(finalTimeToPopup);
var openPrefab = _offerChainsChestManager.OfferChainsChestMain.ChestOpenPrefab;
var openGo = await Addressables.InstantiateAsync(openPrefab, transform).Task;
openGo.transform.Find("root").GetComponent<PlayableDirector>().stopped += _ =>
{
// 展示最终奖励
Log("RunFinalRewardLogic Finished-> ");
Destroy(openGo);
_offerChainsChestManager.OnGetFinalReward();
UIManager.Instance.DestroyUI(gameObject.name);
};
}
private async Task RunGetLotteryAni(int targetIndex)
{
Log("RunGetLotteryAni-> ");
var targetId = _validIds[targetIndex];
_rewardEntities[targetId - 1].Chest.GetComponent<Animation>().Play(aniChestUnlock);
await Awaiters.Seconds(lotteryUnlockTime);
UpdateValidItemUIByStatusAndIndex(targetIndex, 2);
if (_buyItems is { Count: > 0 })
{
GContext.Publish(new ShowData());
}
_rewardGetTask = new TaskCompletionSource<bool>();
_offerChainsChestManager.FinishLottery(targetId);
UpdateBuyStatus();
Log($"RunGetLotteryAni {targetIndex},{targetId}");
await Awaiters.Seconds(unlockToFinalDelay);
}
// 处理触发概率
private void OnClickProbability()
{
_ = UIManager.Instance.ShowUI(UITypes.OfferChainsRewardsPopupPanel);
}
public async void OnClickBuy()
{
_btnBuyGreen.enabled = false;
var item = _offerChainsChestManager.GetTItemById(_offerChainsChestManager.OfferChainsData.ItemId);
var itemDatas = new List<ItemData>
{
new() { count = item.Number, id = item.Item,curCount = (ulong)GContext.container.Resolve<PlayerItemData>().GetItemCount(item.Item) }
};
_buyItems = itemDatas;
var iapItemList = _offerChainsChestManager.GetIapItemListThisTime();
//.GetIapItemListById(_offerChainsChestManager.OfferChainsData.ItemId);
var dropId = _offerChainsChestManager.GetDropIdById(_offerChainsChestManager.OfferChainsData.ItemId);
var shopBuyTypeData = new ShopBuyTypeData
{
type = ShopBuyType.EventPack,
ID = _offerChainsChestManager.OfferChainsData.EventId,
NoShow = true,
};
SaveLotteryContext();
_offerChainsChestManager.UIRunning = true;
//注意: 购买完成之后,会保存数据到管理器,从Manager 获取数据可能会变化
var result = await GContext.container.Resolve<PlayerShopData>().OnBuy(dropId, shopBuyTypeData, iapItemList, itemDatas);
if (result)
{
await StartLotteryAsync(_purchasedIndex);
}
_btnBuyGreen.enabled = true;
}
private void SaveLotteryContext()
{
_validIds = _offerChainsChestManager.ValidIds;
_purchasedId = _offerChainsChestManager.OfferChainsData.ItemId;
_purchasedIndex = _validIds.IndexOf(_purchasedId);
}
private void OnRefreshByEvent(OfferChainsRefreshEvent evt)
{
Log("OnRefreshByEvent -> ");
// if (_offerChainsChestManager.IsFinished())
// {
// UIManager.Instance.DestroyUI(gameObject.name);
// }
}
private void RewardPanelClose(RewardPanelClose rewardPanelClose)
{
Log("RewardPanelClose -> ");
_rewardGetTask.SetResult(true);
}
private void Settle()
{
UIManager.Instance.DestroyUI(gameObject.name);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d7cdaab89bc04fd08ed0d2bec949d9ff
timeCreated: 1755346983

View File

@@ -0,0 +1,90 @@
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using asap.core;
using DataCenter;
using GameCore;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
namespace OfferChainsChest
{
internal class UIElement
{
public RewardItemNew Reward;
public TMP_Text TextProbability;
}
public class OfferChainsInfoPanel : MonoBehaviour
{
private const int MaxElem = 8;
private OfferChainsChestManager _offerChainsChestManager;
private TMP_Text _textInfo;
private TMP_Text _textTitle;
private readonly UIElement[] _uiElements = new UIElement[MaxElem];
//
// private List<ItemData> _itemDataList = new ();
private struct WeightItemData
{
public ItemData ItemData;
public float Weight;
}
private readonly List<WeightItemData> _weightItemDataList = new();
private void Awake()
{
_offerChainsChestManager = GContext.container.Resolve<OfferChainsChestManager>();
_textInfo = transform.Find("root/text_info").GetComponent<TMP_Text>();
_textTitle = transform.Find("root/text_title").GetComponent<TMP_Text>();
for (var i = 0; i < MaxElem; ++i)
{
var fixIdx = i + 1;
_uiElements[i] = new UIElement
{
Reward = transform.Find($"root/layout/reward_{fixIdx}/reward").GetComponent<RewardItemNew>(),
TextProbability = transform.Find($"root/layout/reward_{fixIdx}/text_probability").GetComponent<TMP_Text>(),
};
}
}
private void Start()
{
var items= _offerChainsChestManager.OfferChainsData.Items;
_weightItemDataList.Clear();
foreach (var item in items)
{
_weightItemDataList.Add(new WeightItemData
{
ItemData = new ItemData
{
id = item.Item,
count = item.Number,
},
Weight = item.Status == 2 ? 1000: _offerChainsChestManager.GetProbability(item.Id),
});
}
_weightItemDataList.Sort((p1, p2) => p1.Weight.CompareTo(p2.Weight));
Log($"_weightItemDataList -> {string.Join(',', _weightItemDataList)}");
for ( var i = 0;i < MaxElem; ++i)
{
var weightItemData = _weightItemDataList[i];
_uiElements[i].Reward.SetData(weightItemData.ItemData);
_uiElements[i].TextProbability.text = weightItemData.Weight >= 1000 ? "" : $"{weightItemData.Weight}%";
_uiElements[i].Reward.SetReceived(weightItemData.Weight >= 1000);
}
}
private void Log(string t)
{
Debug.Log($"<color=purple>OfferChainsInfoPanel ->{t}</color>");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8d81217970a8457b898f64e709875e0c
timeCreated: 1755745824