이번 글에서는 개인 프로젝트에서 실제로 적용한 MVVM 패턴 구조를 정리해보려고 합니다.
나는 Unity에서 MVC 패턴을 자주 사용했지만, Unity가 제공하는 기본 메서드나 컴포넌트의 구조적 특성상, View와 Controller를 엄격히 분리하는 것에 한계가 있다고 느꼈다.
첫번째, Model의 변경을 View가 감지하기 어려움
- MVC 패턴에서는 View가 Model의 변경 사항을 감지해야 하는데 Unity에서는 MonoBehaviour 기반의 UI 컴포넌트가 주로 View 역할을 하므로, Model이 변경될 때마다 View로 이벤트를 전달하는 추가 코드가 필요했음
두번째, View와 Controller의 역할이 모호해짐
- Unity의 UI 시스템은 Button 클릭(OnClick), OnValueChanged 등의 이벤트를 직접 처리할 수 있는데, 이로 인해 View가 Controller 역할을 일부 수행하는 경우가 많아 역할이 애매함
그래서 이번에는 MVVM 패턴을 한번 사용해 개발을 해보기로 했다!
예제 코드
1. Model – DateCourseModel (데이터 저장 & 처리)
- 서버(Firebase)와 직접 통신 ❌ UI와 직접 연결 X
- 데이터를 보관하고 변경 사항을 ViewModel에 전달
[FirestoreData]
public class DateCourseModel
{
[FirestoreProperty(Name = "title")]
public string Title { get; set; }
[FirestoreProperty(Name = "description")]
public string Description { get; set; }
public DateCourseModel(){}
public DateCourseModel(string title, string description)
{
Title = title;
Description = description;
}
// ... 기타 필드 생략
}
2. ViewModel – DateCourseViewModel (데이터 처리 & UI 전달)
- Model의 데이터를 UI에서 쉽게 사용할 수 있도록 가공
- UI와 독립적으로 동작하도록 구성 (UI에 직접 의존하지 않음)
- Model의 변경 사항을 UI(View)에 전달
public class DateCourseViewModel
{
public string Title { get; }
public int LikesCount { get; private set; }
public bool IsLikedByUser { get; private set; }
public Action ToggleLikeCommand { get; private set; }
public event Action OnLikeStateChanged;
private readonly IDateCourseRepository dataRepository;
public DateCourseViewModel(..., IDateCourseRepository repository)
{
// 프로퍼티 초기화...
dataRepository = repository;
ToggleLikeCommand = async () =>
{
IsLikedByUser = !IsLikedByUser;
LikesCount += IsLikedByUser ? 1 : -1;
await dataRepository.UpdateLikeStateAsync(Title, IsLikedByUser);
OnLikeStateChanged?.Invoke();
};
}
}
3. View – DateCourseItem (UI 요소 & 데이터 바인딩)
- ViewModel을 받아 UI 업데이트 (UI 로직만 처리)
- 직접 데이터 로직을 처리하지 않고 ViewModel을 통해 변경
public class DateCourseItem : MonoBehaviour
{
public TextMeshProUGUI Title, LikesCount;
public Image LikeImage;
public Button LikeButton;
private DateCourseViewModel viewModel;
public async void Initialize(DateCourseViewModel viewModel)
{
this.viewModel = viewModel;
UpdateUI();
LikeButton.onClick.AddListener(() =>
{
viewModel.ToggleLikeCommand?.Invoke();
});
viewModel.OnLikeStateChanged += UpdateUI;
// 썸네일 이미지 비동기 로드
await LoadImage.LoadFromUrlAsync(viewModel.ThumbnailImage, ThumbnailImage);
}
private void UpdateUI()
{
Title.text = viewModel.Title;
...
}
...
}
이 코드에서 중요한 부분:
- ViewModel이 UI와 직접 연결되지 않고 이벤트(OnLikeStateChanged)를 통해 UI에 변경 사항 전달
- View는 ViewModel에서 데이터를 가져와 UI를 업데이트만 함
- UI Toolkit 없이도 UnityEngine.UI를 활용해 MVVM 구현 가능!!

** MVVM 패턴을 하기 전에 알아두면 좋은 것!
- UI Toolkit이 MVVM 패턴을 작성하는데 필수인가?
=> NO 필수는 아님 : .uxml, .uss, .cs가 UI Toolkit이지만, UnityEngine.UI로도 충분히 MVVM 가능
- 데이터 바인딩을 위해 서버가 꼭 필요한가?
=> NO 필수는 아님 : 데이터 바인딩은 "뷰모델과 뷰 간의 동기화"를 의미하는데 서버는 데이터를 가져오고 저장하는 역할 서버 없이도 로컬 데이터로 MVVM을 구현할 수 있음!!

> Unity 6 이전 버전에서는 자동 바인딩 시스템이 없어 MVVM 구현이 제한적이지만,
> 구조적으로 ViewModel과 View를 분리하고 이벤트나 옵저버 패턴으로 동기화를 구현하면
> 충분히 MVVM 설계 철학에 부합한다고 생각이 든다!
다음엔 Unity 6에서 공식적으로 지원되는 UI Toolkit 데이터 바인딩을 사용해보자
'프로젝트 사례 > 프로젝트 개발일지' 카테고리의 다른 글
[개발일지] Asset Import 시 Script Updating Consent 창 대처 방법 (0) | 2025.04.23 |
---|---|
[Unity] Rest API 특징 및 예제 (0) | 2025.03.21 |
[Unity] Error building Player: BuildFailedException: You have enabled the Vulkan graphics API, which is not supported by ARCore. (0) | 2025.03.15 |
[개발일지] MVC, MVVM 패턴 (2) | 2025.01.13 |