8.8 KiB
8.8 KiB
기존 UI 시스템을 MVVM으로 마이그레이션 가이드
Unity 프로젝트에서 기존 UI 시스템(BaseUi, BasePopupUi, PopupUi)을 MVVM 패턴으로 점진적으로 전환하는 방법을 설명합니다.
호환성 보장
1. 기존 UI 시스템은 그대로 유지됩니다
BaseUi
,BasePopupUi
,PopupUi<T>
클래스들은 변경되지 않음- 기존 UI들은 계속해서 정상 동작
UiManager
와PopupUiState
도 기존 방식 그대로 지원
2. 새로운 Integrated 클래스들 사용
IntegratedBaseUi<TViewModel>
: BaseUi + MVVM 기능 통합IntegratedBasePopupUi<TViewModel>
: BasePopupUi + MVVM 기능 통합IntegratedPopupUi<TInputEnum, TViewModel>
: PopupUi + MVVM 기능 통합
마이그레이션 전략
단계 1: 점진적 전환 계획 수립
-
우선순위 결정
높음: 복잡한 상태 관리가 필요한 UI (InventoryView, ShopView 등) 중간: 중간 규모의 팝업 UI 낮음: 간단한 확인/알림 다이얼로그
-
전환 범위 설정
- 한 번에 하나의 UI씩 전환
- 관련된 ViewModel과 Service 함께 구현
- 철저한 테스트 후 다음 UI 진행
단계 2: ViewModel 및 Service 구현
기존 View 분석
// 기존 InventoryView
public class InventoryView : MonoBehaviour
{
private InventoryCategoryType _currentCategory;
private List<ItemViewModel> _items;
public void UpdateCategoryView(InventoryCategoryType category)
{
_currentCategory = category;
// 복잡한 UI 업데이트 로직
}
}
ViewModel로 분리
// 새로운 InventoryViewModel
public class InventoryViewModel : SimpleViewModel
{
private InventoryCategoryType _currentCategory;
private List<ItemViewModel> _visibleItems;
public InventoryCategoryType CurrentCategory
{
get => _currentCategory;
set => SetField(ref _currentCategory, value);
}
public void SetCategory(InventoryCategoryType category)
{
CurrentCategory = category;
UpdateVisibleItems(); // 비즈니스 로직
}
}
Service 계층 분리
// 비즈니스 로직을 Service로 분리
public class InventoryService : IUiService
{
public IEnumerable<ItemViewModel> FilterItems(InventoryCategoryType category)
{
// 복잡한 필터링 로직
}
}
단계 3: View를 MVVM으로 전환
기존 View 코드
public class InventoryView : MonoBehaviour, IEventHandler<InventoryChangedEvent>
{
[SerializeField] private Transform _slotParent;
[SerializeField] private Text _categoryLabel;
// 많은 상태 변수들과 복잡한 로직들...
}
MVVM View로 전환
public class MvvmInventoryView : MvvmBasePopupUi<InventoryViewModel>
{
[SerializeField] private Transform _slotParent;
[SerializeField] private Text _categoryLabel;
protected override void SetupBindings()
{
BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText));
// 간단한 바인딩 설정만
}
public void OnCategoryButtonClicked(int categoryIndex)
{
ViewModel.SetCategory((InventoryCategoryType)categoryIndex);
}
}
단계 4: 마이그레이션 체크리스트
ViewModel 체크리스트
- SimpleViewModel 상속
- 모든 상태를 속성으로 변환
- SetField 사용한 PropertyChanged 알림
- 계산된 속성 구현
- 비즈니스 로직을 Service로 위임
View 체크리스트
- 적절한 MVVM Base 클래스 상속
- SetupBindings() 구현
- UI 이벤트를 ViewModel 메서드 호출로 변경
- 직접적인 UI 업데이트 코드 제거
- HandleCustomPropertyChanged에서 복잡한 UI 처리
Service 체크리스트
- IService 인터페이스 구현
- 복잡한 비즈니스 로직 포함
- 데이터 접근 로직 캡슐화
- 테스트 가능한 구조
구체적인 마이그레이션 예시
예시 1: 간단한 팝업 UI
Before (기존)
public class SimpleDialogUi : BasePopupUi
{
[SerializeField] private Text _messageText;
[SerializeField] private Button _confirmButton;
public void ShowMessage(string message)
{
_messageText.text = message;
_confirmButton.onClick.AddListener(Close);
}
}
After (MVVM)
// ViewModel
public class DialogViewModel : SimpleViewModel
{
private string _message;
public string Message
{
get => _message;
set => SetField(ref _message, value);
}
}
// View
public class MvvmSimpleDialogUi : MvvmBasePopupUi<DialogViewModel>
{
[SerializeField] private Text _messageText;
[SerializeField] private Button _confirmButton;
protected override void SetupBindings()
{
BindText(_messageText, nameof(DialogViewModel.Message));
}
protected override void Awake()
{
base.Awake();
_confirmButton.onClick.AddListener(() => Close());
}
}
예시 2: 복잡한 목록 UI
Before (기존 InventoryView)
public class InventoryView : MonoBehaviour
{
private InventoryCategoryType _currentCategory;
private Dictionary<string, ItemSlotUi> _slotLookup = new();
public void UpdateCategoryView(InventoryCategoryType category)
{
_currentCategory = category;
// 복잡한 필터링 로직
foreach (var slot in _slotLookup.Values)
{
bool shouldShow = MatchesCategory(slot.Model, category);
slot.SetActive(shouldShow);
}
// 정렬 로직
// UI 업데이트 로직
}
private bool MatchesCategory(ItemViewModel model, InventoryCategoryType category)
{
// 복잡한 카테고리 매칭 로직
}
}
After (MVVM)
// ViewModel (상태 관리)
public class InventoryViewModel : SimpleViewModel
{
private InventoryCategoryType _currentCategory;
private List<ItemViewModel> _visibleItems;
public InventoryCategoryType CurrentCategory
{
get => _currentCategory;
set => SetField(ref _currentCategory, value);
}
public List<ItemViewModel> VisibleItems
{
get => _visibleItems;
private set => SetField(ref _visibleItems, value);
}
public string CategoryDisplayText => GetCategoryDisplayText();
public void SetCategory(InventoryCategoryType category)
{
CurrentCategory = category;
UpdateVisibleItems();
OnPropertyChanged(nameof(CategoryDisplayText));
}
private void UpdateVisibleItems()
{
var filtered = _inventoryService.FilterItems(CurrentCategory, CurrentSortType);
VisibleItems = filtered.ToList();
}
}
// Service (비즈니스 로직)
public class InventoryService : IUiService
{
public IEnumerable<ItemViewModel> FilterItems(InventoryCategoryType category, InventorySortType sortType)
{
// 복잡한 필터링과 정렬 로직을 Service로 이동
}
}
// View (UI 바인딩만)
public class MvvmInventoryView : MvvmBasePopupUi<InventoryViewModel>
{
[SerializeField] private Text _categoryLabel;
[SerializeField] private Transform _slotParent;
protected override void SetupBindings()
{
BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText));
}
protected override void HandleCustomPropertyChanged(string propertyName)
{
if (propertyName == nameof(InventoryViewModel.VisibleItems))
{
UpdateItemSlots(); // 복잡한 UI 업데이트는 여전히 필요
}
}
}
마이그레이션 주의사항
1. 기존 코드와의 의존성
- 기존 UI를 참조하는 다른 코드들 확인
- 이벤트 시스템과의 연동 유지
- ScriptableObject 설정 파일들과의 호환성
2. 성능 고려사항
- PropertyChanged 이벤트의 과도한 발생 방지
- UI 업데이트 배칭 활용
- 메모리 누수 방지 (이벤트 핸들러 정리)
3. 테스트 전략
- ViewModel 단위 테스트 작성
- 기존 UI와 새 UI 동시 테스트
- 사용자 시나리오 기반 통합 테스트
마이그레이션 우선순위 추천
1차 마이그레이션 (높은 우선순위)
- InventoryView: 복잡한 상태 관리, 필터링, 정렬
- ShopView: 상품 목록, 카테고리, 구매 로직
- MenuView: 메뉴 관리, 레시피 선택
2차 마이그레이션 (중간 우선순위)
- SettingsView: 다양한 설정 옵션들
- StatisticsView: 데이터 표시와 계산
3차 마이그레이션 (낮은 우선순위)
- SimpleDialogUi: 간단한 확인 다이얼로그들
- NotificationUi: 알림 팝업들
이 가이드를 따라 점진적으로 UI를 MVVM으로 전환하면, 기존 시스템의 안정성을 유지하면서도 새로운 아키텍처의 이점을 얻을 수 있습니다.