Passion & UI

This commit is contained in:
SoulliesOfficial
2026-06-12 17:11:39 -04:00
parent 7bc1e1722c
commit 6d7ebc5825
3444 changed files with 865284 additions and 463132 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b651e1fc3b3065b40ae2c20a253de26b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using Cielonos.MainGame.Inventory;
using SLSUtilities.General;
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 单条物品描述条目 UI 组件。
/// 渲染管线Localize → DisplayTextResolver.Resolve → InputGlyphParser.Parse → TMP_Text。
/// </summary>
public class ItemDescriptionEntry : MonoBehaviour
{
private const string LocalizationTable = "Items";
[Tooltip("描述文本,显示 descriptionKey 的本地化内容(含动态数值替换和按键图标解析)。")]
public TMP_Text descriptionText;
/// <summary>
/// 使用 <see cref="ItemDescription"/> 的数据填充描述条目。
/// 完整渲染管线:本地化 → {key} 占位符替换 → [Token] 按键图标解析。
/// </summary>
/// <param name="description">描述数据。</param>
/// <param name="descriptionArgs">可选的动态值字典,用于替换本地化文本中的 {key} 占位符。</param>
public void SetDescription(ItemDescription description, Dictionary<string, string> descriptionArgs = null)
{
if (description == null)
{
Clear();
return;
}
if (descriptionText == null) return;
if (string.IsNullOrEmpty(description.descriptionKey))
{
descriptionText.text = string.Empty;
return;
}
string localizedText = description.descriptionKey.Localize(LocalizationTable);
localizedText = DisplayTextResolver.Resolve(localizedText, descriptionArgs);
descriptionText.text = InputGlyphParser.Parse(localizedText);
}
/// <summary>清空条目内容。</summary>
public void Clear()
{
if (descriptionText != null)
{
descriptionText.text = string.Empty;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4bcc59ecfb9425b4a85a1e3ad51c8739

View File

@@ -0,0 +1,177 @@
using System.Collections.Generic;
using Cielonos.MainGame.Inventory;
using Cielonos.MainGame.UI;
using Sirenix.OdinInspector;
using SLSUtilities.General;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.Localization.Settings;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace SLSUtilities.UI
{
/// <summary>
/// 通用物品详情面板,显示物品图标、名称、类型、描述条目、标签等信息。
/// 可被不同的 UIPage 复用(机械台、商店、背包检视等)。
/// </summary>
public class ItemDetailPanel : UIElementBase
{
[Title("Detail Panel References")]
public TMP_Text selectionHintText;
//public Image itemIcon;
public TMP_Text nameText;
public TMP_Text typeRarityText;
public TMP_Text institutionText;
public RectTransform tagContainer;
public GameObject tagPrefab;
// ─────────────────── 描述条目 ───────────────────
[Title("Description Entries")]
[Tooltip("描述条目的容器,应挂载 VerticalLayoutGroup 以实现垂直排列。")]
public RectTransform descriptionContainer;
[Tooltip("描述条目预制体,需挂载 ItemDescriptionEntry 组件。")]
public GameObject descriptionEntryPrefab;
// ─────────────────── 运行时数据 ───────────────────
private ItemBase currentItem;
private readonly List<ItemDescriptionEntry> activeEntries = new List<ItemDescriptionEntry>();
/// <summary>当前显示的物品。</summary>
public ItemBase CurrentItem => currentItem;
/// <summary>
/// 使用指定物品的数据填充详情面板并显示。
/// </summary>
public void SetItem(ItemBase item)
{
currentItem = item;
if (item == null || item.contentData == null)
{
ClearPanel();
return;
}
ContentData data = item.contentData;
// 图标
/*Sprite icon = data.itemIcon;
if (itemIcon != null)
{
itemIcon.sprite = icon;
itemIcon.enabled = icon != null;
}*/
// 名称(本地化)
nameText.text = data.displayNameKey.Localize("Items");
// 类型
typeRarityText.text = data.itemType.ToString() + " - " + data.itemRarity.ToString();
//机构
institutionText.text = string.Empty;
for (var index = 0; index < data.institutions.Count; index++)
{
var institution = data.institutions[index];
string comma = LocalizationSettings.SelectedLocale.Identifier.Code.StartsWith("zh") ? "、" : ", ";
if(index < data.institutions.Count - 1)
institutionText.text += institution.Localize("Items") + comma;
else
institutionText.text += institution.Localize("Items");
}
// 描述条目
RefreshDescriptions(data);
// 标签
RefreshTags(data);
selectionHintText.gameObject.SetActive(false);
}
/// <summary>清空面板内容并隐藏。</summary>
public void ClearPanel()
{
currentItem = null;
//if (itemIcon != null) itemIcon.enabled = false;
if (nameText != null) nameText.text = string.Empty;
if (typeRarityText != null) typeRarityText.text = string.Empty;
ClearDescriptions();
ClearTags();
selectionHintText.gameObject.SetActive(true);
}
// ================================================================
// 描述条目
// ================================================================
private void RefreshDescriptions(ContentData data)
{
ClearDescriptions();
if (descriptionContainer == null || descriptionEntryPrefab == null) return;
if (data.descriptions == null || data.descriptions.Count == 0) return;
Dictionary<string, string> descriptionArgs = currentItem?.GetDescriptionArgs();
foreach (ItemDescription desc in data.descriptions)
{
ItemDescriptionEntry entry = Instantiate(descriptionEntryPrefab, descriptionContainer).GetComponent<ItemDescriptionEntry>();
entry.SetDescription(desc, descriptionArgs);
activeEntries.Add(entry);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(descriptionContainer);
}
private void ClearDescriptions()
{
foreach (ItemDescriptionEntry entry in activeEntries)
{
if (entry != null)
{
Destroy(entry.gameObject);
}
}
activeEntries.Clear();
}
// ================================================================
// 标签
// ================================================================
private void RefreshTags(ContentData data)
{
ClearTags();
if (tagContainer == null || tagPrefab == null || data.tags == null) return;
foreach (string tag in data.tags)
{
GameObject tagObj = Instantiate(tagPrefab, tagContainer);
TMP_Text tagText = tagObj.GetComponentInChildren<TMP_Text>();
if (tagText != null)
{
tagText.text = tag;
}
}
}
private void ClearTags()
{
if (tagContainer == null) return;
for (int i = tagContainer.childCount - 1; i >= 0; i--)
{
Destroy(tagContainer.GetChild(i).gameObject);
}
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using Cielonos.MainGame.Inventory;
using Cielonos.MainGame.Items;
using SLSUtilities.General;
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 单选类别筛选下拉框,基于 <see cref="TMP_Dropdown"/>。
/// 每个选项对应一组 <see cref="ItemType"/>,选中后构建对应的 <see cref="ItemFilter"/>。
/// 第一个选项通常配置为"全部"types 留空即可)。
/// <para>可复用于背包、商店等任何需要按类型筛选物品的 UI 页面。</para>
/// </summary>
public class ItemCategoryDropdown : UIElementBase
{
[Serializable]
public struct CategoryEntry
{
[Tooltip("显示标签,可填入本地化 Key如 'UI_Category_All'),会自动调用 Localize()。")]
public string label;
[Tooltip("对应的物品类型。为空时表示显示所有类型(不筛选)。")]
public ItemType[] types;
}
[Header("Dropdown")]
public TMP_Dropdown dropdown;
[Header("Categories")]
[Tooltip("类别选项列表。第一个通常为'全部'types 留空)。")]
public List<CategoryEntry> categoryEntries = new List<CategoryEntry>
{
new CategoryEntry { label = "全部", types = new[] { ItemType.MainWeapon, ItemType.Support, ItemType.Passive, ItemType.Consumable } },
new CategoryEntry { label = "主武器", types = new[] { ItemType.MainWeapon } },
new CategoryEntry { label = "支援装备", types = new[] { ItemType.Support } },
new CategoryEntry { label = "被动装备", types = new[] { ItemType.Passive } },
new CategoryEntry { label = "消耗品", types = new[] { ItemType.Consumable } }
};
/// <summary>当筛选条件变化时触发,参数为对应的 <see cref="ItemFilter"/>。</summary>
public event Action<ItemFilter> OnFilterChanged;
/// <summary>当前生效的筛选条件。</summary>
public ItemFilter CurrentFilter { get; private set; } = ItemFilter.None;
// ================================================================
// 生命周期
// ================================================================
private void Start()
{
InitializeOptions();
if (dropdown != null)
{
dropdown.onValueChanged.AddListener(OnValueChanged);
}
}
// ================================================================
// 公开接口
// ================================================================
/// <summary>
/// 使用 <see cref="categoryEntries"/> 重新初始化下拉选项。
/// 语言切换后可手动调用以刷新标签文本。
/// </summary>
public void InitializeOptions()
{
if (dropdown == null) return;
dropdown.ClearOptions();
List<string> labels = new List<string>(categoryEntries.Count);
foreach (CategoryEntry entry in categoryEntries)
{
labels.Add(entry.label);
}
dropdown.AddOptions(labels);
}
/// <summary>
/// 根据当前选中索引构建 <see cref="ItemFilter"/>。
/// types 为空的选项返回 <see cref="ItemFilter.None"/>(即显示所有)。
/// </summary>
public ItemFilter BuildFilter()
{
return CurrentFilter;
}
/// <summary>重置为第一个选项(通常为"全部"),不触发事件。</summary>
public void ResetToAll()
{
CurrentFilter = ItemFilter.None;
if (dropdown != null)
{
dropdown.SetValueWithoutNotify(0);
}
}
// ─────────────────── 内部 ───────────────────
private void OnValueChanged(int index)
{
if (index < 0 || index >= categoryEntries.Count) return;
CategoryEntry entry = categoryEntries[index];
CurrentFilter = (entry.types == null || entry.types.Length == 0)
? ItemFilter.None
: ItemFilter.ByType(entry.types);
OnFilterChanged?.Invoke(CurrentFilter);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b4d9f5140e87ab84f9f53b7fd7839ea3

View File

@@ -1,118 +0,0 @@
using Cielonos.MainGame.Inventory;
using Sirenix.OdinInspector;
using SLSUtilities.General;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace SLSUtilities.UI
{
/// <summary>
/// 通用物品详情面板,显示物品图标、名称、稀有度、描述、标签等信息。
/// 可被不同的 UIPage 复用(机械台、商店、背包检视等)。
/// </summary>
public class ItemDetailPanel : UIElementBase
{
[Title("Detail Panel References")]
public Image itemIcon;
public Image rarityFrame;
public TMP_Text itemNameText;
public TMP_Text itemTypeText;
public TMP_Text itemDescriptionText;
public RectTransform tagContainer;
public GameObject tagPrefab;
private ItemBase currentItem;
/// <summary>当前显示的物品。</summary>
public ItemBase CurrentItem => currentItem;
/// <summary>
/// 使用指定物品的数据填充详情面板并显示。
/// </summary>
public void SetItem(ItemBase item)
{
currentItem = item;
if (item == null || item.contentData == null)
{
ClearPanel();
return;
}
ContentData data = item.contentData;
// 图标:主武器使用 rectIcon其他使用 squareIcon
Sprite icon = data.itemType == ItemType.MainWeapon ? data.rectIcon : data.squareIcon;
if (itemIcon != null)
{
itemIcon.sprite = icon;
itemIcon.enabled = icon != null;
}
// 名称(本地化)
if (itemNameText != null)
{
itemNameText.text = data.displayNameKey.Localize();
}
// 类型
if (itemTypeText != null)
{
itemTypeText.text = data.itemType.ToString();
}
// 描述(本地化)
if (itemDescriptionText != null)
{
itemDescriptionText.text = data.descriptionKey.Localize();
}
// 标签
RefreshTags(data);
Show();
}
/// <summary>清空面板内容并隐藏。</summary>
public void ClearPanel()
{
currentItem = null;
if (itemIcon != null) itemIcon.enabled = false;
if (itemNameText != null) itemNameText.text = string.Empty;
if (itemTypeText != null) itemTypeText.text = string.Empty;
if (itemDescriptionText != null) itemDescriptionText.text = string.Empty;
ClearTags();
Hide();
}
private void RefreshTags(ContentData data)
{
ClearTags();
if (tagContainer == null || tagPrefab == null || data.tags == null) return;
foreach (string tag in data.tags)
{
GameObject tagObj = Instantiate(tagPrefab, tagContainer);
TMP_Text tagText = tagObj.GetComponentInChildren<TMP_Text>();
if (tagText != null)
{
tagText.text = tag;
}
}
}
private void ClearTags()
{
if (tagContainer == null) return;
for (int i = tagContainer.childCount - 1; i >= 0; i--)
{
Destroy(tagContainer.GetChild(i).gameObject);
}
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using Cielonos.MainGame.Items;
using SLSUtilities.General;
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 单选排序模式下拉框,基于 <see cref="TMP_Dropdown"/>。
/// 提供多种 <see cref="ItemSortMode"/>,切换后通知订阅者。
/// <para>可复用于背包、商店等任何需要物品排序的 UI 页面。</para>
/// </summary>
public class ItemSortDropdown : UIElementBase
{
[Header("Dropdown")]
public TMP_Dropdown dropdown;
[Header("Labels")]
[Tooltip("排序模式的显示标签,按顺序对应 Default / ByRarity / ByName。\n" +
"可填入本地化 Key如 'UI_Sort_Default'),会自动调用 Localize()。\n" +
"若无对应翻译则直接显示原文。")]
public List<string> sortModeLabels = new List<string>
{
"默认排序",
"按稀有度",
"按名称"
};
/// <summary>当排序模式变化时触发。</summary>
public event Action<ItemSortMode> OnSortChanged;
/// <summary>当前选中的排序模式。</summary>
public ItemSortMode CurrentMode { get; private set; } = ItemSortMode.Default;
// ================================================================
// 生命周期
// ================================================================
private void Start()
{
InitializeOptions();
if (dropdown != null)
{
dropdown.onValueChanged.AddListener(OnValueChanged);
}
}
// ================================================================
// 公开接口
// ================================================================
/// <summary>
/// 使用 <see cref="sortModeLabels"/> 重新初始化下拉选项。
/// 语言切换后可手动调用以刷新标签文本。
/// </summary>
public void InitializeOptions()
{
if (dropdown == null) return;
dropdown.ClearOptions();
dropdown.AddOptions(sortModeLabels/*.ConvertAll(label => label.Localize("Items"))*/);
}
/// <summary>重置为默认排序模式(不触发事件)。</summary>
public void ResetToDefault()
{
CurrentMode = ItemSortMode.Default;
if (dropdown != null)
{
dropdown.SetValueWithoutNotify(0);
}
}
// ─────────────────── 内部 ───────────────────
private void OnValueChanged(int index)
{
int modeCount = Enum.GetValues(typeof(ItemSortMode)).Length;
if (index >= 0 && index < modeCount)
{
CurrentMode = (ItemSortMode)index;
OnSortChanged?.Invoke(CurrentMode);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9aaaaa3c6c02ad246b7a25a59005f425

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 96289eeb61566e54081a0137954e19a9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,136 @@
using Cielonos.MainGame.Inventory;
using SLSUtilities.General;
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 背包网格中的单个物品槽位。
/// 显示物品图标、名称,消耗品额外显示堆叠数量。
/// 点击后通知 InventoryUIPage 选中该物品并更新详情面板。
/// </summary>
public class InventoryItemSelector : UIElementBase
{
private InventoryUIPage Page => PlayerCanvas.MainGamePages.inventoryPage;
// ─────────────────── 数据 ───────────────────
/// <summary>此槽位绑定的物品实例。</summary>
public ItemBase Item { get; private set; }
// ─────────────────── UI 引用 ───────────────────
[Header("UI References")]
public Button button;
public Image background;
public Image itemIcon;
public Image rarityFrame;
//public Image selectorHint;
//public TMP_Text itemName;
[Tooltip("消耗品堆叠数量显示,非消耗品时隐藏。")]
public TMP_Text stackText;
// ─────────────────── 视觉设置 ───────────────────
[Header("Visual Settings")]
public Color normalColor = new Color(0.2f, 0.2f, 0.2f, 0.8f);
public Color selectedColor = new Color(0.4f, 0.6f, 0.9f, 0.9f);
private bool isSelected;
// ================================================================
// 公开接口
// ================================================================
/// <summary>
/// 用给定的物品数据配置此槽位的显示内容。
/// </summary>
public void Setup(ItemBase item)
{
Item = item;
if (item == null || item.contentData == null)
{
Hide();
return;
}
ContentData data = item.contentData;
Sprite icon = data.itemIcon;
if (itemIcon != null)
{
itemIcon.sprite = icon;
itemIcon.enabled = icon != null;
}
// 名称(本地化)
/*if (itemName != null)
{
itemName.text = data.displayNameKey.Localize();
}*/
// 堆叠数量(仅消耗品)
RefreshStackDisplay();
// 默认取消选中
SetSelected(false);
// 绑定按钮
if (button != null)
{
button.onClick.RemoveAllListeners();
button.onClick.AddListener(OnClicked);
}
Show();
}
/// <summary>
/// 设置选中/取消选中的视觉状态。
/// </summary>
public void SetSelected(bool selected)
{
isSelected = selected;
if (background != null)
{
background.color = isSelected ? selectedColor : normalColor;
}
/*if (selectorHint != null)
{
selectorHint.enabled = isSelected;
}*/
}
/// <summary>
/// 刷新消耗品堆叠数量显示。非消耗品时隐藏堆叠文本。
/// </summary>
public void RefreshStackDisplay()
{
if (stackText == null) return;
if (Item is ConsumableBase consumable)
{
stackText.gameObject.SetActive(true);
stackText.text = consumable.stackAmount.ToString();
}
else
{
stackText.gameObject.SetActive(false);
}
}
// ─────────────────── 内部 ───────────────────
private void OnClicked()
{
Page?.SelectItem(this);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2abecad67abab4e4692a134234d722c8

View File

@@ -0,0 +1,284 @@
using System.Collections.Generic;
using Cielonos.MainGame.Characters;
using Cielonos.MainGame.Inventory;
using Cielonos.MainGame.Items;
using Sirenix.OdinInspector;
using SLSUtilities.UI;
using UnityEngine;
using UnityEngine.UI;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 背包/装备界面 UI 页面。
/// 左侧为背包物品网格GridLayoutGroup支持按类型多选筛选和多种排序模式
/// 右侧为物品详情面板ItemDetailPanel选中物品后显示详细信息。
/// </summary>
public class InventoryUIPage : UIPageBase
{
// ─────────────────── 选择器 ───────────────────
[Title("Selectors")]
[Tooltip("背包网格中每个物品槽位的预制体,需挂载 InventoryItemSelector 组件。")]
public GameObject selectorPrefab;
[Tooltip("物品槽位的容器,应挂载 GridLayoutGroup 以实现网格布局。")]
public RectTransform selectorContainer;
[ReadOnly]
public List<InventoryItemSelector> selectors = new List<InventoryItemSelector>();
// ─────────────────── 详情面板 ───────────────────
[Title("Detail Panel")]
public ItemDetailPanel itemDetailPanel;
// ─────────────────── 筛选与排序 ───────────────────
[Title("Filter & Sort")]
[Tooltip("多选类别筛选下拉框,按 ItemType 过滤可见物品。")]
public ItemCategoryDropdown categoryDropdown;
[Tooltip("单选排序模式下拉框,决定物品排列顺序。")]
public ItemSortDropdown sortDropdown;
// ─────────────────── 按钮 ───────────────────
[Title("Buttons")]
public Button closeButton;
// ─────────────────── 运行时数据 ───────────────────
private InventoryItemSelector currentSelected;
/// <summary>当前选中的物品null 表示未选中。</summary>
public ItemBase SelectedItem => currentSelected != null ? currentSelected.Item : null;
// ================================================================
// 生命周期
// ================================================================
protected override void Start()
{
base.Start();
if (closeButton != null)
{
closeButton.onClick.AddListener(OnCloseClicked);
}
if (categoryDropdown != null)
{
categoryDropdown.OnFilterChanged += OnFilterChanged;
}
if (sortDropdown != null)
{
sortDropdown.OnSortChanged += OnSortChanged;
}
}
protected override void OnPageOpened()
{
RefreshInventory();
}
protected override void OnPageClosed()
{
currentSelected = null;
ClearSelectors();
categoryDropdown?.ResetToAll();
sortDropdown?.ResetToDefault();
}
// ================================================================
// 公开接口
// ================================================================
/// <summary>
/// 从玩家背包读取所有物品,重新生成网格选择器并应用排序与筛选。
/// </summary>
public void RefreshInventory()
{
ClearSelectors();
PlayerInventorySubcontroller.BackpackSubmodule backpack =
MainGameManager.Player.inventorySc.backpackSm;
PopulateFromList(backpack.mainWeapons);
PopulateFromList(backpack.supportEquipments);
PopulateFromList(backpack.passiveEquipments);
PopulateFromList(backpack.consumables);
currentSelected = null;
ApplySortAndFilter();
if (itemDetailPanel != null)
{
itemDetailPanel.ClearPanel();
}
}
/// <summary>
/// 由 InventoryItemSelector 调用,选中指定的物品槽位。
/// </summary>
public void SelectItem(InventoryItemSelector selector)
{
if (selector == null || !selectors.Contains(selector)) return;
if (currentSelected != null)
{
currentSelected.SetSelected(false);
}
currentSelected = selector;
currentSelected.SetSelected(true);
if (itemDetailPanel != null)
{
itemDetailPanel.SetItem(currentSelected.Item);
}
}
// ================================================================
// 筛选与排序
// ================================================================
/// <summary>
/// 对选择器列表执行排序,然后按当前筛选条件显示/隐藏。
/// 当任一 Dropdown 变化或背包刷新时调用。
/// </summary>
private void ApplySortAndFilter()
{
// ── 排序 ──
ItemSortMode sortMode = sortDropdown != null ? sortDropdown.CurrentMode : ItemSortMode.Default;
List<ItemBase> items = selectors.ConvertAll(s => s.Item);
ItemSorter.Sort(items, sortMode);
// 按排序结果重排选择器列表
Dictionary<ItemBase, InventoryItemSelector> itemToSelector =
new Dictionary<ItemBase, InventoryItemSelector>(selectors.Count);
foreach (InventoryItemSelector sel in selectors)
{
itemToSelector.TryAdd(sel.Item, sel);
}
List<InventoryItemSelector> sorted = new List<InventoryItemSelector>(selectors.Count);
foreach (ItemBase item in items)
{
if (itemToSelector.TryGetValue(item, out InventoryItemSelector sel))
{
sorted.Add(sel);
}
}
selectors.Clear();
selectors.AddRange(sorted);
// 更新 UI 层级顺序
for (int i = 0; i < selectors.Count; i++)
{
selectors[i].transform.SetSiblingIndex(i);
}
// ── 筛选 ──
ItemFilter filter = categoryDropdown != null ? categoryDropdown.BuildFilter() : ItemFilter.None;
bool selectedHidden = false;
foreach (InventoryItemSelector selector in selectors)
{
if (selector == null || selector.Item == null) continue;
bool visible = filter.Match(selector.Item);
selector.gameObject.SetActive(visible);
if (!visible && selector == currentSelected)
{
selectedHidden = true;
}
}
if (selectedHidden)
{
currentSelected.SetSelected(false);
currentSelected = null;
itemDetailPanel?.ClearPanel();
}
}
// ─────────────────── Dropdown 回调 ───────────────────
private void OnFilterChanged(ItemFilter filter)
{
ApplySortAndFilter();
}
private void OnSortChanged(ItemSortMode mode)
{
ApplySortAndFilter();
}
// ================================================================
// 选择器管理
// ================================================================
private void PopulateFromList<T>(List<T> items) where T : ItemBase
{
if (items == null) return;
foreach (T item in items)
{
if (item == null || item.contentData == null) continue;
InventoryItemSelector selector = CreateSelector();
if (selector != null)
{
selector.Setup(item);
}
}
}
private InventoryItemSelector CreateSelector()
{
if (selectorPrefab == null || selectorContainer == null)
{
Debug.LogError("[InventoryUIPage] selectorPrefab 或 selectorContainer 未配置。");
return null;
}
GameObject obj = Instantiate(selectorPrefab, selectorContainer);
InventoryItemSelector selector = obj.GetComponent<InventoryItemSelector>();
if (selector == null)
{
Debug.LogError("[InventoryUIPage] selectorPrefab 缺少 InventoryItemSelector 组件。");
Destroy(obj);
return null;
}
selectors.Add(selector);
return selector;
}
private void ClearSelectors()
{
foreach (InventoryItemSelector selector in selectors)
{
if (selector != null)
{
Destroy(selector.gameObject);
}
}
selectors.Clear();
}
// ─────────────────── 按钮回调 ───────────────────
private void OnCloseClicked()
{
Close();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b8ea321de103c2e4ab8833abc7897452

View File

@@ -57,7 +57,7 @@ namespace Cielonos.MainGame.UI
ContentData data = item.contentData;
// 图标
Sprite icon = data.itemType == ItemType.MainWeapon ? data.rectIcon : data.squareIcon;
Sprite icon = data.itemIcon;
if (itemIcon != null)
{
itemIcon.sprite = icon;
@@ -67,7 +67,7 @@ namespace Cielonos.MainGame.UI
// 名称(本地化)
if (itemName != null)
{
itemName.text = data.displayNameKey.Localize();
itemName.text = data.displayNameKey.Localize("Items");
}
// 价格

View File

@@ -7,6 +7,7 @@ namespace Cielonos.MainGame.UI
{
public MechanicalTableUIPage mechanicalTablePage;
public LogisticsCenterUIPage logisticsCenterPage;
public InventoryUIPage inventoryPage;
public MapUIPage mapPage;
public SettlementUIPage settlementPage;
}

View File

@@ -46,7 +46,7 @@ namespace Cielonos.MainGame.UI
ContentData data = item.contentData;
// 图标
Sprite icon = data.itemType == ItemType.MainWeapon ? data.rectIcon : data.squareIcon;
Sprite icon = data.itemIcon;
if (itemIcon != null)
{
itemIcon.sprite = icon;
@@ -56,7 +56,7 @@ namespace Cielonos.MainGame.UI
// 名称(本地化)
if (itemName != null)
{
itemName.text = data.displayNameKey.Localize();
itemName.text = data.displayNameKey.Localize("Items");
}
// 堆叠数量

View File

@@ -41,14 +41,20 @@ namespace Cielonos.MainGame.UI
protected override void Start()
{
base.Start();
RunManager.Instance.OnPhaseChanged += OnPhaseChanged;
restartButton.onClick.AddListener(OnRestartClicked);
mainMenuButton.onClick.AddListener(OnMainMenuClicked);
if (MainGameManager.Instance.sceneSm.IsCityArena)
{
RunManager.Instance.OnPhaseChanged += OnPhaseChanged;
restartButton.onClick.AddListener(OnRestartClicked);
mainMenuButton.onClick.AddListener(OnMainMenuClicked);
}
}
private void OnDestroy()
{
RunManager.Instance.OnPhaseChanged -= OnPhaseChanged;
if (MainGameManager.Instance.sceneSm.IsCityArena)
{
RunManager.Instance.OnPhaseChanged -= OnPhaseChanged;
}
}
// ================================================================

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8afdf8c1f5c61d34283e8f2025435c24
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,121 @@
using System.Collections.Generic;
using Cielonos.Core.SceneManagement;
using DG.Tweening;
using SLSUtilities.General;
using SLSUtilities.UI;
using SLSUtilities.WwiseAssistance;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 暂停界面页面。
/// <para>
/// 打开时暂停游戏(<c>Time.timeScale = 0</c>
/// 关闭时恢复游戏。
/// 提供三个按钮:继续游戏、打开设置、返回标题界面。
/// 返回标题界面时会弹出 <see cref="ConfirmUIPage"/> 二次确认。
/// </para>
/// </summary>
public class PauseUIPage : UIPageBase
{
// ──────────────────── 按钮引用 ────────────────────
[FormerlySerializedAs("continueButton")]
[Header("Buttons")]
[SerializeField] private Button resumeButton;
[SerializeField] private Button settingsButton;
[SerializeField] private Button returnToMenuButton;
// ──────────────────── 配置 ────────────────────────
[Header("Scene")]
[Tooltip("标题界面的场景名称,通过 SceneBus 加载。")]
[SerializeField] private string titleSceneName = "Menu";
// ──────────────────── 生命周期 ────────────────────
protected override void Start()
{
base.Start();
resumeButton.onClick.AddListener(OnContinueClicked);
settingsButton.onClick.AddListener(OnSettingsClicked);
returnToMenuButton.onClick.AddListener(OnReturnToTitleClicked);
}
public override void Open()
{
if (IsOpen) return;
IsOpen = true;
Time.timeScale = 0f;
UIPageManager.Instance.RegisterPage(this);
EventSystem.current?.SetSelectedGameObject(null);
gameObject.SetActive(true);
canvasGroup.interactable = true;
canvasGroup.DOFade(1f, 0.3f).From(0f).SetUpdate(true)
.OnComplete(() =>
{
canvasGroup.blocksRaycasts = true;
OnPageOpened();
}).Play();
}
public override void Close()
{
if (!IsOpen) return;
IsOpen = false;
UIPageManager.Instance.UnregisterPage(this);
canvasGroup.DOFade(0f, 0.3f).From(1f).SetUpdate(true)
.OnComplete(() =>
{
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
OnPageClosed();
Time.timeScale = 1f;
}).Play();
}
// ──────────────────── 按钮回调 ────────────────────
private void OnContinueClicked()
{
Close();
}
private void OnSettingsClicked()
{
PlayerCanvas.SettingsUIPage.Open();
}
/// <summary>
/// 点击"返回主菜单"按钮时,弹出确认弹窗而非直接切换场景。
/// </summary>
private void OnReturnToTitleClicked()
{
ConfirmUIPage.Show(new ConfirmPageConfig
{
Title = "ConfirmReturnToMenu_Title".Localize("UI"),
Description = "ConfirmReturnToMenu_Desc".Localize("UI"),
Buttons = new List<ConfirmButtonConfig>
{
new("Confirm".Localize("UI"), ReturnToMenu),
new("Cancel".Localize("UI"))
}
});
}
/// <summary>
/// 用户确认后执行的返回主菜单逻辑。
/// </summary>
private void ReturnToMenu()
{
Time.timeScale = 1f;
AudioManager.Instance.backgroundMusicManager.TransitionToNextScene();
UIPageManager.Instance.CloseAllPages();
SceneBus.LoadScene(titleSceneName);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5b190c38fdc79d24b8c7cd550c838030

View File

@@ -1,4 +1,5 @@
using System;
using Cielonos.UI;
using SLSUtilities.General;
using SLSUtilities.UI;
using TMPro;
@@ -24,6 +25,12 @@ namespace Cielonos.MainGame.UI
private CombatSystemsUIArea combatSystemsUIArea;
[SerializeField]
private MainGamePages mainGamePages;
[SerializeField]
private PauseUIPage pauseUIPage;
[SerializeField]
private SettingsUIPage settingsUIPage;
[SerializeField]
private InteractionUIArea interactionUIArea;
private void Update()
{
@@ -41,5 +48,8 @@ namespace Cielonos.MainGame.UI
public static EnemyInfoUIArea EnemyInfoUIArea => Instance.enemyInfoUIArea;
public static CombatSystemsUIArea CombatSystemsUIArea => Instance.combatSystemsUIArea;
public static MainGamePages MainGamePages => Instance.mainGamePages;
public static PauseUIPage PauseUIPage => Instance.pauseUIPage;
public static SettingsUIPage SettingsUIPage => Instance.settingsUIPage;
public static InteractionUIArea InteractionUIArea => Instance?.interactionUIArea;
}
}

View File

@@ -31,7 +31,7 @@ namespace Cielonos.MainGame.UI
else
{
this.supportEquipment = supportEquipment;
iconImage.sprite = supportEquipment.contentData.squareIcon;
iconImage.sprite = supportEquipment.contentData.itemIcon;
if (supportEquipment.functionSm != null)
{