528 lines
17 KiB
C#
528 lines
17 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
|
|
public class ShopUIManager : MonoBehaviour
|
|
{
|
|
[Header("Shop Data")]
|
|
public GShopLoader shopLoader;
|
|
|
|
[Header("UI Panels")]
|
|
public GameObject shopMainPanel;
|
|
public GameObject shopDetailPanel;
|
|
public GameObject subTypeShopPanel;
|
|
[Header("Item Display")]
|
|
public Transform itemContainer; // Parent for item panels
|
|
public GameObject itemPanelPrefab; // Prefab for individual item panels
|
|
|
|
[Header("Object Pooling")]
|
|
public ItemPanelPool itemPanelPool; // Object pool for item panels
|
|
public bool useObjectPooling = true;
|
|
|
|
[Header("Category Buttons")]
|
|
public Button[] categoryButtons; // 6 buttons for categories
|
|
|
|
[Header("Detail Panel Components")]
|
|
public TextMeshProUGUI detailItemName;
|
|
public TextMeshProUGUI detailItemDescription;
|
|
public Image detailItemIcon;
|
|
public TextMeshProUGUI detailItemPrice;
|
|
public TextMeshProUGUI detailItemQuantity;
|
|
public Button buyButton;
|
|
public Button closeDetailButton;
|
|
public ScrollRect detailScrollRect;
|
|
|
|
[Header("Main Panel Components")]
|
|
public Button closeShopButton;
|
|
|
|
private List<GameObject> currentItemPanels = new List<GameObject>();
|
|
private GShopItem selectedItem;
|
|
private int currentCategory = 0; // 0-5 for the 6 merged categories
|
|
[Header("Sub Type Shop")]
|
|
//-1 means all sub types
|
|
public SubTypeShop subTypeShop;
|
|
private int currentSubType = -1;
|
|
void Start()
|
|
{
|
|
InitializeUI();
|
|
SetupEventListeners();
|
|
InitializePool();
|
|
}
|
|
|
|
void InitializePool()
|
|
{
|
|
if (useObjectPooling && itemPanelPool == null)
|
|
{
|
|
// Auto-create pool if not assigned
|
|
GameObject poolObj = new GameObject("ItemPanelPool");
|
|
poolObj.transform.SetParent(transform);
|
|
itemPanelPool = poolObj.AddComponent<ItemPanelPool>();
|
|
itemPanelPool.itemPanelPrefab = itemPanelPrefab;
|
|
}
|
|
}
|
|
|
|
void InitializeUI()
|
|
{
|
|
// Initially hide panels
|
|
if (shopMainPanel != null)
|
|
shopMainPanel.SetActive(false);
|
|
if (shopDetailPanel != null)
|
|
shopDetailPanel.SetActive(false);
|
|
if (subTypeShop != null)
|
|
subTypeShopPanel.SetActive(false);
|
|
}
|
|
|
|
void SetupEventListeners()
|
|
{
|
|
// Setup category buttons
|
|
for (int i = 0; i < categoryButtons.Length && i < 6; i++)
|
|
{
|
|
int categoryIndex = i; // Capture for closure
|
|
categoryButtons[i].onClick.AddListener(() => OnCategorySelected(categoryIndex));
|
|
}
|
|
|
|
// Setup detail panel buttons
|
|
if (buyButton != null)
|
|
buyButton.onClick.AddListener(OnBuyItem);
|
|
if (closeDetailButton != null)
|
|
closeDetailButton.onClick.AddListener(CloseDetailPanel);
|
|
|
|
// Setup main panel close button
|
|
if (closeShopButton != null)
|
|
closeShopButton.onClick.AddListener(CloseShop);
|
|
}
|
|
|
|
public void OpenShop()
|
|
{
|
|
if (shopMainPanel != null)
|
|
{
|
|
OnCategorySelected(0);
|
|
RefreshShopDisplay();
|
|
}
|
|
}
|
|
|
|
public void CloseShop()
|
|
{
|
|
if (shopMainPanel != null)
|
|
shopMainPanel.SetActive(false);
|
|
if (shopDetailPanel != null)
|
|
shopDetailPanel.SetActive(false);
|
|
}
|
|
|
|
void OnCategorySelected(int categoryIndex)
|
|
{
|
|
if (categoryIndex == currentCategory)
|
|
return;
|
|
|
|
float startTime = Time.realtimeSinceStartup;
|
|
|
|
currentCategory = categoryIndex;
|
|
RefreshShopDisplay();
|
|
|
|
// Update button states
|
|
for (int i = 0; i < categoryButtons.Length; i++)
|
|
{
|
|
if (categoryButtons[i] != null)
|
|
{
|
|
categoryButtons[i].interactable = (i != categoryIndex);
|
|
}
|
|
}
|
|
|
|
// Log performance
|
|
float switchTime = Time.realtimeSinceStartup - startTime;
|
|
|
|
Debug.Log($"Category switch to {categoryIndex} completed in {switchTime * 1000f:F2}ms");
|
|
}
|
|
|
|
// Allow external components (e.g., ShopCategoryManager) to switch category
|
|
public void SetCategoryIndex(int categoryIndex)
|
|
{
|
|
OnCategorySelected(categoryIndex);
|
|
}
|
|
|
|
public void RefreshShopDisplay()
|
|
{
|
|
// Return all current panels to pool
|
|
ReturnAllPanelsToPool();
|
|
|
|
if (shopLoader == null || shopLoader.primaryShop == null)
|
|
{
|
|
Debug.LogWarning("ShopLoader or primary shop data not available");
|
|
return;
|
|
}
|
|
|
|
// Get items for current category
|
|
List<GShopItem> categoryItems = GetItemsForCategory(currentCategory);
|
|
// Create item panels using pooling
|
|
int siblingIndexCounter = 0;
|
|
foreach (GShopItem item in categoryItems)
|
|
{
|
|
CreateItemPanelFromPool(item, siblingIndexCounter);
|
|
siblingIndexCounter++;
|
|
}
|
|
|
|
List<string> subTypeNames = GetSubTypeNamesForCategory(currentCategory);
|
|
currentSubType = -1;
|
|
subTypeShopPanel.SetActive(subTypeNames.Count > 0);
|
|
if (subTypeShop != null)
|
|
subTypeShop.GenerateSubTypeShop(subTypeNames, this);
|
|
|
|
|
|
|
|
}
|
|
|
|
public void SetSubTypeFilter(int subTypeIndex)
|
|
{
|
|
currentSubType = subTypeIndex;
|
|
ApplySubTypeFilter();
|
|
}
|
|
|
|
public void SetSubTypeFilterByName(string subTypeName)
|
|
{
|
|
int subTypeIndex = GetSubTypeIndexFromName(subTypeName);
|
|
SetSubTypeFilter(subTypeIndex);
|
|
}
|
|
|
|
public void ApplySubTypeFilter()
|
|
{
|
|
ReturnAllPanelsToPool();
|
|
|
|
if (shopLoader == null || shopLoader.primaryShop == null)
|
|
{
|
|
Debug.LogWarning("ShopLoader or primary shop data not available");
|
|
return;
|
|
}
|
|
if(currentSubType == -1)
|
|
{
|
|
RefreshShopDisplay();
|
|
return;
|
|
}
|
|
List<GShopItem> categoryItems = GetItemsForCategory(currentCategory, currentSubType);
|
|
int siblingIndexCounter = 0;
|
|
foreach (GShopItem item in categoryItems)
|
|
{
|
|
CreateItemPanelFromPool(item, siblingIndexCounter);
|
|
siblingIndexCounter++;
|
|
}
|
|
}
|
|
List<GShopItem> GetItemsForCategory(int categoryIndex, int subTypeIndex=-1)
|
|
{
|
|
List<GShopItem> items = new List<GShopItem>();
|
|
|
|
if (shopLoader.primaryShop.items == null)
|
|
return items;
|
|
|
|
foreach (GShopItem item in shopLoader.primaryShop.items)
|
|
{
|
|
if (IsItemInCategory(item, categoryIndex))
|
|
{
|
|
if(IsItemInSubType(item, subTypeIndex))
|
|
{
|
|
items.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
return items;
|
|
}
|
|
List<string> GetSubTypeNamesForCategory(int categoryIndex)
|
|
{
|
|
List<string> subTypeNames = new List<string>();
|
|
|
|
if (shopLoader?.primaryShop?.mainTypes == null)
|
|
return subTypeNames;
|
|
|
|
// Use the same mapping as IsItemInCategory: 0=0, 1=2, 2=5, 3=1+3+4, 4=6, 5=7
|
|
List<int> mainTypeIndices = new List<int>();
|
|
switch (categoryIndex)
|
|
{
|
|
case 0: mainTypeIndices.Add(0); break;
|
|
case 1: mainTypeIndices.Add(2); break;
|
|
case 2: mainTypeIndices.Add(5); break;
|
|
case 3: mainTypeIndices.Add(1); mainTypeIndices.Add(3); mainTypeIndices.Add(4); break;
|
|
case 4: mainTypeIndices.Add(6); break;
|
|
case 5: mainTypeIndices.Add(7); break;
|
|
default: break;
|
|
}
|
|
|
|
foreach (int mainTypeIndex in mainTypeIndices)
|
|
{
|
|
if (mainTypeIndex >= 0 && mainTypeIndex < shopLoader.primaryShop.mainTypes.Count)
|
|
{
|
|
foreach (string subType in shopLoader.primaryShop.mainTypes[mainTypeIndex].subTypes)
|
|
{
|
|
if (!subTypeNames.Contains(subType))
|
|
{
|
|
subTypeNames.Add(subType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return subTypeNames;
|
|
}
|
|
bool IsItemInCategory(GShopItem item, int categoryIndex)
|
|
{
|
|
// Category mapping: 0=1, 1=2, 2=1+3+4, 3=6, 4=7, 5=8
|
|
switch (categoryIndex)
|
|
{
|
|
case 0: return item.mainType == 0; // Category 1
|
|
case 1: return item.mainType == 2; // Category 2
|
|
case 2: return item.mainType == 5; // Categories 1, 3, 4 merged
|
|
case 3: return item.mainType == 1 || item.mainType == 3 || item.mainType == 4; // Category 6
|
|
case 4: return item.mainType == 6; // Category 7
|
|
case 5: return item.mainType == 7; // Category 8
|
|
default: return false;
|
|
}
|
|
}
|
|
bool IsItemInSubType(GShopItem item, int combinedSubTypeIndex)
|
|
{
|
|
if(combinedSubTypeIndex == -1)
|
|
return true;
|
|
|
|
// Get the subType name for this item's mainType and subType index
|
|
if (shopLoader?.primaryShop?.mainTypes == null)
|
|
return false;
|
|
|
|
if (item.mainType < 0 || item.mainType >= shopLoader.primaryShop.mainTypes.Count)
|
|
return false;
|
|
|
|
var mainType = shopLoader.primaryShop.mainTypes[item.mainType];
|
|
if (item.subType < 0 || item.subType >= mainType.subTypes.Count)
|
|
return false;
|
|
|
|
string itemSubTypeName = mainType.subTypes[item.subType];
|
|
|
|
// Get the subType name for the filter index
|
|
string filterSubTypeName = GetSubTypeNameFromIndex(combinedSubTypeIndex);
|
|
|
|
return itemSubTypeName == filterSubTypeName;
|
|
}
|
|
|
|
int GetSubTypeIndexFromName(string subTypeName)
|
|
{
|
|
if (shopLoader?.primaryShop?.mainTypes == null || string.IsNullOrEmpty(subTypeName))
|
|
return -1;
|
|
|
|
// Find the subType name in the current category's mainTypes
|
|
List<int> mainTypeIndices = new List<int>();
|
|
switch (currentCategory)
|
|
{
|
|
case 0: mainTypeIndices.Add(0); break;
|
|
case 1: mainTypeIndices.Add(2); break;
|
|
case 2: mainTypeIndices.Add(5); break;
|
|
case 3: mainTypeIndices.Add(1); mainTypeIndices.Add(3); mainTypeIndices.Add(4); break;
|
|
case 4: mainTypeIndices.Add(6); break;
|
|
case 5: mainTypeIndices.Add(7); break;
|
|
default: break;
|
|
}
|
|
|
|
// Find the first matching subType across all relevant mainTypes
|
|
// We use a combined index: mainTypeIndex * 1000 + subTypeIndex
|
|
// This allows us to uniquely identify subTypes across different mainTypes
|
|
foreach (int mainTypeIndex in mainTypeIndices)
|
|
{
|
|
if (mainTypeIndex >= 0 && mainTypeIndex < shopLoader.primaryShop.mainTypes.Count)
|
|
{
|
|
var mainType = shopLoader.primaryShop.mainTypes[mainTypeIndex];
|
|
for (int i = 0; i < mainType.subTypes.Count; i++)
|
|
{
|
|
if (mainType.subTypes[i] == subTypeName)
|
|
{
|
|
// Return a combined index: mainTypeIndex * 1000 + subTypeIndex
|
|
return mainTypeIndex * 1000 + i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
string GetSubTypeNameFromIndex(int combinedIndex)
|
|
{
|
|
if (combinedIndex < 0 || shopLoader?.primaryShop?.mainTypes == null)
|
|
return null;
|
|
|
|
int mainTypeIndex = combinedIndex / 1000;
|
|
int subTypeIndex = combinedIndex % 1000;
|
|
|
|
if (mainTypeIndex >= 0 && mainTypeIndex < shopLoader.primaryShop.mainTypes.Count)
|
|
{
|
|
var mainType = shopLoader.primaryShop.mainTypes[mainTypeIndex];
|
|
if (subTypeIndex >= 0 && subTypeIndex < mainType.subTypes.Count)
|
|
{
|
|
return mainType.subTypes[subTypeIndex];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
void CreateItemPanelFromPool(GShopItem item, int targetSiblingIndex)
|
|
{
|
|
GameObject itemPanel = null;
|
|
|
|
if (useObjectPooling && itemPanelPool != null)
|
|
{
|
|
// Get panel from pool
|
|
itemPanel = itemPanelPool.GetPanel();
|
|
}
|
|
else
|
|
{
|
|
// Fallback to instantiate if pooling disabled
|
|
if (itemPanelPrefab != null && itemContainer != null)
|
|
{
|
|
itemPanel = Instantiate(itemPanelPrefab, itemContainer);
|
|
}
|
|
}
|
|
|
|
if (itemPanel != null && itemContainer != null)
|
|
{
|
|
// Set parent and position
|
|
itemPanel.transform.SetParent(itemContainer, false);
|
|
itemPanel.transform.localScale = Vector3.one;
|
|
// Ensure deterministic ordering regardless of pool retrieval order
|
|
itemPanel.transform.SetSiblingIndex(targetSiblingIndex);
|
|
|
|
// Setup the panel
|
|
ShopItemPanel itemPanelScript = itemPanel.GetComponent<ShopItemPanel>();
|
|
if (itemPanelScript != null)
|
|
{
|
|
itemPanelScript.SetupItem(item, this);
|
|
}
|
|
|
|
currentItemPanels.Add(itemPanel);
|
|
}
|
|
}
|
|
|
|
void ReturnAllPanelsToPool()
|
|
{
|
|
if (useObjectPooling && itemPanelPool != null)
|
|
{
|
|
// Return all panels to pool
|
|
foreach (GameObject panel in currentItemPanels)
|
|
{
|
|
if (panel != null)
|
|
{
|
|
itemPanelPool.ReturnPanel(panel);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Destroy panels if not using pooling
|
|
foreach (GameObject panel in currentItemPanels)
|
|
{
|
|
if (panel != null)
|
|
Destroy(panel);
|
|
}
|
|
}
|
|
|
|
currentItemPanels.Clear();
|
|
}
|
|
|
|
public void ShowItemDetail(GShopItem item)
|
|
{
|
|
selectedItem = item;
|
|
Debug.Log($"$ Local ID of selected item: {item.localId}");
|
|
if (shopDetailPanel != null)
|
|
{
|
|
shopDetailPanel.SetActive(true);
|
|
|
|
// Get the ShopDetailPanel component and call its SetupDetailPanel method
|
|
ShopDetailPanel detailPanelScript = shopDetailPanel.GetComponent<ShopDetailPanel>();
|
|
if (detailPanelScript != null)
|
|
{
|
|
detailPanelScript.SetupDetailPanel(item, this);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("ShopDetailPanel script not found on shopDetailPanel GameObject!");
|
|
// Fallback to the old method
|
|
UpdateDetailPanel(item);
|
|
detailScrollRect.verticalNormalizedPosition = 1f;
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(detailScrollRect.content);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateDetailPanel(GShopItem item)
|
|
{
|
|
if (detailItemName != null)
|
|
detailItemName.text = item.name;
|
|
|
|
if (detailItemDescription != null)
|
|
detailItemDescription.text = item.desc;
|
|
|
|
if (detailItemQuantity != null)
|
|
detailItemQuantity.text = $"Quantity: {item.num}";
|
|
|
|
// Find the best buy option (first non-zero price)
|
|
uint bestPrice = 0;
|
|
for (int i = 0; i < item.buy.Length; i++)
|
|
{
|
|
if (item.buy[i].price > 0)
|
|
{
|
|
bestPrice = item.buy[i].price;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (detailItemPrice != null)
|
|
detailItemPrice.text = $"Price: {bestPrice}";
|
|
Debug.Log($"Detail item price: {detailItemPrice.text}, detail item price != null: {detailItemPrice != null}");
|
|
// Load icon based on item name (you'll need to implement icon loading)
|
|
if (detailItemIcon != null)
|
|
{
|
|
LoadItemIcon(detailItemIcon, item.name);
|
|
}
|
|
}
|
|
|
|
void LoadItemIcon(Image iconImage, string itemName)
|
|
{
|
|
// TODO: Implement icon loading based on item name
|
|
// This is where you'd load the appropriate icon sprite
|
|
// For now, we'll leave it empty for you to implement
|
|
Debug.Log($"Loading icon for item: {itemName}");
|
|
}
|
|
|
|
void CloseDetailPanel()
|
|
{
|
|
if (shopDetailPanel != null)
|
|
shopDetailPanel.SetActive(false);
|
|
}
|
|
|
|
void OnBuyItem()
|
|
{
|
|
if (selectedItem.id == 0)
|
|
return;
|
|
|
|
// TODO: Implement purchase logic
|
|
Debug.Log($"Attempting to buy item: {selectedItem.name} (ID: {selectedItem.id})");
|
|
|
|
// Close detail panel after purchase attempt
|
|
CloseDetailPanel();
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
// Clean up pooled objects
|
|
if (useObjectPooling && itemPanelPool != null)
|
|
{
|
|
itemPanelPool.ReturnAllPanels();
|
|
}
|
|
else
|
|
{
|
|
// Fallback cleanup
|
|
foreach (GameObject panel in currentItemPanels)
|
|
{
|
|
if (panel != null)
|
|
Destroy(panel);
|
|
}
|
|
}
|
|
|
|
currentItemPanels.Clear();
|
|
}
|
|
}
|