From c30b256d7a6ea37f94e46db2a89b21ca3d58bff9 Mon Sep 17 00:00:00 2001 From: basil <2428390463@qq.com> Date: Wed, 17 Jun 2026 09:37:44 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20Shop=20=E4=B8=8E=20ItemTooltip=20=E8=B7=A8=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E8=BE=B9=E7=95=8C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 _tooltipLocked 状态从 ItemTooltipController 移到 ShopController 维护 - DisplayItem 只发 Shop 模块 UI 事件(Hover/Lock/Hide) - ItemTooltipForm 取消按钮发 ItemTooltip 模块事件自关闭 - ShopController 通过 UIRouter 跨模块控制 ItemTooltipForm - 移除 DisplayItemHideEventArgs 的 Force 字段,分离两种关闭场景 --- .../ItemTooltipHideEventArgs.cs | 7 +- .../ItemTooltipLockEventArgs.cs.meta | 3 - .../ItemTooltipShowEventArgs.cs.meta | 3 - .../Event/Shop/DisplayItemHideEventArgs.cs | 27 +++ .../Shop/DisplayItemHideEventArgs.cs.meta | 11 ++ .../DisplayItemHoverEventArgs.cs} | 20 +- .../Shop/DisplayItemHoverEventArgs.cs.meta | 11 ++ .../DisplayItemLockEventArgs.cs} | 12 +- .../Shop/DisplayItemLockEventArgs.cs.meta | 11 ++ .../Base/Event/Shop/ShopContinueEventArgs.cs | 2 +- .../Event/Shop/ShopContinueEventArgs.cs.meta | 12 +- .../Base/Event/Shop/ShopPurchaseEventArgs.cs | 13 +- .../Event/Shop/ShopPurchaseEventArgs.cs.meta | 12 +- .../Base/Event/Shop/ShopRefreshEventArgs.cs | 33 ++++ .../Event/Shop/ShopRefreshEventArgs.cs.meta | 11 ++ .../Main/Context/GoodsItemContext.cs | 35 ++-- .../Main/ItemTooltip/ItemTooltipController.cs | 64 ++----- .../Main/ItemTooltip/ItemTooltipForm.cs | 2 +- .../Presentation/Main/Shop/ShopContext.cs | 8 +- .../Presentation/Main/Shop/ShopController.cs | 171 +++++++++++------- .../Presentation/Main/Shop/ShopForm.cs | 67 ++++--- .../Presentation/Main/View/DisplayItem.cs | 6 +- .../Runtime/UIBase/Main/Shop/ShopUseCase.cs | 7 + docs/UI-5层架构设计规范.md | 5 + 24 files changed, 340 insertions(+), 213 deletions(-) delete mode 100644 Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipLockEventArgs.cs.meta delete mode 100644 Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipShowEventArgs.cs.meta create mode 100644 Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs create mode 100644 Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs.meta rename Assets/GameMain/Scripts/Base/Event/{ItemTooltipForm/ItemTooltipShowEventArgs.cs => Shop/DisplayItemHoverEventArgs.cs} (50%) create mode 100644 Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHoverEventArgs.cs.meta rename Assets/GameMain/Scripts/Base/Event/{ItemTooltipForm/ItemTooltipLockEventArgs.cs => Shop/DisplayItemLockEventArgs.cs} (50%) create mode 100644 Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemLockEventArgs.cs.meta create mode 100644 Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs create mode 100644 Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs.meta diff --git a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipHideEventArgs.cs b/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipHideEventArgs.cs index 497c89a..e3eac8b 100644 --- a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipHideEventArgs.cs +++ b/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipHideEventArgs.cs @@ -9,24 +9,19 @@ namespace SepCore.Event public override int Id => EventId; - public bool Force = false; - public ItemTooltipHideEventArgs() { - Force = false; } public static ItemTooltipHideEventArgs Create(bool force = false) { var args = ReferencePool.Acquire(); - args.Force = force; return args; } public override void Clear() { - Force = false; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipLockEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipLockEventArgs.cs.meta deleted file mode 100644 index ee1ff24..0000000 --- a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipLockEventArgs.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 17754f00ac924fc29b3604cfed7a5efe -timeCreated: 1771488601 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipShowEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipShowEventArgs.cs.meta deleted file mode 100644 index 1f461ac..0000000 --- a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipShowEventArgs.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 51bff2c6818f44398b4b8f7d1448b310 -timeCreated: 1771071724 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs new file mode 100644 index 0000000..d53b935 --- /dev/null +++ b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs @@ -0,0 +1,27 @@ +using GameFramework; +using GameFramework.Event; + +namespace SepCore.Event +{ + public class DisplayItemHideEventArgs : GameEventArgs + { + public static readonly int EventId = typeof(DisplayItemHideEventArgs).GetHashCode(); + + public override int Id => EventId; + + public DisplayItemHideEventArgs() + { + } + + public static DisplayItemHideEventArgs Create() + { + var args = ReferencePool.Acquire(); + + return args; + } + + public override void Clear() + { + } + } +} diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs.meta new file mode 100644 index 0000000..a5abc46 --- /dev/null +++ b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHideEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb8173a4a0fb12245bc81b5bd66b5948 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipShowEventArgs.cs b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHoverEventArgs.cs similarity index 50% rename from Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipShowEventArgs.cs rename to Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHoverEventArgs.cs index 8aa1786..6b6e07d 100644 --- a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipShowEventArgs.cs +++ b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHoverEventArgs.cs @@ -4,30 +4,30 @@ using UnityEngine; namespace SepCore.Event { - public class ItemTooltipShowEventArgs : GameEventArgs + public class DisplayItemHoverEventArgs : GameEventArgs { - public static readonly int EventId = typeof(ItemTooltipShowEventArgs).GetHashCode(); + public static readonly int EventId = typeof(DisplayItemHoverEventArgs).GetHashCode(); public override int Id => EventId; - public int Index { get; private set; } + public int Index; + public bool IsWeapon; + public Vector3 TargetPos; - public bool IsWeapon { get; private set; } - - public Vector3 TargetPos { get; private set; } - - public ItemTooltipShowEventArgs() + public DisplayItemHoverEventArgs() { Index = -1; IsWeapon = false; + TargetPos = Vector3.zero; } - public static ItemTooltipShowEventArgs Create(int index, bool isWeapon, Vector3 targetPos) + public static DisplayItemHoverEventArgs Create(int index, bool isWeapon, Vector3 targetPos) { - var args = ReferencePool.Acquire(); + var args = ReferencePool.Acquire(); args.Index = index; args.IsWeapon = isWeapon; args.TargetPos = targetPos; + return args; } diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHoverEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHoverEventArgs.cs.meta new file mode 100644 index 0000000..11bbe4e --- /dev/null +++ b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemHoverEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f43032aabf2403746bf0977988e91bbb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipLockEventArgs.cs b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemLockEventArgs.cs similarity index 50% rename from Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipLockEventArgs.cs rename to Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemLockEventArgs.cs index 730a554..eadb9e3 100644 --- a/Assets/GameMain/Scripts/Base/Event/ItemTooltipForm/ItemTooltipLockEventArgs.cs +++ b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemLockEventArgs.cs @@ -3,19 +3,19 @@ using GameFramework.Event; namespace SepCore.Event { - public class ItemTooltipLockEventArgs : GameEventArgs + public class DisplayItemLockEventArgs : GameEventArgs { - public static readonly int EventId = typeof(ItemTooltipLockEventArgs).GetHashCode(); + public static readonly int EventId = typeof(DisplayItemLockEventArgs).GetHashCode(); public override int Id => EventId; - public ItemTooltipLockEventArgs() + public DisplayItemLockEventArgs() { } - public static ItemTooltipLockEventArgs Create() + public static DisplayItemLockEventArgs Create() { - var args = ReferencePool.Acquire(); + var args = ReferencePool.Acquire(); return args; } @@ -24,4 +24,4 @@ namespace SepCore.Event { } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemLockEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemLockEventArgs.cs.meta new file mode 100644 index 0000000..85e222b --- /dev/null +++ b/Assets/GameMain/Scripts/Base/Event/Shop/DisplayItemLockEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5ad9bcc1ac9ad4141b810d224a31a201 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs b/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs index 5533c6b..d53f6ce 100644 --- a/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs +++ b/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs @@ -24,4 +24,4 @@ namespace SepCore.Event { } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs.meta index 4cc0ef4..0f581b9 100644 --- a/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs.meta +++ b/Assets/GameMain/Scripts/Base/Event/Shop/ShopContinueEventArgs.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 -guid: b09ad1baac8a41338c0e42baf398beb8 -timeCreated: 1770986449 \ No newline at end of file +guid: e5e26cd3da0c8bc46ac72f106b73357d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs b/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs index c78c0aa..cb904d4 100644 --- a/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs +++ b/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs @@ -8,27 +8,26 @@ namespace SepCore.Event public static readonly int EventId = typeof(ShopPurchaseEventArgs).GetHashCode(); public override int Id => EventId; - + public int GoodsIndex { get; private set; } public ShopPurchaseEventArgs() { GoodsIndex = -1; } - + public static ShopPurchaseEventArgs Create(int index) { var args = ReferencePool.Acquire(); - + args.GoodsIndex = index; - + return args; } - + public override void Clear() { GoodsIndex = -1; } - } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs.meta index e5a8a54..41c7f0e 100644 --- a/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs.meta +++ b/Assets/GameMain/Scripts/Base/Event/Shop/ShopPurchaseEventArgs.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 -guid: 11c82e4a45804087b1aaf337e38a8836 -timeCreated: 1770986490 \ No newline at end of file +guid: a39ac97844dfeea4dacfdd80316ddb1e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs b/Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs new file mode 100644 index 0000000..6f4d93d --- /dev/null +++ b/Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs @@ -0,0 +1,33 @@ +using GameFramework; +using GameFramework.Event; + +namespace SepCore.Event +{ + public class ShopRefreshEventArgs : GameEventArgs + { + public static readonly int EventId = typeof(ShopRefreshEventArgs).GetHashCode(); + + public override int Id => EventId; + + public int Cost { get; private set; } + + public ShopRefreshEventArgs() + { + Cost = 0; + } + + public static ShopRefreshEventArgs Create(int cost) + { + var args = ReferencePool.Acquire(); + + args.Cost = cost; + + return args; + } + + public override void Clear() + { + Cost = 0; + } + } +} diff --git a/Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs.meta b/Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs.meta new file mode 100644 index 0000000..e7088ac --- /dev/null +++ b/Assets/GameMain/Scripts/Base/Event/Shop/ShopRefreshEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ebfa286438bc924f8c0fb8ebdfc5ce1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Presentation/Main/Context/GoodsItemContext.cs b/Assets/GameMain/Scripts/Presentation/Main/Context/GoodsItemContext.cs index 1258a62..4101912 100644 --- a/Assets/GameMain/Scripts/Presentation/Main/Context/GoodsItemContext.cs +++ b/Assets/GameMain/Scripts/Presentation/Main/Context/GoodsItemContext.cs @@ -25,37 +25,32 @@ namespace SepCore.UI ItemType = ItemType.None; } - public static async UniTask CreateAsync(GoodsItemRawData rawData) + public GoodsItemContext(GoodsItemRawData rawData) { - var context = new GoodsItemContext - { - Price = rawData.Price, - Rarity = rawData.Rarity, - ItemType = rawData.ItemType - }; - if (context.ItemType == ItemType.None) + Price = rawData.Price; + Rarity = rawData.Rarity; + ItemType = rawData.ItemType; + + + if (ItemType == ItemType.None) { - context.Description = string.Empty; - context.Icon = null; - context.Title = string.Empty; + Description = string.Empty; + Icon = null; + Title = string.Empty; } - else if (context.ItemType == ItemType.Weapon) + else if (ItemType == ItemType.Weapon) { var weapon = (DRWeapon)rawData.DataRow; - context.Title = weapon.Title; - context.Description = ItemDescUtility.CreateWeaponDescription(weapon); - context.Icon = await GameEntry.SpriteCache.GetSprite(weapon.IconAssetName); + Title = weapon.Title; + Description = ItemDescUtility.CreateWeaponDescription(weapon); } else { var prop = (DRProp)rawData.DataRow; - context.Title = prop.Title; - context.Description = ItemDescUtility.CreatePropDescription(prop.Modifiers); - context.Icon = await GameEntry.SpriteCache.GetSprite(prop.IconAssetName); + Title = prop.Title; + Description = ItemDescUtility.CreatePropDescription(prop.Modifiers); } - - return context; } } } diff --git a/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipController.cs b/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipController.cs index 34dbcaa..cce682c 100644 --- a/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipController.cs +++ b/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipController.cs @@ -3,7 +3,6 @@ using Cysharp.Threading.Tasks; using SepCore.Event; using SepCore.Definition; using GameFramework.Event; -using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.UI @@ -12,21 +11,24 @@ namespace SepCore.UI { protected override UIFormType UIFormType => UIFormType.ItemTooltipForm; - private bool _locked = false; private Action _onRecycle; + private bool _subscribed = false; + protected override void SubscribeCustomEvents() { - GameEntry.Event.Subscribe(DisplayItemLockEventArgs.EventId, OnDisplayItemLock); - GameEntry.Event.Subscribe(DisplayItemHideEventArgs.EventId, OnDisplayItemHide); + if (_subscribed) return; GameEntry.Event.Subscribe(ItemTooltipRecycleEventArgs.EventId, ItemTooltipRecycle); + GameEntry.Event.Subscribe(ItemTooltipHideEventArgs.EventId, ItemTooltipHide); + _subscribed = true; } protected override void UnsubscribeCustomEvents() { - GameEntry.Event.Unsubscribe(DisplayItemLockEventArgs.EventId, OnDisplayItemLock); - GameEntry.Event.Unsubscribe(DisplayItemHideEventArgs.EventId, OnDisplayItemHide); + if (!_subscribed) return; GameEntry.Event.Unsubscribe(ItemTooltipRecycleEventArgs.EventId, ItemTooltipRecycle); + GameEntry.Event.Unsubscribe(ItemTooltipHideEventArgs.EventId, ItemTooltipHide); + _subscribed = false; } protected override void RefreshUI(ItemTooltipForm form, ItemTooltipContext context) @@ -79,7 +81,6 @@ namespace SepCore.UI public override async UniTask CloseUIAsync(object userData = null, float timeout = 30f) { - _locked = false; _onRecycle = null; await base.CloseUIAsync(userData, timeout); } @@ -92,45 +93,8 @@ namespace SepCore.UI } } - private bool IsCurrentFormSender(object sender) - { - if (sender is ItemTooltipForm ItemTooltipForm) - { - return ItemTooltipForm == Form; - } - - if (sender is Component component && Form != null) - { - return component.transform.IsChildOf(Form.transform); - } - - return false; - } - #region Event Handlers - private void OnDisplayItemLock(object sender, GameEventArgs e) - { - if (e is not DisplayItemLockEventArgs) - { - return; - } - - _locked = true; - } - - private void OnDisplayItemHide(object sender, GameEventArgs e) - { - if (e is not DisplayItemHideEventArgs args) - { - return; - } - - if (_locked && !args.Force) return; - - GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget(); - } - private void ItemTooltipRecycle(object sender, GameEventArgs e) { if (e is not ItemTooltipRecycleEventArgs args) @@ -138,14 +102,16 @@ namespace SepCore.UI return; } - if (!IsCurrentFormSender(sender)) - { - return; - } - _onRecycle?.Invoke(args.Index, args.Price); } + private void ItemTooltipHide(object sender, GameEventArgs e) + { + if (e is not ItemTooltipHideEventArgs args) return; + + CloseUIAsync().Forget(); + } + #endregion } } diff --git a/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipForm.cs b/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipForm.cs index b52d9e7..505542f 100644 --- a/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipForm.cs +++ b/Assets/GameMain/Scripts/Presentation/Main/ItemTooltip/ItemTooltipForm.cs @@ -197,7 +197,7 @@ namespace SepCore.UI public void OnCancelButtonClick() { - GameEntry.Event.Fire(this, ItemTooltipHideEventArgs.Create(true)); + GameEntry.Event.Fire(this, ItemTooltipHideEventArgs.Create()); } private string BuildTypeText(ItemType type) diff --git a/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopContext.cs b/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopContext.cs index a32c1d6..2bf503d 100644 --- a/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopContext.cs +++ b/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopContext.cs @@ -10,6 +10,12 @@ namespace SepCore.UI public List GoodsItems; public DisplayListAreaContext PropListContext; public DisplayListAreaContext WeaponListContext; - public float WeaponRecycleRate = 0.3f; + + // 控制字段 + public bool NeedRefreshGoodsItems; + public bool NeedRefreshPlayerCoin; + public bool NeedRefreshWeaponList; + public bool NeedRefreshPropList; + public bool NeedRefreshPrice; } } diff --git a/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopController.cs b/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopController.cs index 4eb050c..3ec2fb5 100644 --- a/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopController.cs +++ b/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopController.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using Cysharp.Threading.Tasks; using SepCore.Event; using SepCore.Definition; -using SepCore.CustomUtility; using SepCore.Entity.Weapon; using GameFramework.Event; +using SepCore.DataTable; using UnityEngine; using UnityGameFramework.Runtime; @@ -14,6 +14,7 @@ namespace SepCore.UI { private ShopUseCase _useCase; private ShopRawData _rawData; + private bool _tooltipLocked = false; protected override UIFormType UIFormType => UIFormType.ShopForm; @@ -24,18 +25,26 @@ namespace SepCore.UI protected override void SubscribeCustomEvents() { - GameEntry.Event.Subscribe(RefreshEventArgs.EventId, Refresh); + GameEntry.Event.Subscribe(ShopRefreshEventArgs.EventId, ShopRefresh); GameEntry.Event.Subscribe(ShopPurchaseEventArgs.EventId, ShopPurchase); GameEntry.Event.Subscribe(ShopContinueEventArgs.EventId, ShopContinue); - GameEntry.Event.Subscribe(ItemTooltipShowEventArgs.EventId, DisplayItemShow); + GameEntry.Event.Subscribe(DisplayItemHoverEventArgs.EventId, DisplayItemHover); + GameEntry.Event.Subscribe(DisplayItemLockEventArgs.EventId, DisplayItemLock); + GameEntry.Event.Subscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide); + GameEntry.Event.Subscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange); + GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormComplete); } protected override void UnsubscribeCustomEvents() { - GameEntry.Event.Unsubscribe(RefreshEventArgs.EventId, Refresh); + GameEntry.Event.Unsubscribe(ShopRefreshEventArgs.EventId, ShopRefresh); GameEntry.Event.Unsubscribe(ShopPurchaseEventArgs.EventId, ShopPurchase); GameEntry.Event.Unsubscribe(ShopContinueEventArgs.EventId, ShopContinue); - GameEntry.Event.Unsubscribe(ItemTooltipShowEventArgs.EventId, DisplayItemShow); + GameEntry.Event.Unsubscribe(DisplayItemHoverEventArgs.EventId, DisplayItemHover); + GameEntry.Event.Unsubscribe(DisplayItemLockEventArgs.EventId, DisplayItemLock); + GameEntry.Event.Unsubscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide); + GameEntry.Event.Unsubscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange); + GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormComplete); } #region BuildContext @@ -53,7 +62,8 @@ namespace SepCore.UI List goodsItems = new List(); foreach (var item in rawData.GoodsItems) { - goodsItems.Add(await GoodsItemContext.CreateAsync(item)); + var context = await CreateGoodsItemContextAsync(item); + goodsItems.Add(context); } return new ShopContext @@ -65,7 +75,12 @@ namespace SepCore.UI PropListContext = BuildDisplayListAreaContext(DisplayListAreaType.Prop, rawData.PropItems, rawData.PropMaxCount), WeaponListContext = BuildDisplayListAreaContext(DisplayListAreaType.Weapon, rawData.WeaponItems, - rawData.WeaponMaxCount) + rawData.WeaponMaxCount), + NeedRefreshGoodsItems = true, + NeedRefreshPlayerCoin = true, + NeedRefreshPropList = true, + NeedRefreshWeaponList = true, + NeedRefreshPrice = true }; } @@ -199,14 +214,28 @@ namespace SepCore.UI listContext.CurrentCount = oldCount + 1; } + private async UniTask CreateGoodsItemContextAsync(GoodsItemRawData rawData, + float timeout = 30f) + { + var context = new GoodsItemContext(rawData); + context.Icon = context.ItemType switch + { + ItemType.Weapon => await GameEntry.SpriteCache.GetSprite(((DRWeapon)rawData.DataRow).IconAssetName), + ItemType.Prop => await GameEntry.SpriteCache.GetSprite(((DRProp)rawData.DataRow).IconAssetName), + _ => null + }; + return context; + } + #endregion #region UI Methods public override async UniTask CloseUIAsync(object userData = null, float timeout = 30f) { - GameEntry.Event.Fire(this, ItemTooltipHideEventArgs.Create(true)); + GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget(); _rawData = null; + _tooltipLocked = false; await base.CloseUIAsync(userData, timeout); } @@ -271,8 +300,10 @@ namespace SepCore.UI for (int i = 0; i < result.GoodsItems.Count; i++) { if (i < Context.GoodsItems.Count) - Context.GoodsItems[i] = await GoodsItemContext.CreateAsync(result.GoodsItems[i]); - else Context.GoodsItems.Add(await GoodsItemContext.CreateAsync(result.GoodsItems[i])); + { + Context.GoodsItems[i] = await CreateGoodsItemContextAsync(result.GoodsItems[i]); + } + else Context.GoodsItems.Add(await CreateGoodsItemContextAsync(result.GoodsItems[i])); } if (Context.GoodsItems.Count != result.GoodsItems.Count) @@ -282,15 +313,10 @@ namespace SepCore.UI } Context.RefreshPrice = result.RefreshPrice; + Context.NeedRefreshGoodsItems = true; + Context.NeedRefreshPrice = true; - if (Form == null) - { - Log.Error("ShopFormController.RefreshGoodsItems() Form is null."); - return; - } - - Form.RefreshGoodsItems(Context.GoodsItems); - Form.RefreshRefreshPrice(Context.RefreshPrice); + RefreshUI(Form, Context); } private void ApplyGoodsPurchased(ShopPurchaseResult result) @@ -319,29 +345,17 @@ namespace SepCore.UI if (result.DisplayItem.IsWeapon) { AppendDisplayItemContext(Context.WeaponListContext, context); + Context.NeedRefreshWeaponList = true; } else { AppendDisplayItemContext(Context.PropListContext, context); + Context.NeedRefreshPropList = true; } } - Form?.ApplyGoodsPurchased(result.GoodsIndex, context); - } - - private bool IsCurrentFormEventSender(object sender) - { - if (sender is ShopForm shopForm) - { - return shopForm == Form; - } - - if (sender is Component component && Form != null) - { - return component.transform.IsChildOf(Form.transform); - } - - return false; + Context.NeedRefreshGoodsItems = true; + RefreshUI(Form, Context); } private bool TryGetWeaponInfoRawData(int index, Vector3 targetPos, out ItemTooltipRawData rawData) @@ -380,8 +394,8 @@ namespace SepCore.UI WeaponData = weapon.WeaponData, Rarity = weapon.WeaponData.Rarity, ItemType = ItemType.Weapon, - Price = Mathf.FloorToInt(weapon.WeaponData.Price * Context.WeaponRecycleRate), - OnRecycle = (recycleIndex, recyclePrice) => RecycleWeapon(recycleIndex, recyclePrice), + Price = _useCase.CalculateRecyclePrice(weapon.WeaponData.Price), + OnRecycle = RecycleWeapon, }; return true; } @@ -441,22 +455,18 @@ namespace SepCore.UI Context.WeaponListContext.CurrentCount = currentCount; } - Form?.RemoveWeaponDisplayItem(index); - GameEntry.Event.Fire(this, ItemTooltipHideEventArgs.Create(true)); + Context.NeedRefreshWeaponList = true; + RefreshUI(Form, Context); + GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget(); } #endregion #region Event Handlers - private async void Refresh(object sender, GameEventArgs e) + private async void ShopRefresh(object sender, GameEventArgs e) { - if (sender is not ShopForm) - { - return; - } - - if (e is not RefreshEventArgs args) + if (e is not ShopRefreshEventArgs args) { return; } @@ -472,16 +482,11 @@ namespace SepCore.UI private void ShopPurchase(object sender, GameEventArgs e) { - ShopPurchaseAsync(sender, e).Forget(); + OnPurchaseRequestAsync(sender, e).Forget(); } - private async UniTaskVoid ShopPurchaseAsync(object sender, GameEventArgs e) + private async UniTaskVoid OnPurchaseRequestAsync(object sender, GameEventArgs e) { - if (sender is not ShopForm) - { - return; - } - if (e is not ShopPurchaseEventArgs args) { return; @@ -498,11 +503,6 @@ namespace SepCore.UI private void ShopContinue(object sender, GameEventArgs e) { - if (sender is not ShopForm) - { - return; - } - if (e is not ShopContinueEventArgs) { return; @@ -511,21 +511,16 @@ namespace SepCore.UI _useCase?.Continue(); } - private void DisplayItemShow(object sender, GameEventArgs e) + private void DisplayItemHover(object sender, GameEventArgs e) { - if (e is not ItemTooltipShowEventArgs args) - { - return; - } - - if (!IsCurrentFormEventSender(sender)) + if (e is not DisplayItemHoverEventArgs args) { return; } if (_rawData == null) { - Log.Error("ShopFormController.DisplayItemShow() _rawData is null."); + Log.Error("ShopFormController.OnDisplayItemHover() _rawData is null."); return; } @@ -539,9 +534,55 @@ namespace SepCore.UI return; } + _tooltipLocked = false; GameEntry.UIRouter.OpenUIAsync(UIFormType.ItemTooltipForm, rawData).Forget(); } + private void DisplayItemLock(object sender, GameEventArgs e) + { + if (e is not DisplayItemLockEventArgs) + { + return; + } + + _tooltipLocked = true; + } + + private void DisplayItemHide(object sender, GameEventArgs e) + { + if (e is not DisplayItemHideEventArgs args) + { + return; + } + + if (_tooltipLocked) + { + return; + } + + GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget(); + } + + private void PlayerCoinChange(object sender, GameEventArgs e) + { + if (e is not PlayerCoinChangeEventArgs args) return; + if (Context == null) return; + + Context.PlayerCoin = args.CoinCount; + Context.NeedRefreshPlayerCoin = true; + RefreshUI(Form, Context); + } + + private void OpenUIFormComplete(object sender, GameEventArgs e) + { + if (e is not OpenUIFormSuccessEventArgs ne) return; + + if (ne.UIForm.Logic is ItemTooltipForm) + { + _tooltipLocked = false; + } + } + #endregion } } diff --git a/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopForm.cs b/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopForm.cs index 4d2a105..eef9a2c 100644 --- a/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopForm.cs +++ b/Assets/GameMain/Scripts/Presentation/Main/Shop/ShopForm.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using SepCore.Event; -using GameFramework.Event; using TMPro; using UnityEngine; using UnityGameFramework.Runtime; @@ -18,7 +17,6 @@ namespace SepCore.UI [SerializeField] private TMP_Text _refreshPriceText; [SerializeField] private TMP_Text _playerCoinText; - private int _currentCoin; [SerializeField] private ShopGoodsItem[] _goodsItems; @@ -35,25 +33,41 @@ namespace SepCore.UI { _context = context; - _titleText.text = $"商店 (Lv.{context.CurrentLevel})"; - _continueButtonText.text = $"继续 (Lv.{context.CurrentLevel + 1})"; - RefreshRefreshPrice(context.RefreshPrice); - _playerCoinText.text = $" {context.PlayerCoin}"; - - RefreshGoodsItems(context.GoodsItems); - - if (_propListArea != null && context.PropListContext != null) + // 首次打开或需要全量刷新时 + if (context.NeedRefreshGoodsItems) { - _propListArea.OnInit(context.PropListContext); + _titleText.text = $"商店 (Lv.{context.CurrentLevel})"; + _continueButtonText.text = $"继续 (Lv.{context.CurrentLevel + 1})"; + RefreshGoodsItems(context.GoodsItems); + context.NeedRefreshGoodsItems = false; } - if (_weaponListArea != null && context.WeaponListContext != null) + if (context.NeedRefreshPrice) + { + RefreshRefreshPrice(context.RefreshPrice); + context.NeedRefreshPrice = false; + } + + if (context.NeedRefreshPlayerCoin) + { + _playerCoinText.text = $" {context.PlayerCoin}"; + context.NeedRefreshPlayerCoin = false; + } + + if (context.NeedRefreshPropList && _propListArea != null && context.PropListContext != null) + { + _propListArea.OnInit(context.PropListContext); + context.NeedRefreshPropList = false; + } + + if (context.NeedRefreshWeaponList && _weaponListArea != null && context.WeaponListContext != null) { _weaponListArea.OnInit(context.WeaponListContext); + context.NeedRefreshWeaponList = false; } } - internal void RefreshGoodsItems(List goodsItems) + private void RefreshGoodsItems(List goodsItems) { foreach (var item in _goodsItems) { @@ -68,17 +82,19 @@ namespace SepCore.UI int count = Mathf.Min(_goodsItems.Length, goodsItems.Count); for (int i = 0; i < count; i++) { + if (goodsItems[i] == null) continue; + _goodsItems[i].Init(goodsItems[i]); _goodsItems[i].gameObject.SetActive(true); } } - internal void RefreshRefreshPrice(int refreshPrice) + private void RefreshRefreshPrice(int refreshPrice) { - _refreshPriceText.text = $"刷新 -{refreshPrice} "; + _refreshPriceText.text = $"刷新 -{refreshPrice} "; } - internal void ApplyGoodsPurchased(int goodsIndex, DisplayItemContext displayItem) + private void ApplyGoodsPurchased(int goodsIndex, DisplayItemContext displayItem) { SetGoodsItemVisible(goodsIndex, false); @@ -140,7 +156,7 @@ namespace SepCore.UI public void OnRefreshButtonClick() { - GameEntry.Event.Fire(this, RefreshEventArgs.Create(_context.RefreshPrice)); + GameEntry.Event.Fire(this, ShopRefreshEventArgs.Create(_context.RefreshPrice)); } #endregion @@ -151,8 +167,6 @@ namespace SepCore.UI { base.OnOpen(userData); - GameEntry.Event.Subscribe(PlayerCoinChangeEventArgs.EventId, OnPlayerCoinChange); - if (userData is ShopContext context) { RefreshUI(context); @@ -166,8 +180,6 @@ namespace SepCore.UI { _context = null; - GameEntry.Event.Unsubscribe(PlayerCoinChangeEventArgs.EventId, OnPlayerCoinChange); - _propListArea?.OnReset(); _weaponListArea?.OnReset(); @@ -175,18 +187,5 @@ namespace SepCore.UI } #endregion - - #region Event Handlers - - private void OnPlayerCoinChange(object sender, GameEventArgs e) - { - if (!(e is PlayerCoinChangeEventArgs args)) return; - if (args.CoinCount == _currentCoin) return; - - _currentCoin = args.CoinCount; - _playerCoinText.text = $" {_currentCoin}"; - } - - #endregion } } diff --git a/Assets/GameMain/Scripts/Presentation/Main/View/DisplayItem.cs b/Assets/GameMain/Scripts/Presentation/Main/View/DisplayItem.cs index 7970a52..c1d10b0 100644 --- a/Assets/GameMain/Scripts/Presentation/Main/View/DisplayItem.cs +++ b/Assets/GameMain/Scripts/Presentation/Main/View/DisplayItem.cs @@ -56,17 +56,17 @@ namespace SepCore.UI rect.yMax, 0f) ); - GameEntry.Event.Fire(this, ItemTooltipShowEventArgs.Create(_index, _context.IsWeapon, targetPos)); + GameEntry.Event.Fire(this, DisplayItemHoverEventArgs.Create(_index, _context.IsWeapon, targetPos)); } public void OnItemInfoLock() { - GameEntry.Event.Fire(this, ItemTooltipLockEventArgs.Create()); + GameEntry.Event.Fire(this, DisplayItemLockEventArgs.Create()); } public void OnItemInfoHide() { - GameEntry.Event.Fire(this, ItemTooltipHideEventArgs.Create()); + GameEntry.Event.Fire(this, DisplayItemHideEventArgs.Create()); } } } \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/UIBase/Main/Shop/ShopUseCase.cs b/Assets/GameMain/Scripts/Runtime/UIBase/Main/Shop/ShopUseCase.cs index 2729339..784a33e 100644 --- a/Assets/GameMain/Scripts/Runtime/UIBase/Main/Shop/ShopUseCase.cs +++ b/Assets/GameMain/Scripts/Runtime/UIBase/Main/Shop/ShopUseCase.cs @@ -28,6 +28,8 @@ namespace SepCore.UI public class ShopUseCase : IUIUseCase { + private const float WeaponRecycleRate = 0.3f; + private readonly Player _player; private readonly int _currentLevel; private readonly Action _onShopFinish; @@ -404,5 +406,10 @@ namespace SepCore.UI Player.Coin += recyclePrice; return true; } + + public int CalculateRecyclePrice(int basePrice) + { + return Mathf.FloorToInt(basePrice * WeaponRecycleRate); + } } } diff --git a/docs/UI-5层架构设计规范.md b/docs/UI-5层架构设计规范.md index 13acfb3..29ab3b2 100644 --- a/docs/UI-5层架构设计规范.md +++ b/docs/UI-5层架构设计规范.md @@ -138,6 +138,7 @@ View - 在 `Controller` 中堆叠大段领域业务规则 - 绕过 `Context` 直接把业务对象塞给 `View` - 直接修改其他 UI 的内部 `View` +- **直接调用 View 的公开方法进行局部刷新**;应通过更新 `Context` 并设置刷新粒度控制字段,再调用 `RefreshUI` 让 View 自行决定刷新内容 ### 3.4 Context 层 @@ -152,12 +153,16 @@ View - **不允许携带回调委托或行为**,交互行为由 Controller 注册,View 通过 UI 专用事件通知 Controller - 允许组合子 `Context` - 不进入 `UseCase` +- **允许提供构造函数**,但只能由对应的 `Controller` 调用,用于封装从 `RawData` 到展示数据的转换逻辑 +- **允许提供刷新粒度控制字段**(如 `NeedRefreshXxx` 布尔字段),用于支持局部刷新;View 根据这些字段决定刷新哪些部分,Controller 在更新数据后设置对应字段并调用 `RefreshUI` 说明: - `Context` 可以包含展示层需要的最终数据 - `Context` 可以是“已格式化”的显示数据 - 但这些数据必须由 `Controller` 负责准备,而不是 `UseCase` +- `Context` 的构造函数可以包含轻量的展示逻辑转换(如格式化文本、选择图标),但不应包含复杂业务规则 +- 刷新粒度控制字段的典型用法:Controller 更新 Context 数据后,设置 `NeedRefreshXxx = true`,然后调用 `RefreshUI`;View 在 `RefreshUI` 中检查这些字段,只刷新需要更新的部分,最后将字段重置为 `false` ### 3.5 View 层