├── .gitignore ├── Demo Scripts.meta ├── Demo Scripts ├── InventoryTester.cs └── InventoryTester.cs.meta ├── Demo.unity ├── Demo.unity.meta ├── Item Data.meta ├── Item Data ├── Item_Armor_Helmet.asset ├── Item_Armor_Helmet.asset.meta ├── Item_Armor_White Armor.asset ├── Item_Armor_White Armor.asset.meta ├── Item_Portion_HP.asset ├── Item_Portion_HP.asset.meta ├── Item_Portion_MP.asset ├── Item_Portion_MP.asset.meta ├── Item_Weapon_Blue Sword.asset ├── Item_Weapon_Blue Sword.asset.meta ├── Item_Weapon_Red Sword.asset └── Item_Weapon_Red Sword.asset.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Scripts.meta ├── Scripts ├── Inventory.cs ├── Inventory.cs.meta ├── Item Data.meta ├── Item Data │ ├── ArmorItemData.cs │ ├── ArmorItemData.cs.meta │ ├── Bases.meta │ ├── Bases │ │ ├── CountableItemData.cs │ │ ├── CountableItemData.cs.meta │ │ ├── EquipmentItemData.cs │ │ ├── EquipmentItemData.cs.meta │ │ ├── ItemData.cs │ │ └── ItemData.cs.meta │ ├── PortionItemData.cs │ ├── PortionItemData.cs.meta │ ├── WeaponItemData.cs │ └── WeaponItemData.cs.meta ├── Item.meta ├── Item │ ├── ArmorItem.cs │ ├── ArmorItem.cs.meta │ ├── Bases.meta │ ├── Bases │ │ ├── CountableItem.cs │ │ ├── CountableItem.cs.meta │ │ ├── EquipmentItem.cs │ │ ├── EquipmentItem.cs.meta │ │ ├── IUsableItem.cs │ │ ├── IUsableItem.cs.meta │ │ ├── Item.cs │ │ └── Item.cs.meta │ ├── PortionItem.cs │ ├── PortionItem.cs.meta │ ├── WeaponItem.cs │ └── WeaponItem.cs.meta ├── UI.meta └── UI │ ├── InventoryPopupUI.cs │ ├── InventoryPopupUI.cs.meta │ ├── InventoryUI.cs │ ├── InventoryUI.cs.meta │ ├── ItemSlotUI.cs │ ├── ItemSlotUI.cs.meta │ ├── ItemTooltipUI.cs │ ├── ItemTooltipUI.cs.meta │ ├── MovableHeaderUI.cs │ └── MovableHeaderUI.cs.meta ├── Sprites.meta └── Sprites ├── ElixirBlue_64.png ├── ElixirBlue_64.png.meta ├── ElixirRed_64.png ├── ElixirRed_64.png.meta ├── Helmet_64.png ├── Helmet_64.png.meta ├── SwordBlue_64.png ├── SwordBlue_64.png.meta ├── SwordRed_64.png ├── SwordRed_64.png.meta ├── WhiteArmor_64.png └── WhiteArmor_64.png.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | 62 | # Additions by Rito 63 | *.cmd 64 | *.cmd.meta 65 | _* 66 | -------------------------------------------------------------------------------- /Demo Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19fafcdf7b1cf404795067b4cbfd845f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Demo Scripts/InventoryTester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | using Rito.InventorySystem; 7 | 8 | // 날짜 : 2021-03-19 PM 11:01:36 9 | // 작성자 : Rito 10 | 11 | public class InventoryTester : MonoBehaviour 12 | { 13 | public Inventory _inventory; 14 | 15 | public ItemData[] _itemDataArray; 16 | 17 | [Space(12)] 18 | public Button _removeAllButton; 19 | 20 | [Space(8)] 21 | public Button _AddArmorA1; 22 | public Button _AddArmorB1; 23 | public Button _AddSwordA1; 24 | public Button _AddSwordB1; 25 | public Button _AddPortionA1; 26 | public Button _AddPortionA50; 27 | public Button _AddPortionB1; 28 | public Button _AddPortionB50; 29 | 30 | private void Start() 31 | { 32 | if (_itemDataArray?.Length > 0) 33 | { 34 | for (int i = 0; i < _itemDataArray.Length; i++) 35 | { 36 | _inventory.Add(_itemDataArray[i], 3); 37 | 38 | if(_itemDataArray[i] is CountableItemData) 39 | _inventory.Add(_itemDataArray[i], 255); 40 | } 41 | } 42 | 43 | _removeAllButton.onClick.AddListener(() => 44 | { 45 | int capacity = _inventory.Capacity; 46 | for(int i = 0; i < capacity; i++) 47 | _inventory.Remove(i); 48 | }); 49 | 50 | _AddArmorA1.onClick.AddListener(() => _inventory.Add(_itemDataArray[0])); 51 | _AddArmorB1.onClick.AddListener(() => _inventory.Add(_itemDataArray[1])); 52 | 53 | _AddSwordA1.onClick.AddListener(() => _inventory.Add(_itemDataArray[2])); 54 | _AddSwordB1.onClick.AddListener(() => _inventory.Add(_itemDataArray[3])); 55 | 56 | _AddPortionA1.onClick.AddListener(() => _inventory.Add(_itemDataArray[4])); 57 | _AddPortionA50.onClick.AddListener(() => _inventory.Add(_itemDataArray[4], 50)); 58 | _AddPortionB1.onClick.AddListener(() => _inventory.Add(_itemDataArray[5])); 59 | _AddPortionB50.onClick.AddListener(() => _inventory.Add(_itemDataArray[5], 50)); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /Demo Scripts/InventoryTester.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7367a7830ed812249a4b205472fa9313 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Demo.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 312290db893094f49b25c39265d09882 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Item Data.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f7d09e6180ef70c47921c0ca3c86992d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Item Data/Item_Armor_Helmet.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: b50f428ada62c3a40b63fb5674b59ee3, type: 3} 13 | m_Name: Item_Armor_Helmet 14 | m_EditorClassIdentifier: 15 | _id: 5 16 | _name: "\uAE30\uC0AC\uC6A9 \uD22C\uAD6C" 17 | _tooltip: "\uAE30\uC0AC\uAC00 \uC4F8 \uAC83 \uAC19\uC740 \uD22C\uAD6C\uC774\uB2E4." 18 | _iconSprite: {fileID: 21300000, guid: 2a3a301121704ab45bb55331eb763958, type: 3} 19 | _dropItemPrefab: {fileID: 0} 20 | _maxDurability: 100 21 | _defence: 5 22 | -------------------------------------------------------------------------------- /Item Data/Item_Armor_Helmet.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8f19a473f3e8c5c408faaffb1802abea 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Item Data/Item_Armor_White Armor.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: b50f428ada62c3a40b63fb5674b59ee3, type: 3} 13 | m_Name: Item_Armor_White Armor 14 | m_EditorClassIdentifier: 15 | _id: 4 16 | _name: "\uD558\uC580 \uAC11\uC637" 17 | _tooltip: "\uD558\uC580 \uAC11\uC637\uC774\uB2E4." 18 | _iconSprite: {fileID: 21300000, guid: a1f270ff46445564e9afa40d0d9574db, type: 3} 19 | _dropItemPrefab: {fileID: 0} 20 | _maxDurability: 150 21 | _defence: 10 22 | -------------------------------------------------------------------------------- /Item Data/Item_Armor_White Armor.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7e18c97d59896341a1a487bee626a6d 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Item Data/Item_Portion_HP.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: b597242af57330945a22e8bf03013df0, type: 3} 13 | m_Name: Item_Portion_HP 14 | m_EditorClassIdentifier: 15 | _id: 0 16 | _name: "\uC18C\uD615 \uCCB4\uB825 \uD3EC\uC158" 17 | _tooltip: "\uCCB4\uB825\uC744 100 \uCC44\uC6CC\uC900\uB2E4." 18 | _iconSprite: {fileID: 21300000, guid: c2ebadfcb9b8cbb4a8ae6f124b17e332, type: 3} 19 | _dropItemPrefab: {fileID: 0} 20 | _maxAmount: 99 21 | _value: 100 22 | -------------------------------------------------------------------------------- /Item Data/Item_Portion_HP.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c76044d8a642b6341882e805becf92df 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Item Data/Item_Portion_MP.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: b597242af57330945a22e8bf03013df0, type: 3} 13 | m_Name: Item_Portion_MP 14 | m_EditorClassIdentifier: 15 | _id: 1 16 | _name: "\uC18C\uD615 \uB9C8\uB098 \uD3EC\uC158" 17 | _tooltip: "\uB9C8\uB098\uB97C 100 \uCC44\uC6CC\uC900\uB2E4." 18 | _iconSprite: {fileID: 21300000, guid: ac64af182bf5c9c4a8a16cb35547a3cb, type: 3} 19 | _dropItemPrefab: {fileID: 0} 20 | _maxAmount: 99 21 | _value: 100 22 | -------------------------------------------------------------------------------- /Item Data/Item_Portion_MP.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 04eef8332317ca946adaf6a2a23b00ca 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Item Data/Item_Weapon_Blue Sword.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: 8be323efc2a71414498d934aca6fd26a, type: 3} 13 | m_Name: Item_Weapon_Blue Sword 14 | m_EditorClassIdentifier: 15 | _id: 2 16 | _name: "\uD478\uB978 \uC7A5\uAC80" 17 | _tooltip: "\uD30C\uB791\uD30C\uB791 \uC7A5\uAC80\uC774\uB2E4." 18 | _iconSprite: {fileID: 21300000, guid: bb1bdd56c1352fb419836f9c2b0bf4bf, type: 3} 19 | _dropItemPrefab: {fileID: 0} 20 | _maxDurability: 100 21 | _damage: 10 22 | -------------------------------------------------------------------------------- /Item Data/Item_Weapon_Blue Sword.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e44506062b1a62e4f9afc1bf9cf480fd 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Item Data/Item_Weapon_Red Sword.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: 8be323efc2a71414498d934aca6fd26a, type: 3} 13 | m_Name: Item_Weapon_Red Sword 14 | m_EditorClassIdentifier: 15 | _id: 3 16 | _name: "\uBD89\uC740 \uB2E8\uAC80" 17 | _tooltip: "\uBD89\uAC8C \uBB3C\uB4E0 \uB2E8\uAC80\uC774\uB2E4." 18 | _iconSprite: {fileID: 21300000, guid: 1f0a1cc5b62b0d747928186abd4be7c2, type: 3} 19 | _dropItemPrefab: {fileID: 0} 20 | _maxDurability: 120 21 | _damage: 20 22 | -------------------------------------------------------------------------------- /Item Data/Item_Weapon_Red Sword.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e73a0198a0431f04a8ff190aae00be09 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 rito15 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07f1f8d828d892e45a786df0607b2c8a 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity RPG Inventory 2 | 3 | - RPG 게임에서 사용될 수 있는 인벤토리의 기본 구조 및 기능을 간단히 제작하였습니다. 4 | 5 | - 6 | 7 |
8 | 9 | ## 구현된 기능 10 | 11 | - 인벤토리 헤더 드래그 앤 드롭 이동 12 | - 아이템 슬롯 동적 생성 13 | - 아이템 추가 14 | - 아이템 제거 15 | - 아이템 사용 16 | - 아이템 위치 이동 17 | - 아이템 개수 합치기 18 | - 아이템 슬롯 하이라이트(강조) 표시 19 | - 아이템 정보 툴팁 표시 20 | - 아이템 버리기 팝업 21 | - 아이템 개수 나누기 팝업 22 | - 인벤토리 빈칸 채우기 23 | - 인벤토리 정렬 24 | - 아이템 타입에 따른 필터링(장비, 소비, ..) 25 | 26 |
27 | 28 | ## Preview 29 | 30 | ![](https://user-images.githubusercontent.com/42164422/117533171-54a0c400-b026-11eb-80b8-f6788f7461b5.gif) 31 | 32 | ![](https://user-images.githubusercontent.com/42164422/117789477-59ae7f00-b283-11eb-989b-cb095c4b47fc.gif) 33 | 34 | ![](https://user-images.githubusercontent.com/42164422/117789483-5b784280-b283-11eb-9c99-4c49ee952f0e.gif) 35 | 36 | ![](https://user-images.githubusercontent.com/42164422/117974153-34e00780-b368-11eb-9ad6-4a1eb014531d.gif) 37 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 48e7e0188c02cbb42a122143e829ef5a 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 21ade3eb7f6b9724d913030dc42ee34c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Inventory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | /* 7 | [Item의 상속구조] 8 | - Item 9 | - CountableItem 10 | - PortionItem : IUsableItem.Use() -> 사용 및 수량 1 소모 11 | - EquipmentItem 12 | - WeaponItem 13 | - ArmorItem 14 | 15 | [ItemData의 상속구조] 16 | (ItemData는 해당 아이템이 공통으로 가질 데이터 필드 모음) 17 | (개체마다 달라져야 하는 현재 내구도, 강화도 등은 Item 클래스에서 관리) 18 | 19 | - ItemData 20 | - CountableItemData 21 | - PortionItemData : 효과량(Value : 회복량, 공격력 등에 사용) 22 | - EquipmentItemData : 최대 내구도 23 | - WeaponItemData : 기본 공격력 24 | - ArmorItemData : 기본 방어력 25 | */ 26 | 27 | /* 28 | [API] 29 | - bool HasItem(int) : 해당 인덱스의 슬롯에 아이템이 존재하는지 여부 30 | - bool IsCountableItem(int) : 해당 인덱스의 아이템이 셀 수 있는 아이템인지 여부 31 | - int GetCurrentAmount(int) : 해당 인덱스의 아이템 수량 32 | - -1 : 잘못된 인덱스 33 | - 0 : 빈 슬롯 34 | - 1 : 셀 수 없는 아이템이거나 수량 1 35 | - ItemData GetItemData(int) : 해당 인덱스의 아이템 정보 36 | - string GetItemName(int) : 해당 인덱스의 아이템 이름 37 | 38 | - int Add(ItemData, int) : 해당 타입의 아이템을 지정한 개수만큼 인벤토리에 추가 39 | - 자리 부족으로 못넣은 개수만큼 리턴(0이면 모두 추가 성공했다는 의미) 40 | - void Remove(int) : 해당 인덱스의 슬롯에 있는 아이템 제거 41 | - void Swap(int, int) : 두 인덱스의 아이템 위치 서로 바꾸기 42 | - void SeparateAmount(int a, int b, int amount) 43 | - a 인덱스의 아이템이 셀 수 있는 아이템일 경우, amount만큼 분리하여 b 인덱스로 복제 44 | - void Use(int) : 해당 인덱스의 아이템 사용 45 | - void UpdateSlot(int) : 해당 인덱스의 슬롯 상태 및 UI 갱신 46 | - void UpdateAllSlot() : 모든 슬롯 상태 및 UI 갱신 47 | - void UpdateAccessibleStatesAll() : 모든 슬롯 UI에 접근 가능 여부 갱신 48 | - void TrimAll() : 앞에서부터 아이템 슬롯 채우기 49 | - void SortAll() : 앞에서부터 아이템 슬롯 채우면서 정렬 50 | */ 51 | 52 | // 날짜 : 2021-03-07 PM 7:33:52 53 | // 작성자 : Rito 54 | 55 | namespace Rito.InventorySystem 56 | { 57 | public class Inventory : MonoBehaviour 58 | { 59 | /*********************************************************************** 60 | * Public Properties 61 | ***********************************************************************/ 62 | #region . 63 | /// 아이템 수용 한도 64 | public int Capacity { get; private set; } 65 | 66 | // /// 현재 아이템 개수 67 | //public int ItemCount => _itemArray.Count; 68 | 69 | #endregion 70 | /*********************************************************************** 71 | * Private Fields 72 | ***********************************************************************/ 73 | #region . 74 | 75 | // 초기 수용 한도 76 | [SerializeField, Range(8, 64)] 77 | private int _initalCapacity = 32; 78 | 79 | // 최대 수용 한도(아이템 배열 크기) 80 | [SerializeField, Range(8, 64)] 81 | private int _maxCapacity = 64; 82 | 83 | [SerializeField] 84 | private InventoryUI _inventoryUI; // 연결된 인벤토리 UI 85 | 86 | /// 아이템 목록 87 | [SerializeField] 88 | private Item[] _items; 89 | 90 | /// 업데이트 할 인덱스 목록 91 | private readonly HashSet _indexSetForUpdate = new HashSet(); 92 | 93 | /// 아이템 데이터 타입별 정렬 가중치 94 | private readonly static Dictionary _sortWeightDict = new Dictionary 95 | { 96 | { typeof(PortionItemData), 10000 }, 97 | { typeof(WeaponItemData), 20000 }, 98 | { typeof(ArmorItemData), 30000 }, 99 | }; 100 | 101 | private class ItemComparer : IComparer 102 | { 103 | public int Compare(Item a, Item b) 104 | { 105 | return (a.Data.ID + _sortWeightDict[a.Data.GetType()]) 106 | - (b.Data.ID + _sortWeightDict[b.Data.GetType()]); 107 | } 108 | } 109 | private static readonly ItemComparer _itemComparer = new ItemComparer(); 110 | 111 | #endregion 112 | /*********************************************************************** 113 | * Unity Events 114 | ***********************************************************************/ 115 | #region . 116 | 117 | #if UNITY_EDITOR 118 | private void OnValidate() 119 | { 120 | if(_initalCapacity > _maxCapacity) 121 | _initalCapacity = _maxCapacity; 122 | } 123 | #endif 124 | private void Awake() 125 | { 126 | _items = new Item[_maxCapacity]; 127 | Capacity = _initalCapacity; 128 | _inventoryUI.SetInventoryReference(this); 129 | } 130 | 131 | private void Start() 132 | { 133 | UpdateAccessibleStatesAll(); 134 | } 135 | 136 | #endregion 137 | /*********************************************************************** 138 | * Private Methods 139 | ***********************************************************************/ 140 | #region . 141 | /// 인덱스가 수용 범위 내에 있는지 검사 142 | private bool IsValidIndex(int index) 143 | { 144 | return index >= 0 && index < Capacity; 145 | } 146 | 147 | /// 앞에서부터 비어있는 슬롯 인덱스 탐색 148 | private int FindEmptySlotIndex(int startIndex = 0) 149 | { 150 | for (int i = startIndex; i < Capacity; i++) 151 | if (_items[i] == null) 152 | return i; 153 | return -1; 154 | } 155 | 156 | /// 앞에서부터 개수 여유가 있는 Countable 아이템의 슬롯 인덱스 탐색 157 | private int FindCountableItemSlotIndex(CountableItemData target, int startIndex = 0) 158 | { 159 | for (int i = startIndex; i < Capacity; i++) 160 | { 161 | var current = _items[i]; 162 | if (current == null) 163 | continue; 164 | 165 | // 아이템 종류 일치, 개수 여유 확인 166 | if (current.Data == target && current is CountableItem ci) 167 | { 168 | if (!ci.IsMax) 169 | return i; 170 | } 171 | } 172 | 173 | return -1; 174 | } 175 | 176 | /// 해당하는 인덱스의 슬롯 상태 및 UI 갱신 177 | private void UpdateSlot(int index) 178 | { 179 | if (!IsValidIndex(index)) return; 180 | 181 | Item item = _items[index]; 182 | 183 | // 1. 아이템이 슬롯에 존재하는 경우 184 | if (item != null) 185 | { 186 | // 아이콘 등록 187 | _inventoryUI.SetItemIcon(index, item.Data.IconSprite); 188 | 189 | // 1-1. 셀 수 있는 아이템 190 | if (item is CountableItem ci) 191 | { 192 | // 1-1-1. 수량이 0인 경우, 아이템 제거 193 | if (ci.IsEmpty) 194 | { 195 | _items[index] = null; 196 | RemoveIcon(); 197 | return; 198 | } 199 | // 1-1-2. 수량 텍스트 표시 200 | else 201 | { 202 | _inventoryUI.SetItemAmountText(index, ci.Amount); 203 | } 204 | } 205 | // 1-2. 셀 수 없는 아이템인 경우 수량 텍스트 제거 206 | else 207 | { 208 | _inventoryUI.HideItemAmountText(index); 209 | } 210 | 211 | // 슬롯 필터 상태 업데이트 212 | _inventoryUI.UpdateSlotFilterState(index, item.Data); 213 | } 214 | // 2. 빈 슬롯인 경우 : 아이콘 제거 215 | else 216 | { 217 | RemoveIcon(); 218 | } 219 | 220 | // 로컬 : 아이콘 제거하기 221 | void RemoveIcon() 222 | { 223 | _inventoryUI.RemoveItem(index); 224 | _inventoryUI.HideItemAmountText(index); // 수량 텍스트 숨기기 225 | } 226 | } 227 | 228 | /// 해당하는 인덱스의 슬롯들의 상태 및 UI 갱신 229 | private void UpdateSlot(params int[] indices) 230 | { 231 | foreach (var i in indices) 232 | { 233 | UpdateSlot(i); 234 | } 235 | } 236 | 237 | /// 모든 슬롯들의 상태를 UI에 갱신 238 | private void UpdateAllSlot() 239 | { 240 | for (int i = 0; i < Capacity; i++) 241 | { 242 | UpdateSlot(i); 243 | } 244 | } 245 | 246 | #endregion 247 | /*********************************************************************** 248 | * Check & Getter Methods 249 | ***********************************************************************/ 250 | #region . 251 | 252 | /// 해당 슬롯이 아이템을 갖고 있는지 여부 253 | public bool HasItem(int index) 254 | { 255 | return IsValidIndex(index) && _items[index] != null; 256 | } 257 | 258 | /// 해당 슬롯이 셀 수 있는 아이템인지 여부 259 | public bool IsCountableItem(int index) 260 | { 261 | return HasItem(index) && _items[index] is CountableItem; 262 | } 263 | 264 | /// 265 | /// 해당 슬롯의 현재 아이템 개수 리턴 266 | /// - 잘못된 인덱스 : -1 리턴 267 | /// - 빈 슬롯 : 0 리턴 268 | /// - 셀 수 없는 아이템 : 1 리턴 269 | /// 270 | public int GetCurrentAmount(int index) 271 | { 272 | if (!IsValidIndex(index)) return -1; 273 | if (_items[index] == null) return 0; 274 | 275 | CountableItem ci = _items[index] as CountableItem; 276 | if (ci == null) 277 | return 1; 278 | 279 | return ci.Amount; 280 | } 281 | 282 | /// 해당 슬롯의 아이템 정보 리턴 283 | public ItemData GetItemData(int index) 284 | { 285 | if (!IsValidIndex(index)) return null; 286 | if (_items[index] == null) return null; 287 | 288 | return _items[index].Data; 289 | } 290 | 291 | /// 해당 슬롯의 아이템 이름 리턴 292 | public string GetItemName(int index) 293 | { 294 | if (!IsValidIndex(index)) return ""; 295 | if (_items[index] == null) return ""; 296 | 297 | return _items[index].Data.Name; 298 | } 299 | 300 | #endregion 301 | /*********************************************************************** 302 | * Public Methods 303 | ***********************************************************************/ 304 | #region . 305 | /// 인벤토리 UI 연결 306 | public void ConnectUI(InventoryUI inventoryUI) 307 | { 308 | _inventoryUI = inventoryUI; 309 | _inventoryUI.SetInventoryReference(this); 310 | } 311 | 312 | /// 인벤토리에 아이템 추가 313 | /// 넣는 데 실패한 잉여 아이템 개수 리턴 314 | /// 리턴이 0이면 넣는데 모두 성공했다는 의미 315 | /// 316 | public int Add(ItemData itemData, int amount = 1) 317 | { 318 | int index; 319 | 320 | // 1. 수량이 있는 아이템 321 | if (itemData is CountableItemData ciData) 322 | { 323 | bool findNextCountable = true; 324 | index = -1; 325 | 326 | while (amount > 0) 327 | { 328 | // 1-1. 이미 해당 아이템이 인벤토리 내에 존재하고, 개수 여유 있는지 검사 329 | if (findNextCountable) 330 | { 331 | index = FindCountableItemSlotIndex(ciData, index + 1); 332 | 333 | // 개수 여유있는 기존재 슬롯이 더이상 없다고 판단될 경우, 빈 슬롯부터 탐색 시작 334 | if (index == -1) 335 | { 336 | findNextCountable = false; 337 | } 338 | // 기존재 슬롯을 찾은 경우, 양 증가시키고 초과량 존재 시 amount에 초기화 339 | else 340 | { 341 | CountableItem ci = _items[index] as CountableItem; 342 | amount = ci.AddAmountAndGetExcess(amount); 343 | 344 | UpdateSlot(index); 345 | } 346 | } 347 | // 1-2. 빈 슬롯 탐색 348 | else 349 | { 350 | index = FindEmptySlotIndex(index + 1); 351 | 352 | // 빈 슬롯조차 없는 경우 종료 353 | if (index == -1) 354 | { 355 | break; 356 | } 357 | // 빈 슬롯 발견 시, 슬롯에 아이템 추가 및 잉여량 계산 358 | else 359 | { 360 | // 새로운 아이템 생성 361 | CountableItem ci = ciData.CreateItem() as CountableItem; 362 | ci.SetAmount(amount); 363 | 364 | // 슬롯에 추가 365 | _items[index] = ci; 366 | 367 | // 남은 개수 계산 368 | amount = (amount > ciData.MaxAmount) ? (amount - ciData.MaxAmount) : 0; 369 | 370 | UpdateSlot(index); 371 | } 372 | } 373 | } 374 | } 375 | // 2. 수량이 없는 아이템 376 | else 377 | { 378 | // 2-1. 1개만 넣는 경우, 간단히 수행 379 | if (amount == 1) 380 | { 381 | index = FindEmptySlotIndex(); 382 | if (index != -1) 383 | { 384 | // 아이템을 생성하여 슬롯에 추가 385 | _items[index] = itemData.CreateItem(); 386 | amount = 0; 387 | 388 | UpdateSlot(index); 389 | } 390 | } 391 | 392 | // 2-2. 2개 이상의 수량 없는 아이템을 동시에 추가하는 경우 393 | index = -1; 394 | for (; amount > 0; amount--) 395 | { 396 | // 아이템 넣은 인덱스의 다음 인덱스부터 슬롯 탐색 397 | index = FindEmptySlotIndex(index + 1); 398 | 399 | // 다 넣지 못한 경우 루프 종료 400 | if (index == -1) 401 | { 402 | break; 403 | } 404 | 405 | // 아이템을 생성하여 슬롯에 추가 406 | _items[index] = itemData.CreateItem(); 407 | 408 | UpdateSlot(index); 409 | } 410 | } 411 | 412 | return amount; 413 | } 414 | 415 | /// 해당 슬롯의 아이템 제거 416 | public void Remove(int index) 417 | { 418 | if (!IsValidIndex(index)) return; 419 | 420 | _items[index] = null; 421 | _inventoryUI.RemoveItem(index); 422 | } 423 | 424 | /// 두 인덱스의 아이템 위치를 서로 교체 425 | public void Swap(int indexA, int indexB) 426 | { 427 | if (!IsValidIndex(indexA)) return; 428 | if (!IsValidIndex(indexB)) return; 429 | 430 | Item itemA = _items[indexA]; 431 | Item itemB = _items[indexB]; 432 | 433 | // 1. 셀 수 있는 아이템이고, 동일한 아이템일 경우 434 | // indexA -> indexB로 개수 합치기 435 | if (itemA != null && itemB != null && 436 | itemA.Data == itemB.Data && 437 | itemA is CountableItem ciA && itemB is CountableItem ciB) 438 | { 439 | int maxAmount = ciB.MaxAmount; 440 | int sum = ciA.Amount + ciB.Amount; 441 | 442 | if (sum <= maxAmount) 443 | { 444 | ciA.SetAmount(0); 445 | ciB.SetAmount(sum); 446 | } 447 | else 448 | { 449 | ciA.SetAmount(sum - maxAmount); 450 | ciB.SetAmount(maxAmount); 451 | } 452 | } 453 | // 2. 일반적인 경우 : 슬롯 교체 454 | else 455 | { 456 | _items[indexA] = itemB; 457 | _items[indexB] = itemA; 458 | } 459 | 460 | // 두 슬롯 정보 갱신 461 | UpdateSlot(indexA, indexB); 462 | } 463 | 464 | /// 셀 수 있는 아이템의 수량 나누기(A -> B 슬롯으로) 465 | public void SeparateAmount(int indexA, int indexB, int amount) 466 | { 467 | // amount : 나눌 목표 수량 468 | 469 | if(!IsValidIndex(indexA)) return; 470 | if(!IsValidIndex(indexB)) return; 471 | 472 | Item _itemA = _items[indexA]; 473 | Item _itemB = _items[indexB]; 474 | 475 | CountableItem _ciA = _itemA as CountableItem; 476 | 477 | // 조건 : A 슬롯 - 셀 수 있는 아이템 / B 슬롯 - Null 478 | // 조건에 맞는 경우, 복제하여 슬롯 B에 추가 479 | if (_ciA != null && _itemB == null) 480 | { 481 | _items[indexB] = _ciA.SeperateAndClone(amount); 482 | 483 | UpdateSlot(indexA, indexB); 484 | } 485 | } 486 | 487 | /// 해당 슬롯의 아이템 사용 488 | public void Use(int index) 489 | { 490 | if (!IsValidIndex(index)) return; 491 | if (_items[index] == null) return; 492 | 493 | // 사용 가능한 아이템인 경우 494 | if (_items[index] is IUsableItem uItem) 495 | { 496 | // 아이템 사용 497 | bool succeeded = uItem.Use(); 498 | 499 | if (succeeded) 500 | { 501 | UpdateSlot(index); 502 | } 503 | } 504 | } 505 | 506 | /// 모든 슬롯 UI에 접근 가능 여부 업데이트 507 | public void UpdateAccessibleStatesAll() 508 | { 509 | _inventoryUI.SetAccessibleSlotRange(Capacity); 510 | } 511 | 512 | /// 빈 슬롯 없이 앞에서부터 채우기 513 | public void TrimAll() 514 | { 515 | // 가장 빠른 배열 빈공간 채우기 알고리즘 516 | 517 | // i 커서와 j 커서 518 | // i 커서 : 가장 앞에 있는 빈칸을 찾는 커서 519 | // j 커서 : i 커서 위치에서부터 뒤로 이동하며 기존재 아이템을 찾는 커서 520 | 521 | // i커서가 빈칸을 찾으면 j 커서는 i+1 위치부터 탐색 522 | // j커서가 아이템을 찾으면 아이템을 옮기고, i 커서는 i+1 위치로 이동 523 | // j커서가 Capacity에 도달하면 루프 즉시 종료 524 | 525 | _indexSetForUpdate.Clear(); 526 | 527 | int i = -1; 528 | while (_items[++i] != null) ; 529 | int j = i; 530 | 531 | while (true) 532 | { 533 | while (++j < Capacity && _items[j] == null); 534 | 535 | if (j == Capacity) 536 | break; 537 | 538 | _indexSetForUpdate.Add(i); 539 | _indexSetForUpdate.Add(j); 540 | 541 | _items[i] = _items[j]; 542 | _items[j] = null; 543 | i++; 544 | } 545 | 546 | foreach (var index in _indexSetForUpdate) 547 | { 548 | UpdateSlot(index); 549 | } 550 | _inventoryUI.UpdateAllSlotFilters(); 551 | } 552 | 553 | /// 빈 슬롯 없이 채우면서 아이템 종류별로 정렬하기 554 | public void SortAll() 555 | { 556 | // 1. Trim 557 | int i = -1; 558 | while (_items[++i] != null) ; 559 | int j = i; 560 | 561 | while (true) 562 | { 563 | while (++j < Capacity && _items[j] == null) ; 564 | 565 | if (j == Capacity) 566 | break; 567 | 568 | _items[i] = _items[j]; 569 | _items[j] = null; 570 | i++; 571 | } 572 | 573 | // 2. Sort 574 | Array.Sort(_items, 0, i, _itemComparer); 575 | 576 | // 3. Update 577 | UpdateAllSlot(); 578 | _inventoryUI.UpdateAllSlotFilters(); // 필터 상태 업데이트 579 | } 580 | 581 | #endregion 582 | } 583 | } -------------------------------------------------------------------------------- /Scripts/Inventory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd347083a75615f448ffa248fb2c97d4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item Data.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2abbec82c3388847b38df9750b9606e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Item Data/ArmorItemData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-28 PM 10:42:17 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 장비 - 방어구 아이템 12 | [CreateAssetMenu(fileName = "Item_Armor_", menuName = "Inventory System/Item Data/Armor", order = 2)] 13 | public class ArmorItemData : EquipmentItemData 14 | { 15 | /// 방어력 16 | public int Defence => _defence; 17 | 18 | [SerializeField] private int _defence = 1; 19 | public override Item CreateItem() 20 | { 21 | return new ArmorItem(this); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Scripts/Item Data/ArmorItemData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b50f428ada62c3a40b63fb5674b59ee3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item Data/Bases.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d57b80d4bb50f24ca5602ee9304a895 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Item Data/Bases/CountableItemData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-27 PM 11:34:48 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 셀 수 있는 아이템 데이터 12 | public abstract class CountableItemData : ItemData 13 | { 14 | public int MaxAmount => _maxAmount; 15 | [SerializeField] private int _maxAmount = 99; 16 | } 17 | } -------------------------------------------------------------------------------- /Scripts/Item Data/Bases/CountableItemData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1971c5cefe151b44683b2606035af84e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item Data/Bases/EquipmentItemData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-27 PM 11:07:41 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 장비 아이템 12 | public abstract class EquipmentItemData : ItemData 13 | { 14 | /// 최대 내구도 15 | public int MaxDurability => _maxDurability; 16 | 17 | [SerializeField] private int _maxDurability = 100; 18 | } 19 | } -------------------------------------------------------------------------------- /Scripts/Item Data/Bases/EquipmentItemData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e5d999ba525e58243be0f4bfa5c8e9ec 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item Data/Bases/ItemData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | // 날짜 : 2021-03-07 PM 8:45:55 8 | // 작성자 : Rito 9 | 10 | namespace Rito.InventorySystem 11 | { 12 | /* 13 | [상속 구조] 14 | 15 | ItemData(abstract) 16 | - CountableItemData(abstract) 17 | - PortionItemData 18 | - EquipmentItemData(abstract) 19 | - WeaponItemData 20 | - ArmorItemData 21 | 22 | */ 23 | 24 | public abstract class ItemData : ScriptableObject 25 | { 26 | public int ID => _id; 27 | public string Name => _name; 28 | public string Tooltip => _tooltip; 29 | public Sprite IconSprite => _iconSprite; 30 | 31 | [SerializeField] private int _id; 32 | [SerializeField] private string _name; // 아이템 이름 33 | [Multiline] 34 | [SerializeField] private string _tooltip; // 아이템 설명 35 | [SerializeField] private Sprite _iconSprite; // 아이템 아이콘 36 | [SerializeField] private GameObject _dropItemPrefab; // 바닥에 떨어질 때 생성할 프리팹 37 | 38 | /// 타입에 맞는 새로운 아이템 생성 39 | public abstract Item CreateItem(); 40 | } 41 | } -------------------------------------------------------------------------------- /Scripts/Item Data/Bases/ItemData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8bf92190e57c1c940a83176f802e600c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item Data/PortionItemData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-28 PM 10:42:48 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 소비 아이템 정보 12 | [CreateAssetMenu(fileName = "Item_Portion_", menuName = "Inventory System/Item Data/Portion", order = 3)] 13 | public class PortionItemData : CountableItemData 14 | { 15 | /// 효과량(회복량 등) 16 | public float Value => _value; 17 | [SerializeField] private float _value; 18 | public override Item CreateItem() 19 | { 20 | return new PortionItem(this); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Scripts/Item Data/PortionItemData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b597242af57330945a22e8bf03013df0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item Data/WeaponItemData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-28 PM 10:38:33 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 장비 - 무기 아이템 12 | [CreateAssetMenu(fileName = "Item_Weapon_", menuName = "Inventory System/Item Data/Weaopn", order = 1)] 13 | public class WeaponItemData : EquipmentItemData 14 | { 15 | /// 공격력 16 | public int Damage => _damage; 17 | 18 | [SerializeField] private int _damage = 1; 19 | 20 | public override Item CreateItem() 21 | { 22 | return new WeaponItem(this); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Scripts/Item Data/WeaponItemData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8be323efc2a71414498d934aca6fd26a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d80e7055b1dd5c4478a01806b1024355 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Item/ArmorItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-28 PM 11:06:16 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 장비 - 방어구 아이템 12 | public class ArmorItem : EquipmentItem 13 | { 14 | public ArmorItem(ArmorItemData data) : base(data) { } 15 | } 16 | } -------------------------------------------------------------------------------- /Scripts/Item/ArmorItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60899e625c944a54ba420ddaf0e3e11e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item/Bases.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 10624e20782c20c4991a7c4b335736c8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Item/Bases/CountableItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-21 PM 11:01:27 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 수량을 셀 수 있는 아이템 12 | public abstract class CountableItem : Item 13 | { 14 | public CountableItemData CountableData { get; private set; } 15 | 16 | /// 현재 아이템 개수 17 | public int Amount { get; protected set; } 18 | 19 | /// 하나의 슬롯이 가질 수 있는 최대 개수(기본 99) 20 | public int MaxAmount => CountableData.MaxAmount; 21 | 22 | /// 수량이 가득 찼는지 여부 23 | public bool IsMax => Amount >= CountableData.MaxAmount; 24 | 25 | /// 개수가 없는지 여부 26 | public bool IsEmpty => Amount <= 0; 27 | 28 | 29 | public CountableItem(CountableItemData data, int amount = 1) : base(data) 30 | { 31 | CountableData = data; 32 | SetAmount(amount); 33 | } 34 | 35 | /// 개수 지정(범위 제한) 36 | public void SetAmount(int amount) 37 | { 38 | Amount = Mathf.Clamp(amount, 0, MaxAmount); 39 | } 40 | 41 | /// 개수 추가 및 최대치 초과량 반환(초과량 없을 경우 0) 42 | public int AddAmountAndGetExcess(int amount) 43 | { 44 | int nextAmount = Amount + amount; 45 | SetAmount(nextAmount); 46 | 47 | return (nextAmount > MaxAmount) ? (nextAmount - MaxAmount) : 0; 48 | } 49 | 50 | /// 개수를 나누어 복제 51 | public CountableItem SeperateAndClone(int amount) 52 | { 53 | // 수량이 한개 이하일 경우, 복제 불가 54 | if(Amount <= 1) return null; 55 | 56 | if(amount > Amount - 1) 57 | amount = Amount - 1; 58 | 59 | Amount -= amount; 60 | return Clone(amount); 61 | } 62 | 63 | protected abstract CountableItem Clone(int amount); 64 | } 65 | } -------------------------------------------------------------------------------- /Scripts/Item/Bases/CountableItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4ba69c1f1c6d3a64387d543e847594c9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item/Bases/EquipmentItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-28 PM 10:55:00 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 장비 아이템 12 | public abstract class EquipmentItem : Item 13 | { 14 | public EquipmentItemData EquipmentData { get; private set; } 15 | 16 | /// 현재 내구도 17 | public int Durability 18 | { 19 | get => _durability; 20 | set 21 | { 22 | if(value < 0) value = 0; 23 | if(value > EquipmentData.MaxDurability) 24 | value = EquipmentData.MaxDurability; 25 | 26 | _durability = value; 27 | } 28 | } 29 | private int _durability; 30 | 31 | public EquipmentItem(EquipmentItemData data) : base(data) 32 | { 33 | EquipmentData = data; 34 | Durability = data.MaxDurability; 35 | } 36 | 37 | // Item Data 외의 필드값에 대한 매개변수를 갖는 생성자는 추가로 제공하지 않음 38 | // 자식들에서 모두 추가해줘야 하므로 유지보수면에서 불편 39 | } 40 | } -------------------------------------------------------------------------------- /Scripts/Item/Bases/EquipmentItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2effbefd157312243beefa076e4a72c4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item/Bases/IUsableItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-04-15 PM 2:59:59 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 사용 가능한 아이템(착용/소모) 12 | public interface IUsableItem 13 | { 14 | /// 아이템 사용하기(사용 성공 여부 리턴) 15 | bool Use(); 16 | } 17 | } -------------------------------------------------------------------------------- /Scripts/Item/Bases/IUsableItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 373c932af49bbd148acd3f2810025687 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item/Bases/Item.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | // 날짜 : 2021-03-07 PM 7:34:39 8 | // 작성자 : Rito 9 | 10 | namespace Rito.InventorySystem 11 | { 12 | /* 13 | [상속 구조] 14 | Item : 기본 아이템 15 | - EquipmentItem : 장비 아이템 16 | - CountableItem : 수량이 존재하는 아이템 17 | */ 18 | public abstract class Item 19 | { 20 | public ItemData Data { get; private set; } 21 | 22 | public Item(ItemData data) => Data = data; 23 | } 24 | } -------------------------------------------------------------------------------- /Scripts/Item/Bases/Item.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ad04de80c001c4442ae031ac57ecad61 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item/PortionItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-28 PM 11:07:23 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 수량 아이템 - 포션 아이템 12 | public class PortionItem : CountableItem, IUsableItem 13 | { 14 | public PortionItem(PortionItemData data, int amount = 1) : base(data, amount) { } 15 | 16 | public bool Use() 17 | { 18 | // 임시 : 개수 하나 감소 19 | Amount--; 20 | 21 | return true; 22 | } 23 | 24 | protected override CountableItem Clone(int amount) 25 | { 26 | return new PortionItem(CountableData as PortionItemData, amount); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Scripts/Item/PortionItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f1024a7001da4a47a4ce4ca5d2322fa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Item/WeaponItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | // 날짜 : 2021-03-28 PM 11:02:03 7 | // 작성자 : Rito 8 | 9 | namespace Rito.InventorySystem 10 | { 11 | /// 장비 - 무기 아이템 12 | public class WeaponItem : EquipmentItem 13 | { 14 | public WeaponItem(WeaponItemData data) : base(data) { } 15 | } 16 | } -------------------------------------------------------------------------------- /Scripts/Item/WeaponItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6bd85c2409af394884b994dc2e34534 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/UI.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ffd06526a7acc06479693b205efaa9c5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/UI/InventoryPopupUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | // 날짜 : 2021-04-13 PM 7:47:35 8 | // 작성자 : Rito 9 | 10 | namespace Rito 11 | { 12 | /// 인벤토리 UI 위에 띄울 작은 팝업들 관리 13 | public class InventoryPopupUI : MonoBehaviour 14 | { 15 | /*********************************************************************** 16 | * Fields 17 | ***********************************************************************/ 18 | #region . 19 | // 1. 아이템 버리기 확인 팝업 20 | [Header("Confirmation Popup")] 21 | [SerializeField] private GameObject _confirmationPopupObject; 22 | [SerializeField] private Text _confirmationItemNameText; 23 | [SerializeField] private Text _confirmationText; 24 | [SerializeField] private Button _confirmationOkButton; // Ok 25 | [SerializeField] private Button _confirmationCancelButton; // Cancel 26 | 27 | // 2. 수량 입력 팝업 28 | [Header("Amount Input Popup")] 29 | [SerializeField] private GameObject _amountInputPopupObject; 30 | [SerializeField] private Text _amountInputItemNameText; 31 | [SerializeField] private InputField _amountInputField; 32 | [SerializeField] private Button _amountPlusButton; // + 33 | [SerializeField] private Button _amountMinusButton; // - 34 | [SerializeField] private Button _amountInputOkButton; // Ok 35 | [SerializeField] private Button _amountInputCancelButton; // Cancel 36 | 37 | // 확인 버튼 눌렀을 때 동작할 이벤트 38 | private event Action OnConfirmationOK; 39 | private event Action OnAmountInputOK; 40 | 41 | // 수량 입력 제한 개수 42 | private int _maxAmount; 43 | 44 | #endregion 45 | /*********************************************************************** 46 | * Unity Events 47 | ***********************************************************************/ 48 | #region . 49 | private void Awake() 50 | { 51 | InitUIEvents(); 52 | HidePanel(); 53 | HideConfirmationPopup(); 54 | HideAmountInputPopup(); 55 | } 56 | 57 | private void Update() 58 | { 59 | if (_confirmationPopupObject.activeSelf) 60 | { 61 | if (Input.GetKeyDown(KeyCode.Return)) 62 | { 63 | _confirmationOkButton.onClick?.Invoke(); 64 | } 65 | else if (Input.GetKeyDown(KeyCode.Escape)) 66 | { 67 | _confirmationCancelButton.onClick?.Invoke(); 68 | } 69 | } 70 | else if (_amountInputPopupObject.activeSelf) 71 | { 72 | if (Input.GetKeyDown(KeyCode.Return)) 73 | { 74 | _amountInputOkButton.onClick?.Invoke(); 75 | } 76 | else if (Input.GetKeyDown(KeyCode.Escape)) 77 | { 78 | _amountInputCancelButton.onClick?.Invoke(); 79 | } 80 | } 81 | } 82 | 83 | #endregion 84 | /*********************************************************************** 85 | * Public Methods 86 | ***********************************************************************/ 87 | #region . 88 | /// 확인/취소 팝업 띄우기 89 | public void OpenConfirmationPopup(Action okCallback, string itemName) 90 | { 91 | ShowPanel(); 92 | ShowConfirmationPopup(itemName); 93 | SetConfirmationOKEvent(okCallback); 94 | } 95 | /// 수량 입력 팝업 띄우기 96 | public void OpenAmountInputPopup(Action okCallback, int currentAmount, string itemName) 97 | { 98 | _maxAmount = currentAmount - 1; 99 | _amountInputField.text = "1"; 100 | 101 | ShowPanel(); 102 | ShowAmountInputPopup(itemName); 103 | SetAmountInputOKEvent(okCallback); 104 | } 105 | 106 | #endregion 107 | /*********************************************************************** 108 | * Private Methods 109 | ***********************************************************************/ 110 | #region . 111 | private void InitUIEvents() 112 | { 113 | // 1. 확인 취소 팝업 114 | _confirmationOkButton.onClick.AddListener(HidePanel); 115 | _confirmationOkButton.onClick.AddListener(HideConfirmationPopup); 116 | _confirmationOkButton.onClick.AddListener(() => OnConfirmationOK?.Invoke()); 117 | 118 | _confirmationCancelButton.onClick.AddListener(HidePanel); 119 | _confirmationCancelButton.onClick.AddListener(HideConfirmationPopup); 120 | 121 | // 2. 수량 입력 팝업 122 | _amountInputOkButton.onClick.AddListener(HidePanel); 123 | _amountInputOkButton.onClick.AddListener(HideAmountInputPopup); 124 | _amountInputOkButton.onClick.AddListener(() => OnAmountInputOK?.Invoke(int.Parse(_amountInputField.text))); 125 | 126 | _amountInputCancelButton.onClick.AddListener(HidePanel); 127 | _amountInputCancelButton.onClick.AddListener(HideAmountInputPopup); 128 | 129 | // [-] 버튼 이벤트 130 | _amountMinusButton.onClick.AddListener(() => 131 | { 132 | int.TryParse(_amountInputField.text, out int amount); 133 | if (amount > 1) 134 | { 135 | // Shift 누르면 10씩 감소 136 | int nextAmount = Input.GetKey(KeyCode.LeftShift) ? amount - 10 : amount - 1; 137 | if(nextAmount < 1) 138 | nextAmount = 1; 139 | _amountInputField.text = nextAmount.ToString(); 140 | } 141 | }); 142 | 143 | // [+] 버튼 이벤트 144 | _amountPlusButton.onClick.AddListener(() => 145 | { 146 | int.TryParse(_amountInputField.text, out int amount); 147 | if (amount < _maxAmount) 148 | { 149 | // Shift 누르면 10씩 증가 150 | int nextAmount = Input.GetKey(KeyCode.LeftShift) ? amount + 10 : amount + 1; 151 | if (nextAmount > _maxAmount) 152 | nextAmount = _maxAmount; 153 | _amountInputField.text = nextAmount.ToString(); 154 | } 155 | }); 156 | 157 | // 입력 값 범위 제한 158 | _amountInputField.onValueChanged.AddListener(str => 159 | { 160 | int.TryParse(str, out int amount); 161 | bool flag = false; 162 | 163 | if (amount < 1) 164 | { 165 | flag = true; 166 | amount = 1; 167 | } 168 | else if (amount > _maxAmount) 169 | { 170 | flag = true; 171 | amount = _maxAmount; 172 | } 173 | 174 | if(flag) 175 | _amountInputField.text = amount.ToString(); 176 | }); 177 | } 178 | 179 | private void ShowPanel() => gameObject.SetActive(true); 180 | private void HidePanel() => gameObject.SetActive(false); 181 | 182 | private void ShowConfirmationPopup(string itemName) 183 | { 184 | _confirmationItemNameText.text = itemName; 185 | _confirmationPopupObject.SetActive(true); 186 | } 187 | private void HideConfirmationPopup() => _confirmationPopupObject.SetActive(false); 188 | 189 | private void ShowAmountInputPopup(string itemName) 190 | { 191 | _amountInputItemNameText.text = itemName; 192 | _amountInputPopupObject.SetActive(true); 193 | } 194 | private void HideAmountInputPopup() => _amountInputPopupObject.SetActive(false); 195 | 196 | private void SetConfirmationOKEvent(Action handler) => OnConfirmationOK = handler; 197 | private void SetAmountInputOKEvent(Action handler) => OnAmountInputOK = handler; 198 | 199 | 200 | #endregion 201 | 202 | } 203 | } -------------------------------------------------------------------------------- /Scripts/UI/InventoryPopupUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87a85f7172c6d864c8dd7b12622b4cdb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/UI/InventoryUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | using UnityEngine.EventSystems; 7 | 8 | /* 9 | [기능 - 에디터 전용] 10 | - 게임 시작 시 동적으로 생성될 슬롯 미리보기(개수, 크기 미리보기 가능) 11 | 12 | [기능 - 유저 인터페이스] 13 | - 슬롯에 마우스 올리기 14 | - 사용 가능 슬롯 : 하이라이트 이미지 표시 15 | - 아이템 존재 슬롯 : 아이템 정보 툴팁 표시 16 | 17 | - 드래그 앤 드롭 18 | - 아이템 존재 슬롯 -> 아이템 존재 슬롯 : 두 아이템 위치 교환 19 | - 아이템 존재 슬롯 -> 아이템 미존재 슬롯 : 아이템 위치 변경 20 | - Shift 또는 Ctrl 누른 상태일 경우 : 셀 수 있는 아이템 수량 나누기 21 | - 아이템 존재 슬롯 -> UI 바깥 : 아이템 버리기 22 | 23 | - 슬롯 우클릭 24 | - 사용 가능한 아이템일 경우 : 아이템 사용 25 | 26 | - 기능 버튼(좌측 상단) 27 | - Trim : 앞에서부터 빈 칸 없이 아이템 채우기 28 | - Sort : 정해진 가중치대로 아이템 정렬 29 | 30 | - 필터 버튼(우측 상단) 31 | - [A] : 모든 아이템 필터링 32 | - [E] : 장비 아이템 필터링 33 | - [P] : 소비 아이템 필터링 34 | 35 | * 필터링에서 제외된 아이템 슬롯들은 조작 불가 36 | 37 | [기능 - 기타] 38 | - InvertMouse(bool) : 마우스 좌클릭/우클릭 반전 여부 설정 39 | */ 40 | 41 | // 날짜 : 2021-03-07 PM 7:34:31 42 | // 작성자 : Rito 43 | 44 | namespace Rito.InventorySystem 45 | { 46 | public class InventoryUI : MonoBehaviour 47 | { 48 | /*********************************************************************** 49 | * Option Fields 50 | ***********************************************************************/ 51 | #region . 52 | [Header("Options")] 53 | [Range(0, 10)] 54 | [SerializeField] private int _horizontalSlotCount = 8; // 슬롯 가로 개수 55 | [Range(0, 10)] 56 | [SerializeField] private int _verticalSlotCount = 8; // 슬롯 세로 개수 57 | [SerializeField] private float _slotMargin = 8f; // 한 슬롯의 상하좌우 여백 58 | [SerializeField] private float _contentAreaPadding = 20f; // 인벤토리 영역의 내부 여백 59 | [Range(32, 64)] 60 | [SerializeField] private float _slotSize = 64f; // 각 슬롯의 크기 61 | 62 | [Space] 63 | [SerializeField] private bool _showTooltip = true; 64 | [SerializeField] private bool _showHighlight = true; 65 | [SerializeField] private bool _showRemovingPopup = true; 66 | 67 | [Header("Connected Objects")] 68 | [SerializeField] private RectTransform _contentAreaRT; // 슬롯들이 위치할 영역 69 | [SerializeField] private GameObject _slotUiPrefab; // 슬롯의 원본 프리팹 70 | [SerializeField] private ItemTooltipUI _itemTooltip; // 아이템 정보를 보여줄 툴팁 UI 71 | [SerializeField] private InventoryPopupUI _popup; // 팝업 UI 관리 객체 72 | 73 | [Header("Buttons")] 74 | [SerializeField] private Button _trimButton; 75 | [SerializeField] private Button _sortButton; 76 | 77 | [Header("Filter Toggles")] 78 | [SerializeField] private Toggle _toggleFilterAll; 79 | [SerializeField] private Toggle _toggleFilterEquipments; 80 | [SerializeField] private Toggle _toggleFilterPortions; 81 | 82 | [Space(16)] 83 | [SerializeField] private bool _mouseReversed = false; // 마우스 클릭 반전 여부 84 | 85 | #endregion 86 | /*********************************************************************** 87 | * Private Fields 88 | ***********************************************************************/ 89 | #region . 90 | 91 | /// 연결된 인벤토리 92 | private Inventory _inventory; 93 | 94 | private List _slotUIList = new List(); 95 | private GraphicRaycaster _gr; 96 | private PointerEventData _ped; 97 | private List _rrList; 98 | 99 | private ItemSlotUI _pointerOverSlot; // 현재 포인터가 위치한 곳의 슬롯 100 | private ItemSlotUI _beginDragSlot; // 현재 드래그를 시작한 슬롯 101 | private Transform _beginDragIconTransform; // 해당 슬롯의 아이콘 트랜스폼 102 | 103 | private int _leftClick = 0; 104 | private int _rightClick = 1; 105 | 106 | private Vector3 _beginDragIconPoint; // 드래그 시작 시 슬롯의 위치 107 | private Vector3 _beginDragCursorPoint; // 드래그 시작 시 커서의 위치 108 | private int _beginDragSlotSiblingIndex; 109 | 110 | /// 인벤토리 UI 내 아이템 필터링 옵션 111 | private enum FilterOption 112 | { 113 | All, Equipment, Portion 114 | } 115 | private FilterOption _currentFilterOption = FilterOption.All; 116 | 117 | #endregion 118 | /*********************************************************************** 119 | * Unity Events 120 | ***********************************************************************/ 121 | #region . 122 | private void Awake() 123 | { 124 | Init(); 125 | InitSlots(); 126 | InitButtonEvents(); 127 | InitToggleEvents(); 128 | } 129 | 130 | private void Update() 131 | { 132 | _ped.position = Input.mousePosition; 133 | 134 | OnPointerEnterAndExit(); 135 | if(_showTooltip) ShowOrHideItemTooltip(); 136 | OnPointerDown(); 137 | OnPointerDrag(); 138 | OnPointerUp(); 139 | } 140 | 141 | #endregion 142 | /*********************************************************************** 143 | * Init Methods 144 | ***********************************************************************/ 145 | #region . 146 | private void Init() 147 | { 148 | TryGetComponent(out _gr); 149 | if (_gr == null) 150 | _gr = gameObject.AddComponent(); 151 | 152 | // Graphic Raycaster 153 | _ped = new PointerEventData(EventSystem.current); 154 | _rrList = new List(10); 155 | 156 | // Item Tooltip UI 157 | if (_itemTooltip == null) 158 | { 159 | _itemTooltip = GetComponentInChildren(); 160 | EditorLog("인스펙터에서 아이템 툴팁 UI를 직접 지정하지 않아 자식에서 발견하여 초기화하였습니다."); 161 | } 162 | } 163 | 164 | /// 지정된 개수만큼 슬롯 영역 내에 슬롯들 동적 생성 165 | private void InitSlots() 166 | { 167 | // 슬롯 프리팹 설정 168 | _slotUiPrefab.TryGetComponent(out RectTransform slotRect); 169 | slotRect.sizeDelta = new Vector2(_slotSize, _slotSize); 170 | 171 | _slotUiPrefab.TryGetComponent(out ItemSlotUI itemSlot); 172 | if (itemSlot == null) 173 | _slotUiPrefab.AddComponent(); 174 | 175 | _slotUiPrefab.SetActive(false); 176 | 177 | // -- 178 | Vector2 beginPos = new Vector2(_contentAreaPadding, -_contentAreaPadding); 179 | Vector2 curPos = beginPos; 180 | 181 | _slotUIList = new List(_verticalSlotCount * _horizontalSlotCount); 182 | 183 | // 슬롯들 동적 생성 184 | for (int j = 0; j < _verticalSlotCount; j++) 185 | { 186 | for (int i = 0; i < _horizontalSlotCount; i++) 187 | { 188 | int slotIndex = (_horizontalSlotCount * j) + i; 189 | 190 | var slotRT = CloneSlot(); 191 | slotRT.pivot = new Vector2(0f, 1f); // Left Top 192 | slotRT.anchoredPosition = curPos; 193 | slotRT.gameObject.SetActive(true); 194 | slotRT.gameObject.name = $"Item Slot [{slotIndex}]"; 195 | 196 | var slotUI = slotRT.GetComponent(); 197 | slotUI.SetSlotIndex(slotIndex); 198 | _slotUIList.Add(slotUI); 199 | 200 | // Next X 201 | curPos.x += (_slotMargin + _slotSize); 202 | } 203 | 204 | // Next Line 205 | curPos.x = beginPos.x; 206 | curPos.y -= (_slotMargin + _slotSize); 207 | } 208 | 209 | // 슬롯 프리팹 - 프리팹이 아닌 경우 파괴 210 | if(_slotUiPrefab.scene.rootCount != 0) 211 | Destroy(_slotUiPrefab); 212 | 213 | // -- Local Method -- 214 | RectTransform CloneSlot() 215 | { 216 | GameObject slotGo = Instantiate(_slotUiPrefab); 217 | RectTransform rt = slotGo.GetComponent(); 218 | rt.SetParent(_contentAreaRT); 219 | 220 | return rt; 221 | } 222 | } 223 | 224 | private void InitButtonEvents() 225 | { 226 | _trimButton.onClick.AddListener(() => _inventory.TrimAll()); 227 | _sortButton.onClick.AddListener(() => _inventory.SortAll()); 228 | } 229 | 230 | private void InitToggleEvents() 231 | { 232 | _toggleFilterAll.onValueChanged.AddListener( flag => UpdateFilter(flag, FilterOption.All)); 233 | _toggleFilterEquipments.onValueChanged.AddListener(flag => UpdateFilter(flag, FilterOption.Equipment)); 234 | _toggleFilterPortions.onValueChanged.AddListener( flag => UpdateFilter(flag, FilterOption.Portion)); 235 | 236 | // Local Method 237 | void UpdateFilter(bool flag, FilterOption option) 238 | { 239 | if (flag) 240 | { 241 | _currentFilterOption = option; 242 | UpdateAllSlotFilters(); 243 | } 244 | } 245 | } 246 | 247 | #endregion 248 | /*********************************************************************** 249 | * Mouse Event Methods 250 | ***********************************************************************/ 251 | #region . 252 | private bool IsOverUI() 253 | => EventSystem.current.IsPointerOverGameObject(); 254 | 255 | /// 레이캐스트하여 얻은 첫 번째 UI에서 컴포넌트 찾아 리턴 256 | private T RaycastAndGetFirstComponent() where T : Component 257 | { 258 | _rrList.Clear(); 259 | 260 | _gr.Raycast(_ped, _rrList); 261 | 262 | if(_rrList.Count == 0) 263 | return null; 264 | 265 | return _rrList[0].gameObject.GetComponent(); 266 | } 267 | /// 슬롯에 포인터가 올라가는 경우, 슬롯에서 포인터가 빠져나가는 경우 268 | private void OnPointerEnterAndExit() 269 | { 270 | // 이전 프레임의 슬롯 271 | var prevSlot = _pointerOverSlot; 272 | 273 | // 현재 프레임의 슬롯 274 | var curSlot = _pointerOverSlot = RaycastAndGetFirstComponent(); 275 | 276 | if (prevSlot == null) 277 | { 278 | // Enter 279 | if (curSlot != null) 280 | { 281 | OnCurrentEnter(); 282 | } 283 | } 284 | else 285 | { 286 | // Exit 287 | if (curSlot == null) 288 | { 289 | OnPrevExit(); 290 | } 291 | 292 | // Change 293 | else if (prevSlot != curSlot) 294 | { 295 | OnPrevExit(); 296 | OnCurrentEnter(); 297 | } 298 | } 299 | 300 | // ===================== Local Methods =============================== 301 | void OnCurrentEnter() 302 | { 303 | if(_showHighlight) 304 | curSlot.Highlight(true); 305 | } 306 | void OnPrevExit() 307 | { 308 | prevSlot.Highlight(false); 309 | } 310 | } 311 | /// 아이템 정보 툴팁 보여주거나 감추기 312 | private void ShowOrHideItemTooltip() 313 | { 314 | // 마우스가 유효한 아이템 아이콘 위에 올라와 있다면 툴팁 보여주기 315 | bool isValid = 316 | _pointerOverSlot != null && _pointerOverSlot.HasItem && _pointerOverSlot.IsAccessible 317 | && (_pointerOverSlot != _beginDragSlot); // 드래그 시작한 슬롯이면 보여주지 않기 318 | 319 | if (isValid) 320 | { 321 | UpdateTooltipUI(_pointerOverSlot); 322 | _itemTooltip.Show(); 323 | } 324 | else 325 | _itemTooltip.Hide(); 326 | } 327 | /// 슬롯에 클릭하는 경우 328 | private void OnPointerDown() 329 | { 330 | // Left Click : Begin Drag 331 | if (Input.GetMouseButtonDown(_leftClick)) 332 | { 333 | _beginDragSlot = RaycastAndGetFirstComponent(); 334 | 335 | // 아이템을 갖고 있는 슬롯만 해당 336 | if (_beginDragSlot != null && _beginDragSlot.HasItem && _beginDragSlot.IsAccessible) 337 | { 338 | EditorLog($"Drag Begin : Slot [{_beginDragSlot.Index}]"); 339 | 340 | // 위치 기억, 참조 등록 341 | _beginDragIconTransform = _beginDragSlot.IconRect.transform; 342 | _beginDragIconPoint = _beginDragIconTransform.position; 343 | _beginDragCursorPoint = Input.mousePosition; 344 | 345 | // 맨 위에 보이기 346 | _beginDragSlotSiblingIndex = _beginDragSlot.transform.GetSiblingIndex(); 347 | _beginDragSlot.transform.SetAsLastSibling(); 348 | 349 | // 해당 슬롯의 하이라이트 이미지를 아이콘보다 뒤에 위치시키기 350 | _beginDragSlot.SetHighlightOnTop(false); 351 | } 352 | else 353 | { 354 | _beginDragSlot = null; 355 | } 356 | } 357 | 358 | // Right Click : Use Item 359 | else if (Input.GetMouseButtonDown(_rightClick)) 360 | { 361 | ItemSlotUI slot = RaycastAndGetFirstComponent(); 362 | 363 | if (slot != null && slot.HasItem && slot.IsAccessible) 364 | { 365 | TryUseItem(slot.Index); 366 | } 367 | } 368 | } 369 | /// 드래그하는 도중 370 | private void OnPointerDrag() 371 | { 372 | if(_beginDragSlot == null) return; 373 | 374 | if (Input.GetMouseButton(_leftClick)) 375 | { 376 | // 위치 이동 377 | _beginDragIconTransform.position = 378 | _beginDragIconPoint + (Input.mousePosition - _beginDragCursorPoint); 379 | } 380 | } 381 | /// 클릭을 뗄 경우 382 | private void OnPointerUp() 383 | { 384 | if (Input.GetMouseButtonUp(_leftClick)) 385 | { 386 | // End Drag 387 | if (_beginDragSlot != null) 388 | { 389 | // 위치 복원 390 | _beginDragIconTransform.position = _beginDragIconPoint; 391 | 392 | // UI 순서 복원 393 | _beginDragSlot.transform.SetSiblingIndex(_beginDragSlotSiblingIndex); 394 | 395 | // 드래그 완료 처리 396 | EndDrag(); 397 | 398 | // 해당 슬롯의 하이라이트 이미지를 아이콘보다 앞에 위치시키기 399 | _beginDragSlot.SetHighlightOnTop(true); 400 | 401 | // 참조 제거 402 | _beginDragSlot = null; 403 | _beginDragIconTransform = null; 404 | } 405 | } 406 | } 407 | 408 | private void EndDrag() 409 | { 410 | ItemSlotUI endDragSlot = RaycastAndGetFirstComponent(); 411 | 412 | // 아이템 슬롯끼리 아이콘 교환 또는 이동 413 | if (endDragSlot != null && endDragSlot.IsAccessible) 414 | { 415 | // 수량 나누기 조건 416 | // 1) 마우스 클릭 떼는 순간 좌측 Ctrl 또는 Shift 키 유지 417 | // 2) begin : 셀 수 있는 아이템 / end : 비어있는 슬롯 418 | // 3) begin 아이템의 수량 > 1 419 | bool isSeparatable = 420 | (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftShift)) && 421 | (_inventory.IsCountableItem(_beginDragSlot.Index) && !_inventory.HasItem(endDragSlot.Index)); 422 | 423 | // true : 수량 나누기, false : 교환 또는 이동 424 | bool isSeparation = false; 425 | int currentAmount = 0; 426 | 427 | // 현재 개수 확인 428 | if (isSeparatable) 429 | { 430 | currentAmount = _inventory.GetCurrentAmount(_beginDragSlot.Index); 431 | if (currentAmount > 1) 432 | { 433 | isSeparation = true; 434 | } 435 | } 436 | 437 | // 1. 개수 나누기 438 | if(isSeparation) 439 | TrySeparateAmount(_beginDragSlot.Index, endDragSlot.Index, currentAmount); 440 | // 2. 교환 또는 이동 441 | else 442 | TrySwapItems(_beginDragSlot, endDragSlot); 443 | 444 | // 툴팁 갱신 445 | UpdateTooltipUI(endDragSlot); 446 | return; 447 | } 448 | 449 | // 버리기(커서가 UI 레이캐스트 타겟 위에 있지 않은 경우) 450 | if (!IsOverUI()) 451 | { 452 | // 확인 팝업 띄우고 콜백 위임 453 | int index = _beginDragSlot.Index; 454 | string itemName = _inventory.GetItemName(index); 455 | int amount = _inventory.GetCurrentAmount(index); 456 | 457 | // 셀 수 있는 아이템의 경우, 수량 표시 458 | if(amount > 1) 459 | itemName += $" x{amount}"; 460 | 461 | if(_showRemovingPopup) 462 | _popup.OpenConfirmationPopup(() => TryRemoveItem(index), itemName); 463 | else 464 | TryRemoveItem(index); 465 | } 466 | // 슬롯이 아닌 다른 UI 위에 놓은 경우 467 | else 468 | { 469 | EditorLog($"Drag End(Do Nothing)"); 470 | } 471 | } 472 | 473 | #endregion 474 | /*********************************************************************** 475 | * Private Methods 476 | ***********************************************************************/ 477 | #region . 478 | 479 | /// UI 및 인벤토리에서 아이템 제거 480 | private void TryRemoveItem(int index) 481 | { 482 | EditorLog($"UI - Try Remove Item : Slot [{index}]"); 483 | 484 | _inventory.Remove(index); 485 | } 486 | 487 | /// 아이템 사용 488 | private void TryUseItem(int index) 489 | { 490 | EditorLog($"UI - Try Use Item : Slot [{index}]"); 491 | 492 | _inventory.Use(index); 493 | } 494 | 495 | /// 두 슬롯의 아이템 교환 496 | private void TrySwapItems(ItemSlotUI from, ItemSlotUI to) 497 | { 498 | if (from == to) 499 | { 500 | EditorLog($"UI - Try Swap Items: Same Slot [{from.Index}]"); 501 | return; 502 | } 503 | 504 | EditorLog($"UI - Try Swap Items: Slot [{from.Index} -> {to.Index}]"); 505 | 506 | from.SwapOrMoveIcon(to); 507 | _inventory.Swap(from.Index, to.Index); 508 | } 509 | 510 | /// 셀 수 있는 아이템 개수 나누기 511 | private void TrySeparateAmount(int indexA, int indexB, int amount) 512 | { 513 | if (indexA == indexB) 514 | { 515 | EditorLog($"UI - Try Separate Amount: Same Slot [{indexA}]"); 516 | return; 517 | } 518 | 519 | EditorLog($"UI - Try Separate Amount: Slot [{indexA} -> {indexB}]"); 520 | 521 | string itemName = $"{_inventory.GetItemName(indexA)} x{amount}"; 522 | 523 | _popup.OpenAmountInputPopup( 524 | amt => _inventory.SeparateAmount(indexA, indexB, amt), 525 | amount, itemName 526 | ); 527 | } 528 | 529 | /// 툴팁 UI의 슬롯 데이터 갱신 530 | private void UpdateTooltipUI(ItemSlotUI slot) 531 | { 532 | if(!slot.IsAccessible || !slot.HasItem) 533 | return; 534 | 535 | // 툴팁 정보 갱신 536 | _itemTooltip.SetItemInfo(_inventory.GetItemData(slot.Index)); 537 | 538 | // 툴팁 위치 조정 539 | _itemTooltip.SetRectPosition(slot.SlotRect); 540 | } 541 | 542 | #endregion 543 | /*********************************************************************** 544 | * Public Methods 545 | ***********************************************************************/ 546 | #region . 547 | 548 | /// 인벤토리 참조 등록 (인벤토리에서 직접 호출) 549 | public void SetInventoryReference(Inventory inventory) 550 | { 551 | _inventory = inventory; 552 | } 553 | 554 | /// 마우스 클릭 좌우 반전시키기 (true : 반전) 555 | public void InvertMouse(bool value) 556 | { 557 | _leftClick = value ? 1 : 0; 558 | _rightClick = value ? 0 : 1; 559 | 560 | _mouseReversed = value; 561 | } 562 | 563 | /// 슬롯에 아이템 아이콘 등록 564 | public void SetItemIcon(int index, Sprite icon) 565 | { 566 | EditorLog($"Set Item Icon : Slot [{index}]"); 567 | 568 | _slotUIList[index].SetItem(icon); 569 | } 570 | 571 | /// 해당 슬롯의 아이템 개수 텍스트 지정 572 | public void SetItemAmountText(int index, int amount) 573 | { 574 | EditorLog($"Set Item Amount Text : Slot [{index}], Amount [{amount}]"); 575 | 576 | // NOTE : amount가 1 이하일 경우 텍스트 미표시 577 | _slotUIList[index].SetItemAmount(amount); 578 | } 579 | 580 | /// 해당 슬롯의 아이템 개수 텍스트 지정 581 | public void HideItemAmountText(int index) 582 | { 583 | EditorLog($"Hide Item Amount Text : Slot [{index}]"); 584 | 585 | _slotUIList[index].SetItemAmount(1); 586 | } 587 | 588 | /// 슬롯에서 아이템 아이콘 제거, 개수 텍스트 숨기기 589 | public void RemoveItem(int index) 590 | { 591 | EditorLog($"Remove Item : Slot [{index}]"); 592 | 593 | _slotUIList[index].RemoveItem(); 594 | } 595 | 596 | /// 접근 가능한 슬롯 범위 설정 597 | public void SetAccessibleSlotRange(int accessibleSlotCount) 598 | { 599 | for (int i = 0; i < _slotUIList.Count; i++) 600 | { 601 | _slotUIList[i].SetSlotAccessibleState(i < accessibleSlotCount); 602 | } 603 | } 604 | 605 | /// 특정 슬롯의 필터 상태 업데이트 606 | public void UpdateSlotFilterState(int index, ItemData itemData) 607 | { 608 | bool isFiltered = true; 609 | 610 | // null인 슬롯은 타입 검사 없이 필터 활성화 611 | if(itemData != null) 612 | switch (_currentFilterOption) 613 | { 614 | case FilterOption.Equipment: 615 | isFiltered = (itemData is EquipmentItemData); 616 | break; 617 | 618 | case FilterOption.Portion: 619 | isFiltered = (itemData is PortionItemData); 620 | break; 621 | } 622 | 623 | _slotUIList[index].SetItemAccessibleState(isFiltered); 624 | } 625 | 626 | /// 모든 슬롯 필터 상태 업데이트 627 | public void UpdateAllSlotFilters() 628 | { 629 | int capacity = _inventory.Capacity; 630 | 631 | for (int i = 0; i < capacity; i++) 632 | { 633 | ItemData data = _inventory.GetItemData(i); 634 | UpdateSlotFilterState(i, data); 635 | } 636 | } 637 | 638 | #endregion 639 | /*********************************************************************** 640 | * Editor Only Debug 641 | ***********************************************************************/ 642 | #region . 643 | 644 | [Header("Editor Options")] 645 | [SerializeField] private bool _showDebug = true; 646 | [System.Diagnostics.Conditional("UNITY_EDITOR")] 647 | private void EditorLog(object message) 648 | { 649 | if (!_showDebug) return; 650 | UnityEngine.Debug.Log($"[InventoryUI] {message}"); 651 | } 652 | 653 | #endregion 654 | /*********************************************************************** 655 | * Editor Preview 656 | ***********************************************************************/ 657 | #region . 658 | #if UNITY_EDITOR 659 | [SerializeField] private bool __showPreview = false; 660 | 661 | [Range(0.01f, 1f)] 662 | [SerializeField] private float __previewAlpha = 0.1f; 663 | 664 | private List __previewSlotGoList = new List(); 665 | private int __prevSlotCountPerLine; 666 | private int __prevSlotLineCount; 667 | private float __prevSlotSize; 668 | private float __prevSlotMargin; 669 | private float __prevContentPadding; 670 | private float __prevAlpha; 671 | private bool __prevShow = false; 672 | private bool __prevMouseReversed = false; 673 | 674 | private void OnValidate() 675 | { 676 | if (__prevMouseReversed != _mouseReversed) 677 | { 678 | __prevMouseReversed = _mouseReversed; 679 | InvertMouse(_mouseReversed); 680 | 681 | EditorLog($"Mouse Reversed : {_mouseReversed}"); 682 | } 683 | 684 | if (Application.isPlaying) return; 685 | 686 | if (__showPreview && !__prevShow) 687 | { 688 | CreateSlots(); 689 | } 690 | __prevShow = __showPreview; 691 | 692 | if (Unavailable()) 693 | { 694 | ClearAll(); 695 | return; 696 | } 697 | if (CountChanged()) 698 | { 699 | ClearAll(); 700 | CreateSlots(); 701 | __prevSlotCountPerLine = _horizontalSlotCount; 702 | __prevSlotLineCount = _verticalSlotCount; 703 | } 704 | if (ValueChanged()) 705 | { 706 | DrawGrid(); 707 | __prevSlotSize = _slotSize; 708 | __prevSlotMargin = _slotMargin; 709 | __prevContentPadding = _contentAreaPadding; 710 | } 711 | if (AlphaChanged()) 712 | { 713 | SetImageAlpha(); 714 | __prevAlpha = __previewAlpha; 715 | } 716 | 717 | bool Unavailable() 718 | { 719 | return !__showPreview || 720 | _horizontalSlotCount < 1 || 721 | _verticalSlotCount < 1 || 722 | _slotSize <= 0f || 723 | _contentAreaRT == null || 724 | _slotUiPrefab == null; 725 | } 726 | bool CountChanged() 727 | { 728 | return _horizontalSlotCount != __prevSlotCountPerLine || 729 | _verticalSlotCount != __prevSlotLineCount; 730 | } 731 | bool ValueChanged() 732 | { 733 | return _slotSize != __prevSlotSize || 734 | _slotMargin != __prevSlotMargin || 735 | _contentAreaPadding != __prevContentPadding; 736 | } 737 | bool AlphaChanged() 738 | { 739 | return __previewAlpha != __prevAlpha; 740 | } 741 | void ClearAll() 742 | { 743 | foreach (var go in __previewSlotGoList) 744 | { 745 | Destroyer.Destroy(go); 746 | } 747 | __previewSlotGoList.Clear(); 748 | } 749 | void CreateSlots() 750 | { 751 | int count = _horizontalSlotCount * _verticalSlotCount; 752 | __previewSlotGoList.Capacity = count; 753 | 754 | // 슬롯의 피벗은 Left Top으로 고정 755 | RectTransform slotPrefabRT = _slotUiPrefab.GetComponent(); 756 | slotPrefabRT.pivot = new Vector2(0f, 1f); 757 | 758 | for (int i = 0; i < count; i++) 759 | { 760 | GameObject slotGo = Instantiate(_slotUiPrefab); 761 | slotGo.transform.SetParent(_contentAreaRT.transform); 762 | slotGo.SetActive(true); 763 | slotGo.AddComponent(); 764 | 765 | slotGo.transform.localScale = Vector3.one; // 버그 해결 766 | 767 | HideGameObject(slotGo); 768 | 769 | __previewSlotGoList.Add(slotGo); 770 | } 771 | 772 | DrawGrid(); 773 | SetImageAlpha(); 774 | } 775 | void DrawGrid() 776 | { 777 | Vector2 beginPos = new Vector2(_contentAreaPadding, -_contentAreaPadding); 778 | Vector2 curPos = beginPos; 779 | 780 | // Draw Slots 781 | int index = 0; 782 | for (int j = 0; j < _verticalSlotCount; j++) 783 | { 784 | for (int i = 0; i < _horizontalSlotCount; i++) 785 | { 786 | GameObject slotGo = __previewSlotGoList[index++]; 787 | RectTransform slotRT = slotGo.GetComponent(); 788 | 789 | slotRT.anchoredPosition = curPos; 790 | slotRT.sizeDelta = new Vector2(_slotSize, _slotSize); 791 | __previewSlotGoList.Add(slotGo); 792 | 793 | // Next X 794 | curPos.x += (_slotMargin + _slotSize); 795 | } 796 | 797 | // Next Line 798 | curPos.x = beginPos.x; 799 | curPos.y -= (_slotMargin + _slotSize); 800 | } 801 | } 802 | void HideGameObject(GameObject go) 803 | { 804 | go.hideFlags = HideFlags.HideAndDontSave; 805 | 806 | Transform tr = go.transform; 807 | for (int i = 0; i < tr.childCount; i++) 808 | { 809 | tr.GetChild(i).gameObject.hideFlags = HideFlags.HideAndDontSave; 810 | } 811 | } 812 | void SetImageAlpha() 813 | { 814 | foreach (var go in __previewSlotGoList) 815 | { 816 | var images = go.GetComponentsInChildren(); 817 | foreach (var img in images) 818 | { 819 | img.color = new Color(img.color.r, img.color.g, img.color.b, __previewAlpha); 820 | var outline = img.GetComponent(); 821 | if (outline) 822 | outline.effectColor = new Color(outline.effectColor.r, outline.effectColor.g, outline.effectColor.b, __previewAlpha); 823 | } 824 | } 825 | } 826 | } 827 | 828 | private class PreviewItemSlot : MonoBehaviour { } 829 | 830 | [UnityEditor.InitializeOnLoad] 831 | private static class Destroyer 832 | { 833 | private static Queue targetQueue = new Queue(); 834 | 835 | static Destroyer() 836 | { 837 | UnityEditor.EditorApplication.update += () => 838 | { 839 | for (int i = 0; targetQueue.Count > 0 && i < 100000; i++) 840 | { 841 | var next = targetQueue.Dequeue(); 842 | DestroyImmediate(next); 843 | } 844 | }; 845 | } 846 | public static void Destroy(GameObject go) => targetQueue.Enqueue(go); 847 | } 848 | #endif 849 | 850 | #endregion 851 | } 852 | } -------------------------------------------------------------------------------- /Scripts/UI/InventoryUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5b81c692dc3d044483c3ae773ada1f0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/UI/ItemSlotUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | using UnityEngine.EventSystems; 7 | 8 | // 날짜 : 2021-03-07 PM 10:20:05 9 | // 작성자 : Rito 10 | 11 | namespace Rito.InventorySystem 12 | { 13 | public class ItemSlotUI : MonoBehaviour 14 | { 15 | /*********************************************************************** 16 | * Option Fields 17 | ***********************************************************************/ 18 | #region . 19 | [Tooltip("슬롯 내에서 아이콘과 슬롯 사이의 여백")] 20 | [SerializeField] private float _padding = 1f; 21 | 22 | [Tooltip("아이템 아이콘 이미지")] 23 | [SerializeField] private Image _iconImage; 24 | 25 | [Tooltip("아이템 개수 텍스트")] 26 | [SerializeField] private Text _amountText; 27 | 28 | [Tooltip("슬롯이 포커스될 때 나타나는 하이라이트 이미지")] 29 | [SerializeField] private Image _highlightImage; 30 | 31 | [Space] 32 | [Tooltip("하이라이트 이미지 알파 값")] 33 | [SerializeField] private float _highlightAlpha = 0.5f; 34 | 35 | [Tooltip("하이라이트 소요 시간")] 36 | [SerializeField] private float _highlightFadeDuration = 0.2f; 37 | 38 | #endregion 39 | /*********************************************************************** 40 | * Properties 41 | ***********************************************************************/ 42 | #region . 43 | /// 슬롯의 인덱스 44 | public int Index { get; private set; } 45 | 46 | /// 슬롯이 아이템을 보유하고 있는지 여부 47 | public bool HasItem => _iconImage.sprite != null; 48 | 49 | /// 접근 가능한 슬롯인지 여부 50 | public bool IsAccessible => _isAccessibleSlot && _isAccessibleItem; 51 | 52 | public RectTransform SlotRect => _slotRect; 53 | public RectTransform IconRect => _iconRect; 54 | 55 | #endregion 56 | /*********************************************************************** 57 | * Fields 58 | ***********************************************************************/ 59 | #region . 60 | private InventoryUI _inventoryUI; 61 | 62 | private RectTransform _slotRect; 63 | private RectTransform _iconRect; 64 | private RectTransform _highlightRect; 65 | 66 | private GameObject _iconGo; 67 | private GameObject _textGo; 68 | private GameObject _highlightGo; 69 | 70 | private Image _slotImage; 71 | 72 | // 현재 하이라이트 알파값 73 | private float _currentHLAlpha = 0f; 74 | 75 | private bool _isAccessibleSlot = true; // 슬롯 접근가능 여부 76 | private bool _isAccessibleItem = true; // 아이템 접근가능 여부 77 | 78 | /// 비활성화된 슬롯의 색상 79 | private static readonly Color InaccessibleSlotColor = new Color(0.2f, 0.2f, 0.2f, 0.5f); 80 | /// 비활성화된 아이콘 색상 81 | private static readonly Color InaccessibleIconColor = new Color(0.5f, 0.5f, 0.5f, 0.5f); 82 | 83 | #endregion 84 | /*********************************************************************** 85 | * Unity Events 86 | ***********************************************************************/ 87 | #region . 88 | private void Awake() 89 | { 90 | InitComponents(); 91 | InitValues(); 92 | } 93 | 94 | #endregion 95 | /*********************************************************************** 96 | * Private Methods 97 | ***********************************************************************/ 98 | #region . 99 | private void InitComponents() 100 | { 101 | _inventoryUI = GetComponentInParent(); 102 | 103 | // Rects 104 | _slotRect = GetComponent(); 105 | _iconRect = _iconImage.rectTransform; 106 | _highlightRect = _highlightImage.rectTransform; 107 | 108 | // Game Objects 109 | _iconGo = _iconRect.gameObject; 110 | _textGo = _amountText.gameObject; 111 | _highlightGo = _highlightImage.gameObject; 112 | 113 | // Images 114 | _slotImage = GetComponent(); 115 | } 116 | private void InitValues() 117 | { 118 | // 1. Item Icon, Highlight Rect 119 | _iconRect.pivot = new Vector2(0.5f, 0.5f); // 피벗은 중앙 120 | _iconRect.anchorMin = Vector2.zero; // 앵커는 Top Left 121 | _iconRect.anchorMax = Vector2.one; 122 | 123 | // 패딩 조절 124 | _iconRect.offsetMin = Vector2.one * (_padding); 125 | _iconRect.offsetMax = Vector2.one * (-_padding); 126 | 127 | // 아이콘과 하이라이트 크기가 동일하도록 128 | _highlightRect.pivot = _iconRect.pivot; 129 | _highlightRect.anchorMin = _iconRect.anchorMin; 130 | _highlightRect.anchorMax = _iconRect.anchorMax; 131 | _highlightRect.offsetMin = _iconRect.offsetMin; 132 | _highlightRect.offsetMax = _iconRect.offsetMax; 133 | 134 | // 2. Image 135 | _iconImage.raycastTarget = false; 136 | _highlightImage.raycastTarget = false; 137 | 138 | // 3. Deactivate Icon 139 | HideIcon(); 140 | _highlightGo.SetActive(false); 141 | } 142 | 143 | private void ShowIcon() => _iconGo.SetActive(true); 144 | private void HideIcon() => _iconGo.SetActive(false); 145 | 146 | private void ShowText() => _textGo.SetActive(true); 147 | private void HideText() => _textGo.SetActive(false); 148 | 149 | #endregion 150 | /*********************************************************************** 151 | * Public Methods 152 | ***********************************************************************/ 153 | #region . 154 | 155 | public void SetSlotIndex(int index) => Index = index; 156 | 157 | /// 슬롯 자체의 활성화/비활성화 여부 설정 158 | public void SetSlotAccessibleState(bool value) 159 | { 160 | // 중복 처리는 지양 161 | if (_isAccessibleSlot == value) return; 162 | 163 | if (value) 164 | { 165 | _slotImage.color = Color.black; 166 | } 167 | else 168 | { 169 | _slotImage.color = InaccessibleSlotColor; 170 | HideIcon(); 171 | HideText(); 172 | } 173 | 174 | _isAccessibleSlot = value; 175 | } 176 | 177 | /// 아이템 활성화/비활성화 여부 설정 178 | public void SetItemAccessibleState(bool value) 179 | { 180 | // 중복 처리는 지양 181 | if(_isAccessibleItem == value) return; 182 | 183 | if (value) 184 | { 185 | _iconImage.color = Color.white; 186 | _amountText.color = Color.white; 187 | } 188 | else 189 | { 190 | _iconImage.color = InaccessibleIconColor; 191 | _amountText.color = InaccessibleIconColor; 192 | } 193 | 194 | _isAccessibleItem = value; 195 | } 196 | 197 | /// 다른 슬롯과 아이템 아이콘 교환 198 | public void SwapOrMoveIcon(ItemSlotUI other) 199 | { 200 | if (other == null) return; 201 | if (other == this) return; // 자기 자신과 교환 불가 202 | if (!this.IsAccessible) return; 203 | if (!other.IsAccessible) return; 204 | 205 | var temp = _iconImage.sprite; 206 | 207 | // 1. 대상에 아이템이 있는 경우 : 교환 208 | if (other.HasItem) SetItem(other._iconImage.sprite); 209 | 210 | // 2. 없는 경우 : 이동 211 | else RemoveItem(); 212 | 213 | other.SetItem(temp); 214 | } 215 | 216 | /// 슬롯에 아이템 등록 217 | public void SetItem(Sprite itemSprite) 218 | { 219 | //if (!this.IsAccessible) return; 220 | 221 | if (itemSprite != null) 222 | { 223 | _iconImage.sprite = itemSprite; 224 | ShowIcon(); 225 | } 226 | else 227 | { 228 | RemoveItem(); 229 | } 230 | } 231 | 232 | /// 슬롯에서 아이템 제거 233 | public void RemoveItem() 234 | { 235 | _iconImage.sprite = null; 236 | HideIcon(); 237 | HideText(); 238 | } 239 | 240 | /// 아이템 이미지 투명도 설정 241 | public void SetIconAlpha(float alpha) 242 | { 243 | _iconImage.color = new Color( 244 | _iconImage.color.r, _iconImage.color.g, _iconImage.color.b, alpha 245 | ); 246 | } 247 | 248 | /// 아이템 개수 텍스트 설정(amount가 1 이하일 경우 텍스트 미표시) 249 | public void SetItemAmount(int amount) 250 | { 251 | //if (!this.IsAccessible) return; 252 | 253 | if (HasItem && amount > 1) 254 | ShowText(); 255 | else 256 | HideText(); 257 | 258 | _amountText.text = amount.ToString(); 259 | } 260 | 261 | /// 슬롯에 하이라이트 표시/해제 262 | public void Highlight(bool show) 263 | { 264 | if (!this.IsAccessible) return; 265 | 266 | if (show) 267 | StartCoroutine(nameof(HighlightFadeInRoutine)); 268 | else 269 | StartCoroutine(nameof(HighlightFadeOutRoutine)); 270 | } 271 | 272 | /// 하이라이트 이미지를 아이콘 이미지의 상단/하단으로 표시 273 | public void SetHighlightOnTop(bool value) 274 | { 275 | if(value) 276 | _highlightRect.SetAsLastSibling(); 277 | else 278 | _highlightRect.SetAsFirstSibling(); 279 | } 280 | 281 | #endregion 282 | /*********************************************************************** 283 | * Coroutines 284 | ***********************************************************************/ 285 | #region . 286 | /// 하이라이트 알파값 서서히 증가 287 | private IEnumerator HighlightFadeInRoutine() 288 | { 289 | StopCoroutine(nameof(HighlightFadeOutRoutine)); 290 | _highlightGo.SetActive(true); 291 | 292 | float unit = _highlightAlpha / _highlightFadeDuration; 293 | 294 | for (; _currentHLAlpha <= _highlightAlpha; _currentHLAlpha += unit * Time.deltaTime) 295 | { 296 | _highlightImage.color = new Color( 297 | _highlightImage.color.r, 298 | _highlightImage.color.g, 299 | _highlightImage.color.b, 300 | _currentHLAlpha 301 | ); 302 | 303 | yield return null; 304 | } 305 | } 306 | 307 | /// 하이라이트 알파값 0%까지 서서히 감소 308 | private IEnumerator HighlightFadeOutRoutine() 309 | { 310 | StopCoroutine(nameof(HighlightFadeInRoutine)); 311 | 312 | float unit = _highlightAlpha / _highlightFadeDuration; 313 | 314 | for (; _currentHLAlpha >= 0f; _currentHLAlpha -= unit * Time.deltaTime) 315 | { 316 | _highlightImage.color = new Color( 317 | _highlightImage.color.r, 318 | _highlightImage.color.g, 319 | _highlightImage.color.b, 320 | _currentHLAlpha 321 | ); 322 | 323 | yield return null; 324 | } 325 | 326 | _highlightGo.SetActive(false); 327 | } 328 | 329 | #endregion 330 | } 331 | } -------------------------------------------------------------------------------- /Scripts/UI/ItemSlotUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8199efbdbf68e8548b9c046b339fab53 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/UI/ItemTooltipUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | // 날짜 : 2021-04-01 PM 8:33:22 8 | // 작성자 : Rito 9 | 10 | namespace Rito.InventorySystem 11 | { 12 | /// 슬롯 내의 아이템 아이콘에 마우스를 올렸을 때 보이는 툴팁 13 | public class ItemTooltipUI : MonoBehaviour 14 | { 15 | /*********************************************************************** 16 | * Inspector Option Fields 17 | ***********************************************************************/ 18 | #region . 19 | [SerializeField] 20 | private Text _titleText; // 아이템 이름 텍스트 21 | 22 | [SerializeField] 23 | private Text _contentText; // 아이템 설명 텍스트 24 | 25 | #endregion 26 | /*********************************************************************** 27 | * Private Fields 28 | ***********************************************************************/ 29 | #region . 30 | private RectTransform _rt; 31 | private CanvasScaler _canvasScaler; 32 | 33 | private static readonly Vector2 LeftTop = new Vector2(0f, 1f); 34 | private static readonly Vector2 LeftBottom = new Vector2(0f, 0f); 35 | private static readonly Vector2 RightTop = new Vector2(1f, 1f); 36 | private static readonly Vector2 RightBottom = new Vector2(1f, 0f); 37 | 38 | #endregion 39 | /*********************************************************************** 40 | * Unity Events 41 | ***********************************************************************/ 42 | #region . 43 | private void Awake() 44 | { 45 | Init(); 46 | Hide(); 47 | } 48 | 49 | #endregion 50 | /*********************************************************************** 51 | * Private Methods 52 | ***********************************************************************/ 53 | #region . 54 | private void Init() 55 | { 56 | TryGetComponent(out _rt); 57 | _rt.pivot = LeftTop; 58 | _canvasScaler = GetComponentInParent(); 59 | 60 | DisableAllChildrenRaycastTarget(transform); 61 | } 62 | 63 | /// 모든 자식 UI에 레이캐스트 타겟 해제 64 | private void DisableAllChildrenRaycastTarget(Transform tr) 65 | { 66 | // 본인이 Graphic(UI)를 상속하면 레이캐스트 타겟 해제 67 | tr.TryGetComponent(out Graphic gr); 68 | if(gr != null) 69 | gr.raycastTarget = false; 70 | 71 | // 자식이 없으면 종료 72 | int childCount = tr.childCount; 73 | if (childCount == 0) return; 74 | 75 | for (int i = 0; i < childCount; i++) 76 | { 77 | DisableAllChildrenRaycastTarget(tr.GetChild(i)); 78 | } 79 | } 80 | 81 | #endregion 82 | /*********************************************************************** 83 | * Public Methods 84 | ***********************************************************************/ 85 | #region . 86 | /// 툴팁 UI에 아이템 정보 등록 87 | public void SetItemInfo(ItemData data) 88 | { 89 | _titleText.text = data.Name; 90 | _contentText.text = data.Tooltip; 91 | } 92 | 93 | /// 툴팁의 위치 조정 94 | public void SetRectPosition(RectTransform slotRect) 95 | { 96 | // 캔버스 스케일러에 따른 해상도 대응 97 | float wRatio = Screen.width / _canvasScaler.referenceResolution.x; 98 | float hRatio = Screen.height / _canvasScaler.referenceResolution.y; 99 | float ratio = 100 | wRatio * (1f - _canvasScaler.matchWidthOrHeight) + 101 | hRatio * (_canvasScaler.matchWidthOrHeight); 102 | 103 | float slotWidth = slotRect.rect.width * ratio; 104 | float slotHeight = slotRect.rect.height * ratio; 105 | 106 | // 툴팁 초기 위치(슬롯 우하단) 설정 107 | _rt.position = slotRect.position + new Vector3(slotWidth, -slotHeight); 108 | Vector2 pos = _rt.position; 109 | 110 | // 툴팁의 크기 111 | float width = _rt.rect.width * ratio; 112 | float height = _rt.rect.height * ratio; 113 | 114 | // 우측, 하단이 잘렸는지 여부 115 | bool rightTruncated = pos.x + width > Screen.width; 116 | bool bottomTruncated = pos.y - height < 0f; 117 | 118 | ref bool R = ref rightTruncated; 119 | ref bool B = ref bottomTruncated; 120 | 121 | // 오른쪽만 잘림 => 슬롯의 Left Bottom 방향으로 표시 122 | if (R && !B) 123 | { 124 | _rt.position = new Vector2(pos.x - width - slotWidth, pos.y); 125 | } 126 | // 아래쪽만 잘림 => 슬롯의 Right Top 방향으로 표시 127 | else if (!R && B) 128 | { 129 | _rt.position = new Vector2(pos.x, pos.y + height + slotHeight); 130 | } 131 | // 모두 잘림 => 슬롯의 Left Top 방향으로 표시 132 | else if (R && B) 133 | { 134 | _rt.position = new Vector2(pos.x - width - slotWidth, pos.y + height + slotHeight); 135 | } 136 | // 잘리지 않음 => 슬롯의 Right Bottom 방향으로 표시 137 | // Do Nothing 138 | } 139 | 140 | public void Show() => gameObject.SetActive(true); 141 | 142 | public void Hide() => gameObject.SetActive(false); 143 | 144 | #endregion 145 | } 146 | } -------------------------------------------------------------------------------- /Scripts/UI/ItemTooltipUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: da0ca6a2d0d960540ae2cb7cd6bd64ec 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/UI/MovableHeaderUI.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.EventSystems; 3 | 4 | // 날짜 : 2021-03-18 PM 9:05:42 5 | // 작성자 : Rito 6 | 7 | namespace Rito 8 | { 9 | /// 헤더 드래그 앤 드롭에 의한 UI 이동 10 | public class MovableHeaderUI : MonoBehaviour, IPointerDownHandler, IDragHandler 11 | { 12 | [SerializeField] 13 | private Transform _targetTr; // 이동될 UI 14 | 15 | private Vector2 _beginPoint; 16 | private Vector2 _moveBegin; 17 | 18 | private void Awake() 19 | { 20 | // 이동 대상 UI를 지정하지 않은 경우, 자동으로 부모로 초기화 21 | if(_targetTr == null) 22 | _targetTr = transform.parent; 23 | } 24 | 25 | // 드래그 시작 위치 지정 26 | void IPointerDownHandler.OnPointerDown(PointerEventData eventData) 27 | { 28 | _beginPoint = _targetTr.position; 29 | _moveBegin = eventData.position; 30 | } 31 | 32 | // 드래그 : 마우스 커서 위치로 이동 33 | void IDragHandler.OnDrag(PointerEventData eventData) 34 | { 35 | _targetTr.position = _beginPoint + (eventData.position - _moveBegin); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Scripts/UI/MovableHeaderUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5cefe2861ad5fe649b5ac07155fce36c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Sprites.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bcf4a0f9a366c6a4e995c26717728302 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Sprites/ElixirBlue_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rito15/Unity-RPG-Inventory/e0ed8276483161d4f5ba24fc142d8a3dccc386cb/Sprites/ElixirBlue_64.png -------------------------------------------------------------------------------- /Sprites/ElixirBlue_64.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ac64af182bf5c9c4a8a16cb35547a3cb 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: -1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | applyGammaDecoding: 0 61 | platformSettings: 62 | - serializedVersion: 3 63 | buildTarget: DefaultTexturePlatform 64 | maxTextureSize: 64 65 | resizeAlgorithm: 0 66 | textureFormat: -1 67 | textureCompression: 1 68 | compressionQuality: 50 69 | crunchedCompression: 0 70 | allowsAlphaSplitting: 0 71 | overridden: 0 72 | androidETC2FallbackOverride: 0 73 | forceMaximumCompressionQuality_BC6H_BC7: 0 74 | - serializedVersion: 3 75 | buildTarget: Standalone 76 | maxTextureSize: 64 77 | resizeAlgorithm: 0 78 | textureFormat: -1 79 | textureCompression: 1 80 | compressionQuality: 50 81 | crunchedCompression: 0 82 | allowsAlphaSplitting: 0 83 | overridden: 0 84 | androidETC2FallbackOverride: 0 85 | forceMaximumCompressionQuality_BC6H_BC7: 0 86 | spriteSheet: 87 | serializedVersion: 2 88 | sprites: [] 89 | outline: [] 90 | physicsShape: [] 91 | bones: [] 92 | spriteID: 5e97eb03825dee720800000000000000 93 | internalID: 0 94 | vertices: [] 95 | indices: 96 | edges: [] 97 | weights: [] 98 | secondaryTextures: [] 99 | spritePackingTag: 100 | pSDRemoveMatte: 0 101 | pSDShowRemoveMatteOption: 0 102 | userData: 103 | assetBundleName: 104 | assetBundleVariant: 105 | -------------------------------------------------------------------------------- /Sprites/ElixirRed_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rito15/Unity-RPG-Inventory/e0ed8276483161d4f5ba24fc142d8a3dccc386cb/Sprites/ElixirRed_64.png -------------------------------------------------------------------------------- /Sprites/ElixirRed_64.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c2ebadfcb9b8cbb4a8ae6f124b17e332 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: -1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | applyGammaDecoding: 0 61 | platformSettings: 62 | - serializedVersion: 3 63 | buildTarget: DefaultTexturePlatform 64 | maxTextureSize: 64 65 | resizeAlgorithm: 0 66 | textureFormat: -1 67 | textureCompression: 1 68 | compressionQuality: 50 69 | crunchedCompression: 0 70 | allowsAlphaSplitting: 0 71 | overridden: 0 72 | androidETC2FallbackOverride: 0 73 | forceMaximumCompressionQuality_BC6H_BC7: 0 74 | - serializedVersion: 3 75 | buildTarget: Standalone 76 | maxTextureSize: 64 77 | resizeAlgorithm: 0 78 | textureFormat: -1 79 | textureCompression: 1 80 | compressionQuality: 50 81 | crunchedCompression: 0 82 | allowsAlphaSplitting: 0 83 | overridden: 0 84 | androidETC2FallbackOverride: 0 85 | forceMaximumCompressionQuality_BC6H_BC7: 0 86 | spriteSheet: 87 | serializedVersion: 2 88 | sprites: [] 89 | outline: [] 90 | physicsShape: [] 91 | bones: [] 92 | spriteID: 5e97eb03825dee720800000000000000 93 | internalID: 0 94 | vertices: [] 95 | indices: 96 | edges: [] 97 | weights: [] 98 | secondaryTextures: [] 99 | spritePackingTag: 100 | pSDRemoveMatte: 0 101 | pSDShowRemoveMatteOption: 0 102 | userData: 103 | assetBundleName: 104 | assetBundleVariant: 105 | -------------------------------------------------------------------------------- /Sprites/Helmet_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rito15/Unity-RPG-Inventory/e0ed8276483161d4f5ba24fc142d8a3dccc386cb/Sprites/Helmet_64.png -------------------------------------------------------------------------------- /Sprites/Helmet_64.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2a3a301121704ab45bb55331eb763958 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: -1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | applyGammaDecoding: 0 61 | platformSettings: 62 | - serializedVersion: 3 63 | buildTarget: DefaultTexturePlatform 64 | maxTextureSize: 64 65 | resizeAlgorithm: 0 66 | textureFormat: -1 67 | textureCompression: 1 68 | compressionQuality: 50 69 | crunchedCompression: 0 70 | allowsAlphaSplitting: 0 71 | overridden: 0 72 | androidETC2FallbackOverride: 0 73 | forceMaximumCompressionQuality_BC6H_BC7: 0 74 | - serializedVersion: 3 75 | buildTarget: Standalone 76 | maxTextureSize: 64 77 | resizeAlgorithm: 0 78 | textureFormat: -1 79 | textureCompression: 1 80 | compressionQuality: 50 81 | crunchedCompression: 0 82 | allowsAlphaSplitting: 0 83 | overridden: 0 84 | androidETC2FallbackOverride: 0 85 | forceMaximumCompressionQuality_BC6H_BC7: 0 86 | spriteSheet: 87 | serializedVersion: 2 88 | sprites: [] 89 | outline: [] 90 | physicsShape: [] 91 | bones: [] 92 | spriteID: 5e97eb03825dee720800000000000000 93 | internalID: 0 94 | vertices: [] 95 | indices: 96 | edges: [] 97 | weights: [] 98 | secondaryTextures: [] 99 | spritePackingTag: 100 | pSDRemoveMatte: 0 101 | pSDShowRemoveMatteOption: 0 102 | userData: 103 | assetBundleName: 104 | assetBundleVariant: 105 | -------------------------------------------------------------------------------- /Sprites/SwordBlue_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rito15/Unity-RPG-Inventory/e0ed8276483161d4f5ba24fc142d8a3dccc386cb/Sprites/SwordBlue_64.png -------------------------------------------------------------------------------- /Sprites/SwordBlue_64.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bb1bdd56c1352fb419836f9c2b0bf4bf 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: -1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | applyGammaDecoding: 0 61 | platformSettings: 62 | - serializedVersion: 3 63 | buildTarget: DefaultTexturePlatform 64 | maxTextureSize: 64 65 | resizeAlgorithm: 0 66 | textureFormat: -1 67 | textureCompression: 1 68 | compressionQuality: 50 69 | crunchedCompression: 0 70 | allowsAlphaSplitting: 0 71 | overridden: 0 72 | androidETC2FallbackOverride: 0 73 | forceMaximumCompressionQuality_BC6H_BC7: 0 74 | - serializedVersion: 3 75 | buildTarget: Standalone 76 | maxTextureSize: 64 77 | resizeAlgorithm: 0 78 | textureFormat: -1 79 | textureCompression: 1 80 | compressionQuality: 50 81 | crunchedCompression: 0 82 | allowsAlphaSplitting: 0 83 | overridden: 0 84 | androidETC2FallbackOverride: 0 85 | forceMaximumCompressionQuality_BC6H_BC7: 0 86 | spriteSheet: 87 | serializedVersion: 2 88 | sprites: [] 89 | outline: [] 90 | physicsShape: [] 91 | bones: [] 92 | spriteID: 5e97eb03825dee720800000000000000 93 | internalID: 0 94 | vertices: [] 95 | indices: 96 | edges: [] 97 | weights: [] 98 | secondaryTextures: [] 99 | spritePackingTag: 100 | pSDRemoveMatte: 0 101 | pSDShowRemoveMatteOption: 0 102 | userData: 103 | assetBundleName: 104 | assetBundleVariant: 105 | -------------------------------------------------------------------------------- /Sprites/SwordRed_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rito15/Unity-RPG-Inventory/e0ed8276483161d4f5ba24fc142d8a3dccc386cb/Sprites/SwordRed_64.png -------------------------------------------------------------------------------- /Sprites/SwordRed_64.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1f0a1cc5b62b0d747928186abd4be7c2 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: -1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | applyGammaDecoding: 0 61 | platformSettings: 62 | - serializedVersion: 3 63 | buildTarget: DefaultTexturePlatform 64 | maxTextureSize: 64 65 | resizeAlgorithm: 0 66 | textureFormat: -1 67 | textureCompression: 1 68 | compressionQuality: 50 69 | crunchedCompression: 0 70 | allowsAlphaSplitting: 0 71 | overridden: 0 72 | androidETC2FallbackOverride: 0 73 | forceMaximumCompressionQuality_BC6H_BC7: 0 74 | - serializedVersion: 3 75 | buildTarget: Standalone 76 | maxTextureSize: 64 77 | resizeAlgorithm: 0 78 | textureFormat: -1 79 | textureCompression: 1 80 | compressionQuality: 50 81 | crunchedCompression: 0 82 | allowsAlphaSplitting: 0 83 | overridden: 0 84 | androidETC2FallbackOverride: 0 85 | forceMaximumCompressionQuality_BC6H_BC7: 0 86 | spriteSheet: 87 | serializedVersion: 2 88 | sprites: [] 89 | outline: [] 90 | physicsShape: [] 91 | bones: [] 92 | spriteID: 5e97eb03825dee720800000000000000 93 | internalID: 0 94 | vertices: [] 95 | indices: 96 | edges: [] 97 | weights: [] 98 | secondaryTextures: [] 99 | spritePackingTag: 100 | pSDRemoveMatte: 0 101 | pSDShowRemoveMatteOption: 0 102 | userData: 103 | assetBundleName: 104 | assetBundleVariant: 105 | -------------------------------------------------------------------------------- /Sprites/WhiteArmor_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rito15/Unity-RPG-Inventory/e0ed8276483161d4f5ba24fc142d8a3dccc386cb/Sprites/WhiteArmor_64.png -------------------------------------------------------------------------------- /Sprites/WhiteArmor_64.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a1f270ff46445564e9afa40d0d9574db 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: -1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | applyGammaDecoding: 0 61 | platformSettings: 62 | - serializedVersion: 3 63 | buildTarget: DefaultTexturePlatform 64 | maxTextureSize: 64 65 | resizeAlgorithm: 0 66 | textureFormat: -1 67 | textureCompression: 1 68 | compressionQuality: 50 69 | crunchedCompression: 0 70 | allowsAlphaSplitting: 0 71 | overridden: 0 72 | androidETC2FallbackOverride: 0 73 | forceMaximumCompressionQuality_BC6H_BC7: 0 74 | - serializedVersion: 3 75 | buildTarget: Standalone 76 | maxTextureSize: 64 77 | resizeAlgorithm: 0 78 | textureFormat: -1 79 | textureCompression: 1 80 | compressionQuality: 50 81 | crunchedCompression: 0 82 | allowsAlphaSplitting: 0 83 | overridden: 0 84 | androidETC2FallbackOverride: 0 85 | forceMaximumCompressionQuality_BC6H_BC7: 0 86 | spriteSheet: 87 | serializedVersion: 2 88 | sprites: [] 89 | outline: [] 90 | physicsShape: [] 91 | bones: [] 92 | spriteID: 5e97eb03825dee720800000000000000 93 | internalID: 0 94 | vertices: [] 95 | indices: 96 | edges: [] 97 | weights: [] 98 | secondaryTextures: [] 99 | spritePackingTag: 100 | pSDRemoveMatte: 0 101 | pSDShowRemoveMatteOption: 0 102 | userData: 103 | assetBundleName: 104 | assetBundleVariant: 105 | --------------------------------------------------------------------------------