UI 아키텍처 패턴 중 "UI 코드와 로직 코드를 분리하자" 는 목표를 가지고 있는 MVC, MVP, MVVM 에 대하여 알아보자.
사실 UI 아키텍처에 따라서 맞춰야해!!!! 라는 느낌으로 구조를 짜진 않는데, 알고보니 이미 사용하고 있었다. 그러니 이왕이면 아키텍쳐 공부 후 멋있게 사용하고 있다고 말해버리기~

가장 오래된 패턴. Controller가 중간에서 Model과 View 를 연결한다.

Model 이 해당 View를 직접 알고 있다. (Observer 패턴으로 갱신). View도 Model 을 직접 읽을 수 있다. Controller 는 "사용자 입력 -> Model 업데이트" 만 담당한다.
Model <-> View 사이의 의존성이 생겨서 테스트가 어렵고, View가 복잡해질 수 록 Model 과 결합도가 높아진다.
예를 들어, 플레이어의 HP 를 표시하는 시스템ㅇ을 MVC 로 구현했다고 가정하자.
// Model
public class PlayerModel
{
public int HP { get; private set; } = 100;
private PlayerHPView _view; // ⚠️ Model이 View를 직접 참조!
public PlayerModel(PlayerHPView view)
{
_view = view;
}
public void TakeDamage(int damage)
{
HP -= damage;
_view.UpdateHP(HP); // ⚠️ Model이 View를 직접 호출
}
}
// View
public class PlayerHPView : MonoBehaviour
{
public Text hpText;
private PlayerModel _model; // ⚠️ View도 Model을 직접 참조!
void Start()
{
_model = new PlayerModel(this); // 서로가 서로를 물고 있음
}
public void UpdateHP(int hp)
{
hpText.text = $"HP: {hp}";
}
void OnClick_AttackSelf()
{
_model.TakeDamage(10);
}
}
여기서 Player의 HP를 테스트 하고 싶다면 PlayerModel을 생성할 때 PlayerHPView도 생성해야 한다. 그러나
[Test]
public void TakeDamage_Should_Reduce_HP()
{
// PlayerModel을 만들려면 PlayerHPView가 필요
// PlayerHPView는 MonoBehaviour → Unity 씬이 필요
// Unity 씬은 PlayMode 테스트 환경이 필요...
var view = new PlayerHPView(); // ❌ MonoBehaviour라 new 불가
var model = new PlayerModel(view); // ❌ 결국 테스트 불가
model.TakeDamage(10);
Assert.AreEqual(90, model.HP);
}
MonoBehaviour 인 View를 위해 과하게 test 환경을 셋팅해야 하는 문제가 있다. (과한 의존성)
위를 보완하기 위해 나온 패턴이 뒤에 설명할 MVP와 MVVM 이다.
View와 Model을 완전히 분리하고, Presenter가 모든 걸 중재한다.

View는 interface 만 갖고, 실제 구현을 Presenter 에 위임한다. Presenter는 View를 인터페이스로만 알기 때문에 View 없이 Presenter만 단독 테스트가 가능하다.
View마다 Presenter 를 하나씩 만들어야 해서 코드가 많아진다. Presenter가 비대해지기 쉽다.
view가 ViewModel의 데이터에 자동으로 바인딩되어 동기화 된다.

ViewModel은 View를 전혀 모른다. 대신 View가 ViewModel의 속성을 바인딩(구독) 해서 값이 바뀌면 자동으로 화면이 갱신된다.
Unity로 게임개발할 때 주로 ReactiveProperty 를 사용해서 바인딩 했다.
그 외에도 다양한 UI 바인딩이 많은데, 인터넷에서 찾아보니까 Unity6의 UIToolkit 을 네이티브로 지원하고 있단다. 나중에 한번 공부해봐야겠다.