5. 핵심 개념
모든 CGS 서비스 뒤에 있는 다섯 가지 아이디어 — 부트스트랩, 서비스 로케이터, 클럭, 이벤트, 널 구현 — 와 용어집.
다섯 가지 아이디어가 프레임워크 전체를 설명합니다. 이것만 알면 23개 서비스 하나하나가 모두 같은 방식으로 동작합니다. 이 장은 각 아이디어를 쉬운 말로 다루며, 프로젝트에 붙여 넣을 수 있는 짧은 코드를 함께 제공합니다. 마지막의 용어집은 이 매뉴얼에 쓰인 모든 기술 용어를 정의합니다.
5.1 부트스트랩: 모든 것이 스스로 시작된다
Common Game System에는 씬에 끌어다 놓을 프리팹도, 매니저 오브젝트도, 초기화 호출도 없습니다. Play를 누르면 Unity가 첫 씬이 로드되기 전에 프레임워크의 시작 코드를 자동으로 실행합니다. 이 시작 단계를 부트스트랩이라고 부릅니다. 부트스트랩은 23개 서비스를 올바른 순서로 생성하고, 코드가 찾을 수 있도록 하나하나 등록합니다.
Play를 누른 뒤 Console을 보면 동작을 확인할 수 있습니다. 마지막 시작 줄은 다음과 같습니다:
bootstrap complete (v2.1.0, 23 services)
부트스트랩을 직접 호출할 일은 없습니다. Play 세션마다 정확히 한 번 실행되며, 두 번째 호출은 의도적으로 오류를 던집니다. 씬의 어떤 Awake()가 실행될 때쯤에는 모든 서비스가 이미 살아서 기다리고 있습니다. 전체 레퍼런스: Bootstrap.
5.2 서비스 로케이터: 한 번 묻고, 답을 간직하라
서비스는 하나의 자체 완결적 기능입니다 — 저장, 오디오, 타이머, 입력 — 그리고 단일 C# 인터페이스를 통해 접근합니다. 서비스 로케이터는 그 모두를 담고 있는 전화번호부입니다. 인터페이스로 서비스를 요청하면 실행 중인 인스턴스를 건네줍니다:
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)] // run after the framework has started
public class GameSetup : MonoBehaviour
{
private ITimeService _time;
private ISaveService _save;
private void Awake()
{
// Ask once, keep the answer.
_time = ServiceLocator.Resolve<ITimeService>();
_save = ServiceLocator.Resolve<ISaveService>();
}
}
두 가지 습관이 이것을 빠르고 안전하게 유지합니다:
Awake()나Start()에서 한 번만 해석하고, 결과를 필드에 저장하세요. 조회 한 번은 딕셔너리 검색입니다. 매 프레임 호출하는 것은 아무 이득 없이 시간만 낭비합니다.Awake()에서 서비스를 해석하는 클래스에는[DefaultExecutionOrder(100)]을 붙이세요. Unity가 프레임워크 자체 오브젝트들 다음에 여러분의 스크립트를 실행하게 하여, 모든 서비스가 준비된 상태를 보장합니다.
서비스가 없을 수도 있다면 — 예를 들어 선택적 Unity 패키지를 제거한 경우 — 대신 ServiceLocator.TryResolve<T>(out var service)를 쓰세요. 예외를 던지는 대신 false를 반환합니다. 전체 레퍼런스: Service Locator.

