1
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa7bc334f6405104db8bcd505104c32e
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,201 @@
|
||||
using Assets.Framework.Tool;
|
||||
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 抽奖 item 层(第三层):挂在 toggle 层里的每个奖品格子上,
|
||||
/// 记录道具信息以及奖励的详细信息(prize:类型/数量/权重/库存/道具 product 等),
|
||||
/// 并负责展示——图标 + 数量文本由代码动态生成在格子下;
|
||||
/// 已抽中的奖项置灰(奖励不重复)。
|
||||
///
|
||||
/// itemId = 本格子的位置编号(对应 prizeId 后两位,如 588203 → 3),
|
||||
/// 初始化时奖品按它对号入座,Inspector 里按美术摆放顺序填 1、2、3……
|
||||
/// </summary>
|
||||
public class ChouJiangItem : MonoBehaviour
|
||||
{
|
||||
[Tooltip("本格子的位置编号 = prizeId 后两位(如 588203 → 3),初始化时奖品按它对号入座")]
|
||||
public int itemId;
|
||||
|
||||
[Header("动态生成参数")]
|
||||
[Tooltip("图标占格子短边的比例")]
|
||||
public float iconRatio = 0.85f;
|
||||
[Tooltip("图标中心相对格子中心的纵向偏移(0=居中,负数=往下贴)")]
|
||||
public float iconOffsetY = 0f;
|
||||
[Tooltip("数量文本字号")]
|
||||
public float fontSize = 22f;
|
||||
[Tooltip("已抽中置灰的颜色")]
|
||||
public Color drawnColor = new Color(0.35f, 0.35f, 0.35f, 0.5f);
|
||||
|
||||
[Tooltip("遮罩蒙版图片(可选):适配不规则格子;蒙版大小=格子(背景)大小,图标放进蒙版里被裁出形状")]
|
||||
public Sprite maskSprite;
|
||||
|
||||
[Header("奖励数据(Init 时由代码填充,Play 模式选中物体可实时查看)")]
|
||||
[Tooltip("道具信息 + 奖励详细信息(prizeId/名称/图标/类型/数量/道具 product/权重/库存)")]
|
||||
public LotteryPrize prize;
|
||||
|
||||
[Tooltip("是否已被抽中(奖励不重复,抽中后置灰)")]
|
||||
[SerializeField] private bool isDrawn;
|
||||
|
||||
/// <summary>是否已被抽中</summary>
|
||||
public bool IsDrawn => isDrawn;
|
||||
|
||||
private Image img_Icon; // 动态生成的图标
|
||||
private TextMeshProUGUI tmp_Count; // 动态生成的数量文本
|
||||
private GameObject maskObj; // 动态生成的遮罩蒙版(不规则裁剪用)
|
||||
|
||||
// 金币/钻石本地图标(Resources 根目录,与 BackPackPropItem 同一套),静态缓存避免重复加载
|
||||
private static Sprite _goldSprite;
|
||||
private static Sprite _diamondSprite;
|
||||
|
||||
/// <summary>记录奖励数据并展示(图标 + 数量)</summary>
|
||||
public void Init(LotteryPrize prize)
|
||||
{
|
||||
this.prize = prize; // 记录道具信息以及奖励的详细信息
|
||||
ClearUI();
|
||||
if (prize == null) return;
|
||||
|
||||
RectTransform rt = transform as RectTransform;
|
||||
if (rt == null)
|
||||
{
|
||||
Debug.LogWarning($"[ChouJiangItem] 格子不是 UI(缺 RectTransform):{name}");
|
||||
return;
|
||||
}
|
||||
|
||||
float w = rt.rect.width, h = rt.rect.height;
|
||||
if (w < 1f || h < 1f) { w = 90f; h = 79f; } // 无尺寸兜底
|
||||
|
||||
// 遮罩蒙版:大小=格子(背景)大小,图标放进蒙版里被裁出形状(适配不规则格子)
|
||||
RectTransform iconParent = rt;
|
||||
if (maskSprite != null)
|
||||
iconParent = CreateMask(rt, w, h);
|
||||
|
||||
// 图标:方形占短边 iconRatio,居中(iconOffsetY 可微调)
|
||||
float side = Mathf.Min(w, h) * iconRatio;
|
||||
img_Icon = CreateIcon(iconParent, side);
|
||||
SetPrizeIcon(img_Icon, prize);
|
||||
|
||||
// 数量:压在图标底部(文字叠在图片上面,不进蒙版)
|
||||
tmp_Count = CreateCountText(rt, w, side);
|
||||
tmp_Count.text = prize.count.ToString();
|
||||
|
||||
// 新数据默认未抽中,已抽列表由面板 Init 后统一刷新
|
||||
RefreshDrawnState(false);
|
||||
}
|
||||
|
||||
/// <summary>置灰/恢复(奖励不重复,抽中后置灰)</summary>
|
||||
public void RefreshDrawnState(bool drawn)
|
||||
{
|
||||
isDrawn = drawn;
|
||||
if (img_Icon != null)
|
||||
img_Icon.color = drawn ? drawnColor : Color.white;
|
||||
}
|
||||
|
||||
/// <summary>清掉动态生成的 UI(重复 Init 复用前调用)</summary>
|
||||
public void ClearUI()
|
||||
{
|
||||
if (img_Icon != null) { Destroy(img_Icon.gameObject); img_Icon = null; }
|
||||
if (tmp_Count != null) { Destroy(tmp_Count.gameObject); tmp_Count = null; }
|
||||
if (maskObj != null) { Destroy(maskObj); maskObj = null; }
|
||||
}
|
||||
|
||||
/// <summary>生成遮罩蒙版:Image(maskSprite)+ Mask,居中、大小=格子(背景)大小;图标放它下面被裁剪</summary>
|
||||
private RectTransform CreateMask(RectTransform parent, float w, float h)
|
||||
{
|
||||
maskObj = new GameObject("Mask", typeof(RectTransform));
|
||||
RectTransform r = (RectTransform)maskObj.transform;
|
||||
r.SetParent(parent, false);
|
||||
r.anchorMin = r.anchorMax = r.pivot = Vector2.one * 0.5f;
|
||||
r.anchoredPosition = Vector2.zero;
|
||||
// 遮罩 0.9 倍背景大小(试过比全尺寸好看)
|
||||
r.sizeDelta = new Vector2(w * 0.9f, h * 0.9f);
|
||||
|
||||
Image maskImg = maskObj.AddComponent<Image>();
|
||||
maskImg.sprite = maskSprite;
|
||||
maskImg.raycastTarget = false;
|
||||
|
||||
// 只当裁剪形状用,不显示蒙版图本身
|
||||
Mask mask = maskObj.AddComponent<Mask>();
|
||||
mask.showMaskGraphic = false;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>生成格子里的图标物体(往下贴 iconOffsetY)</summary>
|
||||
private Image CreateIcon(RectTransform parent, float side)
|
||||
{
|
||||
GameObject go = new GameObject("Icon", typeof(RectTransform));
|
||||
RectTransform r = (RectTransform)go.transform;
|
||||
r.SetParent(parent, false);
|
||||
r.anchorMin = r.anchorMax = r.pivot = Vector2.one * 0.5f;
|
||||
r.anchoredPosition = new Vector2(0f, iconOffsetY);
|
||||
r.sizeDelta = new Vector2(side, side);
|
||||
Image img = go.AddComponent<Image>();
|
||||
img.raycastTarget = false;
|
||||
return img;
|
||||
}
|
||||
|
||||
/// <summary>生成压在图标底部的数量文本</summary>
|
||||
private TextMeshProUGUI CreateCountText(RectTransform parent, float w, float iconSide)
|
||||
{
|
||||
GameObject go = new GameObject("Count", typeof(RectTransform));
|
||||
RectTransform r = (RectTransform)go.transform;
|
||||
r.SetParent(parent, false);
|
||||
r.anchorMin = r.anchorMax = r.pivot = Vector2.one * 0.5f;
|
||||
// 图标底边往上 0.8 个字高:文字压在图片上面
|
||||
r.anchoredPosition = new Vector2(0f, iconOffsetY - iconSide / 2f + fontSize * 0.8f);
|
||||
r.sizeDelta = new Vector2(w, fontSize * 1.6f);
|
||||
TextMeshProUGUI tmp = go.AddComponent<TextMeshProUGUI>();
|
||||
TMP_FontAsset font = TMP_Settings.defaultFontAsset;
|
||||
if (font != null) tmp.font = font;
|
||||
tmp.fontSize = fontSize;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.color = Color.white;
|
||||
tmp.raycastTarget = false;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置奖励图标:1=金币 / 2=钻石 用 Resources 本地图,
|
||||
/// 3=道具 / 4=道具碎片 下载 icon(为空时用 product.imageUrl)。
|
||||
/// </summary>
|
||||
private void SetPrizeIcon(Image img, LotteryPrize prize)
|
||||
{
|
||||
switch (prize.type)
|
||||
{
|
||||
case 1: // 金币
|
||||
Sprite gold = _goldSprite != null ? _goldSprite
|
||||
: (_goldSprite = Resources.Load<Sprite>("gold"));
|
||||
if (gold != null) img.sprite = gold;
|
||||
break;
|
||||
|
||||
case 2: // 钻石
|
||||
Sprite diamond = _diamondSprite != null ? _diamondSprite
|
||||
: (_diamondSprite = Resources.Load<Sprite>("diamond"));
|
||||
if (diamond != null) img.sprite = diamond;
|
||||
break;
|
||||
|
||||
default: // 道具/碎片:下载图标
|
||||
string url = prize.icon;
|
||||
if (string.IsNullOrEmpty(url) && prize.product != null)
|
||||
url = prize.product.imageUrl;
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
WebTool.DownImage(url, tex =>
|
||||
{
|
||||
// 回调触发时本物体可能已被销毁(关闭面板清空父节点),需判空
|
||||
if (this == null || img == null) return;
|
||||
if (tex != null)
|
||||
img.sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height),
|
||||
Vector2.one * 0.5f);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearUI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59c98b7739d292e409a9c611858e6ee5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,299 @@
|
||||
using Assets.MyGame.Scripts.Net;
|
||||
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
|
||||
using Assets.MyGame.Scripts.Net.APIRequest.Params;
|
||||
using Assets.MyGame.Scripts.Net.APIResponses;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>奖池分组(swagger:0=旧调用兼容,1=炮台,2=战神,3=翅膀)</summary>
|
||||
public enum LotteryPoolType
|
||||
{
|
||||
旧调用 = 0,
|
||||
炮台 = 1,
|
||||
战神 = 2,
|
||||
翅膀 = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 抽奖活动总面板(第一层·最外层,ChouJiang.prefab 根节点)。
|
||||
///
|
||||
/// 三层结构:
|
||||
/// 第一层 本面板:拉配置、切分组、抽奖、花费/次数显示;
|
||||
/// 第二层 ChouJiangToggle:挂 PT炮台/CB翅膀/ZS战神 三个 toggle 面板,一个 toggle 对应一个面板,
|
||||
/// 面板里一堆 item 分为 A/B/C 三个列表(items_A/items_B/items_C);
|
||||
/// 第三层 ChouJiangItem:挂每个奖品格子,记录道具信息以及奖励的详细信息。
|
||||
///
|
||||
/// 流程:OnShow 拉取活动配置(/api/ActivityConfig/lotteryConfig)→ 初始化各 toggle 的 item
|
||||
/// → 拉取已抽奖项置灰 + 用户状态(/api/Lottery/UserStatus)显示花费/次数
|
||||
/// → 点抽奖调 /api/Lottery/Draw?poolType=当前分组。
|
||||
///
|
||||
/// 花费规则(swagger):costs 独立计费非累加——第 N+1 次抽花 costs[N],超出长度取最后一个;
|
||||
/// userMaxDraw=0 不限次数;isFinished 或达到上限后不能再抽。
|
||||
/// </summary>
|
||||
public class ChouJiangPanel : SubUIBase
|
||||
{
|
||||
[Tooltip("三个 toggle 面板按顺序拖:[0]=炮台,[1]=战神,[2]=翅膀(poolType 按列表顺序自动赋值)")]
|
||||
public List<ChouJiangToggle> toggles;
|
||||
|
||||
[Tooltip("分组切换按钮按顺序拖:[0]=炮台,[1]=战神,[2]=翅膀(还没做就留空)")]
|
||||
public List<Toggle> toggles_PoolType;
|
||||
|
||||
[Header("可选 UI(没摆就留空,不影响逻辑)")]
|
||||
public Button btn_Draw; // 抽奖按钮
|
||||
public TextMeshProUGUI tmp_ActivityName; // 活动名
|
||||
public TextMeshProUGUI tmp_Cost; // 下一次抽奖花费
|
||||
public TextMeshProUGUI tmp_DrawCount; // 已抽次数/上限
|
||||
public TextMeshProUGUI tmp_EndTime; // 活动结束时间/状态
|
||||
|
||||
/// <summary>当前展示/抽奖的奖池分组</summary>
|
||||
public LotteryPoolType currPoolType { get; private set; }
|
||||
|
||||
private LotteryActivityConfig config; // 活动配置
|
||||
private LotteryUserStatus userStatus; // 用户抽奖状态(次数决定花费与上限)
|
||||
private bool isDrawing; // 防连点
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// toggles 列表顺序即分组:[0]=炮台,[1]=战神,[2]=翅膀(按顺序自动赋 poolType,不用逐个手设)
|
||||
if (toggles != null)
|
||||
{
|
||||
int n = Mathf.Min(toggles.Count, 3);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (toggles[i] != null)
|
||||
toggles[i].poolType = (LotteryPoolType)(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 分组切换:一个按钮对应一个 toggle 面板(索引 0/1/2 = 炮台/战神/翅膀)
|
||||
if (toggles_PoolType != null)
|
||||
{
|
||||
for (int i = 0; i < toggles_PoolType.Count; i++)
|
||||
{
|
||||
if (toggles_PoolType[i] == null) continue;
|
||||
LotteryPoolType type = (LotteryPoolType)(i + 1);
|
||||
toggles_PoolType[i].onValueChanged.AddListener(isOn =>
|
||||
{
|
||||
if (isOn) ShowPoolType(type);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (btn_Draw != null)
|
||||
btn_Draw.onClick.AddListener(OnDrawClick);
|
||||
|
||||
// 初始分组 = 预制体里当前激活的面板,并同步一次面板/按钮状态
|
||||
ChouJiangToggle first = GetActiveToggle();
|
||||
currPoolType = first != null ? first.poolType : LotteryPoolType.炮台;
|
||||
ShowPoolType(currPoolType);
|
||||
}
|
||||
|
||||
protected override void OnShow(object args = null)
|
||||
{
|
||||
base.OnShow(args);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>拉取活动配置 + 刷新各 toggle 与用户状态</summary>
|
||||
public void Refresh()
|
||||
{
|
||||
APIRequestList.GetLotteryActivityConfig(cfg =>
|
||||
{
|
||||
if (this == null) return;
|
||||
config = cfg;
|
||||
UpdateActivityUI();
|
||||
InitToggles();
|
||||
RefreshUserStatus();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>初始化各 toggle 面板的 item(A/B/C 三个列表),并置灰已抽中的奖项</summary>
|
||||
private void InitToggles()
|
||||
{
|
||||
if (toggles == null || config == null) return;
|
||||
for (int i = 0; i < toggles.Count; i++)
|
||||
{
|
||||
if (toggles[i] != null)
|
||||
toggles[i].Init(config);
|
||||
}
|
||||
RefreshDrawnPrizes();
|
||||
}
|
||||
|
||||
/// <summary>拉取用户抽奖状态(已抽次数决定下一次花费与是否到上限)</summary>
|
||||
private void RefreshUserStatus()
|
||||
{
|
||||
APIRequestList.GetLotteryUserStatus(status =>
|
||||
{
|
||||
if (this == null) return;
|
||||
userStatus = status;
|
||||
UpdateDrawUI();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>拉取已抽奖项列表,置灰各 toggle 的对应 item(奖励不重复)</summary>
|
||||
private void RefreshDrawnPrizes()
|
||||
{
|
||||
APIRequestList.GetLotteryDrawnPrizes(ids =>
|
||||
{
|
||||
if (this == null || toggles == null) return;
|
||||
for (int i = 0; i < toggles.Count; i++)
|
||||
{
|
||||
if (toggles[i] != null)
|
||||
toggles[i].RefreshDrawnState(ids);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>切到指定分组的 toggle 面板</summary>
|
||||
public void ShowPoolType(LotteryPoolType type)
|
||||
{
|
||||
currPoolType = type;
|
||||
|
||||
if (toggles != null)
|
||||
{
|
||||
for (int i = 0; i < toggles.Count; i++)
|
||||
{
|
||||
if (toggles[i] == null) continue;
|
||||
toggles[i].gameObject.SetActive(toggles[i].poolType == type);
|
||||
}
|
||||
}
|
||||
|
||||
// 同步切换按钮勾选状态(不触发回调)
|
||||
if (toggles_PoolType != null)
|
||||
{
|
||||
int idx = (int)type - 1;
|
||||
for (int i = 0; i < toggles_PoolType.Count; i++)
|
||||
{
|
||||
if (toggles_PoolType[i] == null || i == idx) continue;
|
||||
toggles_PoolType[i].SetIsOnWithoutNotify(false);
|
||||
}
|
||||
if (idx >= 0 && idx < toggles_PoolType.Count && toggles_PoolType[idx] != null)
|
||||
toggles_PoolType[idx].SetIsOnWithoutNotify(true);
|
||||
}
|
||||
|
||||
UpdateDrawButtonState();
|
||||
}
|
||||
|
||||
/// <summary>取当前激活的 toggle 面板(都没有激活时取第一个)</summary>
|
||||
private ChouJiangToggle GetActiveToggle()
|
||||
{
|
||||
if (toggles == null || toggles.Count == 0) return null;
|
||||
for (int i = 0; i < toggles.Count; i++)
|
||||
{
|
||||
if (toggles[i] != null && toggles[i].gameObject.activeSelf)
|
||||
return toggles[i];
|
||||
}
|
||||
return toggles[0];
|
||||
}
|
||||
|
||||
/// <summary>活动信息显示</summary>
|
||||
private void UpdateActivityUI()
|
||||
{
|
||||
if (config == null) return;
|
||||
|
||||
if (tmp_ActivityName != null)
|
||||
tmp_ActivityName.text = config.activityName;
|
||||
|
||||
if (tmp_EndTime != null)
|
||||
{
|
||||
if (!config.isOpen)
|
||||
tmp_EndTime.text = "活动未开启";
|
||||
else if (config.endTime > 0)
|
||||
tmp_EndTime.text = "结束时间 " + DateTimeOffset
|
||||
.FromUnixTimeSeconds(config.endTime).LocalDateTime.ToString("yyyy/MM/dd HH:mm");
|
||||
else
|
||||
tmp_EndTime.text = "长期活动";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>花费/次数/按钮状态显示</summary>
|
||||
private void UpdateDrawUI()
|
||||
{
|
||||
int drawn = userStatus != null ? userStatus.totalDrawCount : 0;
|
||||
|
||||
if (tmp_Cost != null)
|
||||
tmp_Cost.text = GetNextCost().ToString();
|
||||
|
||||
if (tmp_DrawCount != null)
|
||||
tmp_DrawCount.text = config != null && config.userMaxDraw > 0
|
||||
? $"{drawn}/{config.userMaxDraw}"
|
||||
: drawn.ToString();
|
||||
|
||||
UpdateDrawButtonState();
|
||||
}
|
||||
|
||||
private void UpdateDrawButtonState()
|
||||
{
|
||||
if (btn_Draw != null)
|
||||
btn_Draw.interactable = CanDraw() && !isDrawing;
|
||||
}
|
||||
|
||||
/// <summary>下一次抽奖花费:第 N+1 次取 costs[N],超出长度取最后一个(独立计费非累加)</summary>
|
||||
private long GetNextCost()
|
||||
{
|
||||
if (config == null || config.costs == null || config.costs.Count == 0) return 0;
|
||||
int drawn = userStatus != null ? userStatus.totalDrawCount : 0;
|
||||
int idx = Mathf.Min(drawn, config.costs.Count - 1);
|
||||
return config.costs[idx];
|
||||
}
|
||||
|
||||
/// <summary>能否再抽:活动开启、在时间内、未抽完、未达次数上限</summary>
|
||||
private bool CanDraw()
|
||||
{
|
||||
if (config == null || !config.isOpen) return false;
|
||||
if (userStatus != null && userStatus.isFinished) return false;
|
||||
|
||||
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
if (config.startTime > 0 && now < config.startTime) return false;
|
||||
if (config.endTime > 0 && now > config.endTime) return false;
|
||||
|
||||
if (config.userMaxDraw > 0 && userStatus != null
|
||||
&& userStatus.totalDrawCount >= config.userMaxDraw) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>抽奖:GET /api/Lottery/Draw?poolType=当前分组</summary>
|
||||
private void OnDrawClick()
|
||||
{
|
||||
if (isDrawing || !CanDraw()) return;
|
||||
isDrawing = true;
|
||||
UpdateDrawButtonState();
|
||||
|
||||
bool done = false;
|
||||
APIRequestList.LotteryDraw(new LotteryDrawParam { poolType = (int)currPoolType }, res =>
|
||||
{
|
||||
done = true;
|
||||
if (this == null) return;
|
||||
|
||||
// 抽中结果(结算 UI 摆好后在这里接入)
|
||||
Debug.Log($"[ChouJiangPanel] 抽中:{res.name} x{res.count}(type={res.type},消耗={res.cost},分组={(LotteryPoolType)res.poolType})");
|
||||
|
||||
// 花费随次数增长、已抽奖项要置灰,重拉状态刷新
|
||||
RefreshUserStatus();
|
||||
RefreshDrawnPrizes();
|
||||
});
|
||||
|
||||
// code!=0 时 APIManager 只弹错误不回调,超时兜底解锁防连点锁死
|
||||
StartCoroutine(IEWaitDrawDone(() => done));
|
||||
}
|
||||
|
||||
/// <summary>等待抽奖回调完成(最多 10 秒),结束解锁按钮</summary>
|
||||
private IEnumerator IEWaitDrawDone(Func<bool> done)
|
||||
{
|
||||
float t = 0f;
|
||||
while (!done() && t < 10f)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
isDrawing = false;
|
||||
UpdateDrawButtonState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c3692baaedece14fa8c7527b163ced3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,142 @@
|
||||
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
|
||||
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;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
FillLevel("A", items_A, prizesByLevel);
|
||||
FillLevel("B", items_B, prizesByLevel);
|
||||
FillLevel("C", items_C, prizesByLevel);
|
||||
}
|
||||
|
||||
/// <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));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 940297b369ce43043bf02ea74c3ed80f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user