278 lines
7.3 KiB
Markdown
278 lines
7.3 KiB
Markdown
![]() |
# Unity MVVM 시스템 가이드
|
||
|
|
||
|
Unity에서 MVVM(Model-View-ViewModel) 패턴을 구현한 시스템입니다. Attribute 기반 데이터 바인딩과 `nameof`를 활용한 타입 안전한 구조를 제공합니다.
|
||
|
|
||
|
## 폴더 구조
|
||
|
|
||
|
```
|
||
|
GameUi/New/
|
||
|
├── ViewModels/ # ViewModel 클래스들
|
||
|
│ ├── Base/ # 기본 ViewModel 클래스
|
||
|
│ │ └── SimpleViewModel.cs
|
||
|
│ └── InventoryViewModel.cs # 예시 ViewModel
|
||
|
├── Views/ # View 클래스들
|
||
|
│ └── Base/
|
||
|
│ └── AutoBindView.cs # 자동 바인딩 View 기본 클래스
|
||
|
├── Services/ # 서비스 계층 클래스들
|
||
|
│ ├── IService.cs # 서비스 인터페이스
|
||
|
│ └── InventoryService.cs # 예시 서비스
|
||
|
├── Utils/ # 유틸리티 클래스들
|
||
|
│ ├── BindToAttribute.cs # 바인딩 Attribute
|
||
|
│ └── BindingContext.cs # 바인딩 컨텍스트
|
||
|
└── Converters/ # 값 변환기들
|
||
|
├── IValueConverter.cs # 컨버터 인터페이스
|
||
|
└── CommonConverters.cs # 공통 컨버터들
|
||
|
```
|
||
|
|
||
|
## 핵심 클래스
|
||
|
|
||
|
### 1. SimpleViewModel
|
||
|
- ViewModel의 기본 클래스
|
||
|
- `INotifyPropertyChanged` 구현
|
||
|
- 속성 변경 알림 자동 처리
|
||
|
- 배치 업데이트 지원
|
||
|
|
||
|
### 2. IntegratedBaseUi<TViewModel>
|
||
|
- UI의 기본 클래스 (BaseUi + MVVM 기능 통합)
|
||
|
- Attribute 기반 자동 바인딩
|
||
|
- ViewModel과 UI 요소 자동 연결
|
||
|
|
||
|
### 3. BindToAttribute
|
||
|
- UI 요소를 ViewModel 속성에 바인딩
|
||
|
- `nameof()` 사용으로 타입 안전성 보장
|
||
|
- 컨버터 지원
|
||
|
|
||
|
## 사용법
|
||
|
|
||
|
### 1. ViewModel 생성
|
||
|
|
||
|
```csharp
|
||
|
namespace DDD.MVVM
|
||
|
{
|
||
|
public class MyViewModel : SimpleViewModel
|
||
|
{
|
||
|
private string _title = "기본 제목";
|
||
|
private int _count = 0;
|
||
|
private bool _isVisible = true;
|
||
|
|
||
|
public string Title
|
||
|
{
|
||
|
get => _title;
|
||
|
set => SetField(ref _title, value);
|
||
|
}
|
||
|
|
||
|
public int Count
|
||
|
{
|
||
|
get => _count;
|
||
|
set => SetField(ref _count, value);
|
||
|
}
|
||
|
|
||
|
public bool IsVisible
|
||
|
{
|
||
|
get => _isVisible;
|
||
|
set => SetField(ref _isVisible, value);
|
||
|
}
|
||
|
|
||
|
public string CountText => $"개수: {Count}";
|
||
|
|
||
|
public void IncrementCount()
|
||
|
{
|
||
|
Count++;
|
||
|
OnPropertyChanged(nameof(CountText));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 2. View 생성
|
||
|
|
||
|
```csharp
|
||
|
namespace DDD
|
||
|
{
|
||
|
public class MyView : IntegratedBaseUi<MyViewModel>
|
||
|
{
|
||
|
[SerializeField, BindTo(nameof(MyViewModel.Title))]
|
||
|
private Text _titleText;
|
||
|
|
||
|
[SerializeField, BindTo(nameof(MyViewModel.CountText))]
|
||
|
private Text _countText;
|
||
|
|
||
|
[SerializeField, BindTo(nameof(MyViewModel.IsVisible))]
|
||
|
private GameObject _panel;
|
||
|
|
||
|
// UI 이벤트 핸들러
|
||
|
public void OnIncrementButtonClicked()
|
||
|
{
|
||
|
ViewModel.IncrementCount();
|
||
|
}
|
||
|
|
||
|
public void OnToggleVisibilityClicked()
|
||
|
{
|
||
|
ViewModel.IsVisible = !ViewModel.IsVisible;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 3. 컨버터 사용
|
||
|
|
||
|
```csharp
|
||
|
[SerializeField, BindTo(nameof(MyViewModel.IsVisible), typeof(InvertBoolConverter))]
|
||
|
private GameObject _hiddenPanel;
|
||
|
|
||
|
[SerializeField, BindTo(nameof(MyViewModel.Count), typeof(ItemCountConverter))]
|
||
|
private Text _formattedCountText;
|
||
|
```
|
||
|
|
||
|
## 기존 InventoryView 변환 예시
|
||
|
|
||
|
### 기존 코드 (InventoryView)
|
||
|
```csharp
|
||
|
public class InventoryView : MonoBehaviour
|
||
|
{
|
||
|
private InventoryCategoryType _currentCategory = InventoryCategoryType.Food;
|
||
|
|
||
|
public void UpdateCategoryView(InventoryCategoryType category)
|
||
|
{
|
||
|
_currentCategory = category;
|
||
|
// 복잡한 UI 업데이트 로직...
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### MVVM 변환 후
|
||
|
|
||
|
**InventoryViewModel.cs**
|
||
|
```csharp
|
||
|
namespace DDD.MVVM
|
||
|
{
|
||
|
public class InventoryViewModel : SimpleViewModel
|
||
|
{
|
||
|
private InventoryCategoryType _currentCategory = InventoryCategoryType.Food;
|
||
|
|
||
|
public InventoryCategoryType CurrentCategory
|
||
|
{
|
||
|
get => _currentCategory;
|
||
|
set => SetField(ref _currentCategory, value);
|
||
|
}
|
||
|
|
||
|
public string CategoryDisplayText => CurrentCategory switch
|
||
|
{
|
||
|
InventoryCategoryType.Food => "음식",
|
||
|
InventoryCategoryType.Drink => "음료",
|
||
|
_ => "전체"
|
||
|
};
|
||
|
|
||
|
public void SetCategory(InventoryCategoryType category)
|
||
|
{
|
||
|
CurrentCategory = category;
|
||
|
OnPropertyChanged(nameof(CategoryDisplayText));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**InventoryView.cs**
|
||
|
```csharp
|
||
|
namespace DDD.MVVM
|
||
|
{
|
||
|
public class InventoryView : AutoBindView<InventoryViewModel>
|
||
|
{
|
||
|
[SerializeField, BindTo(nameof(InventoryViewModel.CategoryDisplayText))]
|
||
|
private Text _categoryLabel;
|
||
|
|
||
|
public void OnCategoryButtonClicked(int categoryIndex)
|
||
|
{
|
||
|
ViewModel.SetCategory((InventoryCategoryType)categoryIndex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## 장점
|
||
|
|
||
|
### 1. 타입 안전성
|
||
|
- `nameof()` 사용으로 컴파일 타임 검증
|
||
|
- 속성명 변경 시 자동 업데이트
|
||
|
- IDE IntelliSense 지원
|
||
|
|
||
|
### 2. Unity 통합성
|
||
|
- Inspector에서 바인딩 확인
|
||
|
- MonoBehaviour 패턴 유지
|
||
|
- 기존 Unity 워크플로우와 호환
|
||
|
|
||
|
### 3. 유지보수성
|
||
|
- View와 비즈니스 로직 분리
|
||
|
- 테스트 가능한 ViewModel
|
||
|
- 재사용 가능한 컴포넌트
|
||
|
|
||
|
### 4. 개발 생산성
|
||
|
- 자동 바인딩으로 보일러플레이트 코드 감소
|
||
|
- 데이터 변경 시 UI 자동 업데이트
|
||
|
- 일관된 개발 패턴
|
||
|
|
||
|
## 컨버터 목록
|
||
|
|
||
|
### 기본 컨버터들
|
||
|
- `InvertBoolConverter`: 불린 값 반전
|
||
|
- `ItemCountConverter`: 숫자를 "아이템 수: N" 형식으로 변환
|
||
|
- `InventoryCategoryConverter`: 카테고리 열거형을 한국어로 변환
|
||
|
- `CurrencyConverter`: 숫자를 통화 형식으로 변환
|
||
|
- `PercentageConverter`: 소수를 백분율로 변환
|
||
|
|
||
|
### 커스텀 컨버터 생성
|
||
|
```csharp
|
||
|
public class CustomConverter : IValueConverter
|
||
|
{
|
||
|
public object Convert(object value)
|
||
|
{
|
||
|
// 변환 로직 구현
|
||
|
return convertedValue;
|
||
|
}
|
||
|
|
||
|
public object ConvertBack(object value)
|
||
|
{
|
||
|
// 역변환 로직 구현 (선택사항)
|
||
|
return originalValue;
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## 베스트 프랙티스
|
||
|
|
||
|
### 1. ViewModel 설계
|
||
|
- 단일 책임 원칙 준수
|
||
|
- UI 관련 Unity API 직접 사용 금지
|
||
|
- 계산된 속성 적극 활용
|
||
|
- 이벤트를 통한 느슨한 결합
|
||
|
|
||
|
### 2. 바인딩 설정
|
||
|
- `nameof()` 사용 필수
|
||
|
- 적절한 컨버터 활용
|
||
|
- 복잡한 로직은 ViewModel에서 처리
|
||
|
|
||
|
### 3. 성능 고려사항
|
||
|
- 배치 업데이트 활용
|
||
|
- 불필요한 PropertyChanged 이벤트 방지
|
||
|
- 컬렉션 변경 시 효율적인 업데이트
|
||
|
|
||
|
## 마이그레이션 가이드
|
||
|
|
||
|
### 단계별 적용 방법
|
||
|
|
||
|
#### 1단계: 기존 View에서 ViewModel 분리
|
||
|
1. View의 상태 변수들을 ViewModel로 이동
|
||
|
2. 비즈니스 로직을 Service로 분리
|
||
|
3. UI 업데이트 로직을 바인딩으로 대체
|
||
|
|
||
|
#### 2단계: 자동 바인딩 적용
|
||
|
1. `AutoBindView<TViewModel>` 상속
|
||
|
2. UI 요소에 `BindTo` Attribute 추가
|
||
|
3. 수동 UI 업데이트 코드 제거
|
||
|
|
||
|
#### 3단계: 최적화 및 리팩토링
|
||
|
1. 컨버터 활용으로 로직 단순화
|
||
|
2. 계산된 속성으로 중복 제거
|
||
|
3. 이벤트 시스템과 통합
|
||
|
|
||
|
이 MVVM 시스템을 통해 Unity UI 개발의 생산성과 유지보수성을 크게 향상시킬 수 있습니다.
|