Unity 개발에서의 MVC 기초
-
2026년 02월 27일
안녕하세요.
Cocone Engineering에서 클라이언트 엔지니어로 근무 중인 이마후쿠입니다.
이번에는 Unity 개발에서 MVC(Model-View-Controller) 아키텍처를 어떻게 적용할지에 대해 설명드리겠습니다.
시작하며
이번에 설명드릴 내용은 ‘이렇게 해야 한다’는 딱딱한 규칙이 아니라, ‘이런 방법도 있다’는 하나의 방법론입니다.
이걸 보고 계신 Unity 개발 경험이 있으신 분들은 이미 체감하셨을 텐데, Unity 개발, 특히 게임 앱 개발 같은 경우에는 일반적인 아키텍처를 그대로 적용하려다 보면 오히려 비정상적인 구성이 되는 경우가 종종 있습니다.
그럴 때 생각이나 구성을 정리하는 데 조금이나마 도움이 되길 바랍니다.
MVC란?
기능을 데이터나 비즈니스 로직을 다루는 Model, 표시물을 다루는 View, Model과 View 간의 처리를 수행하는 Controller의 세 가지로 나누어 개발하는 기법으로, 매우 오랜 역사를 가진 아키텍처입니다.

특히 비즈니스 로직과 표시의 분리라는 관점은 현재 소프트웨어 설계나 아키텍처에서도 널리 사용되는 것이며, 실제로 이 아키텍처를 이용해 설계, 개발을 해본 경험이 있는 분들도 많을 것입니다.
Unity 개발에서의 MVC
Unity로 개발할 경우, 컴포넌트로 개발할지 여부를 고려해야 합니다.
Unity에서 MVC의 각 모듈을 생성할 때, 기본적으로 Model을 제외한 나머지는 컴포넌트로 만드는 것이 더 나을 것입니다.
| 모듈 | MonoBehaviour 를 계승하는가 | 설명 |
|---|---|---|
| Model | ✗ | 데이터나 비즈니스 로직뿐이며, Unity 컴포넌트에는 의존하지 않는다. |
| View | ◯ | 표시할 오브젝트 등의 컴포넌트를 직렬화하기 위해, 컴포넌트화가 필요 |
| Controller | ◯ | View를 직렬화하기 위해, 컴포넌트화가 필요 |
각 모듈을 컴포넌트화하지 않고 구현하는 것이 불가능한 것은 아니지만, Unity에서 오브젝트를 다룰 때 컴포넌트를 사용하지 않으면 오히려 복잡해지기 쉬워 장점보다 단점이 커지기 쉽습니다.
MVC에서의 의존 관계
MVC에서의 의존 관계는 다소 정의가 모호한 부분도 있지만, 다음과 같습니다.
| 모듈 | 의존하는 모듈 |
|---|---|
| Model | 없음 |
| View | Model ※ |
| Controller | Model、View |
View에서 Model로의 참조는 MVC에서 제한되지 않지만, 이 부분 역시 Controller가 Model에서 데이터를 가져와 View에 전달하는 형태로 하는 것이 개인적으로 더 좋다고 생각합니다.
이는 MVP(Model-View-Presenter)에 가까운 사고방식이지만, View가 Model에 대한 참조를 유지하면 View에서도 Model의 메서드를 호출할 수 있어 의도하지 않은 구현으로 데이터가 변경되는 등의 버그 위험이 높아지기 때문입니다.
MVC 각 모듈에 대해
Model
Model은 주로 데이터와 비즈니스 로직을 다루는 모듈입니다.
또한 통신이나 DB 접근 등 인프라스트럭처 계층과의 상호작용도 Model의 역할입니다.
- View, Controller에 필요한 데이터
- 단, 다음과 같이 실체가 있는 객체나 리소스는 보유하지 않습니다.
- GameObject
- Component
- Texture
- 단, 다음과 같이 실체가 있는 객체나 리소스는 보유하지 않습니다.
- 비즈니스 로직
- 통신
- DB 접근
Model을 구현할 때는 가능한 한 단위로 독립적으로 작동하도록 의식하는 것이 좋습니다.
이는 SOLID 원칙에 기반한 관점뿐만 아니라 테스트를 수행할 경우에도 매우 중요해집니다.
Unity 클라이언트 개발에서는 아직 테스트 문화가 충분히 정착되지 않았다고 느껴지지만, Model 내 비즈니스 로직에 대해 제대로 테스트를 작성해 두면 구현 안정성을 높일 수 있습니다.
테스트에 대한 세부 설명은 끝이 없으므로 생략하지만, 최근 AI 부상에 따른 개발 환경 변화 속에서도 테스트의 중요성은 높아지고 있다고 생각합니다.
또한 Unity에서의 구현에서는 Model이 텍스처 등의 리소스 경로를 보유하게 되므로, 리소스 로딩이나 보유도 함께 구현하고 싶어지기 쉽습니다. 그러나 이는 Model의 책임 범위를 벗어난 것이 되어 Model이 무엇이든 다 하는 상태가 되므로, 구현하지 않도록 합시다.
View
View는 주로 표시와 관련된 것을 다루는 모듈입니다.
Unity의 경우 표시할 오브젝트에 대한 참조를 시리얼라이즈하여 보관하기 때문에, 필연적으로 View는 컴포넌트로 구현하게 됩니다.
- 표시할 오브젝트의 시리얼라이즈
- Image
- Button
- 표시물에 한정된 로직
- 비즈니스 로직이라면 Model, Model과 View 사이의 로직은 Controller의 책임이므로, Unity 개발에서는 View에 로직을 구현할 기회가 그리 많지 않습니다.
- 사용자 입력 수신
- 받은 입력을 처리하는 것은 Controller
View를 구현할 때는 수동적인 모듈로 구현하는 것을 의식하는 것이 좋습니다.
또한 앞서 언급했듯이, View가 직접 Model을 참조하지 않도록 구현하는 것이 안전성이 높습니다.
사용자 입력에 관해서는, Button 컴포넌트로부터의 입력 자체를 수신하는 단계까지는 View가 수행하고, Controller에 콜백 메서드 등을 사용하여 알리는 형태로 하는 것이 좋다고 생각합니다.
다만, 실제 상황에서는 입력을 수신해도 그대로 콜백만 하는 상태가 되는 경우도 적지 않으므로, 경우에 따라서는 Controller 측에서 직접 Button 컴포넌트에 대한 입력 콜백을 설정해도 괜찮다고 생각합니다.
Controller
Controller는 View와 Model 간의 처리를 수행하거나 다른 모듈과의 상호작용을 담당합니다.
View를 컴포넌트로 구현하기 때문에 View에 대한 참조를 시리얼라이즈해야 하므로 Controller 역시 컴포넌트가 됩니다.
- Model의 데이터를 View에 설정
- 외부 모듈과의 상호작용
- Model이나 View를 외부 모듈이 직접 참조하는 일이 없도록 하기 위해
- 사용자 입력 후 처리
- 객체나 리소스 로드
Controller는 Model과 View 사이의 처리를 구현하거나 외부 모듈과의 상호작용을 수행하는 특성상 어느 정도 로직을 구현해야 합니다.
이때 구현하는 로직이 비즈니스 로직으로서 Model에 구현해야 하는 것이 아닌지 항상 고려하며 구현하는 것이 좋습니다.
예제: 팝업
간단한 구현 예시로 팝업에서의 MVC 구현 예를 기재합니다.
예시로 여러 기능을 부여했기 때문에 실제와는 다소 거리가 있는 부분도 있지만, 참고로 봐 주시기 바랍니다.
요구사항
- 제목 표시
- 메시지 표시
- 메세지 형식은 아이템명 + 텍스트
- {item} 을 획득했습니다.
- {item} 을 받겠습니까?
- 메세지 형식은 아이템명 + 텍스트
- 버튼 표시
- 버튼을 표시 방법은 2 종류
- OK 만
- Yes / No 의 2개 표시
- 버튼을 표시 방법은 2 종류
Model
public class PopupModel
{
public string Title { get; }
private string itemId;
private PopupType type;
public bool IsNotice { get { return type == PopupType.Notice; } }
public PopupModel(string title, string itemId, PopupType type)
{
Title = title;
this.itemId= itemId;
this.type = type;
}
private string GetItemName()
{
return Database.Master.GetItemName(itemId);
}
public string GetMessage()
{
string itemName = GetItemName();
return IsNotice ? $"{itemName}을 획득했습니다." : $"{itemName}을 받겠습니까?";
}
public async UniTask<Result> ReceiveItem()
{
// 서버와 통신하여, 결과를 반환
}
}public enum PopupType
{
Notice,
Confirm
}View
public class PopupView : MonoBehaviour
{
[SerializeField] private Text title;
[SerializeField] private Text message;
[SerializeField] private Button okButton;
[SerializeField] private Button yesButton;
[SerializeField] private Button noButton;
private Action onClickOk;
private Action onClickYes;
private Action onClickNo;
public void Init()
{
okButton.onClick.AddListener(OnClickOk);
yesButton.onClick.AddListener(OnClickYes);
noButton.onClick.AddListener(OnClickNo);
}
public void SetButtonCallback(Action onClickOk, Action onClickYes, Action onClickNo)
{
this.onClickOk = onClickOk;
this.onClickYes = onClickYes;
this.onClickNo = onClickNo;
}
public void SetTitle(string title)
{
this.title.text = title;
}
public void SetMessage(string message)
{
this.message.text = message;
}
public void UpdateButtonVisible(bool isNotice)
{
okButton.SetActive(isNotice);
yesButton.SetActive(!isNotice);
noButton.SetActive(!isNotice);
}
private void OnClickOk() => onClickOk?.Invoke();
private void OnClickYes() => onClickYes?.Invoke();
private void OnClickNo() => onClickNo?.Invoke();
}Controller
public class PopupController : MonoBehaviour
{
private PopupModel model;
[SerializeField] private PopupView view;
public void Init(string title, string itemId, PopupType type)
{
model = new PopupModel(title, itemId, type);
view.SetButtonCallback(OnClickOk, OnClickYes, OnClickNo);
view.SetTitle(model.Title);
view.SetMessage(model.GetMessage());
view.UpdateButtonVisible(model.IsNotice);
}
private void OnClickOk()
{
Close();
}
private async void OnClickYes()
{
Result result = await model.ReceiveItem();
Close();
}
private void OnClickNo()
{
Close();
}
private void Close()
{
Object.Destroy(gameObject);
}
}마지막으로
Unity 개발에서의 MVC에 대해 설명해 드렸는데 어떠셨습니까?
Unity 개발에서는 MVC뿐만 아니라 다양한 아키텍처가 그대로 적용되지 않는 경우가 많고, 정답이 없는 가운데 각자가 시행착오를 거치며 개발을 진행하고 계실 텐데, 이 글이 그런 여러분께 조금이나마 도움이 되길 바랍니다.
Unity를 중심으로 클라이언트 개발을 이끌어갈 엔지니어 및 다양한 포지션을 채용 중입니다.
이번 포스팅에서 다룬 내용처럼, 아키텍처나 설계 단계부터 주도적으로 고민하며 개발하고 싶은 분, 그리고 팀원들과 함께 더 나은 구현 방식을 찾아가고 싶은 분들을 진심으로 환영합니다!
정식 지원 전 가벼운 커피챗(캐주얼 면담)도 가능하니, 조금이라도 관심이 생기셨다면 부담 없이 지원해 주세요.
🔗채용사이트는여기