From 9e65e01b7cf573890cb8e350ad2cbd431a8bbdb2 Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Wed, 24 Dec 2025 15:06:43 +0700 Subject: [PATCH] Reapply "Merge pull request 'Update Prefabs Inventory UI, add drag-and-drop equipment functionality.' (#77) from feature/color-text into develop" This reverts commit 2d6a15eb401df78775969594ff7777ab2f2de630. --- .../Scripts/Managers/EC_InventoryUI.cs | 674 +++++++++------ Assets/Prefabs/UI/InventoryUI.prefab | 784 +++++++++++++++++- Assets/Prefabs/UI/SelectCharacterUI.prefab | 190 ++++- 3 files changed, 1366 insertions(+), 282 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs index 70d4966f54..4107c5d1fb 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs @@ -1,16 +1,17 @@ -using System; -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.UI; -using System.Reflection; -using System.Text; -using CSNetwork.GPDataType; -using BrewMonster.Network; using BrewMonster; using BrewMonster.Common; +using BrewMonster.Network; +using BrewMonster.Scripts; +using CSNetwork.GPDataType; using ModelRenderer.Scripts.GameData; using PerfectWorld.Scripts.Managers; -using BrewMonster.Scripts; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; namespace BrewMonster.Scripts.Managers { @@ -23,7 +24,7 @@ namespace BrewMonster.Scripts.Managers [Header("Detail Panel (assign in Inspector)")] [SerializeField] private GameObject detailPanelRoot; - [SerializeField] private Vector2 detailPanelOffset = new Vector2(20f, 0f); + [SerializeField] private Vector2 detailPanelOffset = new Vector2(20f, 0f); [SerializeField] private bool hideDetailOnStart = true; [SerializeField] private TextOutlet nameText; [SerializeField] private TextOutlet descriptionText; @@ -35,35 +36,41 @@ namespace BrewMonster.Scripts.Managers [SerializeField] private bool autoRefresh = true; [SerializeField] private float refreshInterval = 1.0f; [SerializeField] private bool showEquipmentDetails = true; - - [Header("Money UI (assign any text fields to mirror money amount)")] - [SerializeField] private List moneyTextsLegacy = new List(); - [SerializeField] private List moneyTextsTMP = new List(); - - [Header("Cash UI (assign any text fields to mirror cash amount)")] - [SerializeField] private List cashTextsLegacy = new List(); - [SerializeField] private List cashTextsTMP = new List(); + + [Header("Money UI (assign any text fields to mirror money amount)")] + [SerializeField] private List moneyTextsLegacy = new List(); + [SerializeField] private List moneyTextsTMP = new List(); + + [Header("Cash UI (assign any text fields to mirror cash amount)")] + [SerializeField] private List cashTextsLegacy = new List(); + [SerializeField] private List cashTextsTMP = new List(); private float lastRefreshTime; - // Pending currency cache for when UI is not yet active - private static bool s_hasPendingMoney; - private static ulong s_pendingMoneyAmount; - private static ulong s_pendingMoneyMaxAmount; - private static bool s_hasPendingCash; - private static int s_pendingCashAmount; + // Pending currency cache for when UI is not yet active + private static bool s_hasPendingMoney; + private static ulong s_pendingMoneyAmount; + private static ulong s_pendingMoneyMaxAmount; + private static bool s_hasPendingCash; + private static int s_pendingCashAmount; - // Flags to prevent log spam for extended description warnings - // 防止扩展描述警告日志刷屏的标志 - private static bool m_HasLoggedExtDescNull = false; - private static bool m_HasLoggedExtDescNotInit = false; - private static bool m_HasLoggedExtDescError = false; + // Flags to prevent log spam for extended description warnings + // 防止扩展描述警告日志刷屏的标志 + private static bool m_HasLoggedExtDescNull = false; + private static bool m_HasLoggedExtDescNotInit = false; + private static bool m_HasLoggedExtDescError = false; private InventoryModel model; private InventoryView view; - + + // Drag-and-drop state + private int draggedItemSourceSlot = -1; + private byte draggedItemSourcePackage = 0; + [SerializeField] private Image currentDragImage; + private bool isDragging = false; + // === Text Formatting Methods === - + /// /// Format text for TextMeshPro components with rich text support /// @@ -73,7 +80,7 @@ namespace BrewMonster.Scripts.Managers { return EC_Utility.FormatForTextMeshPro(text); } - + // Current selected item for equip/unequip operations private byte currentSelectedPackage; private int currentSelectedSlot; @@ -88,6 +95,20 @@ namespace BrewMonster.Scripts.Managers { model = new InventoryModel(); view = new InventoryView(); + + if (currentDragImage == null) + { + var canvas = GetComponentInParent(); + if (canvas == null) + { + canvas = FindAnyObjectByType(); + } + var go = new GameObject("DragImage", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image)); + go.transform.SetParent(canvas.transform, false); + currentDragImage = go.GetComponent(); + currentDragImage.raycastTarget = false; + currentDragImage.gameObject.SetActive(false); + } } private void Start() @@ -97,15 +118,15 @@ namespace BrewMonster.Scripts.Managers { ShowDetailPanel(false); } - // Apply any pending currency values captured before the UI became active - ApplyPendingCurrency(); + // Apply any pending currency values captured before the UI became active + ApplyPendingCurrency(); } - private void OnEnable() - { - // Ensure cached values are pushed when the UI is enabled - ApplyPendingCurrency(); - } + private void OnEnable() + { + // Ensure cached values are pushed when the UI is enabled + ApplyPendingCurrency(); + } private void Update() { @@ -128,109 +149,109 @@ namespace BrewMonster.Scripts.Managers view.RenderPackage(fashionPackButtons, fshItems, PKG_FASHION, OnInventoryButtonClicked, GetDisplayTextForItem); } - /// - /// Update all configured money text components with the current amount. - /// Call this when GET_OWN_MONEY arrives. - /// - public void UpdateMoney(ulong amount, ulong maxAmount) - { - string text = amount.ToString(); - if (moneyTextsLegacy != null) - { - for (int i = 0; i < moneyTextsLegacy.Count; i++) - { - var t = moneyTextsLegacy[i]; - if (t != null) t.text = text; - } - } - if (moneyTextsTMP != null) - { - for (int i = 0; i < moneyTextsTMP.Count; i++) - { - var t = moneyTextsTMP[i]; - if (t != null) t.text = text; - } - } - } - - /// - /// Update all configured cash text components with the current boutique cash amount. - /// Call this when PLAYER_CASH arrives. - /// - public void UpdateCash(int amount) - { - string text = amount.ToString(); - if (cashTextsLegacy != null) - { - for (int i = 0; i < cashTextsLegacy.Count; i++) - { - var t = cashTextsLegacy[i]; - if (t != null) t.text = text; - } - } - if (cashTextsTMP != null) - { - for (int i = 0; i < cashTextsTMP.Count; i++) - { - var t = cashTextsTMP[i]; - if (t != null) t.text = text; - } - } - } + /// + /// Update all configured money text components with the current amount. + /// Call this when GET_OWN_MONEY arrives. + /// + public void UpdateMoney(ulong amount, ulong maxAmount) + { + string text = amount.ToString(); + if (moneyTextsLegacy != null) + { + for (int i = 0; i < moneyTextsLegacy.Count; i++) + { + var t = moneyTextsLegacy[i]; + if (t != null) t.text = text; + } + } + if (moneyTextsTMP != null) + { + for (int i = 0; i < moneyTextsTMP.Count; i++) + { + var t = moneyTextsTMP[i]; + if (t != null) t.text = text; + } + } + } - // Public static entry points to cache values when UI is unavailable - public static void CacheMoney(ulong amount, ulong maxAmount) - { - s_pendingMoneyAmount = amount; - s_pendingMoneyMaxAmount = maxAmount; - s_hasPendingMoney = true; - // If an instance exists (even inactive), push immediately so the value is ready - var all = Resources.FindObjectsOfTypeAll(); - if (all != null) - { - for (int i = 0; i < all.Length; i++) - { - var ui = all[i]; - if (ui != null && ui.gameObject.scene.IsValid()) - { - ui.ApplyPendingCurrency(); - break; - } - } - } - } + /// + /// Update all configured cash text components with the current boutique cash amount. + /// Call this when PLAYER_CASH arrives. + /// + public void UpdateCash(int amount) + { + string text = amount.ToString(); + if (cashTextsLegacy != null) + { + for (int i = 0; i < cashTextsLegacy.Count; i++) + { + var t = cashTextsLegacy[i]; + if (t != null) t.text = text; + } + } + if (cashTextsTMP != null) + { + for (int i = 0; i < cashTextsTMP.Count; i++) + { + var t = cashTextsTMP[i]; + if (t != null) t.text = text; + } + } + } - public static void CacheCash(int amount) - { - s_pendingCashAmount = amount; - s_hasPendingCash = true; - // If an instance exists (even inactive), push immediately so the value is ready - var all = Resources.FindObjectsOfTypeAll(); - if (all != null) - { - for (int i = 0; i < all.Length; i++) - { - var ui = all[i]; - if (ui != null && ui.gameObject.scene.IsValid()) - { - ui.ApplyPendingCurrency(); - break; - } - } - } - } + // Public static entry points to cache values when UI is unavailable + public static void CacheMoney(ulong amount, ulong maxAmount) + { + s_pendingMoneyAmount = amount; + s_pendingMoneyMaxAmount = maxAmount; + s_hasPendingMoney = true; + // If an instance exists (even inactive), push immediately so the value is ready + var all = Resources.FindObjectsOfTypeAll(); + if (all != null) + { + for (int i = 0; i < all.Length; i++) + { + var ui = all[i]; + if (ui != null && ui.gameObject.scene.IsValid()) + { + ui.ApplyPendingCurrency(); + break; + } + } + } + } - private void ApplyPendingCurrency() - { - if (s_hasPendingMoney) - { - UpdateMoney(s_pendingMoneyAmount, s_pendingMoneyMaxAmount); - } - if (s_hasPendingCash) - { - UpdateCash(s_pendingCashAmount); - } - } + public static void CacheCash(int amount) + { + s_pendingCashAmount = amount; + s_hasPendingCash = true; + // If an instance exists (even inactive), push immediately so the value is ready + var all = Resources.FindObjectsOfTypeAll(); + if (all != null) + { + for (int i = 0; i < all.Length; i++) + { + var ui = all[i]; + if (ui != null && ui.gameObject.scene.IsValid()) + { + ui.ApplyPendingCurrency(); + break; + } + } + } + } + + private void ApplyPendingCurrency() + { + if (s_hasPendingMoney) + { + UpdateMoney(s_pendingMoneyAmount, s_pendingMoneyMaxAmount); + } + if (s_hasPendingCash) + { + UpdateCash(s_pendingCashAmount); + } + } private void OnInventoryButtonClicked(byte package, int slot) { @@ -242,13 +263,13 @@ namespace BrewMonster.Scripts.Managers currentSelectedPackage = package; currentSelectedSlot = slot; currentSelectedItem = itemData; - + // Create equipment object if this is equipment currentSelectedEquipment = CreateEquipmentFromItemData(itemData); - // Position detail panel near the clicked item button - PositionDetailPanelNearButton(package, slot); - + // Position detail panel near the clicked item button + PositionDetailPanelNearButton(package, slot); + FillDetailPanel(package, itemData); } else @@ -266,19 +287,19 @@ namespace BrewMonster.Scripts.Managers return null; var equipment = new EC_IvtrEquip(itemData.m_tid, itemData.m_expire_date); - + // Set basic properties (use default values since InventoryItemData doesn't have these) equipment.Price = 0; equipment.Count = itemData.m_iCount; equipment.PriceScale = 1.0f; equipment.ScaleType = 0; - + // Parse item info if available (use Content field) if (itemData.Content != null && itemData.Content.Length > 0) { equipment.SetItemInfo(itemData.Content, itemData.Content.Length); } - + return equipment; } @@ -418,7 +439,7 @@ namespace BrewMonster.Scripts.Managers // Call RequestDropIvrtItem with slot index and amount UnityGameSession.RequestDropIvrtItem((byte)currentSelectedSlot, 1); Debug.Log($"[InventoryUI] Drop request sent for inventory item {currentSelectedItem.m_tid} from slot {currentSelectedSlot} with amount {currentSelectedItem.m_iCount}"); - + // Refresh inventory after drop RefreshAll(); } @@ -430,12 +451,12 @@ namespace BrewMonster.Scripts.Managers // Call RequestDropEquipItem with slot index UnityGameSession.RequestDropEquipItem((byte)currentSelectedSlot); Debug.Log($"[InventoryUI] Drop request sent for equipment item {currentSelectedItem.m_tid} from slot {currentSelectedSlot}"); - + // Refresh inventory after drop RefreshAll(); } - + private int FindEmptyInventorySlot() { var inventoryData = model.GetInventoryData(PKG_INVENTORY); @@ -451,7 +472,7 @@ namespace BrewMonster.Scripts.Managers } return -1; } - + /// /// Get item description from string table /// @@ -490,10 +511,10 @@ namespace BrewMonster.Scripts.Managers { Debug.LogWarning($"[InventoryUI] Error getting item description for ID {templateId}: {ex.Message}"); } - + return null; } - + /// /// Get extended item description from string table /// @@ -532,10 +553,10 @@ namespace BrewMonster.Scripts.Managers { Debug.LogWarning($"[InventoryUI] Error getting extended item description for ID {templateId}: {ex.Message}"); } - + return null; } - + /// /// Get user-friendly text for item state /// @@ -602,7 +623,7 @@ namespace BrewMonster.Scripts.Managers bool hasItem = items != null && items.TryGetValue(slot, out itemData); button.onClick.RemoveAllListeners(); int capturedSlot = slot; - button.onClick.AddListener(() => onClick(package, capturedSlot)); + button.onClick.AddListener(() => onClick(package, capturedSlot)); // Optional visual tweaks based on state/count var image = button.GetComponent(); if (image != null) @@ -627,6 +648,24 @@ namespace BrewMonster.Scripts.Managers image.enabled = true; } } + + var eventTrigger = button.GetComponent(); + if (eventTrigger == null) + eventTrigger = button.gameObject.AddComponent(); + + eventTrigger.triggers.Clear(); + + void AddEvent(EventTriggerType type, UnityEngine.Events.UnityAction action) + { + var entry = new EventTrigger.Entry { eventID = type }; + entry.callback.AddListener(action); + eventTrigger.triggers.Add(entry); + } + + AddEvent(EventTriggerType.BeginDrag, (data) => ((EC_InventoryUI)button.GetComponentInParent()).OnBeginDrag((PointerEventData)data)); + AddEvent(EventTriggerType.Drag, (data) => ((EC_InventoryUI)button.GetComponentInParent()).OnDrag((PointerEventData)data)); + AddEvent(EventTriggerType.EndDrag, (data) => ((EC_InventoryUI)button.GetComponentInParent()).OnEndDrag((PointerEventData)data)); + AddEvent(EventTriggerType.Drop, (data) => ((EC_InventoryUI)button.GetComponentInParent()).OnDrop((PointerEventData)data)); } } } @@ -649,7 +688,7 @@ namespace BrewMonster.Scripts.Managers tmp.text = FormatForTextMeshPro(value ?? string.Empty); } } - + /// /// Set text with explicit formatting preference /// @@ -657,10 +696,10 @@ namespace BrewMonster.Scripts.Managers /// Whether to prefer TextMeshPro formatting public void SetFormatted(string value, bool preferTextMeshPro = true) { - string formattedText = preferTextMeshPro ? - FormatForTextMeshPro(value ?? string.Empty) : + string formattedText = preferTextMeshPro ? + FormatForTextMeshPro(value ?? string.Empty) : EC_Utility.FormatForLegacyText(value ?? string.Empty); - + if (legacy != null) { legacy.text = formattedText; @@ -675,7 +714,7 @@ namespace BrewMonster.Scripts.Managers public void RefreshLayout(GameObject gameObject) { var parent = gameObject.GetComponent(); - + // Force Unity to rebuild layout immediately parent.ForceUpdateRectTransforms(); LayoutRebuilder.ForceRebuildLayoutImmediate(parent); @@ -689,115 +728,115 @@ namespace BrewMonster.Scripts.Managers } } - private Button GetButtonForSlot(byte package, int slot) - { - List