From d60116139000cc5e268a3b7de3229b71a17ccddd Mon Sep 17 00:00:00 2001 From: aswew1538 Date: Wed, 24 Jun 2026 17:13:40 +0900 Subject: [PATCH] 2026.06.24 --- .../Fonts/Pretendard-Black SDF.asset | 4 +- .../Prefabs/InventoryMiniUI.prefab.meta | 2 +- .../Inventory/Scripts/InventoryItemType.cs | 14 - .../Scripts/InventoryItemType.cs.meta | 2 - .../Inventory/Scripts/InventoryManager.cs | 722 +++++- .../Inventory/Scripts/InventorySlotUI.cs | 194 +- .../Inventory/Scripts/InventoryTooltipUI.cs | 86 +- .../Inventory/Scripts/InventoryUI.cs | 546 +++- .../Scripts/InventoryUILayoutController.cs | 2242 +++++++++++++++++ .../InventoryUILayoutController.cs.meta | 2 + .../Inventory/Scripts/ItemPickup.cs | 290 ++- .../Inventory/Scripts/ItemReward.cs | 242 +- .../Scripts/PlayerInventoryCollector.cs | 199 ++ .../Scripts/PlayerInventoryCollector.cs.meta | 2 + .../Memory Scripts/UI/MemoryPiecePickup.cs | 85 +- .../Memory Scripts/UI/MemoryPieceReward.cs | 104 +- .../UI/MemoryProgressDebugResetter.cs | 65 + .../UI/MemoryProgressDebugResetter.cs.meta | 2 + .../UI/MemoryProgressManager.cs | 397 ++- .../UI/MemoryProgressPopupUI.cs | 202 +- .../Memory Scripts/UI/MemoryProgressUI.cs | 273 +- ...…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (1)-Photoroom.png | 3 + ...›” 23์ผ ์˜ค์ „ 11_53_35 (1)-Photoroom.png.meta | 117 + ...…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (2)-Photoroom.png | 3 + ...›” 23์ผ ์˜ค์ „ 11_53_35 (2)-Photoroom.png.meta | 117 + ...…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (3)-Photoroom.png | 3 + ...›” 23์ผ ์˜ค์ „ 11_53_35 (3)-Photoroom.png.meta | 117 + ...…„ 6์›” 23์ผ ์˜ค์ „ 11_53_36 (4)-Photoroom.png | 3 + ...›” 23์ผ ์˜ค์ „ 11_53_36 (4)-Photoroom.png.meta | 117 + ...…„ 6์›” 23์ผ ์˜ค์ „ 11_53_36 (6)-Photoroom.png | 3 + ...›” 23์ผ ์˜ค์ „ 11_53_36 (6)-Photoroom.png.meta | 117 + ...…„ 6์›” 23์ผ ์˜ค์ „ 11_53_37 (7)-Photoroom.png | 3 + ...›” 23์ผ ์˜ค์ „ 11_53_37 (7)-Photoroom.png.meta | 117 + ...026๋…„ 6์›” 23์ผ ์˜คํ›„ 01_03_59-Photoroom.png | 3 + ...„ 6์›” 23์ผ ์˜คํ›„ 01_03_59-Photoroom.png.meta | 117 + .../prefabs/MemoryProgressUI.prefab.meta | 2 +- ...์ผ ์˜คํ›„ 01_28_13 (1)-Photoroom-Photoroom.png | 3 + ...˜คํ›„ 01_28_13 (1)-Photoroom-Photoroom.png.meta | 117 + ...…„ 6์›” 22์ผ ์˜ค์ „ 10_08_26 (1)-Photoroom.png | 3 + ...›” 22์ผ ์˜ค์ „ 10_08_26 (1)-Photoroom.png.meta | 117 + ...…„ 6์›” 22์ผ ์˜ค์ „ 10_08_26 (2)-Photoroom.png | 3 + ...›” 22์ผ ์˜ค์ „ 10_08_26 (2)-Photoroom.png.meta | 117 + ...026๋…„ 6์›” 22์ผ ์˜คํ›„ 02_56_32-Photoroom.png | 3 + ...„ 6์›” 22์ผ ์˜คํ›„ 02_56_32-Photoroom.png.meta | 117 + ...026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_38_40-Photoroom.png | 3 + ...„ 6์›” 22์ผ ์˜คํ›„ 03_38_40-Photoroom.png.meta | 117 + ...…„ 6์›” 22์ผ ์˜คํ›„ 03_49_28 (6)-Photoroom.png | 3 + ...›” 22์ผ ์˜คํ›„ 03_49_28 (6)-Photoroom.png.meta | 117 + ...026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_06_21-Photoroom.png | 3 + ...„ 6์›” 22์ผ ์˜คํ›„ 04_06_21-Photoroom.png.meta | 117 + ...026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_06_37-Photoroom.png | 3 + ...„ 6์›” 22์ผ ์˜คํ›„ 04_06_37-Photoroom.png.meta | 117 + ...026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_33_47-Photoroom.png | 3 + ...„ 6์›” 22์ผ ์˜คํ›„ 04_33_47-Photoroom.png.meta | 117 + ...…„ 6์›” 22์ผ ์˜คํ›„ 05_27_49 (1)-Photoroom.png | 3 + ...›” 22์ผ ์˜คํ›„ 05_27_49 (1)-Photoroom.png.meta | 117 + ...…„ 6์›” 22์ผ ์˜คํ›„ 05_27_52 (7)-Photoroom.png | 3 + ...›” 22์ผ ์˜คํ›„ 05_27_52 (7)-Photoroom.png.meta | 117 + ...…„ 6์›” 22์ผ ์˜คํ›„ 05_27_52 (8)-Photoroom.png | 3 + ...›” 22์ผ ์˜คํ›„ 05_27_52 (8)-Photoroom.png.meta | 117 + .../Materials/M_LeftFootprint.mat | 23 +- .../Materials/M_RightFootprint.mat | 8 +- .../maze Scripts/Materials/Wall.mat | 137 + .../maze Scripts/Materials/Wall.mat.meta | 8 + .../maze Scripts/Prefab/Footprint Left.prefab | 4 +- .../Prefab/Footprint Right.prefab | 4 +- .../maze Scripts/Prefab/MazeUIRoot.prefab | 3 + .../Prefab/MazeUIRoot.prefab.meta | 7 + .../maze Scripts/Ui/FootprintEffect.cs | 247 ++ .../maze Scripts/Ui/FootprintEffect.cs.meta | 2 + .../maze Scripts/Ui/FootprintHintManager.cs | 357 ++- .../maze Scripts/Ui/MazeDirection.cs | 2 +- .../maze Scripts/Ui/MazeGameManager.cs | 677 ++++- .../Ui/MazeMixedUIPanelFollower.cs | 330 +++ .../Ui/MazeMixedUIPanelFollower.cs.meta | 2 + .../maze Scripts/Ui/MazeUIManager.cs | 603 ++++- .../maze Scripts/Ui/MovingMazeWall.cs | 104 + .../maze Scripts/Ui/MovingMazeWall.cs.meta | 2 + ...026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_41_52-Photoroom.png | 3 + ...„ 6์›” 22์ผ ์˜คํ›„ 03_41_52-Photoroom.png.meta | 117 + Assets/My project/test scene.unity | 4 +- .../Interactables/FireHand/FireHand.prefab | 4 +- .../Robot Hand/Material/RobotHand Red.mat | 7 +- Packages/manifest.json | 1 + Packages/packages-lock.json | 6 + ProjectSettings/TagManager.asset | 4 +- 86 files changed, 10287 insertions(+), 336 deletions(-) delete mode 100644 Assets/My project/Inventory/Scripts/InventoryItemType.cs delete mode 100644 Assets/My project/Inventory/Scripts/InventoryItemType.cs.meta create mode 100644 Assets/My project/Inventory/Scripts/InventoryUILayoutController.cs create mode 100644 Assets/My project/Inventory/Scripts/InventoryUILayoutController.cs.meta create mode 100644 Assets/My project/Inventory/Scripts/PlayerInventoryCollector.cs create mode 100644 Assets/My project/Inventory/Scripts/PlayerInventoryCollector.cs.meta create mode 100644 Assets/My project/Memory Scripts/UI/MemoryProgressDebugResetter.cs create mode 100644 Assets/My project/Memory Scripts/UI/MemoryProgressDebugResetter.cs.meta create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (1)-Photoroom.png create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (1)-Photoroom.png.meta create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (2)-Photoroom.png create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (2)-Photoroom.png.meta create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (3)-Photoroom.png create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_35 (3)-Photoroom.png.meta create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_36 (4)-Photoroom.png create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_36 (4)-Photoroom.png.meta create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_36 (6)-Photoroom.png create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_36 (6)-Photoroom.png.meta create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_37 (7)-Photoroom.png create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜ค์ „ 11_53_37 (7)-Photoroom.png.meta create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜คํ›„ 01_03_59-Photoroom.png create mode 100644 Assets/My project/Memory Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 23์ผ ์˜คํ›„ 01_03_59-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 17์ผ ์˜คํ›„ 01_28_13 (1)-Photoroom-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 17์ผ ์˜คํ›„ 01_28_13 (1)-Photoroom-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜ค์ „ 10_08_26 (1)-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜ค์ „ 10_08_26 (1)-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜ค์ „ 10_08_26 (2)-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜ค์ „ 10_08_26 (2)-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 02_56_32-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 02_56_32-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_38_40-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_38_40-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_49_28 (6)-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_49_28 (6)-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_06_21-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_06_21-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_06_37-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_06_37-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_33_47-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 04_33_47-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 05_27_49 (1)-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 05_27_49 (1)-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 05_27_52 (7)-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 05_27_52 (7)-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 05_27_52 (8)-Photoroom.png create mode 100644 Assets/My project/maze Scripts/Image/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 05_27_52 (8)-Photoroom.png.meta create mode 100644 Assets/My project/maze Scripts/Materials/Wall.mat create mode 100644 Assets/My project/maze Scripts/Materials/Wall.mat.meta create mode 100644 Assets/My project/maze Scripts/Prefab/MazeUIRoot.prefab create mode 100644 Assets/My project/maze Scripts/Prefab/MazeUIRoot.prefab.meta create mode 100644 Assets/My project/maze Scripts/Ui/FootprintEffect.cs create mode 100644 Assets/My project/maze Scripts/Ui/FootprintEffect.cs.meta create mode 100644 Assets/My project/maze Scripts/Ui/MazeMixedUIPanelFollower.cs create mode 100644 Assets/My project/maze Scripts/Ui/MazeMixedUIPanelFollower.cs.meta create mode 100644 Assets/My project/maze Scripts/Ui/MovingMazeWall.cs create mode 100644 Assets/My project/maze Scripts/Ui/MovingMazeWall.cs.meta create mode 100644 Assets/My project/shell Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_41_52-Photoroom.png create mode 100644 Assets/My project/shell Scripts/lmage/ChatGPT Image 2026๋…„ 6์›” 22์ผ ์˜คํ›„ 03_41_52-Photoroom.png.meta diff --git a/Assets/My project/Fonts/Pretendard-Black SDF.asset b/Assets/My project/Fonts/Pretendard-Black SDF.asset index 77056f6e..0c0f60d5 100644 --- a/Assets/My project/Fonts/Pretendard-Black SDF.asset +++ b/Assets/My project/Fonts/Pretendard-Black SDF.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:416ee431de75a308492db33c9dfd1ac9327fa2de91f3b6237de8675601c4fd0e -size 41425872 +oid sha256:8bd3b336696b6ef91fd7881fc4e2b0f7bd00b231ae68b0ef4a9108fd4c3b669f +size 7883524 diff --git a/Assets/My project/Inventory/Prefabs/InventoryMiniUI.prefab.meta b/Assets/My project/Inventory/Prefabs/InventoryMiniUI.prefab.meta index 4ff5fb16..02c69322 100644 --- a/Assets/My project/Inventory/Prefabs/InventoryMiniUI.prefab.meta +++ b/Assets/My project/Inventory/Prefabs/InventoryMiniUI.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b48b2192c45050d4592c9211d7817e28 +guid: f0db9ef8810ed2e4fad874ce8a39d817 PrefabImporter: externalObjects: {} userData: diff --git a/Assets/My project/Inventory/Scripts/InventoryItemType.cs b/Assets/My project/Inventory/Scripts/InventoryItemType.cs deleted file mode 100644 index 00aea677..00000000 --- a/Assets/My project/Inventory/Scripts/InventoryItemType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using UnityEngine; - -/// -/// ์ธ๋ฒคํ† ๋ฆฌ์—์„œ ๊ด€๋ฆฌํ•  ์•„์ดํ…œ ์ข…๋ฅ˜์ž…๋‹ˆ๋‹ค. -/// ๋ฌธ์ž์—ด ๋Œ€์‹  enum์„ ์‚ฌ์šฉํ•˜๋ฉด ์˜คํƒ€๋กœ ์ธํ•œ ๋ฒ„๊ทธ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -/// -public enum InventoryItemType -{ - Fish, - OldCompass, - Trash, - Bottle, - MemoryPiece -} diff --git a/Assets/My project/Inventory/Scripts/InventoryItemType.cs.meta b/Assets/My project/Inventory/Scripts/InventoryItemType.cs.meta deleted file mode 100644 index 709ea638..00000000 --- a/Assets/My project/Inventory/Scripts/InventoryItemType.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 9fc654a578c829244936abec2cf40091 \ No newline at end of file diff --git a/Assets/My project/Inventory/Scripts/InventoryManager.cs b/Assets/My project/Inventory/Scripts/InventoryManager.cs index 2d5c0c14..eeacaa8b 100644 --- a/Assets/My project/Inventory/Scripts/InventoryManager.cs +++ b/Assets/My project/Inventory/Scripts/InventoryManager.cs @@ -3,6 +3,33 @@ using UnityEngine; using UnityEngine.Events; +/// +/// ์ธ๋ฒคํ† ๋ฆฌ์—์„œ ๊ด€๋ฆฌํ•  ์•„์ดํ…œ ์ข…๋ฅ˜์ž…๋‹ˆ๋‹ค. +/// ์•„์ดํ…œ์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ์—ฌ๊ธฐ์— enum ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜๊ณ , InventoryManager์˜ itemDefinitions์—๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. +/// +public enum InventoryItemType +{ + Fish, + OldCompass, + Trash, + Bottle, + MemoryPiece +} + +/// +/// ์ธ๋ฒคํ† ๋ฆฌ UI ํ•„ํ„ฐ/์ •๋ ฌ์— ์‚ฌ์šฉํ•  ๊ฐ„๋‹จํ•œ ์นดํ…Œ๊ณ ๋ฆฌ์ž…๋‹ˆ๋‹ค. +/// +public enum InventoryItemCategory +{ + All, + Consumable, + Quest, + Collectible, + KeyItem, + Material, + Other +} + [Serializable] public class InventoryItemStack { @@ -11,40 +38,209 @@ public class InventoryItemStack } [Serializable] -public class InventoryItemChangedEvent : UnityEvent +public class InventoryItemDefinition { + [Header("Identity")] + public InventoryItemType itemType; + public string displayName; + [TextArea(2, 5)] public string description; + [TextArea(1, 3)] public string goalHint; + + [Header("Visuals")] + public Sprite icon; + public Sprite slotBackground; + public bool importantItem; + + [Header("Rules")] + public InventoryItemCategory category = InventoryItemCategory.Other; + [Tooltip("0 ์ดํ•˜์ด๋ฉด InventoryManager์˜ ๊ธฐ๋ณธ ์ตœ๋Œ€ ์†Œ์ง€ ์ˆ˜๋Ÿ‰์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")] + public int maxCount = 99; + public bool usable = false; + public bool consumeOnUse = true; + public bool requireUseConfirmation = false; + public string useLabel = "์‚ฌ์šฉ"; + public string useSuccessMessage; + public string useFailMessage; + + [Header("Audio")] + public AudioClip acquisitionClip; + public AudioClip useClip; + + [Header("Slot Display Override")] + public bool overrideSlotDisplaySettings = false; + public bool hideWhenZero = false; + public bool dimWhenZero = true; + [Range(0f, 1f)] public float zeroAlpha = 0.35f; + [Range(0f, 1f)] public float ownedAlpha = 1f; + + public string SafeDisplayName => string.IsNullOrWhiteSpace(displayName) ? itemType.ToString() : displayName; } +[Serializable] +public class InventoryLogEntry +{ + public InventoryItemType itemType; + public int amount; + public string action; + public string displayName; + public string timeText; + + public InventoryLogEntry() { } + + public InventoryLogEntry(InventoryItemType itemType, int amount, string action, string displayName) + { + this.itemType = itemType; + this.amount = amount; + this.action = action; + this.displayName = displayName; + timeText = DateTime.Now.ToString("HH:mm:ss"); + } + + public override string ToString() + { + string amountText = amount > 0 ? $" x{amount}" : string.Empty; + return $"[{timeText}] {displayName}{amountText} {action}"; + } +} + +[Serializable] public class InventoryItemChangedEvent : UnityEvent { } +[Serializable] public class InventoryItemAmountEvent : UnityEvent { } +[Serializable] public class InventoryStringEvent : UnityEvent { } +[Serializable] public class InventoryProgressEvent : UnityEvent { } + /// /// ์‹ค์ œ ์•„์ดํ…œ ๊ฐœ์ˆ˜๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ค‘์‹ฌ ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. -/// UI๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ , ์•„์ดํ…œ ๊ฐœ์ˆ˜ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๋งŒ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. +/// ๊ธฐ๋Šฅ: ์‹ฑ๊ธ€ํ†ค, ์ €์žฅ/๋ถˆ๋Ÿฌ์˜ค๊ธฐ, ์ตœ๋Œ€ ์†Œ์ง€ ์ˆ˜๋Ÿ‰, ์‚ฌ์šฉ, ๋ถ€์กฑ ์•ˆ๋‚ด, ํš๋“ ๋กœ๊ทธ, ๊ธฐ์–ต์˜ ์กฐ๊ฐ ์ง„ํ–‰๋„ ์ด๋ฒคํŠธ. /// [DisallowMultipleComponent] public class InventoryManager : MonoBehaviour { + public static InventoryManager Instance { get; private set; } + + [Header("Singleton")] + [SerializeField] private bool useSingleton = true; + [SerializeField] private bool dontDestroyOnLoad = true; + [SerializeField] private bool destroyDuplicateManagers = true; + [Header("Initial Items")] [SerializeField] private List initialItems = new List(); + [Header("Item Definitions")] + [SerializeField] private List itemDefinitions = new List() + { + new InventoryItemDefinition { itemType = InventoryItemType.Fish, displayName = "์ƒ์„ ", description = "๊ณ ์–‘์ด ํ•ฉ์ฐฝ๋‹จ์ด ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค.", goalHint = "๊ณ ์–‘์ด์—๊ฒŒ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", category = InventoryItemCategory.Consumable, maxCount = 99, usable = true, consumeOnUse = true, useSuccessMessage = "์ƒ์„ ์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค." }, + new InventoryItemDefinition { itemType = InventoryItemType.OldCompass, displayName = "๋‚ก์€ ๋‚˜์นจ๋ฐ˜", description = "๋ฏธ๋กœ์—์„œ ๊ธธ์„ ์ฐพ๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.", goalHint = "๋ฏธ๋กœ์—์„œ ํžŒํŠธ๋ฅผ ๋ณผ ๋•Œ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", category = InventoryItemCategory.KeyItem, maxCount = 1, usable = true, consumeOnUse = false, importantItem = true, useSuccessMessage = "๋‚˜์นจ๋ฐ˜์ด ๋ฐฉํ–ฅ์„ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค." }, + new InventoryItemDefinition { itemType = InventoryItemType.Trash, displayName = "์“ฐ๋ ˆ๊ธฐ", description = "๋‚š์‹œํ„ฐ๋ฅผ ์ •ํ™”ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", goalHint = "์ •ํ™” ์žฅ์น˜์— ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", category = InventoryItemCategory.Material, maxCount = 99, usable = true, consumeOnUse = true, useSuccessMessage = "์“ฐ๋ ˆ๊ธฐ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค." }, + new InventoryItemDefinition { itemType = InventoryItemType.Bottle, displayName = "๋งˆ๋ฒ•๋ณ‘", description = "๋ฐ”๋‹ค ์†์—์„œ ๋ฐœ๊ฒฌํ•œ ์ˆ˜์ƒํ•œ ๋ณ‘์ž…๋‹ˆ๋‹ค.", goalHint = "ํŠน์ • ์ด๋ฒคํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", category = InventoryItemCategory.KeyItem, maxCount = 1, usable = true, consumeOnUse = false, importantItem = true, requireUseConfirmation = true, useSuccessMessage = "๋งˆ๋ฒ•๋ณ‘์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค." }, + new InventoryItemDefinition { itemType = InventoryItemType.MemoryPiece, displayName = "๊ธฐ์–ต์˜ ์กฐ๊ฐ", description = "์ œํŽ˜ํ† ๋ฅผ ๊ตฌ์ถœํ•˜๊ธฐ ์œ„ํ•œ ์ค‘์š”ํ•œ ์กฐ๊ฐ์ž…๋‹ˆ๋‹ค.", goalHint = "๋ชจ๋“  ์กฐ๊ฐ์„ ๋ชจ์œผ๋ฉด ์ค‘์š”ํ•œ ์ด๋ฒคํŠธ๊ฐ€ ์—ด๋ฆฝ๋‹ˆ๋‹ค.", category = InventoryItemCategory.Quest, maxCount = 5, usable = false, consumeOnUse = false, importantItem = true } + }; + + [Header("Limits")] + [Tooltip("0 ์ดํ•˜์ด๋ฉด ๊ธฐ๋ณธ ์ตœ๋Œ€ ์ˆ˜๋Ÿ‰ ์ œํ•œ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•„์ดํ…œ ์ •์˜์˜ maxCount๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ๊ฒƒ์ด ์šฐ์„ ๋ฉ๋‹ˆ๋‹ค.")] + [SerializeField] private int defaultMaxCount = 999; + + [Header("Memory Piece Progress")] + [SerializeField] private InventoryItemType memoryPieceItemType = InventoryItemType.MemoryPiece; + [Min(1)] [SerializeField] private int memoryPieceTargetCount = 5; + + [Header("Save / Load")] + [SerializeField] private bool loadOnAwake = true; + [SerializeField] private bool saveOnChange = true; + [Tooltip("Persistent Pickup/Reward ์™„๋ฃŒ ์ƒํƒœ๋Š” ์•„์ดํ…œ ์ˆ˜๋Ÿ‰ ์ž๋™ ์ €์žฅ์„ ๊บผ๋„ ์ €์žฅํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.")] + [SerializeField] private bool forceSavePersistentState = true; + [SerializeField] private string saveKey = "Inventory_SaveData"; + + [Header("Recent Log")] + [SerializeField] private bool keepRecentLogs = true; + [SerializeField] private int maxRecentLogCount = 5; + [Header("Events")] public InventoryItemChangedEvent onItemCountChanged; + public InventoryItemAmountEvent onItemAdded; + public InventoryItemAmountEvent onItemRemoved; + public InventoryItemChangedEvent onItemUsed; + public InventoryStringEvent onMessageRequested; + public InventoryProgressEvent onMemoryPieceProgressChanged; + public UnityEvent onMemoryPieceCompleted; public UnityEvent onInventoryChanged; [Header("Debug")] [SerializeField] private bool showDebugLog = true; private readonly Dictionary itemCounts = new Dictionary(); + private readonly HashSet consumedPersistentKeys = new HashSet(); + private readonly List recentLogs = new List(); public event Action ItemCountChanged; + public event Action ItemAdded; + public event Action ItemRemoved; + public event Action ItemUsed; public event Action InventoryChanged; + public event Action MessageRequested; + public event Action LogAdded; + public event Action MemoryPieceProgressChanged; + public event Action MemoryPieceCompleted; + + private bool memoryPieceCompletedNotified; + + public int MemoryPieceTargetCount => memoryPieceTargetCount; + public InventoryItemType MemoryPieceItemType => memoryPieceItemType; private void Awake() { + if (useSingleton) + { + if (Instance != null && Instance != this) + { + if (showDebugLog) + Debug.LogWarning($"[InventoryManager] ์ค‘๋ณต InventoryManager ๋ฐœ๊ฒฌ: {name}"); + + if (destroyDuplicateManagers) + { + Destroy(gameObject); + return; + } + } + else + { + Instance = this; + + if (dontDestroyOnLoad) + DontDestroyOnLoad(gameObject); + } + } + + EnsureItemDefinitions(); InitializeFromInspector(); + + if (loadOnAwake) + LoadInventory(false); } private void Start() { NotifyAllItemsChanged(); + NotifyMemoryPieceProgress(); + } + + private void EnsureItemDefinitions() + { + if (itemDefinitions == null) + itemDefinitions = new List(); + + foreach (InventoryItemType itemType in Enum.GetValues(typeof(InventoryItemType))) + { + if (GetDefinitionInternal(itemType) == null) + { + itemDefinitions.Add(new InventoryItemDefinition + { + itemType = itemType, + displayName = itemType.ToString(), + maxCount = defaultMaxCount, + category = InventoryItemCategory.Other + }); + } + } } private void InitializeFromInspector() @@ -60,7 +256,7 @@ private void InitializeFromInspector() if (stack == null) continue; - itemCounts[stack.itemType] = Mathf.Max(0, stack.count); + itemCounts[stack.itemType] = ClampCount(stack.itemType, stack.count); } } @@ -71,14 +267,43 @@ public void AddItem(InventoryItemType itemType) public void AddItem(InventoryItemType itemType, int amount) { - if (amount <= 0) - return; + AddItemAndGetAddedAmount(itemType, amount); + } - int newCount = GetItemCount(itemType) + amount; - SetItemCount(itemType, newCount); + /// + /// ์‹ค์ œ๋กœ ์ถ”๊ฐ€๋œ ๊ฐœ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ตœ๋Œ€ ์†Œ์ง€ ์ˆ˜๋Ÿ‰์— ๋ง‰ํžˆ๋ฉด ์š”์ฒญ๋Ÿ‰๋ณด๋‹ค ์ ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + /// + public int AddItemAndGetAddedAmount(InventoryItemType itemType, int amount) + { + if (amount <= 0) + return 0; + + int previousCount = GetItemCount(itemType); + int newCount = ClampCount(itemType, previousCount + amount); + int addedAmount = Mathf.Max(0, newCount - previousCount); + + if (addedAmount <= 0) + { + RequestMessage($"{GetDisplayName(itemType)}์€(๋Š”) ๋” ์ด์ƒ ๊ฐ€์งˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + + if (showDebugLog) + Debug.Log($"[InventoryManager] {itemType} ์ตœ๋Œ€ ์†Œ์ง€ ์ˆ˜๋Ÿ‰ ๋„๋‹ฌ: {previousCount}"); + + return 0; + } + + itemCounts[itemType] = newCount; + NotifyItemChanged(itemType, newCount); + NotifyItemAdded(itemType, addedAmount, newCount); + AddLog(itemType, addedAmount, "ํš๋“"); + + if (saveOnChange) + SaveInventory(); if (showDebugLog) - Debug.Log($"[InventoryManager] {itemType} +{amount} => {newCount}"); + Debug.Log($"[InventoryManager] {itemType} +{addedAmount} => {newCount}"); + + return addedAmount; } public bool RemoveItem(InventoryItemType itemType) @@ -95,19 +320,84 @@ public bool RemoveItem(InventoryItemType itemType, int amount) if (current < amount) { + RequestMessage(GetInsufficientMessage(itemType, amount)); + if (showDebugLog) Debug.LogWarning($"[InventoryManager] {itemType} ๋ถ€์กฑ: ํ˜„์žฌ {current}, ํ•„์š” {amount}"); return false; } - SetItemCount(itemType, current - amount); + int newCount = current - amount; + itemCounts[itemType] = newCount; + NotifyItemChanged(itemType, newCount); + NotifyItemRemoved(itemType, amount, newCount); + AddLog(itemType, amount, "์†Œ๋ชจ"); + + if (saveOnChange) + SaveInventory(); + + return true; + } + + public bool UseItem(InventoryItemType itemType) + { + return UseItem(itemType, 1); + } + + public bool UseItem(InventoryItemType itemType, int amount) + { + InventoryItemDefinition definition = GetDefinition(itemType); + bool consume = definition == null || definition.consumeOnUse; + return TryUseItem(itemType, amount, consume, true, true); + } + + /// + /// ์•„์ดํ…œ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฒŒ์ž„ ํšจ๊ณผ๋Š” ์„ฑ๊ณต ์ด๋ฒคํŠธ๋‚˜ ์™ธ๋ถ€ ์Šคํฌ๋ฆฝํŠธ์—์„œ ์ฒ˜๋ฆฌํ•˜์„ธ์š”. + /// consume=false์ด๋ฉด ๋ณด์œ  ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•˜๊ณ  ๊ฐœ์ˆ˜๋Š” ์ค„์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + /// showMessages=false์ด๋ฉด ๋ถ€์กฑ/์„ฑ๊ณต ์•ˆ๋‚ด๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + /// + public bool TryUseItem(InventoryItemType itemType, int amount, bool consume, bool showMessages = true, bool addUseLog = true) + { + amount = Mathf.Max(1, amount); + + if (!HasItem(itemType, amount)) + { + if (showMessages) + RequestMessage(GetInsufficientMessage(itemType, amount)); + + return false; + } + + if (consume && !RemoveItem(itemType, amount)) + return false; + + ItemUsed?.Invoke(itemType, GetItemCount(itemType)); + onItemUsed?.Invoke(itemType, GetItemCount(itemType)); + + // consume=true์ธ ๊ฒฝ์šฐ RemoveItem์—์„œ ์ด๋ฏธ "์†Œ๋ชจ" ๋กœ๊ทธ๊ฐ€ ๋‚จ์Šต๋‹ˆ๋‹ค. + // consume=false์ธ ๋น ๋ฅธ ์‚ฌ์šฉ(์˜ˆ: ๋‚˜์นจ๋ฐ˜ ํ™•์ธ)์€ ๋ณ„๋„ "์‚ฌ์šฉ" ๋กœ๊ทธ๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค. + if (addUseLog && !consume) + AddLog(itemType, amount, "์‚ฌ์šฉ"); + + InventoryItemDefinition definition = GetDefinition(itemType); + if (showMessages) + { + string message = definition != null && !string.IsNullOrWhiteSpace(definition.useSuccessMessage) + ? definition.useSuccessMessage + : $"{GetDisplayName(itemType)}์„(๋ฅผ) ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค."; + RequestMessage(message); + } + + if (saveOnChange) + SaveInventory(); + return true; } public void SetItemCount(InventoryItemType itemType, int count) { - int clampedCount = Mathf.Max(0, count); + int clampedCount = ClampCount(itemType, count); int previousCount = GetItemCount(itemType); if (previousCount == clampedCount) @@ -115,6 +405,9 @@ public void SetItemCount(InventoryItemType itemType, int count) itemCounts[itemType] = clampedCount; NotifyItemChanged(itemType, clampedCount); + + if (saveOnChange) + SaveInventory(); } public int GetItemCount(InventoryItemType itemType) @@ -135,17 +428,331 @@ public bool HasItem(InventoryItemType itemType, int requiredAmount) return GetItemCount(itemType) >= Mathf.Max(1, requiredAmount); } + public int GetMaxCount(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + + if (definition != null && definition.maxCount > 0) + return definition.maxCount; + + return Mathf.Max(0, defaultMaxCount); + } + + /// + /// ํ˜„์žฌ ์†Œ์ง€ ์ˆ˜๋Ÿ‰๊ณผ ์ตœ๋Œ€ ์†Œ์ง€ ์ˆ˜๋Ÿ‰์„ ๊ธฐ์ค€์œผ๋กœ ์‹ค์ œ๋กœ ๋” ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” ๊ฐœ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + /// maxCount๊ฐ€ 0 ์ดํ•˜์ด๋ฉด ์ œํ•œ ์—†์Œ์œผ๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. + /// + public int GetAddableAmount(InventoryItemType itemType, int requestedAmount) + { + requestedAmount = Mathf.Max(0, requestedAmount); + if (requestedAmount <= 0) + return 0; + + int maxCount = GetMaxCount(itemType); + if (maxCount <= 0) + return requestedAmount; + + int current = GetItemCount(itemType); + return Mathf.Clamp(maxCount - current, 0, requestedAmount); + } + + public bool CanAddItem(InventoryItemType itemType, int amount = 1, bool requireFullAmount = false) + { + amount = Mathf.Max(1, amount); + int addableAmount = GetAddableAmount(itemType, amount); + return requireFullAmount ? addableAmount >= amount : addableAmount > 0; + } + + public InventoryItemDefinition GetDefinition(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinitionInternal(itemType); + return definition; + } + + private InventoryItemDefinition GetDefinitionInternal(InventoryItemType itemType) + { + if (itemDefinitions == null) + return null; + + for (int i = 0; i < itemDefinitions.Count; i++) + { + if (itemDefinitions[i] != null && itemDefinitions[i].itemType == itemType) + return itemDefinitions[i]; + } + + return null; + } + + public string GetDisplayName(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null && !string.IsNullOrWhiteSpace(definition.displayName) + ? definition.displayName + : itemType.ToString(); + } + + public string GetDescription(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null ? definition.description : string.Empty; + } + + public string GetGoalHint(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null ? definition.goalHint : string.Empty; + } + + public Sprite GetIcon(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null ? definition.icon : null; + } + + public Sprite GetSlotBackground(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null ? definition.slotBackground : null; + } + + public AudioClip GetAcquisitionClip(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null ? definition.acquisitionClip : null; + } + + public AudioClip GetUseClip(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null ? definition.useClip : null; + } + + public bool IsImportantItem(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null && definition.importantItem; + } + + public InventoryItemCategory GetCategory(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null ? definition.category : InventoryItemCategory.Other; + } + + public bool IsUsable(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null && definition.usable; + } + + public bool RequiresUseConfirmation(InventoryItemType itemType) + { + InventoryItemDefinition definition = GetDefinition(itemType); + return definition != null && definition.requireUseConfirmation; + } + + public string GetInsufficientMessage(InventoryItemType itemType, int requiredAmount) + { + int current = GetItemCount(itemType); + return $"{GetDisplayName(itemType)}์ด(๊ฐ€) ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”: {Mathf.Max(1, requiredAmount)}๊ฐœ / ํ˜„์žฌ: {current}๊ฐœ"; + } + + public IReadOnlyList GetRecentLogs() + { + return new List(recentLogs); + } + + public IReadOnlyDictionary GetAllItemCounts() + { + return new Dictionary(itemCounts); + } + public void ClearInventory() { foreach (InventoryItemType itemType in Enum.GetValues(typeof(InventoryItemType))) itemCounts[itemType] = 0; NotifyAllItemsChanged(); + NotifyMemoryPieceProgress(); + + if (saveOnChange) + SaveInventory(); } - public IReadOnlyDictionary GetAllItemCounts() + public void ResetToInitialItems() { - return itemCounts; + InitializeFromInspector(); + NotifyAllItemsChanged(); + NotifyMemoryPieceProgress(); + + if (saveOnChange) + SaveInventory(); + } + + public bool IsPersistentKeyConsumed(string key) + { + if (string.IsNullOrWhiteSpace(key)) + return false; + + return consumedPersistentKeys.Contains(key); + } + + public void MarkPersistentKeyConsumed(string key) + { + if (string.IsNullOrWhiteSpace(key)) + return; + + if (consumedPersistentKeys.Add(key) && (saveOnChange || forceSavePersistentState)) + SaveInventory(); + } + + public void ResetPersistentKey(string key) + { + if (string.IsNullOrWhiteSpace(key)) + return; + + if (consumedPersistentKeys.Remove(key) && (saveOnChange || forceSavePersistentState)) + SaveInventory(); + } + + public void RequestMessage(string message) + { + if (string.IsNullOrWhiteSpace(message)) + return; + + MessageRequested?.Invoke(message); + onMessageRequested?.Invoke(message); + + if (showDebugLog) + Debug.Log($"[InventoryManager] Message: {message}"); + } + + private void AddLog(InventoryItemType itemType, int amount, string action) + { + if (!keepRecentLogs) + return; + + InventoryLogEntry entry = new InventoryLogEntry(itemType, amount, action, GetDisplayName(itemType)); + recentLogs.Insert(0, entry); + + while (recentLogs.Count > Mathf.Max(1, maxRecentLogCount)) + recentLogs.RemoveAt(recentLogs.Count - 1); + + LogAdded?.Invoke(entry); + } + + public void SaveInventory() + { + InventorySaveData saveData = new InventorySaveData(); + + foreach (KeyValuePair pair in itemCounts) + { + saveData.items.Add(new InventorySavedItemCount + { + itemTypeName = pair.Key.ToString(), + count = Mathf.Max(0, pair.Value) + }); + } + + foreach (string key in consumedPersistentKeys) + saveData.consumedPersistentKeys.Add(key); + + string json = JsonUtility.ToJson(saveData); + PlayerPrefs.SetString(saveKey, json); + PlayerPrefs.Save(); + + if (showDebugLog) + Debug.Log("[InventoryManager] ์ธ๋ฒคํ† ๋ฆฌ ์ €์žฅ ์™„๋ฃŒ"); + } + + public bool LoadInventory() + { + return LoadInventory(true); + } + + private bool LoadInventory(bool notify) + { + if (!PlayerPrefs.HasKey(saveKey)) + return false; + + string json = PlayerPrefs.GetString(saveKey, string.Empty); + if (string.IsNullOrWhiteSpace(json)) + return false; + + InventorySaveData saveData; + + try + { + saveData = JsonUtility.FromJson(json); + } + catch (Exception exception) + { + Debug.LogWarning($"[InventoryManager] ์ €์žฅ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ ์‹คํŒจ: {exception.Message}"); + return false; + } + + InitializeFromInspector(); + consumedPersistentKeys.Clear(); + + if (saveData != null) + { + for (int i = 0; i < saveData.items.Count; i++) + { + InventorySavedItemCount savedItem = saveData.items[i]; + if (savedItem == null || string.IsNullOrWhiteSpace(savedItem.itemTypeName)) + continue; + + if (Enum.TryParse(savedItem.itemTypeName, out InventoryItemType itemType)) + itemCounts[itemType] = ClampCount(itemType, savedItem.count); + } + + for (int i = 0; i < saveData.consumedPersistentKeys.Count; i++) + { + string key = saveData.consumedPersistentKeys[i]; + if (!string.IsNullOrWhiteSpace(key)) + consumedPersistentKeys.Add(key); + } + } + + if (notify) + { + NotifyAllItemsChanged(); + NotifyMemoryPieceProgress(); + } + + if (showDebugLog) + Debug.Log("[InventoryManager] ์ธ๋ฒคํ† ๋ฆฌ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์™„๋ฃŒ"); + + return true; + } + + public void DeleteSavedInventory() + { + PlayerPrefs.DeleteKey(saveKey); + PlayerPrefs.Save(); + + consumedPersistentKeys.Clear(); + recentLogs.Clear(); + memoryPieceCompletedNotified = false; + + // ResetToInitialItems()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด saveOnChange๊ฐ€ true์ผ ๋•Œ ์‚ญ์ œ ์งํ›„ ๋‹ค์‹œ ์ €์žฅ ํŒŒ์ผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ + // ์—ฌ๊ธฐ์„œ๋Š” ์ง์ ‘ ์ดˆ๊ธฐํ™”์™€ ์•Œ๋ฆผ๋งŒ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. + InitializeFromInspector(); + NotifyAllItemsChanged(); + NotifyMemoryPieceProgress(); + + RequestMessage("์ธ๋ฒคํ† ๋ฆฌ ์ €์žฅ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + + private int ClampCount(InventoryItemType itemType, int count) + { + int safeCount = Mathf.Max(0, count); + int maxCount = GetMaxCount(itemType); + + if (maxCount > 0) + safeCount = Mathf.Min(safeCount, maxCount); + + return safeCount; } private void NotifyItemChanged(InventoryItemType itemType, int count) @@ -155,6 +762,21 @@ private void NotifyItemChanged(InventoryItemType itemType, int count) onItemCountChanged?.Invoke(itemType, count); onInventoryChanged?.Invoke(); + + if (itemType == memoryPieceItemType) + NotifyMemoryPieceProgress(); + } + + private void NotifyItemAdded(InventoryItemType itemType, int addedAmount, int totalCount) + { + ItemAdded?.Invoke(itemType, addedAmount, totalCount); + onItemAdded?.Invoke(itemType, addedAmount, totalCount); + } + + private void NotifyItemRemoved(InventoryItemType itemType, int removedAmount, int totalCount) + { + ItemRemoved?.Invoke(itemType, removedAmount, totalCount); + onItemRemoved?.Invoke(itemType, removedAmount, totalCount); } private void NotifyAllItemsChanged() @@ -169,17 +791,83 @@ private void NotifyAllItemsChanged() onInventoryChanged?.Invoke(); } + private void NotifyMemoryPieceProgress() + { + int current = GetItemCount(memoryPieceItemType); + int target = Mathf.Max(1, memoryPieceTargetCount); + + MemoryPieceProgressChanged?.Invoke(current, target); + onMemoryPieceProgressChanged?.Invoke(current, target); + + if (current < target) + { + memoryPieceCompletedNotified = false; + return; + } + + if (!memoryPieceCompletedNotified) + { + memoryPieceCompletedNotified = true; + MemoryPieceCompleted?.Invoke(); + onMemoryPieceCompleted?.Invoke(); + } + } + + // ๊ฐœ๋ฐœ์šฉ ๋ฒ„ํŠผ/UnityEvent ์—ฐ๊ฒฐ์šฉ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. + public void DebugAddFish() => AddItem(InventoryItemType.Fish, 1); + public void DebugAddCompass() => AddItem(InventoryItemType.OldCompass, 1); + public void DebugAddTrash() => AddItem(InventoryItemType.Trash, 1); + public void DebugAddBottle() => AddItem(InventoryItemType.Bottle, 1); + public void DebugAddMemoryPiece() => AddItem(InventoryItemType.MemoryPiece, 1); + public void DebugClearInventory() => ClearInventory(); + public void DebugDeleteSave() => DeleteSavedInventory(); + #if UNITY_EDITOR private void OnValidate() { - if (initialItems == null) - return; + if (defaultMaxCount < 0) + defaultMaxCount = 0; - for (int i = 0; i < initialItems.Count; i++) + if (memoryPieceTargetCount < 1) + memoryPieceTargetCount = 1; + + if (maxRecentLogCount < 1) + maxRecentLogCount = 1; + + if (string.IsNullOrWhiteSpace(saveKey)) + saveKey = "Inventory_SaveData"; + + if (initialItems != null) { - if (initialItems[i] != null) - initialItems[i].count = Mathf.Max(0, initialItems[i].count); + for (int i = 0; i < initialItems.Count; i++) + { + if (initialItems[i] != null) + initialItems[i].count = Mathf.Max(0, initialItems[i].count); + } + } + + if (itemDefinitions != null) + { + for (int i = 0; i < itemDefinitions.Count; i++) + { + if (itemDefinitions[i] != null) + itemDefinitions[i].maxCount = Mathf.Max(0, itemDefinitions[i].maxCount); + } } } #endif + + [Serializable] + private class InventorySaveData + { + public List items = new List(); + public List consumedPersistentKeys = new List(); + } + + [Serializable] + private class InventorySavedItemCount + { + public string itemTypeName; + public int count; + } } diff --git a/Assets/My project/Inventory/Scripts/InventorySlotUI.cs b/Assets/My project/Inventory/Scripts/InventorySlotUI.cs index fbbd8b76..a8644876 100644 --- a/Assets/My project/Inventory/Scripts/InventorySlotUI.cs +++ b/Assets/My project/Inventory/Scripts/InventorySlotUI.cs @@ -4,12 +4,19 @@ using UnityEngine.EventSystems; using UnityEngine.UI; +public enum InventorySlotClickMode +{ + None, + ShowDetail, + RequestUse +} + /// /// ์ธ๋ฒคํ† ๋ฆฌ ์Šฌ๋กฏ ํ•˜๋‚˜๋ฅผ ๋‹ด๋‹นํ•˜๋Š” UI ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. -/// FishSlot, CompassSlot, TrashSlot, BottleSlot ๋“ฑ์— ๊ฐ๊ฐ ๋ถ™์ž…๋‹ˆ๋‹ค. +/// VR ํฌ์ธํ„ฐ Hover/Select, NEW ๋ฐฐ์ง€, 0๊ฐœ ํ๋ฆผ ํ‘œ์‹œ, ์ค‘์š” ์•„์ดํ…œ ๊ฐ•์กฐ, ์ƒ์„ธ๋ณด๊ธฐ/์‚ฌ์šฉ ํด๋ฆญ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. /// [DisallowMultipleComponent] -public class InventorySlotUI : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, ISelectHandler, IDeselectHandler +public class InventorySlotUI : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler, ISelectHandler, IDeselectHandler, ISubmitHandler { [Header("Item")] [SerializeField] private InventoryItemType itemType; @@ -20,30 +27,75 @@ public class InventorySlotUI : MonoBehaviour, IPointerEnterHandler, IPointerExit [SerializeField] private TMP_Text countText; [SerializeField] private GameObject newBadge; [SerializeField] private CanvasGroup canvasGroup; + [SerializeField] private GameObject importantGlowObject; + [SerializeField] private GameObject filteredOutOverlay; - [Header("Tooltip")] + [Header("Tooltip / Parent UI")] [SerializeField] private InventoryTooltipUI tooltipUI; + [SerializeField] private InventoryUI inventoryUI; + + [Header("Click")] + [SerializeField] private InventorySlotClickMode clickMode = InventorySlotClickMode.ShowDetail; [Header("Settings")] + [Tooltip("VR์—์„œ๋Š” false ๊ถŒ์žฅ. ์Šฌ๋กฏ์ด ์‚ฌ๋ผ์ง€๋ฉด Ray ์กฐ์ž‘ ์œ„์น˜๊ฐ€ ๋ฐ”๋€Œ์–ด ๋ถˆํŽธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")] [SerializeField] private bool hideWhenZero = false; [SerializeField] private bool dimWhenZero = true; [SerializeField] private float zeroAlpha = 0.35f; [SerializeField] private float ownedAlpha = 1f; + [SerializeField] private float filteredAlpha = 0.15f; + [SerializeField] private bool showCountWhenZero = true; + [SerializeField] private bool showMaxCount = false; [SerializeField] private float newBadgeShowTime = 1.2f; + [Header("Hover Effect")] + [SerializeField] private bool useHoverScale = true; + [SerializeField] private float hoverScale = 1.08f; + [SerializeField] private bool bringToFrontOnHover = true; + private int currentCount; + private int currentMaxCount; + private bool filteredOut; private Coroutine newBadgeRoutine; + private Vector3 originalScale; + private InventoryItemDefinition definition; public InventoryItemType ItemType => itemType; public int CurrentCount => currentCount; + public InventoryItemCategory Category => definition != null ? definition.category : InventoryItemCategory.Other; private void Awake() { + originalScale = transform.localScale; + if (canvasGroup == null) canvasGroup = GetComponent(); + if (slotBackground == null) + slotBackground = GetComponent(); + + if (inventoryUI == null) + inventoryUI = GetComponentInParent(); + + if (tooltipUI == null) + tooltipUI = GetComponentInParent(); + if (newBadge != null) newBadge.SetActive(false); + + UpdateVisualState(); + } + + private void OnDisable() + { + HideNewBadge(); + HideTooltip(); + ResetHoverVisual(); + } + + public void SetInventoryUI(InventoryUI ui) + { + inventoryUI = ui; } public void SetItemType(InventoryItemType newItemType) @@ -51,10 +103,52 @@ public void SetItemType(InventoryItemType newItemType) itemType = newItemType; } + public void SetDefinition(InventoryItemDefinition newDefinition) + { + definition = newDefinition; + + if (definition == null) + { + UpdateVisualState(); + return; + } + + if (itemIcon != null) + { + itemIcon.sprite = definition.icon; + itemIcon.enabled = definition.icon != null; + } + + if (slotBackground != null && definition.slotBackground != null) + { + slotBackground.sprite = definition.slotBackground; + slotBackground.enabled = true; + } + + if (definition.overrideSlotDisplaySettings) + { + hideWhenZero = definition.hideWhenZero; + dimWhenZero = definition.dimWhenZero; + zeroAlpha = definition.zeroAlpha; + ownedAlpha = definition.ownedAlpha; + } + + if (importantGlowObject != null) + importantGlowObject.SetActive(definition.importantItem); + + UpdateVisualState(); + } + public void SetCount(int count, bool showNewBadge) + { + SetCount(count, showNewBadge, currentMaxCount); + } + + public void SetCount(int count, bool showNewBadge, int maxCount) { int previousCount = currentCount; currentCount = Mathf.Max(0, count); + currentMaxCount = Mathf.Max(0, maxCount); UpdateVisualState(); @@ -80,6 +174,23 @@ public void SetSlotBackground(Sprite sprite) slotBackground.enabled = sprite != null; } + public void SetFilteredOut(bool value) + { + filteredOut = value; + + if (filteredOutOverlay != null) + filteredOutOverlay.SetActive(value); + + UpdateVisualState(); + } + + public void SetHoverSettings(bool useScale, float scale, bool bringToFront) + { + useHoverScale = useScale; + hoverScale = Mathf.Max(1f, scale); + bringToFrontOnHover = bringToFront; + } + public void ShowNewBadge() { if (newBadge == null) @@ -106,7 +217,7 @@ public void HideNewBadge() private IEnumerator NewBadgeRoutine() { newBadge.SetActive(true); - yield return new WaitForSeconds(newBadgeShowTime); + yield return new WaitForSecondsRealtime(newBadgeShowTime); newBadge.SetActive(false); newBadgeRoutine = null; } @@ -116,39 +227,108 @@ private void UpdateVisualState() bool hasItem = currentCount > 0; if (countText != null) - countText.text = $"x{currentCount}"; + { + if (!showCountWhenZero && currentCount <= 0) + countText.text = string.Empty; + else if (showMaxCount && currentMaxCount > 0) + countText.text = $"x{currentCount}/{currentMaxCount}"; + else + countText.text = $"x{currentCount}"; + } if (hideWhenZero) gameObject.SetActive(hasItem); if (canvasGroup != null && dimWhenZero) - canvasGroup.alpha = hasItem ? ownedAlpha : zeroAlpha; + { + float targetAlpha = hasItem ? ownedAlpha : zeroAlpha; + if (filteredOut) + targetAlpha = Mathf.Min(targetAlpha, filteredAlpha); + + canvasGroup.alpha = targetAlpha; + canvasGroup.interactable = !filteredOut; + canvasGroup.blocksRaycasts = !filteredOut; + } } public void OnPointerEnter(PointerEventData eventData) { + ApplyHoverVisual(); ShowTooltip(); } public void OnPointerExit(PointerEventData eventData) { + ResetHoverVisual(); HideTooltip(); } + public void OnPointerClick(PointerEventData eventData) + { + ActivateSlot(); + } + public void OnSelect(BaseEventData eventData) { + ApplyHoverVisual(); ShowTooltip(); } public void OnDeselect(BaseEventData eventData) { + ResetHoverVisual(); HideTooltip(); } + public void OnSubmit(BaseEventData eventData) + { + ActivateSlot(); + } + + private void ActivateSlot() + { + if (filteredOut) + return; + + if (inventoryUI == null) + inventoryUI = GetComponentInParent(); + + if (inventoryUI == null) + return; + + switch (clickMode) + { + case InventorySlotClickMode.ShowDetail: + inventoryUI.ShowItemDetail(itemType); + break; + case InventorySlotClickMode.RequestUse: + inventoryUI.RequestUseItem(itemType, 1); + break; + } + } + + private void ApplyHoverVisual() + { + if (bringToFrontOnHover) + transform.SetAsLastSibling(); + + if (useHoverScale) + transform.localScale = originalScale * hoverScale; + } + + private void ResetHoverVisual() + { + if (useHoverScale) + transform.localScale = originalScale; + } + public void ShowTooltip() { + if (tooltipUI == null) + tooltipUI = GetComponentInParent(); + if (tooltipUI != null) - tooltipUI.ShowTooltip(itemType, currentCount); + tooltipUI.ShowTooltip(itemType, currentCount, currentMaxCount); } public void HideTooltip() diff --git a/Assets/My project/Inventory/Scripts/InventoryTooltipUI.cs b/Assets/My project/Inventory/Scripts/InventoryTooltipUI.cs index ef8f7dce..79c749ba 100644 --- a/Assets/My project/Inventory/Scripts/InventoryTooltipUI.cs +++ b/Assets/My project/Inventory/Scripts/InventoryTooltipUI.cs @@ -1,19 +1,10 @@ -using System; using System.Collections.Generic; using TMPro; using UnityEngine; -[Serializable] -public class InventoryTooltipEntry -{ - public InventoryItemType itemType; - public string displayName; - [TextArea(2, 4)] public string description; -} - /// /// ์•„์ดํ…œ ์Šฌ๋กฏ์— ๋งˆ์šฐ์Šค/VR ํฌ์ธํ„ฐ๊ฐ€ ์˜ฌ๋ผ๊ฐ”์„ ๋•Œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. -/// InventorySlotUI์—์„œ ShowTooltip, HideTooltip์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. +/// InventoryManager์˜ ItemDefinition ๋ฐ์ดํ„ฐ๋ฅผ ์šฐ์„  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. /// [DisallowMultipleComponent] public class InventoryTooltipUI : MonoBehaviour @@ -23,39 +14,68 @@ public class InventoryTooltipUI : MonoBehaviour [SerializeField] private TMP_Text titleText; [SerializeField] private TMP_Text descriptionText; [SerializeField] private TMP_Text countText; + [SerializeField] private TMP_Text goalHintText; + [SerializeField] private TMP_Text useHintText; - [Header("Entries")] - [SerializeField] private List entries = new List() - { - new InventoryTooltipEntry { itemType = InventoryItemType.Fish, displayName = "์ƒ์„ ", description = "๊ณ ์–‘์ด ํ•ฉ์ฐฝ๋‹จ์ด ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค." }, - new InventoryTooltipEntry { itemType = InventoryItemType.OldCompass, displayName = "๋‚ก์€ ๋‚˜์นจ๋ฐ˜", description = "๋ฏธ๋กœ์—์„œ ๊ธธ์„ ์ฐพ๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค." }, - new InventoryTooltipEntry { itemType = InventoryItemType.Trash, displayName = "์“ฐ๋ ˆ๊ธฐ", description = "๋‚š์‹œํ„ฐ๋ฅผ ์ •ํ™”ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." }, - new InventoryTooltipEntry { itemType = InventoryItemType.Bottle, displayName = "๋งˆ๋ฒ•๋ณ‘", description = "๋ฐ”๋‹ค ์†์—์„œ ๋ฐœ๊ฒฌํ•œ ์ˆ˜์ƒํ•œ ๋ณ‘์ž…๋‹ˆ๋‹ค." }, - new InventoryTooltipEntry { itemType = InventoryItemType.MemoryPiece, displayName = "๊ธฐ์–ต์˜ ์กฐ๊ฐ", description = "์ œํŽ˜ํ† ๋ฅผ ๊ตฌ์ถœํ•˜๊ธฐ ์œ„ํ•œ ์ค‘์š”ํ•œ ์กฐ๊ฐ์ž…๋‹ˆ๋‹ค." } - }; + [Header("Reference")] + [SerializeField] private InventoryManager inventoryManager; + [SerializeField] private bool autoFindManager = true; private void Awake() { + ResolveManager(); HideTooltip(); } public void ShowTooltip(InventoryItemType itemType, int count) { - InventoryTooltipEntry entry = FindEntry(itemType); + int maxCount = ResolveManager() != null ? inventoryManager.GetMaxCount(itemType) : 0; + ShowTooltip(itemType, count, maxCount); + } + + public void ShowTooltip(InventoryItemType itemType, int count, int maxCount) + { + ResolveManager(); + + string displayName = itemType.ToString(); + string description = string.Empty; + string goalHint = string.Empty; + string useHint = string.Empty; + + InventoryItemDefinition definition = inventoryManager != null ? inventoryManager.GetDefinition(itemType) : null; + if (definition != null) + { + displayName = definition.SafeDisplayName; + description = definition.description; + goalHint = definition.goalHint; + + if (definition.usable) + useHint = string.IsNullOrWhiteSpace(definition.useLabel) ? "์‚ฌ์šฉ ๊ฐ€๋Šฅ" : definition.useLabel; + } if (tooltipPanel != null) tooltipPanel.SetActive(true); if (titleText != null) - titleText.text = entry != null && !string.IsNullOrWhiteSpace(entry.displayName) - ? entry.displayName - : itemType.ToString(); + titleText.text = displayName; if (descriptionText != null) - descriptionText.text = entry != null ? entry.description : string.Empty; + descriptionText.text = description; if (countText != null) - countText.text = $"๋ณด์œ : x{Mathf.Max(0, count)}"; + countText.text = maxCount > 0 ? $"๋ณด์œ : x{Mathf.Max(0, count)} / {maxCount}" : $"๋ณด์œ : x{Mathf.Max(0, count)}"; + + if (goalHintText != null) + { + goalHintText.text = goalHint; + goalHintText.gameObject.SetActive(!string.IsNullOrWhiteSpace(goalHint)); + } + + if (useHintText != null) + { + useHintText.text = useHint; + useHintText.gameObject.SetActive(!string.IsNullOrWhiteSpace(useHint)); + } } public void HideTooltip() @@ -64,14 +84,16 @@ public void HideTooltip() tooltipPanel.SetActive(false); } - private InventoryTooltipEntry FindEntry(InventoryItemType itemType) + private InventoryManager ResolveManager() { - for (int i = 0; i < entries.Count; i++) - { - if (entries[i] != null && entries[i].itemType == itemType) - return entries[i]; - } + if (inventoryManager != null) + return inventoryManager; - return null; + if (InventoryManager.Instance != null) + inventoryManager = InventoryManager.Instance; + else if (autoFindManager) + inventoryManager = FindFirstObjectByType(); + + return inventoryManager; } } diff --git a/Assets/My project/Inventory/Scripts/InventoryUI.cs b/Assets/My project/Inventory/Scripts/InventoryUI.cs index 62767035..9855fc9a 100644 --- a/Assets/My project/Inventory/Scripts/InventoryUI.cs +++ b/Assets/My project/Inventory/Scripts/InventoryUI.cs @@ -1,31 +1,158 @@ +using System.Collections; +using System.Collections.Generic; +using TMPro; using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; /// /// ์ „์ฒด ์ธ๋ฒคํ† ๋ฆฌ UI๋ฅผ ๊ฐฑ์‹ ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. -/// InventoryManager์˜ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„ ๊ฐ InventorySlotUI์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. +/// ๊ธฐ๋Šฅ: ํŒจ๋„ ์—ด๊ธฐ/๋‹ซ๊ธฐ, ์Šฌ๋กฏ ๊ฐฑ์‹ , ํš๋“ ํŒ์—…, ๋ถ€์กฑ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€, ํ™•์ธ์ฐฝ, ์ƒ์„ธ๋ณด๊ธฐ, ํš๋“ ๋กœ๊ทธ, ๊ธฐ์–ต์˜ ์กฐ๊ฐ ์ง„ํ–‰๋„, ๊ฐ„๋‹จํ•œ ์†๋ชฉ/ํƒ€๊ฒŸ ์ถ”์ . +/// InventoryUI๊ฐ€ ๋ถ™์€ ์˜ค๋ธŒ์ ํŠธ๋Š” ํ•ญ์ƒ ์ผœ๋‘๊ณ , ์‹ค์ œ ๋ณด์ด๋Š” inventoryPanel๋งŒ ์ผœ๊ณ  ๋„๋Š” ๊ตฌ์กฐ๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. /// [DisallowMultipleComponent] public class InventoryUI : MonoBehaviour { [Header("References")] [SerializeField] private InventoryManager inventoryManager; + [Tooltip("์‹ค์ œ๋กœ ๋ณด์ด๊ฑฐ๋‚˜ ์ˆจ๊ฒจ์งˆ ์ž์‹ ํŒจ๋„์ž…๋‹ˆ๋‹ค. InventoryUI๊ฐ€ ๋ถ™์€ ์ž๊ธฐ ์ž์‹ ์„ ๋„ฃ์ง€ ์•Š๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.")] [SerializeField] private GameObject inventoryPanel; [SerializeField] private InventorySlotUI[] slots; - [Header("Settings")] + [Header("Panel Settings")] [SerializeField] private bool autoFindManager = true; + [SerializeField] private bool autoFindPanelChild = true; + [SerializeField] private string autoPanelChildName = "InventoryPanel"; + [SerializeField] private bool visibleOnStart = false; [SerializeField] private bool refreshOnEnable = true; [SerializeField] private bool showNewBadgeOnIncrease = true; + [SerializeField] private bool preventDisablingSelf = true; + + [Header("Filter")] + [SerializeField] private InventoryItemCategory currentFilter = InventoryItemCategory.All; + [SerializeField] private bool hideSlotsOutsideFilter = false; + + [Header("Acquisition Popup")] + [SerializeField] private GameObject acquisitionPopupPanel; + [SerializeField] private CanvasGroup acquisitionPopupCanvasGroup; + [SerializeField] private Image acquisitionIconImage; + [SerializeField] private TMP_Text acquisitionText; + [SerializeField] private float acquisitionPopupTime = 1.4f; + [SerializeField] private string acquisitionFormat = "{0} x{1} ํš๋“!"; + + [Header("Message Popup")] + [SerializeField] private GameObject messagePanel; + [SerializeField] private CanvasGroup messageCanvasGroup; + [SerializeField] private TMP_Text messageText; + [SerializeField] private float messageShowTime = 1.5f; + + [Header("Confirmation UI")] + [SerializeField] private GameObject confirmationPanel; + [SerializeField] private TMP_Text confirmationTitleText; + [SerializeField] private TMP_Text confirmationBodyText; + [SerializeField] private Button confirmationYesButton; + [SerializeField] private Button confirmationNoButton; + + [Header("Detail UI")] + [SerializeField] private GameObject detailPanel; + [SerializeField] private Image detailIconImage; + [SerializeField] private TMP_Text detailTitleText; + [SerializeField] private TMP_Text detailDescriptionText; + [SerializeField] private TMP_Text detailCountText; + [SerializeField] private TMP_Text detailGoalText; + [SerializeField] private Button detailUseButton; + + [Header("Recent Log UI")] + [SerializeField] private TMP_Text[] recentLogTexts; + + [Header("Memory Piece UI")] + [SerializeField] private TMP_Text memoryProgressText; + [SerializeField] private Slider memoryProgressSlider; + [SerializeField] private string memoryProgressFormat = "๊ธฐ์–ต์˜ ์กฐ๊ฐ {0} / {1}"; + + [Header("Audio")] + [SerializeField] private AudioSource uiAudioSource; + [SerializeField] private AudioClip defaultAcquisitionClip; + [SerializeField] private AudioClip defaultUseClip; + [SerializeField] private AudioClip defaultErrorClip; + + [Header("Optional Follow Target / Wrist UI")] + [SerializeField] private bool followTarget = false; + [SerializeField] private Transform targetToFollow; + [SerializeField] private Vector3 localPositionOffset = new Vector3(0f, 0.08f, 0.12f); + [SerializeField] private Vector3 localEulerOffset = Vector3.zero; + [SerializeField] private bool faceMainCamera = false; + + [Header("Editor Test Toggle")] + [SerializeField] private bool enableKeyboardToggleForTesting = false; + [SerializeField] private KeyCode keyboardToggleKey = KeyCode.I; private bool subscribed; + private Coroutine acquisitionRoutine; + private Coroutine messageRoutine; + private InventoryItemType pendingUseItemType; + private int pendingUseAmount = 1; + private bool hasPendingUse; + private InventoryItemType currentDetailItemType; + private bool hasDetailItem; private void Awake() { - if (inventoryPanel == null) - inventoryPanel = gameObject; + if (inventoryManager == null && InventoryManager.Instance != null) + inventoryManager = InventoryManager.Instance; if (inventoryManager == null && autoFindManager) inventoryManager = FindFirstObjectByType(); + + if (inventoryPanel == null && autoFindPanelChild) + { + Transform panelTransform = transform.Find(autoPanelChildName); + if (panelTransform != null) + inventoryPanel = panelTransform.gameObject; + } + + if (uiAudioSource == null) + uiAudioSource = GetComponent(); + + if (acquisitionPopupPanel != null) + acquisitionPopupPanel.SetActive(false); + + if (messagePanel != null) + messagePanel.SetActive(false); + + if (confirmationPanel != null) + confirmationPanel.SetActive(false); + + if (detailPanel != null) + detailPanel.SetActive(false); + + if (confirmationYesButton != null) + { + confirmationYesButton.onClick.RemoveListener(ConfirmPendingUse); + confirmationYesButton.onClick.AddListener(ConfirmPendingUse); + } + + if (confirmationNoButton != null) + { + confirmationNoButton.onClick.RemoveListener(CancelPendingUse); + confirmationNoButton.onClick.AddListener(CancelPendingUse); + } + + if (detailUseButton != null) + { + detailUseButton.onClick.RemoveListener(UseCurrentDetailItem); + detailUseButton.onClick.AddListener(UseCurrentDetailItem); + } + + ApplyItemDataToSlots(); + } + + private void Start() + { + SetVisible(visibleOnStart); + RefreshAllSlots(); + RefreshRecentLogs(); + RefreshMemoryProgress(); } private void OnEnable() @@ -41,18 +168,54 @@ private void OnDisable() Unsubscribe(); } + private void Update() + { + if (enableKeyboardToggleForTesting && Input.GetKeyDown(keyboardToggleKey)) + ToggleVisible(); + } + + private void LateUpdate() + { + if (!followTarget || targetToFollow == null) + return; + + transform.position = targetToFollow.TransformPoint(localPositionOffset); + transform.rotation = targetToFollow.rotation * Quaternion.Euler(localEulerOffset); + + if (faceMainCamera && Camera.main != null) + { + Vector3 direction = transform.position - Camera.main.transform.position; + if (direction.sqrMagnitude > 0.0001f) + transform.rotation = Quaternion.LookRotation(direction.normalized, Vector3.up); + } + } + public void SetVisible(bool visible) { - if (inventoryPanel != null) - inventoryPanel.SetActive(visible); + if (inventoryPanel == null) + return; + + if (preventDisablingSelf && inventoryPanel == gameObject) + { + Debug.LogWarning("[InventoryUI] InventoryPanel์— ์ž๊ธฐ ์ž์‹ ์ด ๋“ค์–ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. InventoryRoot๋Š” ํ•ญ์ƒ ์ผœ๋‘๊ณ  ์ž์‹ InventoryPanel๋งŒ ์—ฐ๊ฒฐํ•˜์„ธ์š”.", this); + return; + } + + inventoryPanel.SetActive(visible); + + if (visible) + RefreshAllSlots(); } public void ToggleVisible() { if (inventoryPanel != null) - inventoryPanel.SetActive(!inventoryPanel.activeSelf); + SetVisible(!inventoryPanel.activeSelf); } + public void ShowInventory() => SetVisible(true); + public void HideInventory() => SetVisible(false); + public void SetManager(InventoryManager manager) { if (inventoryManager == manager) @@ -61,9 +224,22 @@ public void SetManager(InventoryManager manager) Unsubscribe(); inventoryManager = manager; Subscribe(); + ApplyItemDataToSlots(); RefreshAllSlots(); } + public void SetFilterAll() => SetFilter(InventoryItemCategory.All); + public void SetFilterConsumable() => SetFilter(InventoryItemCategory.Consumable); + public void SetFilterQuest() => SetFilter(InventoryItemCategory.Quest); + public void SetFilterKeyItem() => SetFilter(InventoryItemCategory.KeyItem); + public void SetFilterMaterial() => SetFilter(InventoryItemCategory.Material); + + public void SetFilter(InventoryItemCategory category) + { + currentFilter = category; + ApplyFilterToSlots(); + } + public void RefreshAllSlots() { if (inventoryManager == null || slots == null) @@ -75,9 +251,16 @@ public void RefreshAllSlots() if (slot == null) continue; + InventoryItemDefinition definition = inventoryManager.GetDefinition(slot.ItemType); + slot.SetInventoryUI(this); + slot.SetDefinition(definition); + int count = inventoryManager.GetItemCount(slot.ItemType); - slot.SetCount(count, false); + int maxCount = inventoryManager.GetMaxCount(slot.ItemType); + slot.SetCount(count, false, maxCount); } + + ApplyFilterToSlots(); } private void RefreshSlot(InventoryItemType itemType, int count) @@ -85,13 +268,53 @@ private void RefreshSlot(InventoryItemType itemType, int count) if (slots == null) return; + int maxCount = inventoryManager != null ? inventoryManager.GetMaxCount(itemType) : 0; + for (int i = 0; i < slots.Length; i++) { InventorySlotUI slot = slots[i]; if (slot == null || slot.ItemType != itemType) continue; - slot.SetCount(count, showNewBadgeOnIncrease); + slot.SetCount(count, showNewBadgeOnIncrease, maxCount); + } + + if (hasDetailItem && currentDetailItemType == itemType) + ShowItemDetail(itemType); + } + + private void ApplyItemDataToSlots() + { + if (inventoryManager == null || slots == null) + return; + + for (int i = 0; i < slots.Length; i++) + { + InventorySlotUI slot = slots[i]; + if (slot == null) + continue; + + slot.SetInventoryUI(this); + slot.SetDefinition(inventoryManager.GetDefinition(slot.ItemType)); + } + } + + private void ApplyFilterToSlots() + { + if (slots == null) + return; + + for (int i = 0; i < slots.Length; i++) + { + InventorySlotUI slot = slots[i]; + if (slot == null) + continue; + + bool visible = currentFilter == InventoryItemCategory.All || slot.Category == currentFilter; + if (hideSlotsOutsideFilter) + slot.gameObject.SetActive(visible); + else + slot.SetFilteredOut(!visible); } } @@ -101,6 +324,11 @@ private void Subscribe() return; inventoryManager.ItemCountChanged += RefreshSlot; + inventoryManager.ItemAdded += HandleItemAdded; + inventoryManager.ItemUsed += HandleItemUsed; + inventoryManager.MessageRequested += ShowMessage; + inventoryManager.LogAdded += HandleLogAdded; + inventoryManager.MemoryPieceProgressChanged += HandleMemoryPieceProgressChanged; subscribed = true; } @@ -110,6 +338,306 @@ private void Unsubscribe() return; inventoryManager.ItemCountChanged -= RefreshSlot; + inventoryManager.ItemAdded -= HandleItemAdded; + inventoryManager.ItemUsed -= HandleItemUsed; + inventoryManager.MessageRequested -= ShowMessage; + inventoryManager.LogAdded -= HandleLogAdded; + inventoryManager.MemoryPieceProgressChanged -= HandleMemoryPieceProgressChanged; subscribed = false; } + + private void HandleItemAdded(InventoryItemType itemType, int addedAmount, int totalCount) + { + ShowAcquisitionPopup(itemType, addedAmount); + } + + private void HandleItemUsed(InventoryItemType itemType, int count) + { + AudioClip clip = inventoryManager != null ? inventoryManager.GetUseClip(itemType) : null; + PlayUIClip(clip != null ? clip : defaultUseClip); + } + + private void HandleLogAdded(InventoryLogEntry entry) + { + RefreshRecentLogs(); + } + + private void HandleMemoryPieceProgressChanged(int current, int target) + { + RefreshMemoryProgress(current, target); + } + + public void ShowAcquisitionPopup(InventoryItemType itemType, int amount) + { + if (inventoryManager == null) + return; + + string displayName = inventoryManager.GetDisplayName(itemType); + Sprite icon = inventoryManager.GetIcon(itemType); + + if (acquisitionIconImage != null) + { + acquisitionIconImage.sprite = icon; + acquisitionIconImage.enabled = icon != null; + } + + if (acquisitionText != null) + acquisitionText.text = string.Format(acquisitionFormat, displayName, Mathf.Max(1, amount)); + + AudioClip clip = inventoryManager.GetAcquisitionClip(itemType); + PlayUIClip(clip != null ? clip : defaultAcquisitionClip); + + if (acquisitionRoutine != null) + StopCoroutine(acquisitionRoutine); + + acquisitionRoutine = StartCoroutine(ShowPanelRoutine(acquisitionPopupPanel, acquisitionPopupCanvasGroup, acquisitionPopupTime)); + } + + public void ShowMessage(string message) + { + if (string.IsNullOrWhiteSpace(message)) + return; + + if (messageText != null) + messageText.text = message; + + if (messageRoutine != null) + StopCoroutine(messageRoutine); + + messageRoutine = StartCoroutine(ShowPanelRoutine(messagePanel, messageCanvasGroup, messageShowTime)); + } + + private IEnumerator ShowPanelRoutine(GameObject panel, CanvasGroup canvasGroup, float showTime) + { + if (panel == null) + yield break; + + panel.SetActive(true); + + if (canvasGroup != null) + canvasGroup.alpha = 1f; + + yield return new WaitForSecondsRealtime(Mathf.Max(0.05f, showTime)); + + if (canvasGroup != null) + canvasGroup.alpha = 0f; + + panel.SetActive(false); + } + + public void RequestUseItem(InventoryItemType itemType) + { + RequestUseItem(itemType, 1); + } + + public void RequestUseItem(InventoryItemType itemType, int amount) + { + if (inventoryManager == null) + return; + + if (!inventoryManager.IsUsable(itemType)) + { + ShowMessage($"{inventoryManager.GetDisplayName(itemType)}์€(๋Š”) ์ง€๊ธˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + PlayUIClip(defaultErrorClip); + return; + } + + if (!inventoryManager.HasItem(itemType, amount)) + { + ShowMessage(inventoryManager.GetInsufficientMessage(itemType, amount)); + PlayUIClip(defaultErrorClip); + return; + } + + if (inventoryManager.RequiresUseConfirmation(itemType)) + { + pendingUseItemType = itemType; + pendingUseAmount = Mathf.Max(1, amount); + ShowConfirmation(itemType, amount); + } + else + { + bool result = inventoryManager.UseItem(itemType, amount); + if (!result) + PlayUIClip(defaultErrorClip); + } + } + + private void ShowConfirmation(InventoryItemType itemType, int amount) + { + pendingUseItemType = itemType; + pendingUseAmount = Mathf.Max(1, amount); + hasPendingUse = true; + + if (confirmationPanel == null) + { + ConfirmPendingUse(); + return; + } + + string displayName = inventoryManager != null ? inventoryManager.GetDisplayName(itemType) : itemType.ToString(); + + if (confirmationTitleText != null) + confirmationTitleText.text = "์•„์ดํ…œ ์‚ฌ์šฉ"; + + if (confirmationBodyText != null) + confirmationBodyText.text = $"{displayName}์„(๋ฅผ) ์‚ฌ์šฉํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?"; + + confirmationPanel.SetActive(true); + } + + public void ConfirmPendingUse() + { + if (confirmationPanel != null) + confirmationPanel.SetActive(false); + + if (!hasPendingUse) + return; + + InventoryItemType itemType = pendingUseItemType; + int amount = Mathf.Max(1, pendingUseAmount); + hasPendingUse = false; + + if (inventoryManager != null) + { + bool result = inventoryManager.UseItem(itemType, amount); + if (!result) + PlayUIClip(defaultErrorClip); + } + } + + public void CancelPendingUse() + { + hasPendingUse = false; + + if (confirmationPanel != null) + confirmationPanel.SetActive(false); + } + + public void ShowItemDetail(InventoryItemType itemType) + { + hasDetailItem = true; + currentDetailItemType = itemType; + + if (inventoryManager == null || detailPanel == null) + return; + + InventoryItemDefinition definition = inventoryManager.GetDefinition(itemType); + int count = inventoryManager.GetItemCount(itemType); + int maxCount = inventoryManager.GetMaxCount(itemType); + Sprite icon = inventoryManager.GetIcon(itemType); + + if (detailIconImage != null) + { + detailIconImage.sprite = icon; + detailIconImage.enabled = icon != null; + } + + if (detailTitleText != null) + detailTitleText.text = inventoryManager.GetDisplayName(itemType); + + if (detailDescriptionText != null) + detailDescriptionText.text = inventoryManager.GetDescription(itemType); + + if (detailCountText != null) + detailCountText.text = maxCount > 0 ? $"๋ณด์œ : x{count} / {maxCount}" : $"๋ณด์œ : x{count}"; + + if (detailGoalText != null) + detailGoalText.text = inventoryManager.GetGoalHint(itemType); + + if (detailUseButton != null) + { + bool usable = definition != null && definition.usable && count > 0; + detailUseButton.gameObject.SetActive(definition != null && definition.usable); + detailUseButton.interactable = usable; + } + + detailPanel.SetActive(true); + } + + public void HideItemDetail() + { + hasDetailItem = false; + if (detailPanel != null) + detailPanel.SetActive(false); + } + + public void UseCurrentDetailItem() + { + if (!hasDetailItem) + return; + + RequestUseItem(currentDetailItemType, 1); + } + + private void RefreshRecentLogs() + { + if (recentLogTexts == null || recentLogTexts.Length == 0 || inventoryManager == null) + return; + + IReadOnlyList logs = inventoryManager.GetRecentLogs(); + + for (int i = 0; i < recentLogTexts.Length; i++) + { + if (recentLogTexts[i] == null) + continue; + + recentLogTexts[i].text = i < logs.Count && logs[i] != null ? logs[i].ToString() : string.Empty; + } + } + + private void RefreshMemoryProgress() + { + if (inventoryManager == null) + return; + + RefreshMemoryProgress(inventoryManager.GetItemCount(inventoryManager.MemoryPieceItemType), inventoryManager.MemoryPieceTargetCount); + } + + private void RefreshMemoryProgress(int current, int target) + { + target = Mathf.Max(1, target); + current = Mathf.Clamp(current, 0, target); + + if (memoryProgressText != null) + memoryProgressText.text = string.Format(memoryProgressFormat, current, target); + + if (memoryProgressSlider != null) + { + memoryProgressSlider.minValue = 0f; + memoryProgressSlider.maxValue = target; + memoryProgressSlider.value = current; + } + } + + private void PlayUIClip(AudioClip clip) + { + if (clip == null) + return; + + if (uiAudioSource != null) + uiAudioSource.PlayOneShot(clip); + else + AudioSource.PlayClipAtPoint(clip, transform.position); + } + + public void CheckVRUISetup() + { + Canvas canvas = GetComponentInParent(); + if (canvas == null) + { + Debug.LogWarning("[InventoryUI] Canvas๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", this); + return; + } + + if (canvas.renderMode != RenderMode.WorldSpace) + Debug.LogWarning("[InventoryUI] VR UI์—์„œ๋Š” Canvas Render Mode๋ฅผ World Space๋กœ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.", canvas); + + if (EventSystem.current == null) + Debug.LogWarning("[InventoryUI] EventSystem์ด ์—†์Šต๋‹ˆ๋‹ค. VR ํฌ์ธํ„ฐ UI๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", this); + + GraphicRaycaster graphicRaycaster = canvas.GetComponent(); + if (graphicRaycaster == null) + Debug.LogWarning("[InventoryUI] Canvas์— GraphicRaycaster ๋˜๋Š” Tracked Device Graphic Raycaster๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", canvas); + } } diff --git a/Assets/My project/Inventory/Scripts/InventoryUILayoutController.cs b/Assets/My project/Inventory/Scripts/InventoryUILayoutController.cs new file mode 100644 index 00000000..8d660641 --- /dev/null +++ b/Assets/My project/Inventory/Scripts/InventoryUILayoutController.cs @@ -0,0 +1,2242 @@ +using System; +using System.Collections.Generic; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +public enum InventoryRootUIMode +{ + TestMode, + PlayMode, + BuildMode, + HiddenMode, + LayoutOnly +} + +public enum InventoryUIAnchorPreset +{ + Custom, + MiddleCenter, + TopLeft, + TopCenter, + TopRight, + MiddleLeft, + MiddleRight, + BottomLeft, + BottomCenter, + BottomRight, + StretchAll +} + +[Serializable] +public class InventoryControlledRectTarget +{ + [Header("Target")] + public string name; + public RectTransform rectTransform; + public GameObject activeTarget; + + [Header("Rect Layout")] + public bool applyRect = true; + public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter; + public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f); + public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f); + public Vector2 customPivot = new Vector2(0.5f, 0.5f); + public Vector2 size = new Vector2(100f, 100f); + public Vector2 anchoredPosition = Vector2.zero; + + [Header("Transform")] + public bool resetScaleToOne = true; + public bool applyLocalScale = false; + public Vector3 localScale = Vector3.one; + public bool applyLocalRotation = false; + public Vector3 localEulerAngles = Vector3.zero; + public bool applySiblingIndex = false; + public int siblingIndex = 0; + + [Header("Active By Mode")] + public bool controlActiveState = false; + public bool activeInTestMode = true; + public bool activeInPlayMode = true; + public bool activeInBuildMode = true; + public bool activeInHiddenMode = false; + + [Header("CanvasGroup")] + public bool applyCanvasGroup = false; + public bool addCanvasGroupIfMissing = true; + [Range(0f, 1f)] public float canvasAlpha = 1f; + public bool canvasInteractable = false; + public bool canvasBlocksRaycasts = false; + + [Header("Raycast")] + public bool applySelfImageRaycast = false; + public bool selfImageRaycastTarget = false; + public bool applyChildImageRaycasts = false; + public bool childImageRaycastTarget = false; + public bool preserveButtonImageRaycasts = true; + public bool applyChildTextRaycasts = false; + public bool childTextRaycastTarget = false; +} + +[Serializable] +public class InventoryControlledTextTarget +{ + [Header("Target")] + public string name; + public TMP_Text text; + + [Header("Rect Layout")] + public bool applyRect = false; + public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter; + public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f); + public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f); + public Vector2 customPivot = new Vector2(0.5f, 0.5f); + public Vector2 size = new Vector2(100f, 30f); + public Vector2 anchoredPosition = Vector2.zero; + + [Header("Text Settings")] + public bool applyFontSize = false; + public float fontSize = 20f; + public bool applyAlignment = false; + public TextAlignmentOptions alignment = TextAlignmentOptions.Center; + public bool applyColor = false; + public Color color = Color.white; + public bool applyRaycastTarget = true; + public bool raycastTarget = false; + public bool resetScaleToOne = true; + + [Header("Outline / Shadow")] + public bool applyOutline = false; + public bool addOutlineIfMissing = true; + public Color outlineColor = new Color(0f, 0.75f, 1f, 0.8f); + public Vector2 outlineEffectDistance = new Vector2(1f, -1f); + public bool outlineUseGraphicAlpha = true; + public bool applyShadow = false; + public bool addShadowIfMissing = true; + public Color shadowColor = new Color(0f, 0f, 0f, 0.65f); + public Vector2 shadowEffectDistance = new Vector2(2f, -2f); + public bool shadowUseGraphicAlpha = true; +} + +[Serializable] +public class InventoryControlledImageTarget +{ + [Header("Target")] + public string name; + public Image image; + + [Header("Rect Layout")] + public bool applyRect = false; + public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter; + public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f); + public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f); + public Vector2 customPivot = new Vector2(0.5f, 0.5f); + public Vector2 size = new Vector2(64f, 64f); + public Vector2 anchoredPosition = Vector2.zero; + + [Header("Image Settings")] + public bool applyColor = false; + public Color color = Color.white; + public bool applySprite = false; + public Sprite sprite; + public bool applyRaycastTarget = true; + public bool raycastTarget = false; + public bool resetScaleToOne = true; + + [Header("Outline / Shadow")] + public bool applyOutline = false; + public bool addOutlineIfMissing = true; + public Color outlineColor = new Color(0f, 0.75f, 1f, 0.8f); + public Vector2 outlineEffectDistance = new Vector2(1f, -1f); + public bool outlineUseGraphicAlpha = true; + public bool applyShadow = false; + public bool addShadowIfMissing = true; + public Color shadowColor = new Color(0f, 0f, 0f, 0.55f); + public Vector2 shadowEffectDistance = new Vector2(2f, -2f); + public bool shadowUseGraphicAlpha = true; +} + +[Serializable] +public class InventoryControlledButtonTarget +{ + [Header("Target")] + public string name; + public Button button; + + [Header("Rect Layout")] + public bool applyRect = false; + public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter; + public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f); + public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f); + public Vector2 customPivot = new Vector2(0.5f, 0.5f); + public Vector2 size = new Vector2(100f, 35f); + public Vector2 anchoredPosition = Vector2.zero; + + [Header("Button Settings")] + public bool applyInteractable = false; + public bool interactable = true; + public bool applyImageRaycastTarget = true; + public bool imageRaycastTarget = true; + public bool disableChildTextRaycasts = true; + public bool resetScaleToOne = true; + + [Header("Button Colors")] + public bool applyButtonColors = false; + public Color normalColor = new Color(0.07f, 0.18f, 0.30f, 0.90f); + public Color highlightedColor = new Color(0.12f, 0.42f, 0.70f, 1f); + public Color pressedColor = new Color(0.04f, 0.12f, 0.20f, 1f); + public Color selectedColor = new Color(0.15f, 0.50f, 0.85f, 1f); + public Color disabledColor = new Color(0.20f, 0.20f, 0.20f, 0.45f); + public float colorMultiplier = 1f; + public float fadeDuration = 0.1f; + + [Header("Outline / Shadow")] + public bool applyOutline = false; + public bool addOutlineIfMissing = true; + public Color outlineColor = new Color(0f, 0.75f, 1f, 0.8f); + public Vector2 outlineEffectDistance = new Vector2(1f, -1f); + public bool outlineUseGraphicAlpha = true; + public bool applyShadow = false; + public bool addShadowIfMissing = true; + public Color shadowColor = new Color(0f, 0f, 0f, 0.55f); + public Vector2 shadowEffectDistance = new Vector2(2f, -2f); + public bool shadowUseGraphicAlpha = true; +} + +[Serializable] +public class InventoryLayoutPadding +{ + public int left; + public int right; + public int top; + public int bottom; + + public InventoryLayoutPadding() { } + + public InventoryLayoutPadding(int left, int right, int top, int bottom) + { + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; + } + + public RectOffset ToRectOffset() + { + return new RectOffset(left, right, top, bottom); + } +} + +[Serializable] +public class InventoryControlledGridLayoutTarget +{ + [Header("Target")] + public string name; + public GridLayoutGroup grid; + + [Header("Grid Settings")] + public bool applyGrid = true; + public Vector2 cellSize = new Vector2(100f, 100f); + public Vector2 spacing = Vector2.zero; + public TextAnchor childAlignment = TextAnchor.MiddleCenter; + public GridLayoutGroup.Constraint constraint = GridLayoutGroup.Constraint.FixedColumnCount; + public int constraintCount = 5; + public InventoryLayoutPadding padding; +} + +/// +/// InventoryRoot ์•„๋ž˜ UI๋ฅผ ํ•œ ๊ณณ์—์„œ ์ •๋ฆฌํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์ž…๋‹ˆ๋‹ค. +/// - ๊ธฐ๋ณธ ์ธ๋ฒคํ† ๋ฆฌ ํŒจ๋„๋“ค์˜ ์œ„์น˜/ํฌ๊ธฐ/๋ฐฐ์น˜๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. +/// - Inspector์— ์ง์ ‘ ์—ฐ๊ฒฐํ•œ Rect/Text/Image/Button/Grid ๋Œ€์ƒ๋„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. +/// - ์—†๋Š” CanvasGroup, Image, GridLayoutGroup, AudioSource ๋“ฑ์„ ์ž๋™์œผ๋กœ ๋ถ™์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +/// - Text ๋‚ด์šฉ ์ž์ฒด๋Š” ๋ฐ”๊พธ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‚ด์šฉ ๋ณ€๊ฒฝ์€ InventoryUI๊ฐ€ ๋‹ด๋‹นํ•˜๊ฒŒ ๋‘๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. +/// +[ExecuteAlways] +[DisallowMultipleComponent] +public class InventoryUILayoutController : MonoBehaviour +{ + [Header("Root / Canvas")] + [SerializeField] private bool configureRootCanvas = true; + [SerializeField] private Vector2 rootCanvasSize = new Vector2(800f, 500f); + [SerializeField] private bool applyRootWorldScale = false; + [SerializeField] private Vector3 rootWorldScale = new Vector3(0.0015f, 0.0015f, 0.0015f); + [SerializeField] private bool addGraphicRaycasterIfMissing = true; + [SerializeField] private bool addTrackedDeviceGraphicRaycasterIfAvailable = true; + + [Header("Known UI References")] + [SerializeField] private RectTransform inventoryPanel; + [SerializeField] private RectTransform titleText; + [SerializeField] private RectTransform closeButton; + [SerializeField] private RectTransform categoryButtons; + [SerializeField] private RectTransform memoryProgressArea; + [SerializeField] private RectTransform slotContainer; + [SerializeField] private RectTransform recentLogArea; + [SerializeField] private RectTransform debugPanel; + [SerializeField] private RectTransform quickUseButtons; + + [Header("Popup / Floating Panels")] + [SerializeField] private RectTransform tooltipPanel; + [SerializeField] private RectTransform acquisitionPopupPanel; + [SerializeField] private RectTransform messagePanel; + [SerializeField] private RectTransform confirmationPanel; + [SerializeField] private RectTransform detailPanel; + + [Header("Inventory Scripts")] + [SerializeField] private InventoryUI inventoryUI; + [SerializeField] private InventorySlotUI[] slots; + + [Header("Auto Find Names")] + [SerializeField] private string inventoryPanelName = "InventoryPanel"; + [SerializeField] private string titleTextName = "TitleText"; + [SerializeField] private string closeButtonName = "CloseButton"; + [SerializeField] private string categoryButtonsName = "CategoryButtons"; + [SerializeField] private string memoryProgressAreaName = "MemoryProgressArea"; + [SerializeField] private string slotContainerName = "SlotContainer"; + [SerializeField] private string recentLogAreaName = "RecentLogArea"; + [SerializeField] private string debugPanelName = "DebugPanel"; + [SerializeField] private string quickUseButtonsName = "QuickUseButtons"; + [SerializeField] private string tooltipPanelName = "TooltipPanel"; + [SerializeField] private string acquisitionPopupPanelName = "AcquisitionPopupPanel"; + [SerializeField] private string messagePanelName = "MessagePanel"; + [SerializeField] private string confirmationPanelName = "ConfirmationPanel"; + [SerializeField] private string detailPanelName = "DetailPanel"; + + [Header("Apply Settings")] + [SerializeField] private bool autoFindReferences = true; + [SerializeField] private bool autoAddMissingComponents = true; + [SerializeField] private bool applyKnownLayout = true; + [SerializeField] private bool applyCustomControlledTargets = true; + [SerializeField] private bool resetChildScaleToOne = true; + [SerializeField] private bool applyLayoutOnAwake = true; + [SerializeField] private bool applyModeOnAwake = true; + [Tooltip("InventoryUI.Start()์˜ visibleOnStart ์ฒ˜๋ฆฌ ์ดํ›„์— ๋ชจ๋“œ๋ฅผ ํ•œ ๋ฒˆ ๋” ์ ์šฉํ•ด ํŒจ๋„ ON/OFF ์ถฉ๋Œ์„ ์ค„์ž…๋‹ˆ๋‹ค.")] + [SerializeField] private bool applyModeAfterOneFrame = true; + [SerializeField] private bool applyInEditor = true; + [SerializeField] private InventoryRootUIMode initialMode = InventoryRootUIMode.TestMode; + + [Header("Known Panel Layout")] + [SerializeField] private Vector2 inventoryPanelSize = new Vector2(760f, 460f); + [SerializeField] private Vector2 inventoryPanelPos = Vector2.zero; + [SerializeField] private Vector2 titleTextSize = new Vector2(360f, 50f); + [SerializeField] private Vector2 titleTextPos = new Vector2(0f, -18f); + [SerializeField] private Vector2 closeButtonSize = new Vector2(70f, 38f); + [SerializeField] private Vector2 closeButtonPos = new Vector2(-18f, -18f); + [SerializeField] private Vector2 categoryButtonsSize = new Vector2(620f, 45f); + [SerializeField] private Vector2 categoryButtonsPos = new Vector2(0f, -75f); + [SerializeField] private Vector2 memoryProgressAreaSize = new Vector2(620f, 55f); + [SerializeField] private Vector2 memoryProgressAreaPos = new Vector2(0f, -125f); + [SerializeField] private Vector2 slotContainerSize = new Vector2(500f, 145f); + [SerializeField] private Vector2 slotContainerPos = new Vector2(-80f, 20f); + [SerializeField] private Vector2 recentLogSize = new Vector2(340f, 110f); + [SerializeField] private Vector2 recentLogPos = new Vector2(25f, 25f); + [SerializeField] private Vector2 debugPanelSize = new Vector2(300f, 110f); + [SerializeField] private Vector2 debugPanelPos = new Vector2(-25f, 25f); + [SerializeField] private Vector2 quickUseButtonsSize = new Vector2(130f, 160f); + [SerializeField] private Vector2 quickUseButtonsPos = new Vector2(-20f, -35f); + + [Header("Known Popup / Detail Layout")] + [SerializeField] private Vector2 tooltipPanelSize = new Vector2(520f, 120f); + [SerializeField] private Vector2 tooltipPanelPos = new Vector2(0f, -15f); + [SerializeField] private Vector2 acquisitionPopupSize = new Vector2(360f, 70f); + [SerializeField] private Vector2 acquisitionPopupPos = new Vector2(0f, 40f); + [SerializeField] private Vector2 messagePanelSize = new Vector2(460f, 60f); + [SerializeField] private Vector2 messagePanelPos = new Vector2(0f, -85f); + [SerializeField] private Vector2 confirmationPanelSize = new Vector2(420f, 220f); + [SerializeField] private Vector2 confirmationPanelPos = Vector2.zero; + [SerializeField] private Vector2 detailPanelSize = new Vector2(260f, 340f); + [SerializeField] private Vector2 detailPanelPos = new Vector2(-25f, 0f); + + [Header("Known LayoutGroup Defaults")] + [SerializeField] private Vector2 slotCellSize = new Vector2(85f, 120f); + [SerializeField] private Vector2 slotSpacing = new Vector2(8f, 0f); + [SerializeField] private int slotColumnCount = 5; + [SerializeField] private Vector2 debugButtonCellSize = new Vector2(85f, 28f); + [SerializeField] private Vector2 debugButtonSpacing = new Vector2(5f, 5f); + [SerializeField] private int debugButtonColumnCount = 3; + [SerializeField] private Vector2 quickUseButtonCellSize = new Vector2(115f, 26f); + [SerializeField] private Vector2 quickUseButtonSpacing = new Vector2(0f, 5f); + + [Header("Known Slot Child Layout")] + [SerializeField] private bool arrangeSlotChildren = true; + [SerializeField] private Vector2 slotIconSize = new Vector2(64f, 64f); + [SerializeField] private Vector2 slotIconPos = new Vector2(0f, 18f); + [SerializeField] private Vector2 slotCountTextSize = new Vector2(100f, 28f); + [SerializeField] private Vector2 slotCountTextPos = new Vector2(0f, 8f); + [SerializeField] private Vector2 slotNewBadgeSize = new Vector2(52f, 24f); + [SerializeField] private Vector2 slotNewBadgePos = new Vector2(-4f, -4f); + + [Header("Mode Active States")] + [SerializeField] private bool testShowInventoryPanel = true; + [SerializeField] private bool testShowDebugPanel = true; + [SerializeField] private bool testShowRecentLogArea = true; + [SerializeField] private bool testShowCategoryButtons = true; + [SerializeField] private bool testShowMemoryProgressArea = true; + [SerializeField] private bool testShowQuickUseButtons = false; + + [SerializeField] private bool playShowInventoryPanel = false; + [SerializeField] private bool playShowDebugPanel = false; + [SerializeField] private bool playShowRecentLogArea = true; + [SerializeField] private bool playShowCategoryButtons = true; + [SerializeField] private bool playShowMemoryProgressArea = true; + [SerializeField] private bool playShowQuickUseButtons = false; + + [SerializeField] private bool buildShowInventoryPanel = false; + [SerializeField] private bool buildShowDebugPanel = false; + [SerializeField] private bool buildShowRecentLogArea = true; + [SerializeField] private bool buildShowCategoryButtons = true; + [SerializeField] private bool buildShowMemoryProgressArea = true; + [SerializeField] private bool buildShowQuickUseButtons = false; + + [Header("Custom Inspector-Controlled Targets")] + [Tooltip("ํŒจ๋„, ์˜์—ญ, ๋นˆ ์˜ค๋ธŒ์ ํŠธ ๋“ฑ RectTransform ๊ธฐ์ค€์œผ๋กœ ์ œ์–ดํ•  ๋Œ€์ƒ์ž…๋‹ˆ๋‹ค.")] + [SerializeField] private List controlledRects = new List(); + [Tooltip("ํŒจ๋„ ์ž์‹ TMP_Text์˜ ์œ„์น˜, ํฌ๊ธฐ, ํฐํŠธ ํฌ๊ธฐ, ์ •๋ ฌ, Raycast๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. Text ๋‚ด์šฉ์€ ๋ฐ”๊พธ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")] + [SerializeField] private List controlledTexts = new List(); + [Tooltip("ํŒจ๋„ ์ž์‹ Image์˜ ์œ„์น˜, ํฌ๊ธฐ, ์ƒ‰์ƒ, Raycast๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. Sprite๋Š” Apply Sprite๋ฅผ ์ผฐ์„ ๋•Œ๋งŒ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.")] + [SerializeField] private List controlledImages = new List(); + [Tooltip("ํŒจ๋„ ์ž์‹ Button์˜ ์œ„์น˜, ํฌ๊ธฐ, Interactable, Raycast๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.")] + [SerializeField] private List controlledButtons = new List(); + [Tooltip("SlotContainer, DebugPanel ๊ฐ™์€ GridLayoutGroup์˜ Cell Size, Spacing, Count๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.")] + [SerializeField] private List controlledGridLayouts = new List(); + + [Header("Basic Visual Theme")] + [SerializeField] private bool applyBasicVisualTheme = true; + [SerializeField] private bool styleKnownPanels = true; + [SerializeField] private bool styleKnownButtons = true; + [SerializeField] private bool styleKnownTexts = true; + [SerializeField] private bool styleKnownSlots = true; + [SerializeField] private bool addOutlineToKnownGraphics = true; + [SerializeField] private bool addShadowToKnownTexts = true; + + [Header("Theme Colors - Ocean VR")] + [SerializeField] private Color mainPanelColor = new Color(0.03f, 0.08f, 0.15f, 0.88f); + [SerializeField] private Color subPanelColor = new Color(0.04f, 0.14f, 0.24f, 0.82f); + [SerializeField] private Color popupPanelColor = new Color(0.02f, 0.10f, 0.18f, 0.92f); + [SerializeField] private Color slotColor = new Color(0.05f, 0.16f, 0.26f, 0.90f); + [SerializeField] private Color slotImportantGlowColor = new Color(1.0f, 0.72f, 0.20f, 0.38f); + [SerializeField] private Color filteredOverlayColor = new Color(0f, 0f, 0f, 0.55f); + [SerializeField] private Color newBadgeColor = new Color(1.0f, 0.82f, 0.12f, 0.95f); + [SerializeField] private Color mainTextColor = new Color(0.95f, 0.92f, 0.82f, 1f); + [SerializeField] private Color accentTextColor = new Color(1.0f, 0.78f, 0.35f, 1f); + [SerializeField] private Color mutedTextColor = new Color(0.70f, 0.86f, 0.95f, 1f); + [SerializeField] private Color outlineColor = new Color(0.18f, 0.76f, 1f, 0.65f); + [SerializeField] private Color goldOutlineColor = new Color(1.0f, 0.72f, 0.20f, 0.80f); + [SerializeField] private Color shadowColor = new Color(0f, 0f, 0f, 0.62f); + + [Header("Theme Button Colors")] + [SerializeField] private Color buttonNormalColor = new Color(0.06f, 0.20f, 0.33f, 0.95f); + [SerializeField] private Color buttonHighlightedColor = new Color(0.12f, 0.44f, 0.74f, 1f); + [SerializeField] private Color buttonPressedColor = new Color(0.03f, 0.12f, 0.22f, 1f); + [SerializeField] private Color buttonSelectedColor = new Color(0.16f, 0.50f, 0.82f, 1f); + [SerializeField] private Color buttonDisabledColor = new Color(0.20f, 0.22f, 0.24f, 0.45f); + [SerializeField] private Color debugButtonColor = new Color(0.07f, 0.22f, 0.32f, 0.95f); + [SerializeField] private Color dangerButtonColor = new Color(0.45f, 0.10f, 0.12f, 0.95f); + + [Header("Popup Groups")] + [SerializeField] private GameObject[] extraPopupPanelsToHide; + + [Header("Raycast / Component Fix")] + [SerializeField] private bool fixRaycastTargets = true; + [SerializeField] private bool fixCanvasGroups = true; + [SerializeField] private bool disableTextRaycasts = true; + [SerializeField] private bool disableNonInteractiveImageRaycasts = true; + [SerializeField] private bool setSliderInteractableFalse = true; + [SerializeField] private bool setSlotHoverSafeValues = true; + [SerializeField] private bool slotUseHoverScale = true; + [SerializeField] private float slotHoverScale = 1.03f; + [SerializeField] private bool slotBringToFrontOnHover = false; + + private bool isApplying; + + private void Reset() + { + AutoFindReferences(); + } + + private void Awake() + { + if (!Application.isPlaying) + return; + + if (autoFindReferences) + AutoFindReferences(); + + if (autoAddMissingComponents) + AutoSetupMissingComponents(); + + if (applyLayoutOnAwake) + ApplyFullInventoryRootLayout(); + + if (applyModeOnAwake) + ApplyInitialMode(); + } + + private System.Collections.IEnumerator Start() + { + if (!Application.isPlaying || !applyModeAfterOneFrame) + yield break; + + // InventoryUI.Start()์˜ visibleOnStart ์ ์šฉ์ด ๋๋‚œ ๋’ค ๋ชจ๋“œ๋ฅผ ๋‹ค์‹œ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. + yield return null; + ApplyInitialMode(); + } + +#if UNITY_EDITOR + private void OnValidate() + { + if (!applyInEditor) + return; + + EditorApplication.delayCall -= DelayedApplyInEditor; + EditorApplication.delayCall += DelayedApplyInEditor; + } + + private void DelayedApplyInEditor() + { + if (this == null || isApplying) + return; + + ApplyFullInventoryRootLayout(); + } +#endif + + [ContextMenu("Inventory UI/Apply Full Inventory Root Layout")] + public void ApplyFullInventoryRootLayout() + { + if (isApplying) + return; + + isApplying = true; + + if (autoFindReferences) + AutoFindReferences(); + + if (autoAddMissingComponents) + AutoSetupMissingComponents(); + + if (configureRootCanvas) + ConfigureRootCanvas(); + + if (applyKnownLayout) + ApplyKnownLayout(); + + if (arrangeSlotChildren) + ArrangeSlotChildren(); + + if (applyCustomControlledTargets) + ApplyCustomTargets(); + + if (applyBasicVisualTheme) + ApplyBasicVisualTheme(); + + if (fixRaycastTargets) + FixAllRaycastTargets(); + + isApplying = false; + } + + [ContextMenu("Inventory UI/Apply Custom Inspector Targets Only")] + public void ApplyCustomTargets() + { + ApplyControlledRectTargets(); + ApplyControlledTextTargets(); + ApplyControlledImageTargets(); + ApplyControlledButtonTargets(); + ApplyControlledGridLayoutTargets(); + } + + [ContextMenu("Inventory UI/Auto Find References")] + public void AutoFindReferences() + { + inventoryPanel = FindRectIfNull(inventoryPanel, inventoryPanelName); + titleText = FindRectIfNull(titleText, titleTextName); + closeButton = FindRectIfNull(closeButton, closeButtonName); + categoryButtons = FindRectIfNull(categoryButtons, categoryButtonsName); + memoryProgressArea = FindRectIfNull(memoryProgressArea, memoryProgressAreaName); + slotContainer = FindRectIfNull(slotContainer, slotContainerName); + recentLogArea = FindRectIfNull(recentLogArea, recentLogAreaName); + debugPanel = FindRectIfNull(debugPanel, debugPanelName); + quickUseButtons = FindRectIfNull(quickUseButtons, quickUseButtonsName); + tooltipPanel = FindRectIfNull(tooltipPanel, tooltipPanelName); + acquisitionPopupPanel = FindRectIfNull(acquisitionPopupPanel, acquisitionPopupPanelName); + messagePanel = FindRectIfNull(messagePanel, messagePanelName); + confirmationPanel = FindRectIfNull(confirmationPanel, confirmationPanelName); + detailPanel = FindRectIfNull(detailPanel, detailPanelName); + + if (inventoryUI == null) + inventoryUI = GetComponent(); + + if (slots == null || slots.Length == 0) + slots = GetComponentsInChildren(true); + } + + [ContextMenu("Inventory UI/Auto Setup Missing Components")] + public void AutoSetupMissingComponents() + { + Canvas canvas = GetOrAdd(gameObject); + canvas.renderMode = RenderMode.WorldSpace; + + GetOrAdd(gameObject); + + if (addGraphicRaycasterIfMissing) + GetOrAdd(gameObject); + + if (addTrackedDeviceGraphicRaycasterIfAvailable) + TryAddComponentByTypeName(gameObject, "UnityEngine.XR.Interaction.Toolkit.UI.TrackedDeviceGraphicRaycaster"); + + GetOrAdd(gameObject).playOnAwake = false; + + AddCanvasGroupIfNeeded(tooltipPanel, false, false); + AddCanvasGroupIfNeeded(acquisitionPopupPanel, false, false); + AddCanvasGroupIfNeeded(messagePanel, false, false); + AddCanvasGroupIfNeeded(confirmationPanel, true, true); + AddCanvasGroupIfNeeded(detailPanel, true, true); + + if (slotContainer != null) + GetOrAdd(slotContainer.gameObject); + + if (debugPanel != null) + GetOrAdd(debugPanel.gameObject); + + if (quickUseButtons != null && quickUseButtons.GetComponent() == null) + quickUseButtons.gameObject.AddComponent(); + } + + [ContextMenu("Inventory UI/Add Default Known Targets To Inspector Lists")] + public void AddDefaultKnownTargetsToInspectorLists() + { + AutoFindReferences(); + + AddControlledRect("InventoryPanel", inventoryPanel, null, InventoryUIAnchorPreset.MiddleCenter, inventoryPanelSize, inventoryPanelPos, false, true, true, true, false); + AddControlledRect("TitleText", titleText, null, InventoryUIAnchorPreset.TopCenter, titleTextSize, titleTextPos, false, true, true, true, false); + AddControlledRect("CloseButton", closeButton, null, InventoryUIAnchorPreset.TopRight, closeButtonSize, closeButtonPos, false, true, true, true, false); + AddControlledRect("CategoryButtons", categoryButtons, null, InventoryUIAnchorPreset.TopCenter, categoryButtonsSize, categoryButtonsPos, true, testShowCategoryButtons, playShowCategoryButtons, buildShowCategoryButtons, false); + AddControlledRect("MemoryProgressArea", memoryProgressArea, null, InventoryUIAnchorPreset.TopCenter, memoryProgressAreaSize, memoryProgressAreaPos, true, testShowMemoryProgressArea, playShowMemoryProgressArea, buildShowMemoryProgressArea, false); + AddControlledRect("SlotContainer", slotContainer, null, InventoryUIAnchorPreset.MiddleCenter, slotContainerSize, slotContainerPos, false, true, true, true, false); + AddControlledRect("RecentLogArea", recentLogArea, null, InventoryUIAnchorPreset.BottomLeft, recentLogSize, recentLogPos, true, testShowRecentLogArea, playShowRecentLogArea, buildShowRecentLogArea, false); + AddControlledRect("DebugPanel", debugPanel, null, InventoryUIAnchorPreset.BottomRight, debugPanelSize, debugPanelPos, true, testShowDebugPanel, playShowDebugPanel, buildShowDebugPanel, false); + AddControlledRect("QuickUseButtons", quickUseButtons, null, InventoryUIAnchorPreset.MiddleRight, quickUseButtonsSize, quickUseButtonsPos, true, testShowQuickUseButtons, playShowQuickUseButtons, buildShowQuickUseButtons, false); + AddControlledRect("TooltipPanel", tooltipPanel, null, InventoryUIAnchorPreset.BottomCenter, tooltipPanelSize, tooltipPanelPos, true, false, false, false, false); + AddControlledRect("AcquisitionPopupPanel", acquisitionPopupPanel, null, InventoryUIAnchorPreset.TopCenter, acquisitionPopupSize, acquisitionPopupPos, true, false, false, false, false); + AddControlledRect("MessagePanel", messagePanel, null, InventoryUIAnchorPreset.BottomCenter, messagePanelSize, messagePanelPos, true, false, false, false, false); + AddControlledRect("ConfirmationPanel", confirmationPanel, null, InventoryUIAnchorPreset.MiddleCenter, confirmationPanelSize, confirmationPanelPos, true, false, false, false, false); + AddControlledRect("DetailPanel", detailPanel, null, InventoryUIAnchorPreset.MiddleRight, detailPanelSize, detailPanelPos, true, false, false, false, false); + + AddGrid("SlotContainer Grid", slotContainer != null ? slotContainer.GetComponent() : null, slotCellSize, slotSpacing, GridLayoutGroup.Constraint.FixedColumnCount, slotColumnCount); + AddGrid("DebugPanel Grid", debugPanel != null ? debugPanel.GetComponent() : null, debugButtonCellSize, debugButtonSpacing, GridLayoutGroup.Constraint.FixedColumnCount, debugButtonColumnCount); + + AddChildrenOfPanelsToInspectorLists(); + } + + [ContextMenu("Inventory UI/Add Current Panel Children Texts Images Buttons")] + public void AddChildrenOfPanelsToInspectorLists() + { + RectTransform[] panels = new[] { inventoryPanel, tooltipPanel, acquisitionPopupPanel, messagePanel, confirmationPanel, detailPanel, recentLogArea, debugPanel }; + + foreach (RectTransform panel in panels) + { + if (panel == null) + continue; + + foreach (TMP_Text text in panel.GetComponentsInChildren(true)) + AddText(text.name, text); + + foreach (Image image in panel.GetComponentsInChildren(true)) + AddImage(image.name, image); + + foreach (Button button in panel.GetComponentsInChildren