This commit is contained in:
2026-09-07 01:09:26 +08:00
parent c2279de46b
commit df816008d8
13 changed files with 8743 additions and 7202 deletions
@@ -155,15 +155,17 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
public Button btn_ChangeRoom;
[Header("表情包")]
[Tooltip("表情包配置资产(一份配置多场景共用;未拖时回退下方面板旧字段)")]
public BiaoQingConfig biaoQingConfig;
public Button btn_BiaoQing; // 打开/关闭表情面板
public GameObject obj_BiaoQingPanel; // 表情面板(开/关)
public GameObject obj_BiaoQingZiItemPrefab; // 文字表情项预制体(child0=TextMeshProUGUI)
public GameObject obj_BiaoQingSpineItemPrefab; // spine表情项预制体(child0=SkeletonGraphic)
public GameObject obj_BiaoQingSpineItemParent; // spine表情项生成的父节点
public GameObject obj_BiaoQingZiItemParent; // 文字表情项生成的父节点
[Tooltip("spine 表情配置:每个元素=一个spine文件+动画名列表,按动画名逐个克隆成表情项")]
[Tooltip("(回退用)spine 表情配置:优先读 biaoQingConfig 资产,未拖资产时才用这里")]
public List<BiaoQingSpineData> list_BiaoQingSpine = new List<BiaoQingSpineData>();
[Tooltip("文字表情内容列表,一条文字克隆一个表情项")]
[Tooltip("(回退用)文字表情内容列表:优先读 biaoQingConfig 资产,未拖资产时才用这里")]
public List<string> list_BiaoQingZi = new List<string>();
@@ -600,7 +602,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
bool open = !obj_PropPanel.activeSelf;
obj_PropPanel.SetActive(open);
if (open)
{
BuildPropList();
// ⭐ 数量是动态数据:每次打开面板都刷新一级角标(构建有一次性守卫,刷新无守卫)
RefreshPropFirstLayerCounts();
}
else if (tmp_PropTwoPanel != null)
tmp_PropTwoPanel.SetActive(false);
});
@@ -1274,6 +1280,14 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
#region 表情包(BiaoQingModule:列表生成 + 发送 + 广播接收)
/// <summary>spine 表情列表:优先读 biaoQingConfig 配置资产(多场景共用一份),未拖资产时回退面板字段</summary>
private List<BiaoQingSpineData> BiaoQingSpineList =>
biaoQingConfig != null ? biaoQingConfig.list_BiaoQingSpine : list_BiaoQingSpine;
/// <summary>文字表情列表:优先读 biaoQingConfig 配置资产(多场景共用一份),未拖资产时回退面板字段</summary>
private List<string> BiaoQingZiList =>
biaoQingConfig != null ? biaoQingConfig.list_BiaoQingZi : list_BiaoQingZi;
/// <summary>表情列表是否已生成(首次打开面板时生成一次)</summary>
private bool _biaoQingListBuilt = false;
@@ -1293,9 +1307,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
/// <summary>
/// 生成表情列表(只生成一次),两种形式分开处理:
/// ① spine 表情:遍历 list_BiaoQingSpine,每个配置里的每个动画名克隆一个 spine 项
/// ① spine 表情:遍历 BiaoQingSpineList(优先 biaoQingConfig 资产),每个配置里的每个动画名克隆一个 spine 项
/// (child0=SkeletonGraphic → 换骨骼资源 + 循环播放该动画做列表预览);
/// ② 文字表情:list_BiaoQingZi 每条文字克隆一个文字项(child0=TextMeshProUGUI → 设置文字)。
/// ② 文字表情:BiaoQingZiList 每条文字克隆一个文字项(child0=TextMeshProUGUI → 设置文字)。
/// </summary>
private void BuildBiaoQingList()
{
@@ -1305,9 +1319,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// ---- ① spine 表情:一个动画名 = 一项 ----
if (obj_BiaoQingSpineItemPrefab != null && obj_BiaoQingSpineItemParent != null)
{
for (int i = 0; i < list_BiaoQingSpine.Count; i++)
for (int i = 0; i < BiaoQingSpineList.Count; i++)
{
var cfg = list_BiaoQingSpine[i];
var cfg = BiaoQingSpineList[i];
if (cfg == null || cfg.skeletonDataAsset == null) continue;
foreach (var animName in cfg.animationNames)
@@ -1338,7 +1352,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// ---- ② 文字表情:一条文字 = 一项 ----
if (obj_BiaoQingZiItemPrefab != null && obj_BiaoQingZiItemParent != null)
{
foreach (var text in list_BiaoQingZi)
foreach (var text in BiaoQingZiList)
{
if (string.IsNullOrEmpty(text)) continue;
@@ -1404,9 +1418,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (type == BiaoQingType.spine)
{
// spineIndex = list_BiaoQingSpine 配置下标 → 取骨骼资源交给座位显示
var cfg = (spineIndex >= 0 && spineIndex < list_BiaoQingSpine.Count)
? list_BiaoQingSpine[spineIndex]
// spineIndex = 表情配置列表下标(biaoQingConfig 资产)→ 取骨骼资源交给座位显示
var cfg = (spineIndex >= 0 && spineIndex < BiaoQingSpineList.Count)
? BiaoQingSpineList[spineIndex]
: null;
if (cfg == null || cfg.skeletonDataAsset == null) return;
sit.ShowBiaoQingSpine(cfg.skeletonDataAsset, content);
@@ -1491,6 +1505,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private bool _propListBuilt;
/// <summary>已构建的一级道具项映射(cfg → 克隆项),供数量角标刷新用</summary>
private readonly List<KeyValuePair<PropItemData, GameObject>> _propFirstLayerItems = new List<KeyValuePair<PropItemData, GameObject>>();
/// <summary>
/// 按 PropConfigData 构建一级道具列表(只在首次打开时构建一次)
/// </summary>
@@ -1505,12 +1522,42 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
var cfg = propConfigData.props[i];
if (cfg == null) continue;
var item = Instantiate(tmp_PropItem, obj_PropItemParent.transform);
SetPropItemText(item, cfg.name);
// ⭐ 第一层 child0 = 数量 TMP(只显示数字,不显示名字,靠图标辨识)
SetPropItemIcon(item, cfg.icon);
var data = cfg; // 闭包捕获当前项,避免循环变量被覆盖
var btn = GetPropItemButton(item);
if (btn != null) btn.onClick.AddListener(() => ShowPropTwo(data));
_propFirstLayerItems.Add(new KeyValuePair<PropItemData, GameObject>(cfg, item));
}
RefreshPropFirstLayerCounts();
}
/// <summary>刷新一级道具 child0 数量:仅当该类型下只有 1 个已拥有道具(点击直接使用、不弹二层)时显示个数;
/// 可以打开二层的一级项(0 个显示"暂无道具"占位 / 多个展开子道具列表)直接显示空</summary>
private void RefreshPropFirstLayerCounts()
{
foreach (var kv in _propFirstLayerItems)
{
if (kv.Value == null) continue; // 克隆项已被销毁
var matched = GetMatchedProps(kv.Key != null ? kv.Key.propType : PropEnum.none);
// ⭐ 只有 1 个已拥有道具(点击直达使用)才显示个数;能弹二层的显示空
SetPropItemTextAt(kv.Value, 0, matched.Count == 1 ? matched[0].count.ToString() : string.Empty);
}
}
/// <summary>按类型读取后端已拥有道具(none = 全部类型),只取 count &gt; 0 的(一级角标与二级展开共用同一过滤规则)</summary>
private List<BaseProductCount> GetMatchedProps(PropEnum type)
{
var matched = new List<BaseProductCount>();
var props = GameManager.Instance.user?.props;
if (props == null) return matched;
foreach (var p in props)
{
if (p == null || p.count <= 0) continue;
if (type != PropEnum.none && p.type != (int)type) continue;
matched.Add(p);
}
return matched;
}
/// <summary>
@@ -1530,17 +1577,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}
// ⭐ 读取后端道具:按一级道具配置的类型过滤(none = 显示全部),只显示 count > 0 的
var matched = new List<BaseProductCount>();
var props = GameManager.Instance.user?.props;
if (props != null)
{
foreach (var p in props)
{
if (p == null || p.count <= 0) continue;
if (cfg.propType != PropEnum.none && p.type != (int)cfg.propType) continue;
matched.Add(p);
}
}
// (与一级角标 RefreshPropFirstLayerCounts 共用 GetMatchedProps,规则一致)
var matched = GetMatchedProps(cfg.propType);
// ⭐ 只有 1 个时不用弹二层,直接触发该道具的使用效果
if (matched.Count == 1)
@@ -1552,7 +1590,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
foreach (var p in matched)
{
var item = Instantiate(tmp_PropTwoItem, tmp_PropTwoItemParent.transform);
SetPropItemText(item, $"{p.name} x{p.count}");
// ⭐ child0 = 名字,child1 = 数量(tmp_PropTwoItem 的 child1 是数量 TMP)
SetPropItemText(item, p.name);
SetPropItemTextAt(item, 1, p.count.ToString());
SetPropItemIconUrl(item, p.imageUrl);
var data = p; // 闭包捕获当前项
var btn = GetPropItemButton(item);
@@ -1601,6 +1641,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
{
if (user != null)
GameManager.Instance.UpdataUserData(user);
RefreshPropFirstLayerCounts(); // 数量变化后同步刷新一级角标
});
});
});
@@ -1641,6 +1682,15 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (tmp != null) tmp.text = text;
}
/// <summary>克隆项固定子节点文本:按索引取 child{childIndex} 的 TMP(如第一层child0数量、第二层child1数量)</summary>
private void SetPropItemTextAt(GameObject item, int childIndex, string text)
{
var tmp = item.transform.childCount > childIndex
? item.transform.GetChild(childIndex).GetComponent<TextMeshProUGUI>()
: null;
if (tmp != null) tmp.text = text;
}
/// <summary>克隆项图标:优先取 child0 的 Image,再退回自身/子级查找(icon 不填则跳过)</summary>
private void SetPropItemIcon(GameObject item, Sprite icon)
{