1. MonoBehaviour 생명주기(Lifecycle)란?
씬(Scene)에 배치된 활성화된 게임 오브젝트에 부착된 모든 MonoBehaviour
스크립트 인스턴스는 Unity 엔진에 의해 관리되는 일련의 단계를 거칩니다. 이를 '생명주기'라고 할 수 있으며, 크게 다음과 같은 단계들로 구성됩니다.
- 초기화(Initialization): 스크립트 인스턴스가 로드되고 처음 활성화될 때 필요한 설정을 수행하는 단계.
- 물리 업데이트(Physics Update): 물리 엔진 계산 및 관련 로직이 실행되는 단계.
- 게임 로닉 업데이트(Game Logic Update): 프레임 기반의 주요 게임 로직, 입력 처리 등이 실행되는 단계.
- 렌더링(Rendering): 씬을 화면에 그리는 단계 (스크립트에서 직접 제어하기보다는 결과에 영향을 주는 단계).
- 해체(Decommissioning): 스크립트 인스턴스나 게임 오브젝트가 비활성화되거나 파괴될 때 정리 작업을 수행하는 단계.
Unity는 이러한 각 단계와 관련된 특정 시점에 호출될 수 있는 '매직 메서드(Magic Methods)'들을 제공합니다. 개발자는 MonoBehaviour
를 상속받는 클래스 내에 이 메서드들을 선언하고 코드를 작성함으로써, 원하는 시점에 원하는 로직이 실행되도록 할 수 있습니다.
2. 주요 생명주기 메서드 실행 순서 및 역할
가장 중요하고 자주 사용되는 MonoBehaviour
생명주기 메서드들의 일반적인 실행 순서는 다음과 같습니다. (주의: 이는 단일 스크립트 인스턴스 내에서의 순서이며, 서로 다른 스크립트 간의 실행 순서는 'Script Execution Order' 설정을 통해 제어할 수 있습니다.)
초기화 단계 (Initialization Phase)
Awake()
: 스크립트 인스턴스가 로드될 때 단 한 번 호출됩니다. (게임 오브젝트나 스크립트가 비활성화 상태여도 호출됨). 다른 모든 게임 오브젝트의 초기화나Start()
메서드 호출 전에 실행되는 것이 보장됩니다. 주로 자기 자신의 컴포넌트 참조 설정이나 내부 상태 초기화에 사용됩니다.OnEnable()
: 게임 오브젝트와 스크립트가 활성화될 때마다 호출됩니다.Awake()
다음에 호출되며, 게임 도중 비활성화되었다가 다시 활성화될 때도 호출됩니다. 이벤트 리스너 등록이나 활성화 상태에 따른 초기 설정에 적합합니다.Start()
: 스크립트 인스턴스가 활성화된 상태에서 첫 번째 프레임 업데이트 직전에 단 한 번 호출됩니다. 모든Awake()
호출이 완료된 후에 호출되는 것이 보장됩니다. 다른 스크립트와의 상호작용이 필요한 초기화나, 활성화된 상태에서 시작할 때 필요한 로직에 사용됩니다.
물리 업데이트 단계 (Physics Phase)
FixedUpdate()
: 고정된 시간 간격(Time.fixedDeltaTime, 기본값 0.02초)으로 호출됩니다. 프레임 속도와는 독립적으로 실행됩니다. 모든 물리(Physics) 관련 계산(Rigidbody에 힘 가하기, 충돌 처리 기반 로직 등)은 이 메서드 내에서 수행해야 일관된 결과를 얻을 수 있습니다. 한 프레임 동안 여러 번 호출되거나 한 번도 호출되지 않을 수 있습니다.
게임 로직 업데이트 단계 (Game Logic Phase)
Update()
: 매 프레임마다 한 번 호출됩니다. 호출 간격은 프레임 속도에 따라 달라집니다 (가변적). 대부분의 게임 로직(비-물리 이동, 입력 처리, 타이머, AI 로직 등)을 처리하는 데 사용됩니다.Time.deltaTime
을 사용하여 프레임 속도 변화에 따른 영향을 보정해야 합니다.LateUpdate()
: 모든Update()
함수가 호출된 후, 매 프레임마다 한 번 호출됩니다. 주로 카메라 이동 로직(캐릭터가Update
에서 이동한 후 따라가도록), 캐릭터의 최종 위치/상태에 기반한 IK(Inverse Kinematics) 조정 등 모든 업데이트가 완료된 후에 실행되어야 하는 로직에 사용됩니다.
렌더링 단계 (Rendering Phase)
- (이 단계의 메서드는 게임 로직보다는 렌더링 제어에 관련됩니다)
OnGUI()
: 레거시 GUI(IMGUI) 시스템을 사용하여 UI를 그릴 때 호출됩니다 (현재는 UGUI나 UI Toolkit 사용이 권장됨). 매 프레임 여러 번 호출될 수 있습니다.- 기타 렌더링 콜백 (
OnRenderObject
,OnWillRenderObject
등): 고급 렌더링 제어를 위해 사용됩니다.
비활성화 및 파괴 단계 (Deactivation and Destruction Phase)
OnDisable()
: 게임 오브젝트나 스크립트가 비활성화될 때마다 호출됩니다.OnDestroy()
전에 호출되며, 게임 도중 비활성화될 때도 호출됩니다.OnEnable
에서 등록했던 이벤트 리스너 해제 등 활성화 상태와 관련된 정리 작업에 사용됩니다.OnEnable()
의 반대 역할을 합니다.OnDestroy()
: 게임 오브젝트나 컴포넌트가 파괴되기 직전에 단 한 번 호출됩니다. 애플리케이션 종료 시에도 호출될 수 있습니다. 모든 최종적인 정리 작업(외부 리소스 해제, 구독 완전 해지, 다른 시스템에 파괴 알림 등)을 수행하는 곳입니다.
3. 각 메서드의 상세 설명 및 활용
Awake()
:- 언제? 스크립트 인스턴스 로드 시 1회.
Start()
보다 먼저, 활성화 여부 상관없이. - 무엇을? 자기 자신의 상태 초기화, 같은 게임 오브젝트 내 다른 컴포넌트 참조 설정 (
GetComponent
등). 다른 스크립트와의 상호작용은 피하는 것이 좋음 (아직Awake
가 실행되지 않았을 수 있음).
- 언제? 스크립트 인스턴스 로드 시 1회.
OnEnable()
:- 언제? 객체/스크립트 활성화 시마다.
Awake()
다음,Start()
이전 (첫 활성화 시). - 무엇을? 이벤트 구독(
+=
), 필요한 리소스 할당, 활성화 시 필요한 초기 상태 설정.
- 언제? 객체/스크립트 활성화 시마다.
Start()
:- 언제? 활성화된 상태에서 첫 프레임 업데이트 직전 1회. 모든
Awake()
완료 후. - 무엇을? 다른 스크립트의 초기화가 완료된 후 수행해야 하는 초기화 로직, 다른 객체 참조 설정, 초기 게임 로직 시작.
- 언제? 활성화된 상태에서 첫 프레임 업데이트 직전 1회. 모든
FixedUpdate()
:- 언제? 고정된 시간 간격 (물리 업데이트 주기). 프레임당 0회 또는 여러 번.
- 무엇을?
Rigidbody
를 사용한 물리 기반 이동, 힘/토크 적용, 물리 관련 계산. 반드시Time.fixedDeltaTime
사용.
Update()
:- 언제? 매 프레임 1회. 프레임 속도에 따라 간격 변동.
- 무엇을? 비-물리 기반 로직 (대부분의 게임 로직), 사용자 입력 감지, 타이머 업데이트, 애니메이션 제어(상태 전환 등), 비-물리 이동. 반드시
Time.deltaTime
사용.
LateUpdate()
:- 언제? 매 프레임 1회. 모든
Update()
완료 후. - 무엇을? 카메라 추적 로직, 절차적 애니메이션(IK 등) 조정, 모든 상태 업데이트가 끝난 후 필요한 최종 정리/계산.
- 언제? 매 프레임 1회. 모든
OnDisable()
:- 언제? 객체/스크립트 비활성화 시마다.
OnDestroy()
이전. - 무엇을? 이벤트 구독 취소(
-=
), 리소스 임시 해제, 비활성화 시 필요한 상태 정리.OnEnable
에서 한 작업의 반대 작업 수행.
- 언제? 객체/스크립트 비활성화 시마다.
OnDestroy()
:- 언제? 객체/컴포넌트 파괴 직전 1회.
- 무엇을? 모든 리소스 최종 해제, 영구적인 구독 해지, 외부 시스템에 파괴 알림.
4. 생명주기 메서드 사용 시 주의사항
- 성능:
Update
,FixedUpdate
,LateUpdate
는 매우 자주 호출되므로, 이 메서드들 안에서는 무거운 계산, 불필요한 메모리 할당(가비지 생성), 비효율적인 API 호출(특히Find
계열)을 피해야 합니다. 성능 문제가 발생하면 프로파일러를 통해 이 메서드들을 먼저 확인해야 합니다. - 스크립트 간 실행 순서: 기본적으로 서로 다른 스크립트의 동일한 생명주기 메서드(예: 여러 개의
Update
메서드) 호출 순서는 보장되지 않습니다. 특정 스크립트가 다른 스크립트보다 먼저 실행되어야 하는 의존성이 있다면, Edit > Project Settings > Script Execution Order 메뉴에서 실행 순서를 명시적으로 지정해야 합니다. Awake
vs.Start
: 두 메서드 모두 초기화에 사용되지만 호출 시점과 보장되는 조건이 다릅니다. 일반적으로 자신의 초기화는Awake
에서, 다른 객체와의 상호작용이 필요한 초기화는Start
에서 수행하는 것이 안전합니다.Update
vs.FixedUpdate
: 물리 관련 로직은 반드시FixedUpdate
에서, 그 외 대부분의 로직은Update
에서 처리해야 합니다. 혼용 시 예측 불가능한 동작(예: 떨림 현상, 물리 계산 오류)이 발생할 수 있습니다.- 구독 해지:
OnEnable
에서 이벤트를 구독했다면, 반드시OnDisable
에서 해당 이벤트를 구독 취소해야 메모리 누수와 오류를 방지할 수 있습니다. 이는 매우 중요하고 자주 실수하는 부분입니다. - 코루틴:
StartCoroutine
은Awake
,Start
,Update
등 대부분의 생명주기 메서드 내에서 호출될 수 있습니다. 코루틴은 자체적인 실행 흐름을 가지지만, 시작점은 생명주기와 연관됩니다.
5. 실행 순서 요약
주요 생명주기 메서드의 기본적인 실행 흐름은 다음과 같습니다.
- (최초 로드 시)
Awake()
- (최초 활성화 시)
OnEnable()
- (활성화 상태에서 첫 프레임 전)
Start()
- --- 게임 루프 시작 ---
- (물리 스텝)
FixedUpdate()
(0회 이상 호출될 수 있음) - (게임 로직)
Update()
(1회 호출) - (후처리 로직)
LateUpdate()
(1회 호출) - (렌더링)
- (물리 스텝)
- --- 게임 루프 반복 ---
- (비활성화 시)
OnDisable()
- (활성화 시)
OnEnable()
(다시 활성화될 경우) - (파괴 시)
OnDestroy()
(이 외에도 많은 이벤트 함수들이 존재하며, 자세한 순서는 Unity 공식 문서를 참고하는 것이 가장 정확합니다.)
결론
Unity의 MonoBehaviour
생명주기 메서드들은 개발자가 Unity 엔진의 실행 흐름에 개입하여 커스텀 로직을 삽입할 수 있도록 설계된 강력하고 필수적인 기능입니다. Awake
, Start
를 통한 체계적인 초기화, FixedUpdate
를 이용한 안정적인 물리 처리, Update
와 LateUpdate
를 통한 유연한 게임 로직 및 카메라 제어, 그리고 OnDisable
, OnDestroy
를 통한 깔끔한 정리 작업은 잘 구조화되고 성능 좋은 게임을 만드는 데 핵심적인 역할을 합니다. 각 메서드의 정확한 호출 시점과 목적을 이해하고, 성능 및 스크립트 간 실행 순서와 같은 주의사항을 인지하며 상황에 맞게 올바르게 활용하는 것이 Unity C# 스크립팅의 기본이자 가장 중요한 부분 중 하나입니다. 이 생명주기에 대한 깊이 있는 이해는 Unity 개발의 효율성과 코드 품질을 크게 향상시킬 것입니다.
참고 자료
- Unity 매뉴얼 - 이벤트 함수의 실행 순서 (Order of execution for event functions) (가장 중요하고 상세한 자료)
- Unity 스크립팅 레퍼런스 - MonoBehaviour (각 생명주기 메서드 설명 링크 포함)