핵심 정리
// 초기화 단계 (오브젝트 활성화 시 1회)
private void Awake() { _rb = GetComponent<Rigidbody>(); } // 로컬 컴포넌트 캐싱
private void OnEnable() { GameEvents.OnPause += HandlePause; } // 이벤트 등록
private void Start() { _manager = GameManager.Instance; } // 외부 참조 연결
// 프레임 루프 (매 프레임 반복)
private void Update() { ReadInput(); } // 입력, 일반 로직
private void FixedUpdate() { ApplyMovement(); } // 물리 이동, 힘 적용
private void LateUpdate() { TrackCamera(); } // 카메라 추적, 후처리 보정
// 비활성화/파괴
private void OnDisable() { GameEvents.OnPause -= HandlePause; } // 이벤트 해제
private void OnDestroy() { /* 최종 리소스 해제 */ }구조 이해
Awake — 로컬 초기화
Awake는 스크립트 인스턴스가 로드될 때 호출됩니다. 씬 시작 시 비활성 GameObject에 붙은 스크립트라면, 활성화되기 전까지 Awake가 늦어질 수 있습니다. 또한 다른 오브젝트의 준비 순서를 일반 코드만으로 고정하기 어렵기 때문에, Awake에는 다른 오브젝트에 의존하지 않는 작업을 넣는 편이 안전합니다.
GetComponent<T>()캐싱- 기본 상태 변수 초기화
- 불변 참조 확보
OnEnable / OnDisable — 활성화 수명주기
OnEnable과 OnDisable은 오브젝트가 껐다 켜질 때마다 반복 호출됩니다. Awake/Start보다 훨씬 많은 수명 주기를 다루기 때문에, "한 번만 해야 하는 일"이 아니라 "활성화 상태와 함께 유지해야 하는 일"을 여기에 둡니다.
- 이벤트 구독(
OnEnable) / 해제(OnDisable) - Input System Action Map 활성화 / 비활성화
- 코루틴 재시작 / 중단
OnDisable에서 해제를 빠뜨리면 비활성화된 오브젝트가 이벤트를 계속 받아 NullReferenceException이나 메모리 누수로 이어집니다.
Start — 외부 연결
Start는 첫 프레임 직전에, 그리고 스크립트가 활성 상태일 때 한 번 호출됩니다. 씬 시작 시점에 이미 배치된 오브젝트라면 모든 Awake가 끝난 뒤 호출되므로 다른 오브젝트 준비를 기대하기가 상대적으로 쉽습니다. 다만 런타임에 새로 인스턴스화한 오브젝트까지 같은 전역 순서를 보장하는 것은 아닙니다.
Update / FixedUpdate / LateUpdate — 프레임 루프
세 메서드는 서로 다른 타이밍 축에서 실행됩니다.
Update— 화면 프레임 기준. 입력 읽기, 상태 갱신, 타이머 누적FixedUpdate— 고정 물리 타임스텝 기준. Rigidbody 이동, 힘 적용. 한 프레임에 여러 번 또는 0번 호출될 수 있습니다.LateUpdate— 모든Update가 끝난 뒤. 카메라 추적, 최종 위치 보정
FixedUpdate가 "프레임마다 정확히 한 번"이라는 착각이 흔한 함정입니다. 프레임이 느리면 물리 스텝을 따라잡기 위해 여러 번 돌고, 프레임이 매우 빠르면 한 프레임에서 건너뛰기도 합니다.
OnDestroy — 최종 정리
오브젝트가 파괴되기 직전에 호출됩니다. 단, 비활성화만 되는 경우엔 호출되지 않습니다. 실무에서는 많은 해제 작업이 OnDestroy보다 OnDisable에 있어야 더 안전한 이유입니다.
체크포인트
| 메서드 | 호출 시점 | 추천 용도 |
|---|---|---|
Awake | 활성화 시 최초 1회 | 로컬 컴포넌트 캐싱, 내부 상태 초기화 |
OnEnable | 활성화될 때마다 | 이벤트 구독, 코루틴 시작 |
Start | 첫 프레임 직전 1회 | 외부 시스템 참조, 초기 데이터 주입 |
Update | 매 프레임 | 입력 처리, 일반 프레임 로직, 타이머 |
FixedUpdate | 고정 물리 스텝 | Rigidbody 이동, 힘 적용 |
LateUpdate | 모든 Update 이후 | 카메라 추적, 후처리 위치 보정 |
OnDisable | 비활성화될 때마다 | 이벤트 해제, 코루틴 중단 |
OnDestroy | 파괴 직전 | 최종 리소스 해제 |
| 상황 | 적합한 위치 |
|---|---|
GetComponent 캐싱 | Awake |
| 다른 매니저 참조 | Start |
| 이벤트 구독/해제 | OnEnable / OnDisable |
| 입력 읽기 | Update |
| Rigidbody 이동 | FixedUpdate |
| 카메라 추적 | LateUpdate |
| 비활성화 시 정리 | OnDisable |
| 파괴 직전 최종 정리 | OnDestroy |
| 자주 헷갈리는 쌍 | 더 잘 맞는 선택 | 이유 |
|---|---|---|
Awake vs Start | 로컬 준비는 Awake, 외부 연결은 Start | 다른 오브젝트 준비 시점이 다름 |
OnEnable vs Start | 재활성화마다 반복되면 OnEnable | Start는 한 번만 호출됨 |
Update vs LateUpdate | 입력/일반 로직은 Update, 카메라 보정은 LateUpdate | 추적 대상 이동이 끝난 뒤 보정해야 함 |
OnDisable vs OnDestroy | 대부분 해제는 OnDisable | 비활성화만 되어도 정리돼야 하는 경우가 많음 |
주의할 점
Awake와 Start, OnEnable과 OnDestroy를 "비슷한 초기화/정리 지점"으로 묶어서 생각하면 라이프사이클 버그가 반복됩니다. 로직을 배치하기 전에 세 가지를 먼저 물어보세요.
- 한 번만 해야 하는가 (→
Awake/Start) - 활성화될 때마다 다시 해야 하는가 (→
OnEnable/OnDisable) - 다른 오브젝트 준비를 기다려야 하는가 (→
Start이후)
싱글톤이나 매니저 참조를 Awake에서 바로 믿고 잡으면, 초기화 순서가 바뀌는 순간 간헐 버그가 생길 수 있습니다. 준비 순서를 보장할 수 없다면 Start 이후나 명시적 초기화 단계로 미루세요.
// ❌ 다른 시스템 준비를 가정하고 Awake에서 접근
private void Awake()
{
audioManager = AudioManager.Instance;
audioManager.Register(this);
}
// ✅ 외부 시스템 연결은 Start 또는 Bootstrap 이후
private void Start()
{
audioManager = AudioManager.Instance;
audioManager.Register(this);
}참고 링크
4 sources