💬 Unity 공식 문서의 내용을 바탕으로 공부하며 정리한 글입니다.
📁 Unity 6.0 / Scripting / Object-oriented development / Handling events / Order of execution for event functions
📌 이벤트 함수의 실행 순서
이벤트 함수(Event functions)란, MonoBehaviour 클래스를 상속한 스크립트에서 Unity가 자동으로 호출해 주는 콜백 함수들을 뜻한다.
MonoBehaviour는 Unity에서 개발을 더 쉽게 만들어 주는 생명주기 함수(Life cycle functions)를 제공한다.
이 MonoBehaviour 이벤트 함수는 Unity 엔진 내부 시스템(예: 물리, 렌더링, 사용자 입력 처리 등)에 반응하여 호출되기도 하고, 스크립트 생명 주기(예: 생성, 활성화, 초기화, 소멸 등)에 따라 실행되기도 한다.
Unity의 MonoBehaviour 이벤트 함수 실행 순서는 게임 오브젝트의 생성부터 파괴까지의 전 과정을 체계적으로 관리하는 데 필수가 되는 개념이다.
이 순서를 이해해야 스크립트 간의 의존성을 효과적으로 처리하고, 예기치 않은 동작을 방지할 수 있다.
Unity는 이벤트 함수들을 미리 정해둔 순서대로 호출하기 때문에, 스크립트 생명 주기 실행 순서도로 깔끔하게 흐름을 이해할 수 있다.
(반면, 사용자 입력에 응답하는 이벤트 함수와 같이 게임 실행 중 언제든지 발생할 수 있는 이벤트 함수도 존재한다.)
📌 스크립트 생명 주기 실행 순서도
💡 아래 실행 순서도에 모든 콜백이 담겨있지는 않다. 범위가 MonoBehaviour 스크립트에서 구현할 수 있는 내장 이벤트 함수들에 한정되어 있다. 이 함수들은 MonoBehaviour 문서의 "Messages" 섹션에 정리된 콜백 함수들이며, 일부는 해당 이벤트를 발생시키는 Unity 내부 시스템 함수로, 맥락을 위해 포함되어 있다.
이러한 기본 제공 이벤트, 즉 MonoBehaviour 이벤트 외에도 다른 클래스들(Application, SceneManager, Camera 등)에서 제공하는 델리게이트(delegate)를 통해 사용자 정의 콜백을 등록할 수도 있다.
`RuntimeInitializeOnLoadMethod` 같은 어트리뷰트(Attribute)를 사용하여 특정 시점에 메서드를 자동 실행시킬 수도 있다.
📌 기본 원칙
일반적으로, 동일한 이벤트 함수가 서로 다른 GameObject들에서 어떤 순서로 실행되는지 보장되지 않는다.
👁🗨 예시
- GameObject A와 B에 똑같이 `Update()` 함수가 있다면?
- A가 먼저 호출될 수도 있고, B가 먼저 호출될 수도 있다.
- 부모-자식 관계여도 순서를 보장하지 않는다.
다른 스크립트 간의 이벤트 함수 실행 순서는 설정이 가능하다.
- `Project Setting > Script Execution Order(스크립트 실행 순서)` 창에서 설정할 수 있다.
- 예를 들어, `EngineBehaviour`와 `SteeringBehaviour`라는 스크립트가 있을 때 `EngineBehaviour` → `SteeringBehaviour` 순서로 항상 `Update()` 되게 설정할 수 있다.
- 이처럼 서로 다른 클래스 간에는 실행 순서를 명시적으로 지정할 수 있다.
여러 씬(Scene)을 Additive 방식으로 로드할 때는 씬 단위로 순차 적용된다.
👁🗨 예시
- 현재 로드된 씬 위에 씬을 로드하는 `LoadSceneMode.Additive` 방식을 이용해 불러오는 경우라면?
- Script Execution Order는 씬 단위로 적용된다.
- (씬 A → 씬 B 로드된 상황일 때) 씬 A의 모든 `EngineBehaviour`와 `SteeringBehaviour`가 순서대로 완전히 실행된 다음에, 씬 B의 `EngineBehaviour`와 `SteeringBehaviour`가 완전히 실행된다.
- ❌ 씬 A의 `Engine` → 씬 B의 `Engine` → 씬 A의 `Steering` → 씬 B의 `Steering` 순서가 아니다!
📌 씬 로드 시
씬이 시작될 때 호출되는 이벤트 함수들이다.
씬에서 오브젝트마다 한 번 호출된다.
- `Awake`
- 객체의 새 인스턴스가 생성될 때 제일 먼저 호출되는 함수이다.
- 항상 모든 `Start`함수보다 먼저 호출된다.
- 시작 시, 오브젝트가 비활성 상태인 경우, 활성화될 때까지 호출되지 않는다.
- `OnEnable`
- 오브젝트가 활성화될 때 호출된다. (`Awake`이후, `Start`이전에 호출)
💡 씬에 미리 존재하는 오브젝트에 대해서만 `Awake`와 `OnEnable`이 `Start`보다 먼저 호출된다.
(런타임 중에 생성되는 오브젝트에는 보장 ❌)
🏷 씬 로드 및 언로드
위 실행 순서도에는 없지만, 씬이 로드/언로드 될 때 알림을 받을 수 있는 이벤트인 `SceneManager.sceneLoaded` / `SceneManager.sceneUnloaded`가 있다.
- `sceneLoaded`는 `OnEnable`이후, `Start`이전에 호출된다.
`RuntimeInitializeOnLoadMethodAttribute`의 속성인 `BeforeSceneLoad` 및 `AfterSceneLoad`를 사용하여, 씬 로드 전/후에 실행할 메서드를 지정할 수 있다.
📌 에디터 전용
- `Reset`
- 스크립트가 오브젝트에 처음 연결될 때와 `Reset` 명령을 사용할 때 호출된다.
- 스크립트의 프로퍼티를 초기화하기 위해 호출한다.
- `OnValidate`
- 오브젝트가 역직렬화될 때를 포함하여 스크립트의 프로퍼티가 설정될 때마다 호출된다.
- 역직렬화는 에디터에서 씬을 열거나 도메인을 다시 로드한 후와 같이 다양한 시기에 발행할 수 있다.
📌 첫 번째 프레임 업데이트 전
- `Start`
- 해당 스크립트 인스턴스가 활성 상태일 때, 첫 프레임 업데이트 이전에 한 번 호출된다.
💡 씬에 배치된 모든 오브젝트에 대하여 `Update`실행 전에 `Start`가 호출되는 것을 보장한다.
(런타임 중에 인스턴스화한 오브젝트는 보장 ❌)
👁🗨 예시
- 오브젝트 A의 `Update`에서 B 오브젝트를 인스턴스화한 경우
- `Update`에서 인스턴스화된 오브젝트 B의 `Start`는 인스턴스화 한 즉시 실행되는 것이 아닌, 해당 `Update`이후 실행된다.
📌 프레임 사이
- `OnApplicationPause`
- 일시 정지가 감지된 프레임의 끝, 실질적으로 일반 프레임 업데이트 사이에 호출된다.
- 이 이벤트 함수가 호출된 후에 한 프레임이 추가로 생성되어 게임에서 일시 정지 상태를 나타내는 그래픽을 표시한다.
📌 업데이트 순서 (프레임 중)
게임 로직과 상호작용, 애니메이션, 카메라 위치 등을 추적할 때 사용할 수 있는 이벤트 함수들이다.
- `FixedUpdate`
- 프레임 단위가 아닌 고정된 시간 간격마다 호출되기 때문에, 프레임 속도과 관계없이 독립적으로 수행한다.
- 모든 물리 연산 및 업데이트는 `FixedUpdate` 후 즉시 발생하며, 움직임을 계산할 때 `Time.deltaTime` 값을 곱할 필요가 없다.
- `FixedUpdate` 발생 간격은 `Time.fixedDeltaTime`에 의해 결정되며, 스크립트에서 직접 설정하거나 `Edit > Project Settings > Time` 에서 `Fixed Timestep` 속성을 설정할 수 있다.
- 프레임 속도가 낮은 경우 프레임당 여러 번 호출될 수 있으며, 프레임 속도가 높은 경우 프레임 사이에 호출되지 않을 수 있다.
- `Update`
- 프레임당 한 번 호출된다.
- 주로 로직처리를 하는데 쓰인다.
- 프레임은 일정하지 않기 때문에 `Update`호출도 일정하지 않으며, 컴퓨터 성능의 영향을 받는다.
- 각 프레임의 간격(초)을 나타내는 `Time.deltaTime`를 이용해서 오브젝트의 속도나 크기 등을 일정하게 조절한다.
- `LateUpdate`
- 프레임당 한 번, `Update`가 끝난 후 호출된다.
- `Update`에서 수행된 모든 계산은 `LateUpdate`가 시작할 때 완료된다.
- 주로 3인칭 카메라가 캐릭터를 따라가도록 구현할 때 사용하며, 캐릭터 이동과 회전을 `Update`에서 처리한다면, 카메라의 이동과 회전은 `LateUpdate`에서 처리하면 된다.
- 이렇게 하면 캐릭터가 완전히 이동한 후 카메라가 그 위치를 따라가게 되어 부드러운 추적이 보장된다.
📌 애니메이션 업데이트
- `MonoBehaviour` 에서 파생된 스크립트용 이벤트 함수는 다음과 같다.
- `OnAnimatorMove`
- `OnAnimatorIK`
- `StateMachineBehaviour` 에서 파생된 스크립트용 이벤트 함수는 다음과 같다.
- `OnStateMachineEnter`
- `OnStateMachineExit`
- `OnStateEnter`
- `OnStateUpdate`
- `OnStateExit`
- `OnStateMove`
- `OnStateIK`
위 실행 순서도에 표시된 다른 애니메이션 함수들은 Unity 시스템 내부 전용 함수이며, 일반적으로 사용자가 직접 호출하지 않는다.(문맥을 이해하기 위해 제공된 것이다.)
이 함수들은 프로파일러(Profiler)에서 확인할 수 있는 프로파일 마커(markers)가 있어서, 이걸 이용하여 Unity가 해당 함수들을 프레임 중 언제 호출하는지 확인할 수 있다.
📌 렌더링
💡 이 이벤트 함수들의 실행 순서는 빌트인 렌더 파이프라인(Built-in Render Pipeline)에만 해당된다.
(Scriptable Render Pipeline(SRP) 기반 렌더 파이프라인인 URP/HRP을 사용한다면, 공식 문서에서 해당 파이프라인 전용 실행 순서를 확인해야 한다.)
- `OnPreCull`
- 카메라가 씬을 컬링하기 전에 호출된다.
- 컬링은 어떤 오브젝트를 카메라에 표시할지 결정한다.
- `OnBecameVisible` / `OnBecameInvisible`
- 오브젝트가 카메라에 표시되거나/표시되지 않을 때 호출된다.
- `OnBecameInvisible`은 오브젝트가 언제든지 보이지 않게 될 수 있으므로 위 실행 순서도에 표시되어 있지 않다.
- `OnWillRenderObject`
- 오브젝트가 표시되는 경우 각 카메라에 대해 한 번씩 호출한다.
- `OnPreRender` / `OnPostRender`
- 카메라가 씬 렌더링을 시작하기 전/끝마친 후에 호출된다.
- `OnRenderObject`
- 모든 일반 씬 렌더링 후에 호출된다.
- 이때, GL클래스나 `Graphics.DrawMeshNow`를 사용하여 커스텀 지오메트리를 그릴 수 있다.
- `OnRenderImage`
- 씬 렌더링이 완료된 후 호출되어 Post-processing이 가능하다.
- `OnGUI`
- GUI 이벤트에 응답하여 프레임당 여러 번 호출된다.
- 레이아웃 및 리페인트 이벤트는 먼저 처리되고, 그 후 각 입력 이벤트에 대한 레이아웃 및 키보드/마우스 이벤트가 처리된다.
- `OnDrawGizmos`
- 시각화 목적으로 Scene 뷰에서 기즈모(Gizmo)를 그릴 때 사용된다.
💡 참고
`OnPreCull`, `OnPreRender`, `OnPostRender`, `OnRenderImage`는 MonoBehaviour 스크립트에서 호출되는 Unity 내장 이벤트 함수이지만, 해당 스크립트가 활성화된 Camera 구성 요소와 동일한 오브젝트에 연결된 경우에만 호출된다.
다른 오브젝트에 연결된 MonoBehaviour에서 위 함수에 대한 동일한 콜백을 받으려면 `Camera.onPreCull`과 같은 동일한 델리게이트를 사용해야 한다.
📌 코루틴
일반적인 코루틴 업데이트는 `Update` 가 반환된 후에 실행된다.
코루틴은 `yield`문을 사용해 일시 중지할 수 있는 함수이며, 지정한 `YieldInstruction`이 완료될 때까지 실행을 중단(양보)할 수 있는 함수이다.
코루틴의 다양한 사용법:
- `yield`
- 다음 프레임의 `Update` 이후 실행된다.
- `yield return WaitForSeconds`
- 지정한 시간이 지난 후, 다음 프레임에 재개한다.
- `yield return WaitForFixedUpdate`
- `FixedUpdate` 이후 실행된다.
- `FixedUpdate` 전에 코루틴이 양보하면 현재 프레임의 `FixedUpdate` 이후에 재개한다.
- `yield WWW`
- WWW 다운로드가 완료된 후 재개한다.
- WWW는 현재(유니티 버전 6.0) UnityWebRequest로 대체되었다.
- `yield StartCoroutine`
- 코루틴을 연결한다.
- 예를 들어, `coroutineA`안에서 `coroutineB`를 `yield StartCoroutine(coroutineB());`로 실행한다면 `coroutineA`는 실행을 일시 중지하고, `coroutineB`가 완전히 끝날 때까지 기다린 후에 다시 이어서 실행된다.
📌 오브젝트 파괴 시
- `OnDestroy`
- 마지막 프레임 업데이트 후 호출된다.
📌 게임 종료 시
- `OnApplicationQuit`
- 애플리케이션 종료 전 모든 오브젝트에서 호출된다.
- 에디터에서는 플레이 모드를 중지할 때 호출된다.
- `OnDisable`
- 비활성화되거나 비활성화 상태일 때 호출된다.