From 6c9f31f654f8725e4898de1c66152d92f5ad9b6a Mon Sep 17 00:00:00 2001 From: "DESKTOP-VVOCIJO\\PC" Date: Wed, 20 May 2026 17:59:43 +0900 Subject: [PATCH] =?UTF-8?q?2026-05-20=20=EB=AC=B4=EA=B8=B0=EC=8A=AC?= =?UTF-8?q?=EB=A1=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/01_Scenes/GameScene.unity | 4 +- .../Combat/PlayerWeaponInventory.cs | 10 ++ Assets/02_Scripts/UI/WeaponInventory.cs | 64 +++++++ Assets/02_Scripts/UI/WeaponInventory.cs.meta | 2 + Assets/02_Scripts/UI/WeaponSlot.cs | 61 +++++++ Assets/02_Scripts/UI/WeaponSlot.cs.meta | 2 + Assets/06_Textures/WeaponSlot.png | 3 + Assets/06_Textures/WeaponSlot.png.meta | 169 ++++++++++++++++++ Assets/12_UI.meta | 8 + Assets/12_UI/WeaponSlot.prefab | 3 + Assets/12_UI/WeaponSlot.prefab.meta | 7 + 11 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 Assets/02_Scripts/UI/WeaponInventory.cs create mode 100644 Assets/02_Scripts/UI/WeaponInventory.cs.meta create mode 100644 Assets/02_Scripts/UI/WeaponSlot.cs create mode 100644 Assets/02_Scripts/UI/WeaponSlot.cs.meta create mode 100644 Assets/06_Textures/WeaponSlot.png create mode 100644 Assets/06_Textures/WeaponSlot.png.meta create mode 100644 Assets/12_UI.meta create mode 100644 Assets/12_UI/WeaponSlot.prefab create mode 100644 Assets/12_UI/WeaponSlot.prefab.meta diff --git a/Assets/01_Scenes/GameScene.unity b/Assets/01_Scenes/GameScene.unity index ebd6f21..1bf2152 100644 --- a/Assets/01_Scenes/GameScene.unity +++ b/Assets/01_Scenes/GameScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d3656c07d27be100355d7cefa18109c52754839004e07647000b72aa122e7a2 -size 76524 +oid sha256:03ff4c8c580ae1647bfbcc225c4ff6953addd148672db0830714158f3c0a0f33 +size 79277 diff --git a/Assets/02_Scripts/Combat/PlayerWeaponInventory.cs b/Assets/02_Scripts/Combat/PlayerWeaponInventory.cs index 2d4f80e..6300598 100644 --- a/Assets/02_Scripts/Combat/PlayerWeaponInventory.cs +++ b/Assets/02_Scripts/Combat/PlayerWeaponInventory.cs @@ -27,6 +27,9 @@ public class PlayerWeaponInventory : MonoBehaviour // 무기 교체 시 발화. null이면 맨손. public event Action OnWeaponChanged; + // 인벤토리 상태(목록 추가·장착 변경)가 바뀔 때마다 발화. UI 갱신용. + public event Action OnInventoryChanged; + public WeaponData CurrentWeapon => _currentIndex >= 0 && _currentIndex < _weapons.Count ? _weapons[_currentIndex] @@ -34,6 +37,10 @@ public class PlayerWeaponInventory : MonoBehaviour public bool IsArmed => CurrentWeapon != null; public int Count => _weapons.Count; + // UI 등 외부에서 읽기 전용으로 목록/장착 인덱스 조회 (UI는 데이터를 소유하지 않는다). + public IReadOnlyList Weapons => _weapons; + public int CurrentIndex => _currentIndex; + // 무기 추가. 이미 보유 중이면 false 반환 (중복 픽업 방지). // 첫 픽업 시 자동 장착할지는 호출자가 OnWeaponChanged를 보고 결정. public bool Pickup(WeaponData weapon) @@ -50,6 +57,7 @@ public bool Pickup(WeaponData weapon) OnWeaponChanged?.Invoke(_weapons[0]); } + OnInventoryChanged?.Invoke(); return true; } @@ -59,6 +67,7 @@ public void EquipUnarmed() if (_currentIndex == -1) return; _currentIndex = -1; OnWeaponChanged?.Invoke(null); + OnInventoryChanged?.Invoke(); } // 슬롯 번호로 직접 장착. 슬롯이 비어있으면 무시. @@ -68,6 +77,7 @@ public void EquipSlot(int slotIndex) if (_currentIndex == slotIndex) return; _currentIndex = slotIndex; OnWeaponChanged?.Invoke(_weapons[slotIndex]); + OnInventoryChanged?.Invoke(); } // 디버그용: 슬롯에 해당 무기가 있는지 확인. diff --git a/Assets/02_Scripts/UI/WeaponInventory.cs b/Assets/02_Scripts/UI/WeaponInventory.cs new file mode 100644 index 0000000..20789fb --- /dev/null +++ b/Assets/02_Scripts/UI/WeaponInventory.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using UnityEngine; + +// ============================================================================ +// WeaponInventory (UI) +// ---------------------------------------------------------------------------- +// 무기 인벤토리 화면 표시 — 표시 전용 (무기 교체는 1/2/3 키로). +// +// 이 클래스는 무기 목록을 "소유하지 않는다". 데이터의 단일 진실은 +// PlayerWeaponInventory이고, 이 UI는 그걸 구독해서 그림만 그린다. +// - OnInventoryChanged 이벤트를 받으면 슬롯을 다시 그림 +// - 현재 장착 무기 슬롯에 하이라이트 표시 +// +// _slots는 데이터가 아니라 생성한 화면 오브젝트 캐시일 뿐 (재사용 풀). +// ============================================================================ +public class WeaponInventory : MonoBehaviour +{ + [SerializeField] private PlayerWeaponInventory _wInvenSource; // 비워두면 런타임에 자동 탐색 + [SerializeField] private Transform _slotRoot; // 슬롯이 생성될 부모 + [SerializeField] private WeaponSlot _slotPrefab; // 슬롯 프리팹 + + // 생성된 슬롯 UI 캐시. 무기 수가 늘면 추가 생성하고, 남는 슬롯은 비활성화. + private readonly List _slots = new(); + + private void Start() + { + if (_wInvenSource == null) + _wInvenSource = FindFirstObjectByType(); + + if (_wInvenSource != null) + _wInvenSource.OnInventoryChanged += Refresh; + + Refresh(); + } + + private void OnDestroy() + { + if (_wInvenSource != null) + _wInvenSource.OnInventoryChanged -= Refresh; + } + + + // 인벤토리 상태를 화면에 반영. 슬롯 개수를 무기 수에 맞추고 내용/하이라이트 갱신. + private void Refresh() + { + if (_wInvenSource == null || _slotRoot == null || _slotPrefab == null) return; + + IReadOnlyList weapons = _wInvenSource.Weapons; + + // 슬롯이 모자라면 생성 (한 번 만든 슬롯은 재사용 — GC 최소화). + while (_slots.Count < weapons.Count) + _slots.Add(Instantiate(_slotPrefab, _slotRoot)); + + for (int i = 0; i < _slots.Count; i++) + { + bool used = i < weapons.Count; + _slots[i].gameObject.SetActive(used); + if (!used) continue; + + _slots[i].SetWeapon(weapons[i]); + _slots[i].SetSelected(i == _wInvenSource.CurrentIndex); + } + } +} diff --git a/Assets/02_Scripts/UI/WeaponInventory.cs.meta b/Assets/02_Scripts/UI/WeaponInventory.cs.meta new file mode 100644 index 0000000..6da1089 --- /dev/null +++ b/Assets/02_Scripts/UI/WeaponInventory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 51f5c684956d6ea46baf42375e6f186a \ No newline at end of file diff --git a/Assets/02_Scripts/UI/WeaponSlot.cs b/Assets/02_Scripts/UI/WeaponSlot.cs new file mode 100644 index 0000000..a1314e9 --- /dev/null +++ b/Assets/02_Scripts/UI/WeaponSlot.cs @@ -0,0 +1,61 @@ +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +// ============================================================================ +// WeaponSlot +// ---------------------------------------------------------------------------- +// 무기 인벤토리 UI의 슬롯 한 칸. 데이터는 들고 있지 않고 표시만 한다. +// WeaponInventory가 슬롯을 생성/갱신할 때 SetWeapon / SetSelected를 호출한다. +// ============================================================================ +public class WeaponSlot : MonoBehaviour +{ + [Header("Ref")] + [SerializeField] public Image Weapon_Img; // 무기 아이콘 + [SerializeField] public TMP_Text Weapon_Name; // 무기 이름 + [SerializeField] private GameObject _slotPosObj; // 장착 중일 때 위치를 밀어줄 대상 + [SerializeField] private float _selectedOffsetX = -100f; // 장착 시 _slotPosObj를 x로 미는 양 (음수 = 왼쪽) + + // _slotPosObj의 기준 위치. 최초 SetSelected 호출 시 한 번만 캡처해서 이동 누적 방지. + private RectTransform _slotPosRect; + private Vector2 _slotPosBase; + private bool _slotPosCaptured; + + // 슬롯에 표시할 무기를 채운다. null이면 빈 슬롯으로 표현. + public void SetWeapon(WeaponData weapon) + { + if (Weapon_Img != null) + { + Weapon_Img.sprite = weapon != null ? weapon.PickupSprite : null; + // 스프라이트가 없으면 아이콘 자체를 숨겨 빈 칸이 깨져 보이지 않게 한다. + Weapon_Img.enabled = weapon != null && weapon.PickupSprite != null; + } + + if (Weapon_Name != null) + Weapon_Name.text = weapon != null ? weapon.DisplayName : string.Empty; + } + + // 현재 장착 중인 무기 슬롯이면 _slotPosObj를 x로 _selectedOffsetX만큼 밀어준다. + // 해제되면 기준 위치로 복귀. 기준 위치는 처음 한 번만 캡처해서 이동이 누적되지 않게 한다. + public void SetSelected(bool selected) + { + CaptureSlotPosBase(); + if (_slotPosRect == null) return; + + Vector2 pos = _slotPosBase; + pos.x += selected ? _selectedOffsetX : 0f; + _slotPosRect.anchoredPosition = pos; + } + + // _slotPosObj의 기준 anchoredPosition을 최초 1회 저장 (오프셋 적용 전 값). + private void CaptureSlotPosBase() + { + if (_slotPosCaptured) return; + _slotPosCaptured = true; + + if (_slotPosObj != null) + _slotPosRect = _slotPosObj.transform as RectTransform; + if (_slotPosRect != null) + _slotPosBase = _slotPosRect.anchoredPosition; + } +} diff --git a/Assets/02_Scripts/UI/WeaponSlot.cs.meta b/Assets/02_Scripts/UI/WeaponSlot.cs.meta new file mode 100644 index 0000000..3eb6625 --- /dev/null +++ b/Assets/02_Scripts/UI/WeaponSlot.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b4f30534edb02d346b4f6c570709313d \ No newline at end of file diff --git a/Assets/06_Textures/WeaponSlot.png b/Assets/06_Textures/WeaponSlot.png new file mode 100644 index 0000000..d7b7764 --- /dev/null +++ b/Assets/06_Textures/WeaponSlot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5dcdb22248a96e84d4402b54b25e5dc5b113f6c9bb8da65c6dff34e0370c2d2b +size 1569 diff --git a/Assets/06_Textures/WeaponSlot.png.meta b/Assets/06_Textures/WeaponSlot.png.meta new file mode 100644 index 0000000..7110aaa --- /dev/null +++ b/Assets/06_Textures/WeaponSlot.png.meta @@ -0,0 +1,169 @@ +fileFormatVersion: 2 +guid: beeef09ad73b6e142a75dd99c4a71efe +TextureImporter: + internalIDToNameTable: + - first: + 213: -2634782650692007268 + second: WeaponSlot_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: iOS + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: WeaponSlot_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 669 + height: 356 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: c9a042d832f5f6bd0800000000000000 + internalID: -2634782650692007268 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + WeaponSlot_0: -2634782650692007268 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/12_UI.meta b/Assets/12_UI.meta new file mode 100644 index 0000000..1d3083d --- /dev/null +++ b/Assets/12_UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f5f9c58f7bf76c041b6dd01c69395672 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/12_UI/WeaponSlot.prefab b/Assets/12_UI/WeaponSlot.prefab new file mode 100644 index 0000000..932d0ec --- /dev/null +++ b/Assets/12_UI/WeaponSlot.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:896c53f4af16314bfff7a7c0837c34cecfb80ef100fe83239f8c9515a6c21595 +size 10458 diff --git a/Assets/12_UI/WeaponSlot.prefab.meta b/Assets/12_UI/WeaponSlot.prefab.meta new file mode 100644 index 0000000..1eaf7d9 --- /dev/null +++ b/Assets/12_UI/WeaponSlot.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 622c439e5b8a4574cb7fc49c0995cf85 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: