MVC Pattern

  • 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 패턴
  • Application 구성 요소를 세 가지 역할로 구분
  • 각각의 요소에만 집중래서 개발할 수 있음
  • 재사용성 및 확장성이 용이

모델

  • 데이터 베이스, CharacterData, EnemyData 등
using System; 

[Serializable] public class Model 
{ 
	public int score; 
	
	public void AddScore(int points) 
	{ 
		score += points; 
	} 
}

  • UI 등 유저의 화면에 보이는 모든 것
using UnityEngine; 
using UnityEngine.UI; 

public class View : MonoBehaviour 
{ 
	public Text scoreText; 
	
	public void UpdateScore(int newScore) 
	{ 
		scoreText.text = "Score: " + newScore.ToString(); 
	} 
}

컨트롤러

  • 뷰와 모델의 상호작용을 관리하는 곳
  • 모델과 뷰를 커플링 하는 것이 아닌 컨트롤러를 통해 디커플링 합니다.
  • 우리가 흔히 만드는 Manager 클래스가 해당합니다.
using UnityEngine; 

public class Controller : MonoBehaviour 
{ 
	public Model model; 
	public View view; 
	
	private void Start() 
	{ 
		view.UpdateScore(model.score); 
	} 
	
	private void Update() 
	{ 
		if (Input.GetKeyDown(KeyCode.Space)) 
		{ 
			view model.AddScore(10); 
			view.UpdateScore(model.score); 
		} 
	} 
}

장점

  • 각 역할에 대한 구분이 확실하여 유지 및 보수성이 뛰어남
  • UI를 유지하여 모델만 바꾸어도 작동하며, 모델을 유지하고 UI를 바꿔도 작동합니다.

단점

  • 설계를 잘못하면 굉장히 복잡해지고 각 기능이 모호해집니다.
  • 그로인해 오히려 시간이 더 걸리는 경우가 발생합니다.
  • 디렉토리 및 클래스의 갯수가 많아지면서 관리가 힘들어 질 수 있습니다.

Unity의 MVC에 적합한 시스템

  • 복잡한 게임 로직: MVC는 UI와 분리되어야 하는 복잡한 비즈니스 로직이 있는 게임에 적합합니다. 예를 들어 복잡한 시스템을 갖춘 전략 게임이나 RPG는 MVC의 이점을 누릴 수 있습니다.

  • 재사용 가능한 모델이 포함된 게임: 게임의 로직(모델)을 다양한 플랫폼이나 뷰(예: 모바일 버전 및 PC 버전)에서 사용할 수 있는 경우 MVC는 좋은 구조를 제공합니다.

  • 멀티플레이어 게임: MVC는 멀티플레이어 게임, 특히 게임 상태 및 플레이어 상호 작용 관리와 관련된 복잡한 논리를 처리하는 데 효과적일 수 있습니다.

  • UI 중심 게임: MVC는 본질적으로 UI 관리를 단순화하지는 않지만 UI 로직이 비즈니스 로직과 별도로 유지되는 한 UI가 중요한 구성 요소인 게임을 구성하는 데 도움이 될 수 있습니다.

  • 레벨 편집기 또는 도구 개발: MVC는 도구의 UI(보기), 도구가 조작하는 데이터(모델) 및 도구를 명확하게 구분하는 레벨 편집기와 같은 게임 내 도구를 개발하는 데 좋은 선택이 될 수 있습니다.

MVC는 애플리케이션 설계를 유연한 아키텍처를 제공하지만 그 이점을 완전히 실현하려면 신중한 구현이 필요합니다. 이는 관심사 분리 및 모듈성에 대한 요구 사항이 잘 정의된 복잡한 애플리케이션에서 가장 효과적입니다.


MVP Pattern

  • Model, View, Presenter 로 구성 된 패턴입니다.
  • MVC에서 Controller가 Presenter로 교체 된 패턴 입니다.

  • View와 Presenter는 1 : 1 관계이기 때문에 MVC 패턴보다 강한 결합을 갖습니다.

    모델

public class ScoreModel 
{ 
	public int Score { get; private set; } 
	
	public void AddScore(int points) 
	{ 
		Score += points; 
	} 
}

using UnityEngine.UI; 

public class ScoreView : MonoBehaviour, IScoreView 
{ 
	public Text scoreText; 
	public Button addScoreButton; 
	public ScorePresenter presenter; 
	
	private void Start() 
	{ 
		presenter = new ScorePresenter(this, new ScoreModel()); 
		addScoreButton.onClick.AddListener(() => presenter.OnAddScoreClicked()); 
	}
	
	public void DisplayScore(int score) 
	{ 
		scoreText.text = $"Score: {score}"; 
	} 
}

프레젠터

public class ScorePresenter 
{ 
	private IScoreView view; 
	private ScoreModel model; 
	
	public ScorePresenter(IScoreView view, ScoreModel model) 
	{ 
		this.view = view; 
		this.model = model; 
		view.DisplayScore(model.Score); 
	} 
	
	public void OnAddScoreClicked() 
	{ 
		model.AddScore(10); 
		view.DisplayScore(model.Score); 
	} 
}

장점

관심사 분리

  • MVP는 프레젠테이션 로직, 비즈니스 로직, 사용자 인터페이스 간의 명확한 분리를 제공하여 코드베이스를 구성하는 데 도움을 주고 읽기 쉽고 유지 관리하기 쉽게 만듭니다.

    테스트

  • Presenter는 View와 분리되어 있으므로 사용자 인터페이스와 독립적으로 단위 테스트가 가능합니다.
  • 이러한 분리를 통해 비즈니스 논리를 보다 철저하게 테스트할 수 있습니다.

    재사용성

  • 프레젠터는 필수 뷰 인터페이스를 구현하는 한 다양한 뷰에서 재사용할 수 있습니다.
  • 이렇게 하면 코드의 중복이 줄어들 수 있습니다.

    보기 변경의 유연성

  • 보기와 발표자가 느슨하게 결합되어 있으므로 발표자에서 최소한의 변경만으로 또는 전혀 변경하지 않고도 사용자 인터페이스를 변경할 수 있습니다.
  • 프레젠테이션 로직을 발표자에게 오프로드함으로써 보기는 단순하게 유지되고 사용자 인터페이스 표시에만 집중할 수 있습니다.

단점

복잡성

  • MVP를 도입하면 아키텍처 복잡성 측면에서 초기 오버헤드가 추가될 수 있습니다.
  • 특히 이러한 추상화 수준이 필요하지 않은 소규모 프로젝트의 경우 더욱 그렇습니다.

    상용구 코드 (Boilerplate Code)

  • MVP는 모델, 뷰 및 발표자를 위한 인터페이스와 클래스를 만들어야 하므로 상용구 코드의 양이 증가할 수 있습니다.

    러닝커브

  • 패턴에 익숙하지 않은 개발자는 올바르게 이해하고 구현하는 것이 어려워 패턴의 이점을 활용하지 못하는 부적절한 사용으로 이어질 수 있습니다.

    오버헤드

  • 레이어(모델, 뷰 및 발표자) 간의 통신은 잘 정의되어야 하며,
  • 이로 인해 간단한 작업에 대한 코드가 더 장황해질 수 있습니다.

    프레젠터와 뷰의 커플링

  • 프레젠터와 뷰는 개념적으로는 분리되어 있지만 구현에 따라 긴밀하게 결합되어 재사용을 방해할 수 있습니다.
  • MVP는 발표자의 명시적인 지시 없이 뷰가 모델 변경에 자발적으로 반응해야 하는 매우 동적인 사용자 인터페이스를 처리할 때 번거로울 수 있습니다.

결론적으로 MVP 패턴은 명확한 분리, 유지 관리 용이성 및 테스트 가능성의 이점이 초기 복잡성과 오버헤드보다 더 큰 중대형 애플리케이션에 유용합니다. 소규모 프로젝트나 UI가 덜 복잡한 프로젝트의 경우 더 단순한 디자인이 더 적합할 수 있습니다.

Unity MVP에 적합한 시스템

  • UI 중심 애플리케이션: MVP는 메뉴 시스템, 캐릭터 사용자 정의 화면 또는 인벤토리 시스템과 같이 사용자 인터페이스에 중점을 두는 애플리케이션에 특히 적합합니다.

  • 동적 UI가 포함된 애플리케이션: 사용자 상호 작용이나 게임 이벤트에 따라 UI를 동적으로 업데이트해야 하는 경우 MVP가 좋은 선택이 될 수 있습니다. Presenter는 Model 변경에 대한 응답으로 View를 업데이트하는 중개자 역할을 할 수 있습니다.

  • 모듈형 UI 구성 요소: 게임의 여러 부분에서 재사용해야 하는 모듈형 UI 구성 요소가 있는 경우 MVP는 여러 발표자가 동일한 보기 인터페이스로 작업할 수 있도록 허용하여 이를 용이하게 할 수 있습니다.

  • 간단한 게임 로직: MVP는 대부분의 복잡성이 UI 레이어에 있고 UI와 기본 데이터 간의 상호 작용이 간단한 간단한 로직을 갖춘 게임에 더 적합할 수 있습니다.

  • UI 테스트 및 프로토타이핑: MVP는 프레젠테이션 레이어를 로직에서 분리하므로 다양한 UI 디자인과 상호 작용을 빠르게 프로토타이핑하고 테스트하는 데 도움이 될 수 있습니다.


MVVM Pattern

  • MVC의 Contoller가 뷰모델(ViewModel)로 변형 된 패턴

View Model

  • ViewModel은 뷰에서 모델을 추상화합니다.
  • 이는 뷰가 모델의 구조나 논리에 대해 아무것도 알 필요가 없음을 의미합니다.
  • 대신, ViewModel은 View가 데이터를 표시하고 기본 비즈니스 로직과 상호 작용하는 데 사용할 수 있는 일련의 데이터 및 명령을 제공합니다.
  • MVVM의 주요 기능 중 하나는 View와 ViewModel 간의 데이터 바인딩입니다.
  • View는 ViewModel의 속성에 바인딩되어 모델의 현재 상태를 반영합니다.
  • 모델이 변경되면 ViewModel은 이러한 속성을 업데이트하고 바인딩을 통해 뷰를 자동으로 업데이트합니다.

Warning

  • Unity에서 MVVM(Model-View-ViewModel) 패턴을 구현하는 것은 WPF 또는 Xamarin과 같은 프레임워크에 비해 약간 까다로울 수 있습니다.
  • Unity는 기본적으로 데이터 바인딩 및 명령을 지원하지 않기 때문입니다.
  • 그러나 어느 정도까지는 MVVM 원칙을 따를 수 있습니다. 다음은 간단한 예입니다

Model

public class PlayerModel 
{ 
	public int Health { get; private set; } 
	
	public PlayerModel(int initialHealth) 
	{ 
		Health = initialHealth; 
	} 
	
	public void TakeDamage(int damage) 
	{ 
		Health -= damage; if (Health < 0) Health = 0; 
	} 
}

View Model

using System; 

public class PlayerViewModel 
{ 
	private PlayerModel model; 
	public Action<int> OnHealthChanged; 
	
	public PlayerViewModel(PlayerModel model) 
	{ 
		this.model = model; 
	} 
	
	public void TakeDamage(int damage) 
	{ 
		model.TakeDamage(damage);
		OnHealthChanged?.Invoke(model.Health); 
	} 
}

View

using UnityEngine; 
using UnityEngine.UI; 

public class PlayerView : MonoBehaviour 
{ 
	public Text healthText; 
	public Button damageButton; 
	private PlayerViewModel viewModel; 
	
	private void Start() 
	{ 
		var model = new PlayerModel(100); 
		viewModel = new PlayerViewModel(model); 
		viewModel.OnHealthChanged += UpdateHealthDisplay; 
		damageButton.onClick.AddListener(() => viewModel.TakeDamage(10)); 
		UpdateHealthDisplay(model.Health); 
	} 
	
	private void UpdateHealthDisplay(int health) 
	{ 
		healthText.text = $"Health: {health}"; 
	} 
}