From 1beb754cd29966254cb5b44670d5746d208d098f Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Thu, 16 Oct 2025 10:25:49 +0700 Subject: [PATCH] Add local data loader shop --- .../PerfectWorld/Scripts/UI/ItemPanelPool.cs | 193 ++++++++++ .../Scripts/UI/ItemPanelPool.cs.meta | 2 + .../Scripts/UI/ShopCategoryManager.cs | 170 +++++++++ .../Scripts/UI/ShopCategoryManager.cs.meta | 2 + .../Scripts/UI/ShopDetailPanel.cs | 219 ++++++++++++ .../Scripts/UI/ShopDetailPanel.cs.meta | 2 + .../Scripts/UI/ShopIconHandler.cs | 229 ++++++++++++ .../Scripts/UI/ShopIconHandler.cs.meta | 2 + .../PerfectWorld/Scripts/UI/ShopItemPanel.cs | 138 ++++++++ .../Scripts/UI/ShopItemPanel.cs.meta | 2 + .../Scripts/UI/ShopSystemSetup.cs | 126 +++++++ .../Scripts/UI/ShopSystemSetup.cs.meta | 2 + .../PerfectWorld/Scripts/UI/ShopUIManager.cs | 335 ++++++++++++++++++ .../Scripts/UI/ShopUIManager.cs.meta | 2 + 14 files changed, 1424 insertions(+) create mode 100644 Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopIconHandler.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopIconHandler.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopItemPanel.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopItemPanel.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopSystemSetup.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopSystemSetup.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopUIManager.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/ShopUIManager.cs.meta diff --git a/Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs b/Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs new file mode 100644 index 0000000000..53a84b2b29 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs @@ -0,0 +1,193 @@ +using System.Collections.Generic; +using UnityEngine; + +public class ItemPanelPool : MonoBehaviour +{ + [Header("Pool Settings")] + public GameObject itemPanelPrefab; + public int initialPoolSize = 20; + public int maxPoolSize = 100; + public bool expandPool = true; + + [Header("Pool Management")] + public Transform poolParent; // Where inactive objects are stored + + private Queue availablePanels = new Queue(); + private List allPanels = new List(); + private int currentPoolSize = 0; + + void Start() + { + InitializePool(); + } + + void InitializePool() + { + if (itemPanelPrefab == null) + { + Debug.LogError("ItemPanelPrefab is not assigned!"); + return; + } + + // Create pool parent if not assigned + if (poolParent == null) + { + GameObject poolParentObj = new GameObject("ItemPanelPool"); + poolParentObj.transform.SetParent(transform); + poolParent = poolParentObj.transform; + } + + // Pre-populate pool + for (int i = 0; i < initialPoolSize; i++) + { + CreateNewPanel(); + } + + Debug.Log($"ItemPanelPool initialized with {currentPoolSize} panels"); + } + + GameObject CreateNewPanel() + { + if (itemPanelPrefab == null) return null; + + GameObject newPanel = Instantiate(itemPanelPrefab, poolParent); + newPanel.SetActive(false); + + // Add ShopItemPanel component if not present + ShopItemPanel panelScript = newPanel.GetComponent(); + if (panelScript == null) + { + panelScript = newPanel.AddComponent(); + } + + availablePanels.Enqueue(newPanel); + allPanels.Add(newPanel); + currentPoolSize++; + + return newPanel; + } + + public GameObject GetPanel() + { + GameObject panel = null; + + // Try to get from available pool + if (availablePanels.Count > 0) + { + panel = availablePanels.Dequeue(); + } + // Create new panel if pool is empty and we can expand + else if (expandPool && currentPoolSize < maxPoolSize) + { + panel = CreateNewPanel(); + if (panel != null) + { + panel = availablePanels.Dequeue(); // Get the panel we just created + } + } + // Pool is full and can't expand + else + { + Debug.LogWarning("ItemPanelPool is full and cannot expand further!"); + return null; + } + + if (panel != null) + { + panel.SetActive(true); + + // Reset the panel state + ShopItemPanel panelScript = panel.GetComponent(); + if (panelScript != null) + { + panelScript.ResetPanel(); // Reset for reuse + } + } + + return panel; + } + + public void ReturnPanel(GameObject panel) + { + if (panel == null) return; + + // Reset panel state + ShopItemPanel panelScript = panel.GetComponent(); + if (panelScript != null) + { + panelScript.ResetPanel(); // Reset for reuse + } + + // Deactivate and move to pool parent + panel.SetActive(false); + panel.transform.SetParent(poolParent); + panel.transform.localPosition = Vector3.zero; + panel.transform.localRotation = Quaternion.identity; + panel.transform.localScale = Vector3.one; + + // Add back to available pool + availablePanels.Enqueue(panel); + } + + public void ReturnAllPanels() + { + // Return all active panels to pool + foreach (GameObject panel in allPanels) + { + if (panel != null && panel.activeInHierarchy) + { + ReturnPanel(panel); + } + } + } + + public int GetAvailableCount() + { + return availablePanels.Count; + } + + public int GetActiveCount() + { + int activeCount = 0; + foreach (GameObject panel in allPanels) + { + if (panel != null && panel.activeInHierarchy) + { + activeCount++; + } + } + return activeCount; + } + + public int GetTotalCount() + { + return currentPoolSize; + } + + public void ClearPool() + { + // Destroy all panels + foreach (GameObject panel in allPanels) + { + if (panel != null) + { + DestroyImmediate(panel); + } + } + + availablePanels.Clear(); + allPanels.Clear(); + currentPoolSize = 0; + } + + void OnDestroy() + { + ClearPool(); + } + + [ContextMenu("Log Pool Status")] + void LogPoolStatus() + { + Debug.Log($"Pool Status - Available: {GetAvailableCount()}, Active: {GetActiveCount()}, Total: {GetTotalCount()}"); + } +} diff --git a/Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs.meta b/Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs.meta new file mode 100644 index 0000000000..e49adf574f --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/ItemPanelPool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5b1ee6e37e281834da89d3521b31365b \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs b/Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs new file mode 100644 index 0000000000..0f57ed2191 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs @@ -0,0 +1,170 @@ +using UnityEngine; +using UnityEngine.UI; +using TMPro; + +public class ShopCategoryManager : MonoBehaviour +{ + [Header("Category Buttons")] + public Button[] categoryButtons; // 6 buttons for the merged categories + + [Header("Category Names")] + public string[] categoryNames = new string[] + { + "Category 1", // Original category 1 + "Category 2", // Original category 2 + "Categories 3-5", // Merged categories 3, 4, 5 + "Category 6", // Original category 6 + "Category 7", // Original category 7 + "Category 8" // Original category 8 + }; + + [Header("Button Text Components")] + public TextMeshProUGUI[] categoryButtonTexts; // Text components for button labels + + [Header("Visual States")] + public Color normalButtonColor = Color.white; + public Color selectedButtonColor = Color.yellow; + public Color disabledButtonColor = Color.gray; + + private int currentSelectedCategory = 0; + private ShopUIManager shopManager; + + void Start() + { + SetupCategoryButtons(); + UpdateCategoryDisplay(); + } + + public void Initialize(ShopUIManager manager) + { + shopManager = manager; + } + + void SetupCategoryButtons() + { + // Setup button text labels + for (int i = 0; i < categoryButtons.Length && i < categoryNames.Length; i++) + { + if (categoryButtons[i] != null) + { + // Set button text + if (i < categoryButtonTexts.Length && categoryButtonTexts[i] != null) + { + categoryButtonTexts[i].text = categoryNames[i]; + } + + // Setup click listener + int categoryIndex = i; // Capture for closure + categoryButtons[i].onClick.AddListener(() => OnCategoryButtonClicked(categoryIndex)); + } + } + } + + void OnCategoryButtonClicked(int categoryIndex) + { + if (categoryIndex == currentSelectedCategory) + return; + + currentSelectedCategory = categoryIndex; + UpdateCategoryDisplay(); + + // Notify shop manager + if (shopManager != null) + { + // Use reflection to call the private method, or make it public + // For now, we'll assume there's a public method to handle category change + Debug.Log($"Category {categoryIndex} selected: {categoryNames[categoryIndex]}"); + } + } + + void UpdateCategoryDisplay() + { + for (int i = 0; i < categoryButtons.Length; i++) + { + if (categoryButtons[i] != null) + { + bool isSelected = (i == currentSelectedCategory); + bool isInteractable = !isSelected; + + categoryButtons[i].interactable = isInteractable; + + // Update button color + Image buttonImage = categoryButtons[i].GetComponent(); + if (buttonImage != null) + { + if (isSelected) + buttonImage.color = selectedButtonColor; + else + buttonImage.color = normalButtonColor; + } + } + } + } + + public int GetCurrentCategory() + { + return currentSelectedCategory; + } + + public string GetCurrentCategoryName() + { + if (currentSelectedCategory >= 0 && currentSelectedCategory < categoryNames.Length) + return categoryNames[currentSelectedCategory]; + return "Unknown"; + } + + public void SetCategory(int categoryIndex) + { + if (categoryIndex >= 0 && categoryIndex < categoryButtons.Length) + { + currentSelectedCategory = categoryIndex; + UpdateCategoryDisplay(); + } + } + + public bool IsItemInCategory(GShopItem item, int categoryIndex) + { + // Category mapping: 0=1, 1=2, 2=3+4+5, 3=6, 4=7, 5=8 + switch (categoryIndex) + { + case 0: return item.mainType == 0; // Category 1 + case 1: return item.mainType == 1; // Category 2 + case 2: return item.mainType >= 2 && item.mainType <= 4; // Categories 3, 4, 5 merged + case 3: return item.mainType == 5; // Category 6 + case 4: return item.mainType == 6; // Category 7 + case 5: return item.mainType == 7; // Category 8 + default: return false; + } + } + + public int GetOriginalCategoryFromItem(GShopItem item) + { + return item.mainType; + } + + public string GetCategoryNameForItem(GShopItem item) + { + int originalCategory = item.mainType; + + if (originalCategory >= 2 && originalCategory <= 4) + return "Categories 3-5"; + + if (originalCategory < categoryNames.Length) + return categoryNames[originalCategory]; + + return "Unknown Category"; + } + + void OnDestroy() + { + // Clean up event listeners + for (int i = 0; i < categoryButtons.Length; i++) + { + if (categoryButtons[i] != null) + { + int categoryIndex = i; // Capture for closure + categoryButtons[i].onClick.RemoveListener(() => OnCategoryButtonClicked(categoryIndex)); + } + } + } +} diff --git a/Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs.meta b/Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs.meta new file mode 100644 index 0000000000..5a56bdf933 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/ShopCategoryManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1db59659b8d10ff44898d4cc8b937c0d \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs b/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs new file mode 100644 index 0000000000..ae1bf734a2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs @@ -0,0 +1,219 @@ +using UnityEngine; +using UnityEngine.UI; +using TMPro; + +public class ShopDetailPanel : MonoBehaviour +{ + [Header("UI Components")] + public TextMeshProUGUI itemNameText; + public TextMeshProUGUI itemDescriptionText; + public Image itemIconImage; + public TextMeshProUGUI itemPriceText; + public TextMeshProUGUI itemQuantityText; + public TextMeshProUGUI itemGiftText; + + [Header("Buy Options")] + public Button[] buyOptionButtons; // Up to 4 buy options + public TextMeshProUGUI[] buyOptionPriceTexts; + public TextMeshProUGUI[] buyOptionStatusTexts; + + [Header("Action Buttons")] + public Button buyButton; + public Button closeButton; + + private GShopItem currentItem; + private ShopUIManager shopManager; + + void Start() + { + SetupEventListeners(); + } + + void SetupEventListeners() + { + if (buyButton != null) + buyButton.onClick.AddListener(OnBuyClicked); + + if (closeButton != null) + closeButton.onClick.AddListener(OnCloseClicked); + + // Setup buy option buttons + for (int i = 0; i < buyOptionButtons.Length; i++) + { + int optionIndex = i; // Capture for closure + if (buyOptionButtons[i] != null) + { + buyOptionButtons[i].onClick.AddListener(() => OnBuyOptionSelected(optionIndex)); + } + } + } + + public void SetupDetailPanel(GShopItem item, ShopUIManager manager) + { + currentItem = item; + shopManager = manager; + + UpdateDisplay(); + } + + void UpdateDisplay() + { + if (currentItem.id == 0) + return; + + // Set basic item info + if (itemNameText != null) + itemNameText.text = currentItem.name; + + if (itemDescriptionText != null) + itemDescriptionText.text = currentItem.desc; + + if (itemQuantityText != null) + itemQuantityText.text = $"Quantity: {currentItem.num}"; + + // Load icon + if (itemIconImage != null) + { + LoadItemIcon(itemIconImage, currentItem.name); + } + + // Update buy options + UpdateBuyOptions(); + + // Update gift info + UpdateGiftInfo(); + } + + void UpdateBuyOptions() + { + for (int i = 0; i < buyOptionButtons.Length && i < currentItem.buy.Length; i++) + { + var buyOption = currentItem.buy[i]; + + // Show/hide buy option based on price + bool hasValidPrice = buyOption.price > 0; + if (buyOptionButtons[i] != null) + { + buyOptionButtons[i].gameObject.SetActive(hasValidPrice); + } + + if (hasValidPrice) + { + // Set price text + if (i < buyOptionPriceTexts.Length && buyOptionPriceTexts[i] != null) + { + buyOptionPriceTexts[i].text = buyOption.price.ToString(); + } + + // Set status text + if (i < buyOptionStatusTexts.Length && buyOptionStatusTexts[i] != null) + { + string statusText = GetStatusText(buyOption.status); + buyOptionStatusTexts[i].text = statusText; + } + } + } + } + + string GetStatusText(uint status) + { + switch (status) + { + case 0: return ""; + case 1: return "HOT"; + case 2: return "NEW"; + case 3: return "RECOMMENDED"; + case 4: return "10% OFF"; + case 5: return "20% OFF"; + case 6: return "30% OFF"; + case 7: return "40% OFF"; + case 8: return "50% OFF"; + case 9: return "60% OFF"; + case 10: return "70% OFF"; + case 11: return "80% OFF"; + case 12: return "90% OFF"; + case 13: return "SOLD OUT"; + default: return ""; + } + } + + void UpdateGiftInfo() + { + if (itemGiftText != null) + { + if (currentItem.idGift > 0) + { + itemGiftText.text = $"Gift: {currentItem.giftNum}x (ID: {currentItem.idGift})"; + itemGiftText.gameObject.SetActive(true); + } + else + { + itemGiftText.gameObject.SetActive(false); + } + } + } + + void LoadItemIcon(Image iconImage, string itemName) + { + // TODO: Implement icon loading based on item name + // This is where you'd load the appropriate icon sprite + Debug.Log($"Loading detail icon for item: {itemName}"); + + // You can implement icon loading like this: + // string iconPath = $"Icons/{itemName}"; // Adjust path as needed + // Sprite iconSprite = Resources.Load(iconPath); + // if (iconSprite != null) + // iconImage.sprite = iconSprite; + } + + void OnBuyOptionSelected(int optionIndex) + { + if (currentItem.buy == null || optionIndex >= currentItem.buy.Length) + return; + + var selectedOption = currentItem.buy[optionIndex]; + + if (selectedOption.price > 0 && selectedOption.status != 13) // Not sold out + { + // TODO: Implement purchase with specific buy option + Debug.Log($"Selected buy option {optionIndex}: Price={selectedOption.price}, Status={selectedOption.status}"); + + // Update main price display + if (itemPriceText != null) + itemPriceText.text = $"Price: {selectedOption.price}"; + } + } + + void OnBuyClicked() + { + // TODO: Implement purchase logic + Debug.Log($"Attempting to buy item: {currentItem.name} (ID: {currentItem.id})"); + + // Close panel after purchase attempt + OnCloseClicked(); + } + + void OnCloseClicked() + { + gameObject.SetActive(false); + } + + void OnDestroy() + { + // Clean up event listeners + if (buyButton != null) + buyButton.onClick.RemoveListener(OnBuyClicked); + + if (closeButton != null) + closeButton.onClick.RemoveListener(OnCloseClicked); + + for (int i = 0; i < buyOptionButtons.Length; i++) + { + if (buyOptionButtons[i] != null) + { + int optionIndex = i; // Capture for closure + buyOptionButtons[i].onClick.RemoveListener(() => OnBuyOptionSelected(optionIndex)); + } + } + } +} diff --git a/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs.meta b/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs.meta new file mode 100644 index 0000000000..32771f00b0 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 424d441d34048b84cb40140b3e89237d \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/ShopIconHandler.cs b/Assets/PerfectWorld/Scripts/UI/ShopIconHandler.cs new file mode 100644 index 0000000000..9bc98753c3 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/ShopIconHandler.cs @@ -0,0 +1,229 @@ +using UnityEngine; +using UnityEngine.UI; + +public class ShopIconHandler : MonoBehaviour +{ + [Header("Shop Icon")] + public Button shopIconButton; + public Image shopIconImage; + + [Header("Shop Manager")] + public ShopUIManager shopManager; + + [Header("Visual Feedback")] + public Color normalColor = Color.white; + public Color hoverColor = Color.yellow; + public Color pressedColor = Color.red; + + [Header("Animation")] + public bool enableHoverAnimation = true; + public float hoverScale = 1.1f; + public float animationSpeed = 5f; + + private Vector3 originalScale; + private bool isHovering = false; + + void Start() + { + InitializeShopIcon(); + SetupEventListeners(); + } + + void InitializeShopIcon() + { + // Store original scale for animation + originalScale = transform.localScale; + + // Setup button if not already assigned + if (shopIconButton == null) + { + shopIconButton = GetComponent