# TRD - 플레이어 인벤토리 시스템 ## 📋 기본 정보 - **프로젝트명**: Unity MVVM 게임 프로젝트 - **작성자**: 박개발 (기술 리드) - **작성일**: 2024-01-02 - **버전**: v1.0 - **상태**: Approved - **관련 PRD**: [PRD_Player_Inventory_System.md](PRD_Player_Inventory_System.md) ## 🏗️ 아키텍처 개요 ### 전체 시스템 구조 ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ View Layer │ │ ViewModel Layer │ │ Model Layer │ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ InventoryView │◄──►│InventoryViewModel│◄──►│InventoryModel │ │ ItemSlotView │ │ ItemSlotViewModel│ │ ItemData │ │ TooltipView │ │ FilterViewModel │ │ ItemDatabase │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ▲ ▲ ▲ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Input System │ │ R3 Observable │ │ ScriptableObject│ │ Unity Events │ │ MessagePipe │ │ JSON Serializer │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` ### 디자인 패턴 - **MVVM 패턴**: View-ViewModel-Model 분리로 관심사 분리 - **Observer 패턴**: R3 Observable을 통한 데이터 바인딩 - **Command 패턴**: 사용자 액션을 커맨드 객체로 캡슐화 - **Object Pool 패턴**: UI 요소의 재사용으로 GC 최소화 ### 모듈 구조 ``` Assets/ ├── Features/ │ └── Inventory/ │ ├── Models/ │ │ ├── InventoryModel.cs │ │ ├── ItemData.cs │ │ └── ItemDatabase.cs │ ├── ViewModels/ │ │ ├── InventoryViewModel.cs │ │ ├── ItemSlotViewModel.cs │ │ └── FilterViewModel.cs │ ├── Views/ │ │ ├── InventoryView.cs │ │ ├── ItemSlotView.cs │ │ └── TooltipView.cs │ └── Controllers/ │ ├── InventoryController.cs │ └── DragDropController.cs ├── Prefabs/ │ └── Inventory/ │ ├── InventoryUI.prefab │ ├── ItemSlot.prefab │ └── Tooltip.prefab └── Resources/ └── Inventory/ ├── ItemDatabase.asset └── ItemIcons/ ``` ## 🔧 Unity 기술 스택 ### Unity 환경 - **Unity 버전**: Unity 2022.3.20f1 LTS - **Render Pipeline**: URP (Universal Render Pipeline) - **Target API**: .NET Standard 2.1 ### 필수 패키지 및 라이브러리 | 패키지명 | 버전 | 목적 | 필수 여부 | |----------|------|------|-----------| | R3 | 1.2.4 | 반응형 프로그래밍, UI 데이터 바인딩 | 필수 | | UniTask | 1.7.15 | 비동기 처리, 애니메이션 최적화 | 필수 | | VContainer | 1.15.4 | 의존성 주입, 모듈 결합도 감소 | 필수 | | MessagePipe | 1.8.1 | 메시징 시스템, 모듈 간 통신 | 필수 | | Google Protobuf | 3.21.12 | 네트워크 통신, 인벤토리 데이터 직렬화 | 필수 | | Addressables | 1.21.19 | 에셋 관리, 아이템 아이콘 로딩 | 선택 | ### 코딩 표준 - **네이밍 규칙**: - 클래스/메서드: `InventoryViewModel`, `AddItem()` - 변수/매개변수: `itemCount`, `slotIndex` - private 필드: `_currentItems`, `_isInitialized` - 상수: `MAX_INVENTORY_SIZE`, `DEFAULT_SLOT_COUNT` - **아키텍처 패턴**: MVVM + DI + 메시징 ## 🎯 기능별 기술 요구사항 ### Feature 1: 기본 인벤토리 표시 - **담당 모듈**: Inventory.Views, Inventory.ViewModels - **주요 클래스**: - `InventoryModel`: 인벤토리 데이터 관리, 아이템 추가/제거 로직 - `InventoryViewModel`: UI 바인딩용 데이터 변환, 사용자 액션 처리 - `InventoryView`: Unity UI 표시, 사용자 입력 수신 - **의존성**: ItemDatabase (ScriptableObject), R3 Observable - **성능 요구사항**: - 메모리 사용량: 15MB 이하 - UI 렌더링: 60 FPS 유지 - 슬롯 생성 시간: 0.05초 이내 - **구현 우선순위**: High **코드 구조 예시:** ```csharp // Model [CreateAssetMenu(fileName = "InventoryModel", menuName = "Game/Inventory")] public class InventoryModel : ScriptableObject { [SerializeField] private List<ItemData> _items = new(); [SerializeField] private int _maxSlots = 48; public ReactiveProperty<IReadOnlyList<ItemData>> Items { get; private set; } public ReactiveProperty<int> UsedSlots { get; private set; } } // ViewModel public class InventoryViewModel : IDisposable { [Inject] private InventoryModel _model; [Inject] private IMessagePublisher<ItemAddedMessage> _publisher; public ReadOnlyReactiveProperty<string> SlotCountText { get; private set; } public ReactiveCommand<int> SelectSlotCommand { get; private set; } } // View public class InventoryView : MonoBehaviour { [SerializeField] private GridLayoutGroup _itemGrid; [SerializeField] private Text _slotCountText; [Inject] private InventoryViewModel _viewModel; private void Start() { // R3 Observable을 통한 데이터 바인딩 _viewModel.SlotCountText .Subscribe(text => _slotCountText.text = text) .AddTo(this); } } ``` ### Feature 2: 드래그 앤 드롭 시스템 - **담당 모듈**: Inventory.Controllers - **주요 클래스**: - `DragDropController`: 드래그 상태 관리, 드롭 검증 - `ItemSlotView`: 개별 슬롯의 드래그 이벤트 처리 - `DragDropMessage`: MessagePipe를 통한 드래그 상태 전파 - **의존성**: Unity EventSystem, Physics2D Raycasting - **성능 요구사항**: - 드래그 응답 시간: 0.016초 이내 (60 FPS) - 메모리 할당: 드래그 중 0 allocation - **구현 우선순위**: High **구현 방식:** ```csharp public class DragDropController : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { [Inject] private IMessagePublisher<ItemDragMessage> _dragPublisher; private Canvas _canvas; private RectTransform _draggedItem; private Vector2 _originalPosition; public void OnBeginDrag(PointerEventData eventData) { // 드래그 시작 시 반투명 아이템 생성 CreateDragIcon(eventData); _dragPublisher.Publish(new ItemDragMessage(true, _draggedItem)); } public void OnDrag(PointerEventData eventData) { // 드래그된 아이템을 마우스/터치 위치로 이동 RectTransformUtility.ScreenPointToLocalPointInRectangle( _canvas.transform as RectTransform, eventData.position, _canvas.worldCamera, out Vector2 position); _draggedItem.position = position; } } ``` ### Feature 3: 아이템 필터링 시스템 - **담당 모듈**: Inventory.ViewModels, Inventory.Views - **주요 클래스**: - `FilterViewModel`: 필터 조건 관리, 필터링된 아이템 목록 제공 - `ItemFilterView`: 필터 UI (탭, 드롭다운) - **의존성**: LINQ, R3 Observable - **성능 요구사항**: - 필터링 처리 시간: 1000개 아이템 기준 0.01초 이내 - **구현 우선순위**: Medium ## 🗂️ 데이터 구조 ### 모델 설계 ```csharp [Serializable] public class ItemData : IEquatable<ItemData> { [SerializeField] private string _id; [SerializeField] private string _name; [SerializeField] private ItemType _type; [SerializeField] private ItemRarity _rarity; [SerializeField] private int _stackSize; [SerializeField] private Sprite _icon; [SerializeField] private string _description; // 프로퍼티들... public string Id => _id; public string Name => _name; public ItemType Type => _type; public ItemRarity Rarity => _rarity; public int StackSize => _stackSize; public Sprite Icon => _icon; public string Description => _description; public bool Equals(ItemData other) => _id == other?._id; public override int GetHashCode() => _id?.GetHashCode() ?? 0; } public enum ItemType { Weapon = 0, Armor = 1, Consumable = 2, Material = 3, Misc = 4 } public enum ItemRarity { Common = 0, Uncommon = 1, Rare = 2, Epic = 3, Legendary = 4 } [Serializable] public class InventorySlot { [SerializeField] private ItemData _item; [SerializeField] private int _count; public ItemData Item => _item; public int Count => _count; public bool IsEmpty => _item == null || _count <= 0; public bool CanStack(ItemData item) => _item?.Id == item?.Id && _count < _item.StackSize; } ``` ### 데이터 저장 방식 - **로컬 데이터**: JSON 직렬화하여 PlayerPrefs 또는 Application.persistentDataPath에 저장 - **세이브 파일 위치**: `Application.persistentDataPath/SaveData/inventory.json` - **클라우드 연동**: Firebase Firestore를 통한 클라우드 세이브 (선택적) **저장 데이터 구조:** *JSON 형식 (로컬 저장용):* ```json { "inventory": { "slots": [ { "itemId": "sword_001", "count": 1, "slotIndex": 0 }, { "itemId": "potion_health", "count": 5, "slotIndex": 1 } ], "maxSlots": 48, "lastModified": "2024-01-02T10:30:00Z" } } ``` *Protobuf 스키마 (네트워크 통신용):* ```protobuf syntax = "proto3"; message InventoryData { repeated InventorySlot slots = 1; int32 max_slots = 2; int64 last_modified = 3; } message InventorySlot { string item_id = 1; int32 count = 2; int32 slot_index = 3; } ``` ### ScriptableObject 구조 - **아이템 데이터베이스**: `ItemDatabase.asset` - 모든 게임 아이템 정의 - **인벤토리 설정**: `InventorySettings.asset` - 기본 슬롯 수, UI 설정 등 ```csharp [CreateAssetMenu(fileName = "ItemDatabase", menuName = "Game/ItemDatabase")] public class ItemDatabase : ScriptableObject { [SerializeField] private List<ItemData> _allItems = new(); private Dictionary<string, ItemData> _itemLookup; public void Initialize() { _itemLookup = _allItems.ToDictionary(item => item.Id); } public ItemData GetItem(string itemId) => _itemLookup.TryGetValue(itemId, out ItemData item) ? item : null; } ``` ## 🎨 UI/UX 기술 구현 ### UGUI 구조 - **Canvas 계층**: - Background Canvas (Sort Order: 0) - 게임 배경 - Game UI Canvas (Sort Order: 100) - HUD, 상태바 - Inventory Canvas (Sort Order: 200) - 인벤토리 창 - Tooltip Canvas (Sort Order: 300) - 툴팁 오버레이 ### UI 바인딩 시스템 ```csharp // R3 Observable을 활용한 데이터 바인딩 public class InventoryView : MonoBehaviour { [SerializeField] private Transform _itemSlotParent; [SerializeField] private ItemSlotView _slotPrefab; [Inject] private InventoryViewModel _viewModel; private readonly List<ItemSlotView> _slotViews = new(); private void Start() { // 인벤토리 아이템 변경 시 UI 업데이트 _viewModel.Items .Subscribe(items => UpdateSlotViews(items)) .AddTo(this); // 필터 변경 시 표시되는 아이템 업데이트 _viewModel.FilteredItems .Subscribe(filteredItems => ApplyFilter(filteredItems)) .AddTo(this); } private void UpdateSlotViews(IReadOnlyList<InventorySlot> slots) { for (int i = 0; i < slots.Count; i++) { if (i >= _slotViews.Count) { // 슬롯 뷰 오브젝트 풀링 var slotView = _slotPool.Rent(); _slotViews.Add(slotView); } _slotViews[i].UpdateSlot(slots[i]); } } } ``` ### 로컬라이제이션 ```csharp // 다국어 지원을 위한 LocalizationManager 연동 [Inject] private ILocalizationService _localization; private void UpdateItemTooltip(ItemData item) { var localizedName = _localization.GetText(quot;item.{item.Id}.name"); var localizedDesc = _localization.GetText(quot;item.{item.Id}.description"); _tooltipView.SetContent(localizedName, localizedDesc); } ``` ## 🚀 성능 최적화 ### 메모리 관리 - **오브젝트 풀링**: UI 슬롯, 드래그 아이콘, 툴팁 등 재사용 가능한 UI 요소 - **GC 최소화**: - StringBuilder 사용으로 string 연산 최적화 - LINQ 대신 for문 사용 (필터링 로직) - 이벤트 구독 해제를 통한 메모리 누수 방지 ```csharp public class ItemSlotPool : MonoBehaviour { [SerializeField] private ItemSlotView _slotPrefab; [SerializeField] private int _initialSize = 50; private readonly Queue<ItemSlotView> _pool = new(); private void Start() { // 미리 슬롯 오브젝트들을 생성하여 풀에 저장 for (int i = 0; i < _initialSize; i++) { var slot = Instantiate(_slotPrefab); slot.gameObject.SetActive(false); _pool.Enqueue(slot); } } public ItemSlotView Rent() { if (_pool.Count > 0) { var slot = _pool.Dequeue(); slot.gameObject.SetActive(true); return slot; } return Instantiate(_slotPrefab); } public void Return(ItemSlotView slot) { slot.gameObject.SetActive(false); _pool.Enqueue(slot); } } ``` ### 렌더링 최적화 - **UI 배칭**: 동일한 아틀라스 텍스처 사용으로 Draw Call 최소화 - **캔버스 분리**: 자주 변경되는 UI와 정적 UI 분리 - **마스킹 최적화**: Rect Mask 2D 활용으로 오버드로우 방지 ### 프로파일링 목표 - **Target FPS**: 60 FPS (모바일 환경) - **메모리 사용량**: 최대 50MB (인벤토리 시스템 전체) - **로딩 시간**: 인벤토리 열기 0.1초 이내 - **GC 발생**: 1분당 최대 1회 ## 🔐 보안 및 데이터 보호 ### 데이터 보안 - **세이브 파일 검증**: CRC32 체크섬을 통한 무결성 검사 - **치트 방지**: - 중요 데이터 서버 검증 - 클라이언트 데이터 암호화 - 비정상적 아이템 획득 패턴 감지 - **개인정보 처리**: GDPR 준수, 로컬 데이터만 사용 ```csharp public class SecureInventorySerializer { private const string ENCRYPTION_KEY = "InventoryKey2024"; public string SerializeInventory(InventoryModel inventory) { var json = JsonUtility.ToJson(inventory); var encrypted = EncryptString(json, ENCRYPTION_KEY); var checksum = CalculateChecksum(encrypted); return quot;{checksum}:{encrypted}"; } public InventoryModel DeserializeInventory(string data) { var parts = data.Split(':'); if (parts.Length != 2) throw new InvalidDataException(); var checksum = parts[0]; var encrypted = parts[1]; if (!VerifyChecksum(encrypted, checksum)) throw new InvalidDataException("Data corruption detected"); var json = DecryptString(encrypted, ENCRYPTION_KEY); return JsonUtility.FromJson<InventoryModel>(json); } } ``` ## 🧪 테스트 전략 ### 자동화 테스트 - **Unit Test**: 인벤토리 로직 테스트 (NUnit) - **Integration Test**: MVVM 바인딩 테스트 (Unity Test Framework) - **Performance Test**: UI 성능 테스트 (Unity Performance Testing) ```csharp [TestFixture] public class InventoryModelTests { private InventoryModel _inventory; private ItemDatabase _database; [SetUp] public void Setup() { _inventory = ScriptableObject.CreateInstance<InventoryModel>(); _database = ScriptableObject.CreateInstance<ItemDatabase>(); // 테스트 데이터 설정... } [Test] public void AddItem_ShouldIncreaseItemCount() { // Arrange var testItem = _database.GetItem("test_item"); // Act var result = _inventory.AddItem(testItem, 1); // Assert Assert.IsTrue(result); Assert.AreEqual(1, _inventory.GetItemCount(testItem.Id)); } [Test] public void AddItem_WhenInventoryFull_ShouldReturnFalse() { // Arrange FillInventoryToMax(); var newItem = _database.GetItem("new_item"); // Act var result = _inventory.AddItem(newItem, 1); // Assert Assert.IsFalse(result); } } ``` ### 수동 테스트 - **기능 테스트**: 사용자 시나리오별 테스트 체크리스트 - **호환성 테스트**: 다양한 해상도 및 기기에서 테스트 - **사용성 테스트**: 실제 사용자 대상 UI/UX 테스트 ## 🌍 플랫폼별 고려사항 ### Mobile (Android/iOS) - **터치 입력**: 멀티터치 지원, 제스처 인식 - **메모리 제한**: 모바일 환경의 메모리 제약 고려 - **배터리 최적화**: GPU 사용량 최소화 - **해상도 대응**: Safe Area, Notch 대응 ```csharp public class MobilePlatformAdapter : MonoBehaviour { [SerializeField] private RectTransform _inventoryPanel; private void Start() { AdjustForSafeArea(); OptimizeForMobile(); } private void AdjustForSafeArea() { var safeArea = Screen.safeArea; var rectTransform = _inventoryPanel; var anchorMin = safeArea.position; var anchorMax = anchorMin + safeArea.size; anchorMin.x /= Screen.width; anchorMin.y /= Screen.height; anchorMax.x /= Screen.width; anchorMax.y /= Screen.height; rectTransform.anchorMin = anchorMin; rectTransform.anchorMax = anchorMax; } } ``` ## 📦 빌드 및 배포 ### 빌드 설정 - **Player Settings**: - Scripting Backend: IL2CPP - Target API Level: Android 31, iOS 15.0 - Compression Format: LZ4 (빠른 압축 해제) - **Addressable Settings**: 아이템 아이콘 에셋 번들링 - **Build Settings**: - Development Build: false (릴리스 빌드) - Script Debugging: false - Compression Method: LZ4HC ### 배포 프로세스 - **버전 관리**: 시맨틱 버저닝 (major.minor.patch) - **CI/CD**: GitHub Actions를 통한 자동 빌드 - **QA 프로세스**: - 자동 테스트 → 수동 기능 테스트 → 성능 테스트 → 배포 ## 🔄 개발 워크플로우 ### Git 브랜치 전략 - **main**: 릴리즈 가능한 안정 버전 - **develop**: 개발 통합 브랜치 - **feature/inventory-basic-ui**: 기본 UI 구현 - **feature/inventory-drag-drop**: 드래그 앤 드롭 구현 - **feature/inventory-filtering**: 필터링 시스템 구현 ### 코드 리뷰 규칙 - **리뷰 필수 항목**: - [ ] Unity 코딩 표준 준수 (네이밍, 들여쓰기) - [ ] MVVM 패턴 적용 확인 - [ ] 메모리 누수 위험성 검토 (이벤트 구독 해제 등) - [ ] 성능 영향도 평가 (Update문 내 GetComponent 등) - [ ] 단위 테스트 작성 여부 - **승인 조건**: 최소 2명의 시니어 개발자 승인 - **자동 체크**: SonarQube 정적 분석, 단위 테스트 통과 ## 📅 개발 일정 및 마일스톤 ### 기술적 마일스톤 - **Tech Milestone 1**: MVVM 아키텍처 구축 (2024-01-10) - VContainer DI 컨테이너 설정 - 기본 Model, ViewModel, View 클래스 구현 - R3 Observable 바인딩 시스템 구축 - **Tech Milestone 2**: 핵심 기능 구현 (2024-01-20) - 인벤토리 기본 UI 및 데이터 표시 - 드래그 앤 드롭 시스템 구현 - 아이템 툴팁 시스템 구현 - **Tech Milestone 3**: 고급 기능 및 최적화 (2024-02-05) - 필터링 및 정렬 시스템 - 오브젝트 풀링 및 성능 최적화 - 메모리 프로파일링 및 최적화 - **Tech Milestone 4**: 플랫폼 최적화 및 배포 (2024-02-15) - 모바일 플랫폼 최적화 - 멀티 해상도 대응 - 빌드 최적화 및 배포 준비 ### 리스크 분석 | 리스크 | 확률 | 영향도 | 대응 방안 | |--------|------|--------|-----------| | MVVM 패턴 학습 곡선 | Medium | High | 팀 교육 세션, 프로토타입 우선 구현 | | 모바일 성능 최적화 | High | High | 정기 성능 테스트, 프로파일링 도구 활용 | | UI 복잡성 증가 | Medium | Medium | 단계별 구현, UI 컴포넌트 모듈화 | | 멀티 플랫폼 호환성 | Low | Medium | 플랫폼별 빌드 테스트 자동화 | ## 🔄 승인 및 변경 이력 ### 기술 검토자 - [x] **Technical Lead**: 박개발 - 2024-01-02 - [x] **Senior Developer**: 이시니어 - 2024-01-03 - [x] **Mobile Specialist**: 김모바일 - 2024-01-03 ### 변경 이력 | 버전 | 날짜 | 변경 사항 | 작성자 | |------|------|-----------|--------| | v1.0 | 2024-01-02 | 초기 작성 | 박개발 | --- ## 📚 참고 문서 - [Unity MVVM 패턴 가이드](https://docs.unity3d.com/Manual/UIE-MVVM-pattern.html) - [R3 Observable 공식 문서](https://github.com/Cysharp/R3) - [VContainer 의존성 주입 가이드](https://github.com/hadashiA/VContainer) - [Unity 성능 최적화 베스트 프랙티스](https://docs.unity3d.com/Manual/MobileOptimization.html) - [MessagePipe 메시징 시스템](https://github.com/Cysharp/MessagePipe)