Files
bansonic_beta_main/Assets/playerBagSystem/uBag.cs
T
2026-07-20 22:19:25 +08:00

1515 lines
47 KiB
C#

using System.Collections;
using System.Collections.Generic;
using Bansonic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using TMPro;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class uBag : MonoBehaviour
{
private const string MemorySystemComingSoonMessage = "\"记忆\"系统即将上线,敬请期待!";
private enum BagFilterMode
{
All,
Cultivation,
Precious,
Other
}
private enum BagSortMode
{
Default,
Name,
Rarity,
Count
}
private enum BagCategory
{
All,
Cultivation,
Precious,
Other
}
private enum PreciousKind
{
NotebookClue,
Character,
Level
}
private sealed class BagEntry
{
public string stableId;
public string displayName;
public string countText;
public string amountText;
public string usage;
public string description;
public Sprite icon;
public BagCategory category;
public int sortPriority;
public ItemRarity rarity;
}
[Header("category toggles")]
public ToggleGroup bagToggleGroup;
public Toggle allToggle;
public Toggle cultivateToggle;
public Toggle preciousToggle;
public Toggle otherToggle;
public Toggle equipSystemToggle;
[Header("OBJ")]
public GameObject bagObj;
public GameObject equipObj;
[Header("right panel")]
public Image btmImg;
public Image itemDprofile;
public TextMeshProUGUI itemAmount;
public Text itemName;
public Text itemUse;
public Text itemUsageLegacy;
public Text itemDescription;
public Image bottom_rarityImg;
public Sprite[] bottom_rarityImg_sprites;
[Header("list")]
public Transform itemParent;
public GameObject itemPrefab;
public Text emptyText;
[Min(1)] public int spawnBatchSize = 16;
[Header("introduction")]
public GameObject itemIntroductionPrefab;
public float introductionHoverDelay = 0.5f;
public int introductionHorizontalOffset = 12;
[Header("colors")]
public bool useRarityColorForItemName = true;
public Color fallbackColor = Color.white;
public Sprite fallbackItemIcon;
public Sprite fallbackBackgroundSprite;
public ItemRarityColorConfigSO rarityColorConfig;
[Header("filters")]
public Dropdown bag_filterDropdown;
public Dropdown bag_sortDropdown;
[Header("paths")]
[SerializeField] private string notebookEditorPath = "Assets/Resources/so/notebook";
[SerializeField] private string notebookRuntimePath = "so/notebook";
[SerializeField] private string storeItemEditorPath = "Assets/Resources/so/storeSO";
[SerializeField] private string storeItemRuntimePath = "so/storeSO";
[SerializeField] private string expBottleEditorPath = "Assets/storeSystem/items/medicines";
[SerializeField] private string growthMaterialEditorPath = "Assets/storeSystem/items/medicines";
[SerializeField] private string equipmentConsumableEditorPath = "Assets/storeSystem/items/medicines";
private readonly List<GameObject> spawnedItems = new List<GameObject>();
private readonly List<BagEntry> allEntries = new List<BagEntry>();
private readonly Dictionary<uBagItemPrefab, BagEntry> viewEntries = new Dictionary<uBagItemPrefab, BagEntry>();
private readonly List<Dropdown.OptionData> filterOptions = new List<Dropdown.OptionData>();
private readonly List<Dropdown.OptionData> sortOptions = new List<Dropdown.OptionData>();
private Coroutine rebuildRoutine;
private BagCategory currentCategory = BagCategory.All;
private BagFilterMode currentFilterMode = BagFilterMode.All;
private BagSortMode currentSortMode = BagSortMode.Default;
private GameObject spawnedIntroduction;
private uBagItemPrefab activePreviewSource;
private void Awake()
{
InitializeToggleGroup();
InitializeFilterDropdowns();
BindToggles();
BindFilterDropdowns();
BindLedgerEvents();
InitializeViewMode();
SelectDefaultCategory();
}
private void OnEnable()
{
InitializeToggleGroup();
InitializeFilterDropdowns();
BindToggles();
BindFilterDropdowns();
BindLedgerEvents();
InitializeViewMode();
Rebuild();
}
private void OnDisable()
{
UnbindToggle(allToggle, HandleAllChanged);
UnbindToggle(cultivateToggle, HandleCultivateChanged);
UnbindToggle(preciousToggle, HandlePreciousChanged);
UnbindToggle(otherToggle, HandleOtherChanged);
UnbindToggle(equipSystemToggle, HandleEquipSystemChanged);
UnbindDropdown(bag_filterDropdown, HandleFilterDropdownChanged);
UnbindDropdown(bag_sortDropdown, HandleSortDropdownChanged);
UnbindLedgerEvents();
HideCurrentPreview();
}
private void OnDestroy()
{
btmandtopController.RefreshOverlayVisibilityState();
}
private void InitializeToggleGroup()
{
if (bagToggleGroup == null)
{
return;
}
bagToggleGroup.allowSwitchOff = false;
AssignToggleGroup(allToggle);
AssignToggleGroup(cultivateToggle);
AssignToggleGroup(preciousToggle);
AssignToggleGroup(otherToggle);
}
private void AssignToggleGroup(Toggle toggle)
{
if (toggle == null || bagToggleGroup == null)
{
return;
}
toggle.group = bagToggleGroup;
}
private void BindToggles()
{
RebindToggle(allToggle, HandleAllChanged);
RebindToggle(cultivateToggle, HandleCultivateChanged);
RebindToggle(preciousToggle, HandlePreciousChanged);
RebindToggle(otherToggle, HandleOtherChanged);
RebindToggle(equipSystemToggle, HandleEquipSystemChanged);
}
private void InitializeFilterDropdowns()
{
InitializeFilterDropdown(bag_filterDropdown, filterOptions, new[]
{
"全部",
"培养",
"珍贵",
"其他"
});
InitializeFilterDropdown(bag_sortDropdown, sortOptions, new[]
{
"默认",
"名称",
"稀有度",
"数量"
});
}
private void InitializeFilterDropdown(Dropdown dropdown, List<Dropdown.OptionData> cache, string[] labels)
{
if (dropdown == null)
{
return;
}
bool wasActive = dropdown.gameObject.activeInHierarchy;
cache.Clear();
for (int i = 0; i < labels.Length; i++)
{
cache.Add(new Dropdown.OptionData(labels[i]));
}
dropdown.ClearOptions();
dropdown.AddOptions(cache);
if (dropdown.value < 0 || dropdown.value >= labels.Length)
{
dropdown.value = 0;
}
dropdown.RefreshShownValue();
dropdown.gameObject.SetActive(wasActive);
}
private void BindFilterDropdowns()
{
RebindDropdown(bag_filterDropdown, HandleFilterDropdownChanged);
RebindDropdown(bag_sortDropdown, HandleSortDropdownChanged);
}
private void RebindDropdown(Dropdown dropdown, UnityAction<int> action)
{
if (dropdown == null)
{
return;
}
dropdown.onValueChanged.RemoveListener(action);
dropdown.onValueChanged.AddListener(action);
}
private void UnbindDropdown(Dropdown dropdown, UnityAction<int> action)
{
if (dropdown == null)
{
return;
}
dropdown.onValueChanged.RemoveListener(action);
}
private void HandleFilterDropdownChanged(int value)
{
currentFilterMode = (BagFilterMode)Mathf.Clamp(value, 0, 3);
currentCategory = (BagCategory)currentFilterMode;
Rebuild();
}
private void HandleSortDropdownChanged(int value)
{
currentSortMode = (BagSortMode)Mathf.Clamp(value, 0, 3);
Rebuild();
}
private void RebindToggle(Toggle toggle, UnityAction<bool> action)
{
if (toggle == null)
{
return;
}
toggle.onValueChanged.RemoveListener(action);
toggle.onValueChanged.AddListener(action);
}
private void UnbindToggle(Toggle toggle, UnityAction<bool> action)
{
if (toggle == null)
{
return;
}
toggle.onValueChanged.RemoveListener(action);
}
private void BindLedgerEvents()
{
ExpBottleLedger.EnsureInstance().OnBottleCountChanged -= HandleInventoryChanged;
ExpBottleLedger.EnsureInstance().OnBottleCountChanged += HandleInventoryChanged;
ExpBottleLedger.EnsureInstance().OnLedgerReloaded -= HandleInventoryReloaded;
ExpBottleLedger.EnsureInstance().OnLedgerReloaded += HandleInventoryReloaded;
DushMaterialLedger.EnsureInstance().OnMaterialCountChanged -= HandleGrowthMaterialChanged;
DushMaterialLedger.EnsureInstance().OnMaterialCountChanged += HandleGrowthMaterialChanged;
EquipmentConsumableLedger.EnsureInstance().OnCountChanged -= HandleEquipmentConsumableChanged;
EquipmentConsumableLedger.EnsureInstance().OnCountChanged += HandleEquipmentConsumableChanged;
}
private void UnbindLedgerEvents()
{
ExpBottleLedger.EnsureInstance().OnBottleCountChanged -= HandleInventoryChanged;
ExpBottleLedger.EnsureInstance().OnLedgerReloaded -= HandleInventoryReloaded;
DushMaterialLedger.EnsureInstance().OnMaterialCountChanged -= HandleGrowthMaterialChanged;
EquipmentConsumableLedger.EnsureInstance().OnCountChanged -= HandleEquipmentConsumableChanged;
}
private void SelectDefaultCategory()
{
if (allToggle != null)
{
allToggle.isOn = true;
ApplyCategory(BagCategory.All);
return;
}
ApplyCategory(BagCategory.All);
}
private void HandleAllChanged(bool isOn)
{
if (isOn)
{
ApplyCategory(BagCategory.All);
}
}
private void HandleCultivateChanged(bool isOn)
{
if (isOn)
{
ApplyCategory(BagCategory.Cultivation);
}
}
private void HandlePreciousChanged(bool isOn)
{
if (isOn)
{
ApplyCategory(BagCategory.Precious);
}
}
private void HandleOtherChanged(bool isOn)
{
if (isOn)
{
ApplyCategory(BagCategory.Other);
}
}
private void HandleEquipSystemChanged(bool isOn)
{
if (isOn)
{
ShowMemorySystemComingSoon();
}
ForceBagViewMode();
}
private void ApplyCategory(BagCategory category)
{
ForceBagViewMode();
currentCategory = category;
currentFilterMode = (BagFilterMode)category;
if (bag_filterDropdown != null)
{
bag_filterDropdown.SetValueWithoutNotify((int)currentFilterMode);
bag_filterDropdown.RefreshShownValue();
}
SetToggleInteractable(allToggle, category != BagCategory.All);
SetToggleInteractable(cultivateToggle, category != BagCategory.Cultivation);
SetToggleInteractable(preciousToggle, category != BagCategory.Precious);
SetToggleInteractable(otherToggle, category != BagCategory.Other);
Rebuild();
}
private void SetToggleInteractable(Toggle toggle, bool interactable)
{
if (toggle != null)
{
toggle.interactable = interactable;
}
}
private void SetObjectActive(GameObject target, bool active)
{
if (target != null)
{
target.SetActive(active);
}
}
private void InitializeViewMode()
{
if (equipSystemToggle != null)
{
equipSystemToggle.onValueChanged.RemoveListener(HandleEquipSystemChanged);
equipSystemToggle.onValueChanged.AddListener(HandleEquipSystemChanged);
}
ForceBagViewMode();
}
private void ForceBagViewMode()
{
if (equipSystemToggle != null)
{
equipSystemToggle.SetIsOnWithoutNotify(false);
}
SetObjectActive(bagObj, true);
SetObjectActive(equipObj, false);
}
private static void ShowMemorySystemComingSoon()
{
gNotice.error.display(MemorySystemComingSoonMessage);
}
private void HandleInventoryChanged(ExpBottleKind _, int __)
{
Rebuild();
}
private void HandleInventoryReloaded()
{
Rebuild();
}
private void HandleGrowthMaterialChanged(DushMaterialKind _, int __)
{
Rebuild();
}
private void HandleEquipmentConsumableChanged(EquipmentConsumableKind _, int __)
{
Rebuild();
}
public void Rebuild()
{
BuildEntries();
if (rebuildRoutine != null)
{
StopCoroutine(rebuildRoutine);
rebuildRoutine = null;
}
ClearSpawnedItems();
if (!isActiveAndEnabled)
{
return;
}
ClearRightPanel();
rebuildRoutine = StartCoroutine(RebuildAsync());
}
public void SelectItemForDetail(uBagItemPrefab source)
{
if (source == null)
{
return;
}
if (!viewEntries.TryGetValue(source, out BagEntry entry) || entry == null)
{
ClearRightPanel();
return;
}
ApplyRightPanel(entry);
}
public void ShowPreviewFor(uBagItemPrefab source)
{
if (source == null || itemIntroductionPrefab == null)
{
return;
}
if (!viewEntries.TryGetValue(source, out BagEntry entry) || entry == null)
{
return;
}
HideCurrentPreview();
Transform previewParent = ResolveIntroductionParent(source);
if (previewParent == null)
{
return;
}
spawnedIntroduction = Instantiate(itemIntroductionPrefab, previewParent);
spawnedIntroduction.name = $"uBagPreview_{entry.stableId}";
spawnedIntroduction.SetActive(true);
spawnedIntroduction.transform.SetAsLastSibling();
activePreviewSource = source;
var previewView = spawnedIntroduction.GetComponent<bagItemPreviewPrefab>();
if (previewView != null)
{
Sprite icon = entry.icon != null ? entry.icon : fallbackItemIcon;
Color color = rarityColorConfig != null
? rarityColorConfig.GetColor(entry.rarity, fallbackColor)
: fallbackColor;
Sprite backgroundSprite = rarityColorConfig != null
? rarityColorConfig.GetBackgroundSprite(entry.rarity, fallbackBackgroundSprite)
: fallbackBackgroundSprite;
previewView.Bind(color, backgroundSprite, icon, entry.displayName, entry.usage, entry.description, entry.amountText);
}
PositionIntroduction(source, spawnedIntroduction);
UI_OrderedEntryAnimator.PlayFadeOnly(spawnedIntroduction, 0, 0.16f, 0f, true);
}
public void HidePreviewFor(uBagItemPrefab source)
{
if (source != null && source != activePreviewSource)
{
return;
}
HideCurrentPreview();
}
private void BuildEntries()
{
allEntries.Clear();
List<storeItemSO> storeItems = new List<storeItemSO>(LoadStoreItems());
AddExpBottleEntries(storeItems);
AddGrowthMaterialEntries(storeItems);
AddEquipmentConsumableEntries(storeItems);
AddNotebookPreciousEntries();
AddOwnedStorePreciousEntries(storeItems);
allEntries.Sort(CompareEntries);
ApplySortMode(allEntries);
}
private void AddExpBottleEntries(List<storeItemSO> storeItems)
{
var ledger = ExpBottleLedger.EnsureInstance();
var definitions = LoadExpBottleDefinitions();
foreach (var descriptor in ExpBottleCatalog.All)
{
int count = ledger.GetCount(descriptor.Kind);
if (count <= 0)
{
continue;
}
expBottlesSO definition = FindExpBottleDefinition(definitions, descriptor.Kind);
storeItemSO storeItem = FindStoreItemForExpBottle(storeItems, descriptor.Kind);
expBottlesSO sourceDefinition = storeItem != null && storeItem.associatedExpBottle != null
? storeItem.associatedExpBottle
: definition;
allEntries.Add(new BagEntry
{
stableId = descriptor.Key,
displayName = storeItem != null && !string.IsNullOrEmpty(storeItem.itemName)
? storeItem.itemName
: definition != null && !string.IsNullOrEmpty(definition.expBottleName) ? definition.expBottleName : descriptor.DisplayName,
countText = count.ToString(),
amountText = count.ToString(),
usage = ItemUsageTagUtility.ResolveDisplayName(sourceDefinition),
description = BuildPreferredDescription(
storeItem != null ? storeItem.itemDetailedDescription : null,
sourceDefinition != null ? sourceDefinition.expBottleDescription : null,
storeItem != null ? storeItem.itemDescription : null),
icon = ResolvePreferredSprite(
storeItem != null ? storeItem.itemIcon : null,
sourceDefinition != null ? sourceDefinition.expBottleSprite : null,
fallbackItemIcon),
category = BagCategory.Cultivation,
sortPriority = 10,
rarity = storeItem != null ? storeItem.itemRarity : sourceDefinition != null ? sourceDefinition.itemRarity : ItemRarity.Common
});
}
}
private void AddGrowthMaterialEntries(List<storeItemSO> storeItems)
{
var ledger = DushMaterialLedger.EnsureInstance();
var definitions = LoadGrowthMaterialDefinitions();
foreach (var descriptor in DushMaterialCatalog.All)
{
int count = ledger.GetCount(descriptor.Kind);
if (count <= 0)
{
continue;
}
growthMaterialSO definition = FindGrowthMaterialDefinition(definitions, descriptor.Kind);
storeItemSO storeItem = FindStoreItemForGrowthMaterial(storeItems, descriptor.Kind);
growthMaterialSO sourceDefinition = storeItem != null && storeItem.associatedGrowthMaterial != null
? storeItem.associatedGrowthMaterial
: definition;
allEntries.Add(new BagEntry
{
stableId = descriptor.Key,
displayName = storeItem != null && !string.IsNullOrEmpty(storeItem.itemName)
? storeItem.itemName
: definition != null && !string.IsNullOrEmpty(definition.growthMaterialName) ? definition.growthMaterialName : descriptor.DisplayName,
countText = count.ToString(),
amountText = count.ToString(),
usage = ItemUsageTagUtility.ResolveDisplayName(sourceDefinition),
description = BuildPreferredDescription(
storeItem != null ? storeItem.itemDetailedDescription : null,
sourceDefinition != null ? sourceDefinition.growthMaterialDescription : null,
storeItem != null ? storeItem.itemDescription : null),
icon = ResolvePreferredSprite(
storeItem != null ? storeItem.itemIcon : null,
sourceDefinition != null ? sourceDefinition.growthMaterialSprite : null,
fallbackItemIcon),
category = BagCategory.Cultivation,
sortPriority = 20,
rarity = storeItem != null ? storeItem.itemRarity : sourceDefinition != null ? sourceDefinition.itemRarity : ItemRarity.Uncommon
});
}
}
private void AddEquipmentConsumableEntries(List<storeItemSO> storeItems)
{
var ledger = EquipmentConsumableLedger.EnsureInstance();
var definitions = LoadEquipmentConsumableDefinitions();
foreach (var descriptor in EquipmentConsumableCatalog.All)
{
int count = ledger.GetCount(descriptor.Kind);
if (count <= 0)
{
continue;
}
equipmentConsumableSO definition = FindEquipmentConsumableDefinition(definitions, descriptor.Kind);
storeItemSO storeItem = FindStoreItemForEquipmentConsumable(storeItems, descriptor.Kind);
equipmentConsumableSO sourceDefinition = storeItem != null && storeItem.associatedEquipmentConsumable != null
? storeItem.associatedEquipmentConsumable
: definition;
allEntries.Add(new BagEntry
{
stableId = descriptor.Key,
displayName = storeItem != null && !string.IsNullOrEmpty(storeItem.itemName)
? storeItem.itemName
: definition != null && !string.IsNullOrEmpty(definition.consumableName) ? definition.consumableName : descriptor.DisplayName,
countText = count.ToString(),
amountText = count.ToString(),
usage = ItemUsageTagUtility.ResolveDisplayName(sourceDefinition),
description = BuildPreferredDescription(
storeItem != null ? storeItem.itemDetailedDescription : null,
sourceDefinition != null ? sourceDefinition.consumableDescription : null,
storeItem != null ? storeItem.itemDescription : null),
icon = ResolvePreferredSprite(
storeItem != null ? storeItem.itemIcon : null,
sourceDefinition != null ? sourceDefinition.consumableSprite : null,
fallbackItemIcon),
category = BagCategory.Cultivation,
sortPriority = 30,
rarity = storeItem != null ? storeItem.itemRarity : sourceDefinition != null ? sourceDefinition.itemRarity : ItemRarity.Uncommon
});
}
}
private void AddNotebookPreciousEntries()
{
var seenIds = new HashSet<string>();
foreach (var story in LoadNotebookDefinitions())
{
if (story == null || !story.isUnlocked)
{
continue;
}
if (!story.showInBag)
{
continue;
}
string stableId = $"notebook_{story.class_id}";
if (!seenIds.Add(stableId))
{
continue;
}
allEntries.Add(new BagEntry
{
stableId = stableId,
displayName = story.class_name,
countText = "1",
amountText = "1",
usage = ItemUsageTagUtility.ResolveDisplayName(story),
description = story.class_description,
icon = ResolvePreferredSprite(story.class_image, fallbackItemIcon),
category = BagCategory.Precious,
sortPriority = 100,
rarity = story.itemRarity
});
}
}
private void AddOwnedStorePreciousEntries(List<storeItemSO> storeItems)
{
var ownership = StoreOwnershipLedger.EnsureInstance();
foreach (var item in storeItems)
{
if (item == null || !ownership.IsOwned(item))
{
continue;
}
if (item.itemType != storeItemSO.ItemType.character && item.itemType != storeItemSO.ItemType.song)
{
continue;
}
PreciousKind kind = item.itemType == storeItemSO.ItemType.character ? PreciousKind.Character : PreciousKind.Level;
allEntries.Add(new BagEntry
{
stableId = $"store_{item.itemID}",
displayName = item.itemName,
countText = "1",
amountText = "1",
usage = ItemUsageTagUtility.ResolveDisplayName(item),
description = BuildPreferredDescription(item.itemDetailedDescription, item.itemDescription),
icon = ResolvePreferredSprite(item.itemIcon, fallbackItemIcon),
category = BagCategory.Precious,
sortPriority = kind == PreciousKind.Character ? 110 : 120,
rarity = item.itemRarity
});
}
}
private IEnumerator RebuildAsync()
{
List<BagEntry> visibleEntries = FilterEntries();
UpdateEmptyState(visibleEntries.Count == 0);
uBagItemPrefab firstView = null;
int spawnedInBatch = 0;
for (int i = 0; i < visibleEntries.Count; i++)
{
uBagItemPrefab spawnedView = SpawnEntry(visibleEntries[i]);
if (firstView == null && spawnedView != null)
{
firstView = spawnedView;
}
spawnedInBatch++;
if (spawnedInBatch >= spawnBatchSize)
{
spawnedInBatch = 0;
yield return null;
}
}
if (firstView != null)
{
SelectItemForDetail(firstView);
}
else
{
ClearRightPanel();
}
rebuildRoutine = null;
}
private List<BagEntry> FilterEntries()
{
var result = new List<BagEntry>();
for (int i = 0; i < allEntries.Count; i++)
{
BagEntry entry = allEntries[i];
if (entry == null)
{
continue;
}
if (currentCategory != BagCategory.All && entry.category != currentCategory)
{
continue;
}
result.Add(entry);
}
ApplySortMode(result);
return result;
}
private void ApplySortMode(List<BagEntry> entries)
{
if (entries == null || entries.Count <= 1)
{
return;
}
switch (currentSortMode)
{
case BagSortMode.Name:
entries.Sort((left, right) => string.CompareOrdinal(
left != null ? left.displayName : string.Empty,
right != null ? right.displayName : string.Empty));
break;
case BagSortMode.Rarity:
entries.Sort((left, right) =>
{
int rarityResult = (left != null ? left.rarity : ItemRarity.None).CompareTo(right != null ? right.rarity : ItemRarity.None);
if (rarityResult != 0) return rarityResult;
return CompareEntries(left, right);
});
break;
case BagSortMode.Count:
entries.Sort((left, right) =>
{
int leftCount = ParseSafeInt(left != null ? left.amountText : null);
int rightCount = ParseSafeInt(right != null ? right.amountText : null);
int countResult = rightCount.CompareTo(leftCount);
if (countResult != 0) return countResult;
return CompareEntries(left, right);
});
break;
}
}
private static int ParseSafeInt(string value)
{
return int.TryParse(value, out int parsed) ? parsed : 0;
}
private uBagItemPrefab SpawnEntry(BagEntry entry)
{
if (entry == null || itemPrefab == null || itemParent == null)
{
return null;
}
GameObject instance = Instantiate(itemPrefab, itemParent);
instance.name = $"uBagItem_{entry.stableId}";
spawnedItems.Add(instance);
UI_OrderedEntryAnimator.PlaySingle(instance, spawnedItems.Count - 1, 0.18f, 0.01f, -16f, 0.965f, true);
uBagItemPrefab prefabView = instance.GetComponent<uBagItemPrefab>();
if (prefabView != null)
{
Sprite icon = entry.icon != null ? entry.icon : fallbackItemIcon;
Color color = rarityColorConfig != null
? rarityColorConfig.GetColor(entry.rarity, fallbackColor)
: fallbackColor;
Sprite backgroundSprite = rarityColorConfig != null
? rarityColorConfig.GetBackgroundSprite(entry.rarity, fallbackBackgroundSprite)
: fallbackBackgroundSprite;
prefabView.Bind(this, icon, entry.countText, color, backgroundSprite, introductionHoverDelay);
viewEntries[prefabView] = entry;
}
return prefabView;
}
private void ClearSpawnedItems()
{
HideCurrentPreview();
viewEntries.Clear();
for (int i = itemParent != null ? itemParent.childCount - 1 : -1; i >= 0; i--)
{
Transform child = itemParent.GetChild(i);
if (child != null)
{
Destroy(child.gameObject);
}
}
spawnedItems.Clear();
UpdateEmptyState(false);
}
private void UpdateEmptyState(bool visible)
{
if (emptyText != null)
{
emptyText.gameObject.SetActive(visible);
if (visible)
{
emptyText.text = "暂无物品";
}
}
}
private void ApplyRightPanel(BagEntry entry)
{
if (entry == null)
{
ClearRightPanel();
return;
}
Sprite icon = entry.icon != null ? entry.icon : fallbackItemIcon;
Color color = rarityColorConfig != null
? rarityColorConfig.GetColor(entry.rarity, fallbackColor)
: fallbackColor;
Sprite backgroundSprite = rarityColorConfig != null
? rarityColorConfig.GetBackgroundSprite(entry.rarity, fallbackBackgroundSprite)
: fallbackBackgroundSprite;
if (btmImg != null)
{
btmImg.sprite = backgroundSprite;
btmImg.color = backgroundSprite != null ? Color.white : color;
}
if (bottom_rarityImg != null)
{
Sprite raritySprite = ResolveBottomRaritySprite(entry.rarity);
bottom_rarityImg.sprite = raritySprite;
bottom_rarityImg.enabled = raritySprite != null;
bottom_rarityImg.color = Color.white;
}
if (itemDprofile != null)
{
itemDprofile.sprite = icon;
itemDprofile.enabled = icon != null;
itemDprofile.color = Color.white;
}
if (itemAmount != null)
{
itemAmount.text = entry.amountText ?? string.Empty;
}
if (itemName != null)
{
itemName.supportRichText = false;
itemName.text = StripRichTextTags(entry.displayName);
itemName.color = useRarityColorForItemName ? color : Color.white;
}
if (itemUse != null)
{
itemUse.text = entry.usage ?? string.Empty;
}
if (itemUsageLegacy != null)
{
itemUsageLegacy.text = entry.usage ?? string.Empty;
}
if (itemDescription != null)
{
itemDescription.text = entry.description ?? string.Empty;
}
PlayRightPanelRefresh();
}
private void ClearRightPanel()
{
if (btmImg != null)
{
btmImg.sprite = fallbackBackgroundSprite;
btmImg.color = fallbackBackgroundSprite != null ? Color.white : fallbackColor;
}
if (bottom_rarityImg != null)
{
Sprite fallbackRaritySprite = GetBottomRarityFallbackSprite();
bottom_rarityImg.sprite = fallbackRaritySprite;
bottom_rarityImg.enabled = fallbackRaritySprite != null;
bottom_rarityImg.color = Color.white;
}
if (itemDprofile != null)
{
itemDprofile.sprite = fallbackItemIcon;
itemDprofile.enabled = fallbackItemIcon != null;
itemDprofile.color = Color.white;
}
if (itemAmount != null)
{
itemAmount.text = string.Empty;
}
if (itemName != null)
{
itemName.supportRichText = false;
itemName.text = string.Empty;
itemName.color = Color.white;
}
if (itemUse != null)
{
itemUse.text = string.Empty;
}
if (itemUsageLegacy != null)
{
itemUsageLegacy.text = string.Empty;
}
if (itemDescription != null)
{
itemDescription.text = string.Empty;
}
PlayRightPanelRefresh();
}
private void PlayRightPanelRefresh()
{
GameObject target = null;
if (btmImg != null) target = btmImg.gameObject;
if (target == null && itemDprofile != null) target = itemDprofile.gameObject;
if (target == null && itemName != null) target = itemName.gameObject;
UI_OrderedEntryAnimator.PlayRefresh(target, 0.14f, -6f, 0.99f, true);
}
private static int CompareEntries(BagEntry left, BagEntry right)
{
if (ReferenceEquals(left, right))
{
return 0;
}
if (left == null)
{
return 1;
}
if (right == null)
{
return -1;
}
int categoryResult = left.category.CompareTo(right.category);
if (categoryResult != 0)
{
return categoryResult;
}
int priorityResult = left.sortPriority.CompareTo(right.sortPriority);
if (priorityResult != 0)
{
return priorityResult;
}
return string.CompareOrdinal(left.displayName, right.displayName);
}
private static string StripRichTextTags(string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
bool insideTag = false;
System.Text.StringBuilder builder = null;
for (int i = 0; i < value.Length; i++)
{
char current = value[i];
if (current == '<')
{
if (!insideTag)
{
builder ??= new System.Text.StringBuilder(value.Length);
}
insideTag = true;
continue;
}
if (current == '>')
{
insideTag = false;
continue;
}
if (!insideTag)
{
if (builder != null)
{
builder.Append(current);
}
}
}
return builder != null ? builder.ToString() : value;
}
private Sprite ResolveBottomRaritySprite(ItemRarity rarity)
{
if (bottom_rarityImg_sprites != null)
{
if (bottom_rarityImg_sprites.Length >= System.Enum.GetValues(typeof(ItemRarity)).Length)
{
int enumIndex = (int)rarity;
if (enumIndex >= 0 && enumIndex < bottom_rarityImg_sprites.Length)
{
Sprite directSprite = bottom_rarityImg_sprites[enumIndex];
if (directSprite != null)
{
return directSprite;
}
}
}
else if (rarity != ItemRarity.None)
{
int compactIndex = (int)rarity - 1;
if (compactIndex >= 0 && compactIndex < bottom_rarityImg_sprites.Length)
{
Sprite compactSprite = bottom_rarityImg_sprites[compactIndex];
if (compactSprite != null)
{
return compactSprite;
}
}
}
}
return GetBottomRarityFallbackSprite();
}
private Sprite GetBottomRarityFallbackSprite()
{
if (rarityColorConfig != null)
{
Sprite noneSprite = rarityColorConfig.GetBackgroundSprite(ItemRarity.None, fallbackBackgroundSprite);
if (noneSprite != null)
{
return noneSprite;
}
}
return fallbackBackgroundSprite;
}
private expBottlesSO[] LoadExpBottleDefinitions()
{
var result = new List<expBottlesSO>();
#if UNITY_EDITOR
if (!Application.isPlaying && !string.IsNullOrEmpty(expBottleEditorPath))
{
string[] guids = AssetDatabase.FindAssets("t:expBottlesSO", new[] { expBottleEditorPath });
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
expBottlesSO asset = AssetDatabase.LoadAssetAtPath<expBottlesSO>(path);
if (asset != null)
{
result.Add(asset);
}
}
}
#endif
result.AddRange(Resources.LoadAll<expBottlesSO>(string.Empty));
return result.ToArray();
}
private growthMaterialSO[] LoadGrowthMaterialDefinitions()
{
var result = new List<growthMaterialSO>();
#if UNITY_EDITOR
if (!Application.isPlaying && !string.IsNullOrEmpty(growthMaterialEditorPath))
{
string[] guids = AssetDatabase.FindAssets("t:growthMaterialSO", new[] { growthMaterialEditorPath });
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
growthMaterialSO asset = AssetDatabase.LoadAssetAtPath<growthMaterialSO>(path);
if (asset != null)
{
result.Add(asset);
}
}
}
#endif
result.AddRange(Resources.LoadAll<growthMaterialSO>(string.Empty));
return result.ToArray();
}
private equipmentConsumableSO[] LoadEquipmentConsumableDefinitions()
{
var result = new List<equipmentConsumableSO>();
#if UNITY_EDITOR
if (!Application.isPlaying && !string.IsNullOrEmpty(equipmentConsumableEditorPath))
{
string[] guids = AssetDatabase.FindAssets("t:equipmentConsumableSO", new[] { equipmentConsumableEditorPath });
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
equipmentConsumableSO asset = AssetDatabase.LoadAssetAtPath<equipmentConsumableSO>(path);
if (asset != null)
{
result.Add(asset);
}
}
}
#endif
result.AddRange(Resources.LoadAll<equipmentConsumableSO>(string.Empty));
return result.ToArray();
}
private IEnumerable<notebook_faterType> LoadNotebookDefinitions()
{
var result = new List<notebook_faterType>();
var seen = new HashSet<int>();
#if UNITY_EDITOR
if (!Application.isPlaying && !string.IsNullOrEmpty(notebookEditorPath))
{
string[] guids = AssetDatabase.FindAssets("t:notebook_faterType", new[] { notebookEditorPath });
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
notebook_faterType asset = AssetDatabase.LoadAssetAtPath<notebook_faterType>(path);
if (asset != null && seen.Add(asset.class_id))
{
result.Add(asset);
}
}
}
#endif
if (!string.IsNullOrEmpty(notebookRuntimePath))
{
notebook_faterType[] resources = Resources.LoadAll<notebook_faterType>(notebookRuntimePath);
for (int i = 0; i < resources.Length; i++)
{
notebook_faterType asset = resources[i];
if (asset != null && seen.Add(asset.class_id))
{
result.Add(asset);
}
}
}
return result;
}
private IEnumerable<storeItemSO> LoadStoreItems()
{
var result = new List<storeItemSO>();
var seen = new HashSet<int>();
#if UNITY_EDITOR
if (!Application.isPlaying && !string.IsNullOrEmpty(storeItemEditorPath))
{
string[] guids = AssetDatabase.FindAssets("t:storeItemSO", new[] { storeItemEditorPath });
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
storeItemSO asset = AssetDatabase.LoadAssetAtPath<storeItemSO>(path);
if (asset != null && seen.Add(asset.itemID))
{
result.Add(asset);
}
}
}
#endif
if (!string.IsNullOrEmpty(storeItemRuntimePath))
{
storeItemSO[] resources = Resources.LoadAll<storeItemSO>(storeItemRuntimePath);
for (int i = 0; i < resources.Length; i++)
{
storeItemSO asset = resources[i];
if (asset != null && seen.Add(asset.itemID))
{
result.Add(asset);
}
}
}
return result;
}
private static expBottlesSO FindExpBottleDefinition(expBottlesSO[] definitions, ExpBottleKind kind)
{
if (definitions == null)
{
return null;
}
for (int i = 0; i < definitions.Length; i++)
{
if (definitions[i] != null && definitions[i].bottleKind == kind)
{
return definitions[i];
}
}
return null;
}
private static growthMaterialSO FindGrowthMaterialDefinition(growthMaterialSO[] definitions, DushMaterialKind kind)
{
if (definitions == null)
{
return null;
}
for (int i = 0; i < definitions.Length; i++)
{
if (definitions[i] != null && definitions[i].materialKind == kind)
{
return definitions[i];
}
}
return null;
}
private static equipmentConsumableSO FindEquipmentConsumableDefinition(equipmentConsumableSO[] definitions, EquipmentConsumableKind kind)
{
if (definitions == null)
{
return null;
}
for (int i = 0; i < definitions.Length; i++)
{
if (definitions[i] != null && definitions[i].consumableKind == kind)
{
return definitions[i];
}
}
return null;
}
private static storeItemSO FindStoreItemForExpBottle(List<storeItemSO> storeItems, ExpBottleKind kind)
{
if (storeItems == null)
{
return null;
}
for (int i = 0; i < storeItems.Count; i++)
{
storeItemSO item = storeItems[i];
if (item != null
&& item.itemType == storeItemSO.ItemType.consumable
&& item.associatedExpBottle != null
&& item.associatedExpBottle.bottleKind == kind)
{
return item;
}
}
return null;
}
private static storeItemSO FindStoreItemForGrowthMaterial(List<storeItemSO> storeItems, DushMaterialKind kind)
{
if (storeItems == null)
{
return null;
}
for (int i = 0; i < storeItems.Count; i++)
{
storeItemSO item = storeItems[i];
if (item != null
&& item.itemType == storeItemSO.ItemType.consumable
&& item.associatedGrowthMaterial != null
&& item.associatedGrowthMaterial.materialKind == kind)
{
return item;
}
}
return null;
}
private static storeItemSO FindStoreItemForEquipmentConsumable(List<storeItemSO> storeItems, EquipmentConsumableKind kind)
{
if (storeItems == null)
{
return null;
}
for (int i = 0; i < storeItems.Count; i++)
{
storeItemSO item = storeItems[i];
if (item != null
&& item.itemType == storeItemSO.ItemType.consumable
&& item.associatedEquipmentConsumable != null
&& item.associatedEquipmentConsumable.consumableKind == kind)
{
return item;
}
}
return null;
}
private static Sprite ResolvePreferredSprite(params Sprite[] sprites)
{
for (int i = 0; i < sprites.Length; i++)
{
if (sprites[i] != null)
{
return sprites[i];
}
}
return null;
}
private static string BuildPreferredDescription(params string[] candidates)
{
for (int i = 0; i < candidates.Length; i++)
{
if (!string.IsNullOrWhiteSpace(candidates[i]))
{
return candidates[i];
}
}
return string.Empty;
}
private Transform ResolveIntroductionParent(uBagItemPrefab source)
{
if (source == null)
{
return itemParent;
}
Canvas canvas = source.GetComponentInParent<Canvas>();
if (canvas != null)
{
return canvas.transform;
}
return itemParent;
}
private void PositionIntroduction(uBagItemPrefab source, GameObject introduction)
{
if (source == null || introduction == null)
{
return;
}
RectTransform sourceRect = source.transform as RectTransform;
RectTransform introductionRect = introduction.transform as RectTransform;
Canvas canvas = introduction.GetComponentInParent<Canvas>();
RectTransform canvasRect = canvas != null ? canvas.transform as RectTransform : null;
if (sourceRect == null || introductionRect == null || canvasRect == null)
{
return;
}
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(introductionRect);
Vector3[] corners = new Vector3[4];
sourceRect.GetWorldCorners(corners);
Vector3 sourceRightCenter = (corners[2] + corners[3]) * 0.5f;
Camera uiCamera = canvas != null ? canvas.worldCamera : null;
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(uiCamera, sourceRightCenter);
RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvasRect,
screenPoint,
uiCamera,
out Vector2 localPoint);
introductionRect.anchorMin = new Vector2(0.5f, 0.5f);
introductionRect.anchorMax = new Vector2(0.5f, 0.5f);
introductionRect.pivot = new Vector2(0f, 0.5f);
introductionRect.anchoredPosition = localPoint + new Vector2(introductionHorizontalOffset, 0f);
introductionRect.localScale = Vector3.one;
introductionRect.localRotation = Quaternion.identity;
}
private void HideCurrentPreview()
{
activePreviewSource = null;
if (spawnedIntroduction != null)
{
Destroy(spawnedIntroduction);
spawnedIntroduction = null;
}
}
}