### **Hello World**
VContainer를 애플리케이션에 통합하는 기본적인 방법은 다음과 같습니다:
- **`LifetimeScope` 컴포넌트 생성:** 씬(Scene)에 `LifetimeScope`를 상속받는 컴포넌트를 생성합니다. 이 컴포넌트는 하나의 컨테이너와 하나의 스코프(Scope)를 가집니다.
- **의존성 등록 (Composition Root):** `LifetimeScope`의 서브클래스에서 C# 코드를 사용하여 의존성들을 등록합니다. 이 부분이 **컴포지션 루트(Composition Root)** 역할을 합니다.
- **컨테이너 자동 빌드 및 디스패치:** 씬이 재생될 때, `LifetimeScope`는 자동으로 컨테이너를 빌드하고 이를 VContainer 자체의 `PlayerLoopSystem`에 디스패치합니다.
**참고 (NOTE):**
- 일반적으로 "스코프(Scope)"는 게임 도중에 반복적으로 생성되고 파괴됩니다. `LifetimeScope`는 이러한 특성을 가정하며, 부모-자식 관계를 가질 수 있습니다.
---
**1. 다른 클래스에 의존하는 클래스 작성 (Write a class that depends on other classes)**
"Hello world"를 예시로 들어보겠습니다.
`HelloWorldService.cs`
C#
```
namespace MyGame
{
public class HelloWorldService
{
public void Hello()
{
UnityEngine.Debug.Log("Hello world");
}
}
}
```
이 클래스는 단순히 "Hello world" 메시지를 Unity 콘솔에 출력하는 역할을 합니다.
---
**2. 컴포지션 루트 정의 (Define composition root)**
이제 위에서 작성한 `HelloWorldService` 클래스를 자동으로 연결(auto-wiring)할 수 있도록 설정을 작성해봅시다.
- Project 탭의 폴더 내에서 마우스 오른쪽 버튼을 클릭하여 `Create -> C# Script`를 선택합니다.
- 스크립트 이름을 `GameLifetimeScope.cs`로 지정합니다.
**참고 (NOTE):**
- VContainer는 `*LifetimeScope`로 끝나는 C# 스크립트에 대해 자동으로 템플릿을 생성합니다.
`GameLifetimeScope.cs` 파일에 다음 코드를 추가하여 `HelloWorldService`를 컨테이너에 등록합니다.
C#
```
using VContainer;
using VContainer.Unity; // 이 using 문이 문서에 누락되어 있지만, LifetimeScope 사용을 위해 필요합니다.
namespace MyGame
{
public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.Register<HelloWorldService>(Lifetime.Singleton);
}
}
}
```
**설명:**
- `GameLifetimeScope`는 `LifetimeScope`를 상속받습니다.
- `Configure` 메서드는 컨테이너를 빌드할 때 호출되며, 여기서 의존성을 등록합니다.
- `builder.Register<HelloWorldService>(Lifetime.Singleton);`는 `HelloWorldService` 클래스를 컨테이너에 싱글톤(Singleton)으로 등록합니다. 즉, 이 스코프 내에서 `HelloWorldService`의 인스턴스는 단 하나만 생성되고 재사용됩니다.
**참고 (NOTE):**
- VContainer는 항상 진입점(entry point)을 요구합니다. VContainer의 `PlayerLoop`에 참여하는 클래스를 정의해야 합니다.
---
**3. 엔트리 포인트(Entry Point) 클래스 작성**
`GamePresenter.cs`
C#
```
using VContainer.Unity; // IStartable을 위해 필요
using UnityEngine; // Debug.Log 사용을 위해 필요
namespace MyGame
{
public class GamePresenter : IStartable // 또는 ITickable (문서에 두 가지 옵션이 제시됨)
{
readonly HelloWorldService helloWorldService;
public GamePresenter(HelloWorldService helloWorldService) // 생성자 주입
{
this.helloWorldService = helloWorldService;
}
void IStartable.Start() // IStartable 구현
{
helloWorldService.Hello();
}
// ITickable을 구현하는 경우:
// void ITickable.Tick()
// {
// helloWorldService.Hello(); // 매 프레임마다 호출됨
// }
}
}
```
**설명:**
- `GamePresenter`는 `HelloWorldService`에 의존합니다. 생성자를 통해 `helloWorldService`를 주입받습니다.
- `IStartable` 인터페이스를 구현하여 `Start()` 메서드를 가집니다. VContainer는 `GameLifetimeScope`가 시작될 때 `GamePresenter`의 `Start()` 메서드를 자동으로 호출합니다. 이 메서드에서 `helloWorldService.Hello()`를 호출하여 "Hello world" 메시지를 출력합니다.
---
**4. Scene 설정**
- 새로운 Unity Scene을 생성하고 `Main Camera`와 `Directional Light`를 제거합니다.
- 빈 GameObject를 생성하고 이름을 `GameLifetimeScope`로 지정합니다.
- 이 `GameLifetimeScope` GameObject에 2단계에서 생성한 `GameLifetimeScope.cs` 스크립트를 컴포넌트로 추가합니다.
**5. 빌드 및 실행 (Build and Run)**
- Unity 에디터에서 Play 버튼을 누르면, 콘솔에 "Hello world" 메시지가 출력되는 것을 확인할 수 있습니다.
---
**6. View Component 분리 및 상호작용 (Separating View Component)**
DI의 이점을 강조하기 위해 UI(`HelloScreen`)와 제어 로직(`GamePresenter`)을 분리하는 예시를 추가합니다.
- `HelloScreen.cs` (UI View 컴포넌트)
C#
```
using UnityEngine;
using UnityEngine.UI;
namespace MyGame
{
public class HelloScreen : MonoBehaviour
{
public Button HelloButton; // 인스펙터에서 버튼을 할당할 수 있도록 public 필드로 선언
}
}
```
- 이 클래스는 단순히 UI 요소(`HelloButton`)를 참조하는 `MonoBehaviour`입니다. 직접적인 로직은 포함하지 않습니다.
- `GamePresenter.cs` (제어 로직)
C#
```
using VContainer.Unity;
using UnityEngine; // Debug.Log 사용을 위해 추가
using UnityEngine.UI; // Button 사용을 위해 추가
namespace MyGame
{
public class GamePresenter : IStartable
{
readonly HelloWorldService helloWorldService;
readonly HelloScreen helloScreen; // HelloScreen 의존성 추가
public GamePresenter(
HelloWorldService helloWorldService,
HelloScreen helloScreen) // 생성자 주입
{
this.helloWorldService = helloWorldService;
this.helloScreen = helloScreen;
}
void IStartable.Start()
{
// HelloButton이 클릭될 때 HelloWorldService의 Hello 메서드 호출
helloScreen.HelloButton.onClick.AddListener(() =>
helloWorldService.Hello());
}
}
}
```
- `GamePresenter`는 이제 `HelloScreen`에도 의존합니다.
- `Start()` 메서드에서 `HelloScreen`의 `HelloButton`에 `onClick` 리스너를 추가하여 버튼 클릭 시 `HelloWorldService.Hello()`가 호출되도록 합니다.
- `GameLifetimeScope.cs` 업데이트
C#
```
using VContainer;
using VContainer.Unity;
using UnityEngine; // SerializeField를 위해 필요
namespace MyGame
{
public class GameLifetimeScope : LifetimeScope
{
[SerializeField] // Unity 에디터에서 HelloScreen을 할당할 수 있도록 함
HelloScreen helloScreen;
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterEntryPoint<GamePresenter>();
builder.Register<HelloWorldService>(Lifetime.Singleton);
builder.RegisterComponent(helloScreen); // HelloScreen 인스턴스 등록
}
}
}
```
- `HelloScreen` 인스턴스를 `GameLifetimeScope`의 `SerializeField`로 노출하고, `builder.RegisterComponent(helloScreen);`를 사용하여 컨테이너에 등록합니다. `RegisterComponent`는 `MonoBehaviour` 인스턴스를 등록하는 데 사용됩니다.
**이렇게 하면 다음의 역할 분리가 성공적으로 이루어집니다:**
- **`GamePresenter`:** 오직 제어 흐름(Control Flow)만 책임집니다.
- **`HelloWorldService`:** 언제 어디서든 호출될 수 있는 기능(functionality)만 책임집니다.
- **`HelloScreen`:** 오직 뷰(View)만 책임집니다.
**Scene에서의 설정:**
1. 새로운 UI `Button`을 씬에 추가합니다.
2. 빈 GameObject를 생성하고 이름을 `HelloScreen`으로 지정합니다.
3. `HelloScreen` GameObject에 `HelloScreen.cs` 스크립트를 컴포넌트로 추가합니다.
4. `HelloScreen` 컴포넌트의 인스펙터에서 `HelloButton` 필드에 씬에 추가한 `Button` UI를 드래그하여 할당합니다.
5. `GameLifetimeScope` GameObject를 선택하고 `GameLifetimeScope` 컴포넌트의 인스펙터에서 `Hello Screen` 필드에 씬의 `HelloScreen` GameObject를 드래그하여 할당합니다.
이제 Unity 에디터에서 Play 버튼을 누르고 UI 버튼을 클릭하면 "Hello world" 메시지가 콘솔에 출력될 것입니다. 이는 DI를 통해 뷰와 로직이 효과적으로 분리되었음을 보여주는 예시입니다.
---
이전 : [[2.1 Installation]] | 다음 : [[3.1 Constructor Injection]]