Service Debugger 창(Tools > Common Game System > Service Debugger)은 플레이 중 등록된 모든 서비스를 나열합니다. 기대하는 서비스가 여기에 없다면 해당 선택적 Unity 패키지가 설치되지 않았을 가능성이 큽니다 — 7.2장을 보세요. 표시되는 개수는 23보다 클 수 있습니다 — 프레임워크가 23개의 공개 서비스와 함께 몇 개의 내부 헬퍼를 등록하기 때문입니다.
5.3 클럭: 세 가지 속도의 시간
클럭은 독립적인 시간의 흐름입니다. 프레임워크는 세 개를 운영합니다: Clock.Gameplay, Clock.UI, Clock.Background. 각각은 나머지 둘을 건드리지 않고 일시정지하거나 느려질 수 있습니다. 이것은 고전적인 버그를 해결합니다: 게임을 일시정지했더니 갑자기 메뉴 애니메이션이 얼어붙고 음악까지 멈추는 문제 말입니다.
- Gameplay — 여러분의 월드: 캐릭터, 물리 반응, 쿨다운. 일시정지 메뉴는 이 클럭을 멈춥니다.
- UI — 메뉴와 오버레이. 게임플레이가 일시정지된 동안에도 계속 돌아서 버튼이 계속 움직입니다.
- Background — 음악, 그리고 결코 멈춰서는 안 되는 모든 것.
일시정지 메뉴는 두 줄이 됩니다:
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class PauseMenu : MonoBehaviour
{
private ITimeService _time;
private void Awake() => _time = ServiceLocator.Resolve<ITimeService>();
public void Open() => _time.Pause(Clock.Gameplay); // world freezes
public void Close() => _time.Resume(Clock.Gameplay); // world resumes
}
Unity의 Time.deltaTime 대신 _time.DeltaTime(Clock.Gameplay)로 클럭별 시간을 읽으세요. 슬로 모션은 호출 하나입니다: _time.SetTimeScale(Clock.Gameplay, 0.3f) — UI와 음악은 정상 속도를 유지합니다. 스케줄러의 타이머와 트윈 서비스의 애니메이션은 각각 클럭에 바인딩되므로 추가 코드 없이 올바르게 일시정지되고 느려집니다. 규칙 하나: Unity의 전역 Time.timeScale을 직접 설정하지 마세요. 클럭별 시스템과 충돌하게 됩니다. 전체 레퍼런스: Time.
5.4 이벤트: 구독하고, 핸들을 간직하고, dispose하라
이벤트 버스는 시스템들이 서로를 모른 채 대화하게 해줍니다. 한 스크립트가 이벤트 객체를 발행하면, 그 이벤트 타입의 모든 구독자가 구독한 순서대로 즉시 호출됩니다. 구독하면 작은 핸들이 반환됩니다. 그 핸들을 dispose하면 구독이 해제됩니다. dispose를 잊으면 핸들러가 영원히 등록된 채 남습니다 — 그것이 메모리 누수입니다.
using System;
using CommonGameSystem.Core;
using UnityEngine;
// Events are plain classes you define yourself.
public class ScoreChanged
{
public int NewScore;
}
[DefaultExecutionOrder(100)]
public class ScoreLabel : MonoBehaviour
{
private IEventBus _events;
private IDisposable _subscription;
private void Awake() => _events = ServiceLocator.Resolve<IEventBus>();
private void OnEnable() => _subscription = _events.Subscribe<ScoreChanged>(OnScore);
private void OnDisable() => _subscription?.Dispose(); // always unsubscribe
private void OnScore(ScoreChanged e) => Debug.Log($"Score: {e.NewScore}");
}
발행은 어디서든 한 줄입니다: _events.Publish(new ScoreChanged { NewScore = 100 });. 알아 둘 세부 사항 두 가지: 이벤트 타입은 클래스여야 하며(구조체 불가), 예외를 던지는 구독자가 다른 구독자를 막는 일은 결코 없습니다 — 오류는 로그로 남고 전달은 계속됩니다. 전체 레퍼런스: Event Bus.
5.5 널 구현: 모든 서비스에는 끄기 스위치가 있다
모든 서비스는 짝이 되는 널(Null) 구현과 함께 배포됩니다 — 모든 호출을 받아들이고 아무것도 하지 않는 버전입니다. 이것을 등록하면 호출부를 하나도 바꾸지 않고 해당 서브시스템이 꺼집니다. 모든 곳의 오디오를 전부 음소거하는 것은 한 줄입니다:
ServiceLocator.Replace<IAudioService>(new NullAudio());
프로젝트의 모든 PlayMusic과 PlaySfx 호출은 여전히 컴파일되고 실행됩니다 — 그저 아무 일도 하지 않을 뿐입니다. 테스트, 헤드리스 서버, 음성 녹음 세션, 또는 서브시스템을 자체 솔루션으로 교체할 때 유용합니다. 7.4장에 23개 서비스 전체의 끄기 스위치 목록과, 특정 서비스를 세션 중에 교체할 때 추가해야 하는 정리용 한 줄이 있습니다.
5.6 용어집
| 용어 | 의미 |
|---|---|
| 서비스 | 단일 C# 인터페이스를 통해 사용하는, 자체 완결적인 프레임워크 기능 하나(저장, 오디오, 타이머). |
| 해석(Resolve) | 서비스 로케이터에 인터페이스로 서비스를 요청하는 것. 한 번만 하고 참조를 필드에 간직하세요. |
| 부트스트랩 | 첫 씬이 로드되기 전에 23개 서비스를 모두 생성하고 등록하는 자동 시작 단계. 직접 호출할 일은 없습니다. |
| 클럭 | 세 가지 독립적 시간 흐름(Gameplay, UI, Background) 중 하나. 각각 따로 일시정지하거나 느려질 수 있습니다. |
| 트윈 | 정해진 시간 동안 값을 시작점에서 끝점까지 부드럽게 움직이는 짧은 애니메이션. 이징 커브로 모양이 정해집니다. |
| 액션 맵 | Unity Input System 애셋 안의 이름 있는 입력 액션 그룹("Gameplay", "Menu" 등). 활성화된 맵만 플레이어에게 반응합니다. |
| Addressables | 직접 참조 대신 텍스트 주소로 애셋을 필요할 때 로드하는 Unity의 선택적 패키지. |
| IL2CPP | C#을 네이티브 코드로 변환하는 Unity의 빌드 모드. 사용되지 않아 보이는 클래스를 제거하므로, 세이브 클래스에 link.xml 항목이 필요한 이유입니다(6.3장). |
다음: 6. 그림으로 보는 설정 단계