Container를 사용하면서 자주 헷갈리거나 다시 찾아보게 되는 핵심 개념 3가지를 정리한다.
[Inject] 어노테이션VContainer는 MonoBehaviour가 아닌 순수 C# 클래스(POCO)도 Unity의 생명주기(Awake, Start, Update 등)에 맞춰 동작할 수 있도록 다양한 인터페이스를 제공한다. EntryPoint나 특정 인터페이스를 구현한 클래스는 VContainer가 자동으로 적절한 시점에 해당 메소드를 호출해준다.
| 인터페이스 | Unity 대응 | 호출 시점 | 주요 용도 |
|---|---|---|---|
IInitializable | Awake() | 객체 생성 직후, 주입 직후 | 자신의 내부 상태 초기화 (다른 객체 의존 X) |
IStartable | Start() | 모든 객체 생성/주입 완료 후 | 다른 매니저가 필요한 복잡한 초기화, 로직 시작 |
IAsyncStartable | async Start() | IStartable과 동일 시점 | 서버 통신, 파일 로딩 등 비동기 초기화 |
ITickable | Update() | 매 프레임 | 게임 루프, 입력 처리 등 |
IFixedTickable | FixedUpdate() | 고정 시간 간격 | 물리 계산, Rigidbody 조작 |
ILateTickable | LateUpdate() | Update() 끝난 후 | 카메라 추적 등 후처리 로직 |
IDisposable | OnDestroy() | LifetimeScope 파괴 시 | 이벤트 구독 해제, 리소스 해제 등 정리 작업 |
핵심:
EntryPoint클래스에 이 인터페이스들을 구현하면,MonoBehaviour없이도 게임의 핵심 로직과 루프를 완벽하게 구성할 수 있다.
[Inject] 어노테이션[Inject] 어노테이션은 VContainer에게 "이곳에 의존성을 주입해줘!"라고 알려주는 표시다. 위치에 따라 3가지 방식이 있으며, 각각의 장단점이 명확하다.
클래스의 생성자를 통해 의존성을 주입받는 방식. VContainer에서 가장 권장된다.
public class GameController
{
// readonly로 불변성 보장!
private readonly IAudioManager _audioManager;
private readonly IInputManager _inputManager;
// 생성자를 통해 의존성을 '요구'한다.
// 생성자가 하나뿐이면 [Inject] 어노테이션 생략 가능.
public GameController(IAudioManager audioManager, IInputManager inputManager)
{
_audioManager = audioManager;
_inputManager = inputManager;
}
}
장점:
readonly 키워드로 의존성이 변경될 위험을 원천 차단한다. (가장 안전)new GameController(new MockAudio(), new MockInput()) 처럼 가짜(Mock) 객체를 쉽게 넣어 단위 테스트를 할 수 있다.일반 메소드를 통해 의존성을 주입받는 방식. MonoBehaviour에 주입할 때 사실상의 표준이다.
public class PlayerView : MonoBehaviour
{
private IEffectManager _effectManager;
// MonoBehaviour는 new로 생성하지 않으므로,
// 생성자 대신 이 메소드로 주입받는다.
[Inject]
public void Construct(IEffectManager effectManager)
{
_effectManager = effectManager;
}
void Start()
{
// Start 시점에는 Construct가 이미 호출되었음이 보장된다.
_effectManager.PlaySpawnEffect();
}
}
장점: 씬에 이미 배치된 MonoBehaviour에 의존성을 주입하는 표준적인 방법이다.
주의: 객체 생성 직후 ~ Construct 메소드 호출 전까지 아주 잠깐 필드가 null인 상태가 존재한다. (Awake에서 접근 시 NullReferenceException 위험!)
클래스의 멤버 필드(변수)에 직접 주입하는 방식. 가급적 사용을 피하는 것이 좋다.
public class Enemy : MonoBehaviour
{
[Inject]
private IGameStatus _gameStatus; // 필드에 직접 주입
}
단점:
private 필드에 가짜 객체를 주입하기 매우 번거롭다.결론:
LifetimeScope의 Configure 메소드 안에서 builder.Register...()를 통해 VContainer에게 어떤 객체를 어떻게 관리할지 지시한다.
| 등록 방식 | 설명 | 주요 사용처 |
|---|---|---|
builder.Register<T>(lifetime) | 가장 기본. 순수 C# 클래스를 등록. VContainer가 new T()로 직접 생성. | AudioManager, DataManager 등 모든 서비스/매니저 클래스. |
builder.RegisterComponentInHierarchy<T>() | 씬에 이미 배치된 MonoBehaviour를 찾아서 관리. | 씬에 미리 만들어둔 UIManager, Player 등. |
builder.RegisterInstance(instance) | 이미 생성된 객체를 VContainer에게 넘겨서 관리시킴. | ScriptableObject 설정 파일, 외부 라이브러리 객체. |
builder.RegisterEntryPoint<T>() | 능동적으로 실행되는 시작점을 등록. VContainer가 생성부터 생명주기까지 관리. | GameController, InputProcessor 등 게임 로직을 시작하고 실행하는 클래스. |
builder.RegisterComponentInNewPrefab(prefab) | Prefab을 인스턴스화하면서 주입. | 동적으로 생성되는 적, 아이템, UI 요소 등 Prefab 기반 객체. |
요약:
Register<T>RegisterComponentInHierarchy<T>RegisterEntryPoint<T>이 세 가지 핵심 개념만 확실히 이해하고 있어도 VContainer를 사용하여 대부분의 상황에 유연하게 대처하고, 깨끗하고 테스트하기 쉬운 코드를 작성할 수 있다.