296 lines
11 KiB
C#
296 lines
11 KiB
C#
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
|
||
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// 抽奖 toggle 层(第二层):挂在总面板里的三个 toggle 面板(PT炮台 / CB翅膀 / ZS战神)其中一个上,
|
||
/// poolType 决定本面板显示哪组奖池;面板里有一堆 item(ChouJiangItem,第三层),
|
||
/// 分为三种 item 类型也就是 A/B/C 三个列表:
|
||
/// items_A(A级·大奖)/ items_B(B级·中奖)/ items_C(C级·小奖)
|
||
/// Init 时筛出本组奖池、同等级合并,每个奖品按 prizeId 后两位(itemId)找到自己的格子。
|
||
/// </summary>
|
||
public class ChouJiangToggle : MonoBehaviour
|
||
{
|
||
[Tooltip("本面板的奖池分组:1=炮台,2=战神,3=翅膀(总面板按 toggles 列表顺序自动赋值,一般不用手设)")]
|
||
public LotteryPoolType poolType = LotteryPoolType.炮台;
|
||
|
||
[Tooltip("A级(大奖)item,按展示顺序拖")]
|
||
public List<ChouJiangItem> items_A;
|
||
[Tooltip("B级(中奖)item,按展示顺序拖")]
|
||
public List<ChouJiangItem> items_B;
|
||
[Tooltip("C级(小奖)item,按展示顺序拖")]
|
||
public List<ChouJiangItem> items_C;
|
||
|
||
[Header("抽奖动效参数")]
|
||
[Tooltip("跑马灯圈数:跳完这些圈再停到目标格")]
|
||
public int effectLoops = 2;
|
||
[Tooltip("单步最快间隔(秒)")]
|
||
public float effectStepFast = 0.07f;
|
||
[Tooltip("单步最慢间隔(秒):开头/结尾慢,中间快")]
|
||
public float effectStepSlow = 0.30f;
|
||
|
||
private Coroutine effectCo; // 跑马灯协程
|
||
|
||
/// <summary>
|
||
/// 初始化:筛出本组(poolType)的全部奖池,同等级合并后,
|
||
/// A/B/C 级的奖品各自按 prizeId 后两位(itemId)找到自己的格子。
|
||
/// </summary>
|
||
public void Init(LotteryActivityConfig config)
|
||
{
|
||
// 先清空所有 item 的旧数据
|
||
ClearItems(items_A);
|
||
ClearItems(items_B);
|
||
ClearItems(items_C);
|
||
|
||
if (config == null || config.pools == null || config.pools.Count == 0)
|
||
{
|
||
Debug.LogWarning($"[ChouJiangToggle] 抽奖配置为空(poolType={poolType})");
|
||
return;
|
||
}
|
||
|
||
// 同组同等级可能配多条奖池,先按等级合并再分发
|
||
Dictionary<string, List<LotteryPrize>> prizesByLevel = new Dictionary<string, List<LotteryPrize>>();
|
||
foreach (LotteryPoolLevel pool in config.pools)
|
||
{
|
||
if (pool == null || pool.poolType != (int)poolType) continue;
|
||
|
||
string level = pool.level != null ? pool.level.ToUpperInvariant() : null;
|
||
if (level != "A" && level != "B" && level != "C")
|
||
{
|
||
Debug.LogWarning($"[ChouJiangToggle] 未知奖池等级 {pool.level}(只支持 A/B/C)");
|
||
continue;
|
||
}
|
||
|
||
List<LotteryPrize> list;
|
||
if (!prizesByLevel.TryGetValue(level, out list))
|
||
{
|
||
list = new List<LotteryPrize>();
|
||
prizesByLevel[level] = list;
|
||
}
|
||
if (pool.prizeList != null) list.AddRange(pool.prizeList);
|
||
}
|
||
|
||
// 兜底:没手填 itemId 的列表按拖入顺序自动编 1..n(手填过的列表不受影响)
|
||
AutoNumberItems(items_A);
|
||
AutoNumberItems(items_B);
|
||
AutoNumberItems(items_C);
|
||
|
||
FillLevel("A", items_A, prizesByLevel);
|
||
FillLevel("B", items_B, prizesByLevel);
|
||
FillLevel("C", items_C, prizesByLevel);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 兜底编号:整列表都没手填 itemId(全为 0)时按列表拖入顺序自动编 1..n;
|
||
/// 只要有一个格子手填过编号,该列表就完全按手填的来。
|
||
/// </summary>
|
||
private static void AutoNumberItems(List<ChouJiangItem> items)
|
||
{
|
||
if (items == null || items.Count == 0) return;
|
||
|
||
bool anySet = false;
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
if (items[i] != null && items[i].itemId != 0) { anySet = true; break; }
|
||
}
|
||
if (anySet) return;
|
||
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
if (items[i] != null) items[i].itemId = i + 1;
|
||
}
|
||
Debug.Log($"[ChouJiangToggle] 列表未手填 itemId,按拖入顺序自动编号 1..{items.Count}");
|
||
}
|
||
|
||
/// <summary>按已抽奖项列表置灰本面板所有 item(奖励不重复)</summary>
|
||
public void RefreshDrawnState(List<string> drawnPrizeIds)
|
||
{
|
||
ApplyDrawn(items_A, drawnPrizeIds);
|
||
ApplyDrawn(items_B, drawnPrizeIds);
|
||
ApplyDrawn(items_C, drawnPrizeIds);
|
||
}
|
||
|
||
/// <summary>每个奖品按 prizeId 后两位找到自己的 item(itemId 对号入座)</summary>
|
||
private void FillLevel(string level, List<ChouJiangItem> items, Dictionary<string, List<LotteryPrize>> prizesByLevel)
|
||
{
|
||
if (items == null || items.Count == 0) return;
|
||
|
||
List<LotteryPrize> prizes;
|
||
prizesByLevel.TryGetValue(level, out prizes);
|
||
if (prizes == null || prizes.Count == 0)
|
||
{
|
||
Debug.LogWarning($"[ChouJiangToggle] {poolType} 组没有 {level} 级奖池");
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < prizes.Count; i++)
|
||
{
|
||
int no = ParseItemId(prizes[i].prizeId);
|
||
ChouJiangItem item = FindItemById(items, no);
|
||
if (item == null)
|
||
{
|
||
Debug.LogWarning($"[ChouJiangToggle] {poolType} 组 {level} 级奖品 {prizes[i].prizeId}(编号 {no})找不到对应 itemId 的 item,跳过");
|
||
continue;
|
||
}
|
||
item.Init(prizes[i]);
|
||
}
|
||
}
|
||
|
||
/// <summary>取 prizeId 后两位作为位置编号(如 588203 → 3);解析失败返回 -1</summary>
|
||
private static int ParseItemId(string prizeId)
|
||
{
|
||
if (string.IsNullOrEmpty(prizeId) || prizeId.Length < 2) return -1;
|
||
int no;
|
||
if (!int.TryParse(prizeId.Substring(prizeId.Length - 2), out no)) return -1;
|
||
return no;
|
||
}
|
||
|
||
/// <summary>按位置编号找 item;找不到返回 null</summary>
|
||
private static ChouJiangItem FindItemById(List<ChouJiangItem> items, int itemId)
|
||
{
|
||
if (itemId < 0 || items == null) return null;
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
if (items[i] != null && items[i].itemId == itemId) return items[i];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private void ApplyDrawn(List<ChouJiangItem> items, List<string> drawnPrizeIds)
|
||
{
|
||
if (items == null) return;
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
if (items[i] == null || items[i].prize == null) continue;
|
||
string prizeId = items[i].prize.prizeId;
|
||
items[i].RefreshDrawnState(!string.IsNullOrEmpty(prizeId)
|
||
&& drawnPrizeIds != null && drawnPrizeIds.Contains(prizeId));
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
#region 抽奖跑马灯动效
|
||
// ============================================================
|
||
|
||
/// <summary>
|
||
/// 播放抽奖动效:跑马灯在格子间 加速→匀速→减速 跳动,最后停在 prizeId 对应的格子上。
|
||
/// 方块是各格子自己的 prefab_DrawEffect(ChouJiangItem 上拖),经过/停留时点亮。
|
||
/// 完成后回调 onComplete;没有任何格子配方块/找不到目标格时立即回调。
|
||
/// </summary>
|
||
public void PlayDrawEffect(string prizeId, Action onComplete)
|
||
{
|
||
// 停掉上一次
|
||
if (effectCo != null) { StopCoroutine(effectCo); effectCo = null; }
|
||
|
||
List<ChouJiangItem> path = BuildEffectPath();
|
||
ChouJiangItem target = FindItemByPrizeId(prizeId);
|
||
|
||
bool anyEffect = false;
|
||
for (int i = 0; i < path.Count; i++)
|
||
{
|
||
if (path[i].prefab_DrawEffect != null) { anyEffect = true; break; }
|
||
}
|
||
|
||
if (!anyEffect || path.Count == 0 || target == null || !path.Contains(target))
|
||
{
|
||
onComplete?.Invoke();
|
||
return;
|
||
}
|
||
|
||
effectCo = StartCoroutine(IEPlayEffect(path, target, onComplete));
|
||
}
|
||
|
||
/// <summary>动效路径:A → B → C 三个列表的格子,各自按 itemId 升序(只含有奖品且未抽中的格子——已经抽到的不再跳)</summary>
|
||
private List<ChouJiangItem> BuildEffectPath()
|
||
{
|
||
List<ChouJiangItem> path = new List<ChouJiangItem>();
|
||
AppendPath(items_A, path);
|
||
AppendPath(items_B, path);
|
||
AppendPath(items_C, path);
|
||
return path;
|
||
}
|
||
|
||
private static void AppendPath(List<ChouJiangItem> items, List<ChouJiangItem> path)
|
||
{
|
||
if (items == null) return;
|
||
List<ChouJiangItem> sorted = new List<ChouJiangItem>();
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
ChouJiangItem it = items[i];
|
||
if (it != null && it.prize != null && !it.IsDrawn) sorted.Add(it); // 已抽中(置灰)的跳过
|
||
}
|
||
sorted.Sort((a, b) => a.itemId.CompareTo(b.itemId));
|
||
path.AddRange(sorted);
|
||
}
|
||
|
||
/// <summary>按 prizeId 找本面板的 item</summary>
|
||
private ChouJiangItem FindItemByPrizeId(string prizeId)
|
||
{
|
||
if (string.IsNullOrEmpty(prizeId)) return null;
|
||
ChouJiangItem r = FindInList(items_A, prizeId);
|
||
if (r == null) r = FindInList(items_B, prizeId);
|
||
if (r == null) r = FindInList(items_C, prizeId);
|
||
return r;
|
||
}
|
||
|
||
private static ChouJiangItem FindInList(List<ChouJiangItem> items, string prizeId)
|
||
{
|
||
if (items == null) return null;
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
if (items[i] != null && items[i].prize != null && items[i].prize.prizeId == prizeId)
|
||
return items[i];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private IEnumerator IEPlayEffect(List<ChouJiangItem> path, ChouJiangItem target, Action onComplete)
|
||
{
|
||
int count = path.Count;
|
||
int targetIdx = path.IndexOf(target);
|
||
int cur = 0;
|
||
|
||
// 全部先熄灭(含上一次中奖格上亮着的方块),点亮起点
|
||
for (int i = 0; i < count; i++) path[i].ShowEffect(false);
|
||
path[0].ShowEffect(true);
|
||
|
||
// 总步数 = 圈数 × 格子数 + 到目标格的距离,保证最后一步正好落在目标格
|
||
int totalSteps = effectLoops * count + (targetIdx - cur + count) % count;
|
||
|
||
for (int step = 0; step < totalSteps; step++)
|
||
{
|
||
float p = (float)step / totalSteps;
|
||
yield return new WaitForSeconds(GetStepDuration(p));
|
||
path[cur].ShowEffect(false); // 熄灭当前格
|
||
cur = (cur + 1) % count;
|
||
path[cur].ShowEffect(true); // 点亮下一格
|
||
}
|
||
|
||
// 停在目标格上停留一下再回调(中奖格的方块保持亮着)
|
||
yield return new WaitForSeconds(0.6f);
|
||
|
||
onComplete?.Invoke();
|
||
}
|
||
|
||
/// <summary>单步时长:前 25% 加速 → 中间匀速 → 后 30% 减速(先快后慢的抽奖手感)</summary>
|
||
private float GetStepDuration(float p)
|
||
{
|
||
if (p < 0.25f) return Mathf.Lerp(effectStepSlow, effectStepFast, p / 0.25f);
|
||
if (p < 0.70f) return effectStepFast;
|
||
return Mathf.Lerp(effectStepFast, effectStepSlow, (p - 0.70f) / 0.30f);
|
||
}
|
||
|
||
#endregion
|
||
|
||
private void ClearItems(List<ChouJiangItem> items)
|
||
{
|
||
if (items == null) return;
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
if (items[i] != null) items[i].Init(null);
|
||
}
|
||
}
|
||
}
|