상세 컨텐츠

본문 제목

Unity의 생명주기(Life Cycle)

유니티 학습/C#과 유니티

by 남민우_ 2026. 4. 11. 01:19

본문

생명주기(Life Cycle)이란?

Unity 스크립트를 실행하면 각 함수가 정해진 주기에 맞춰서 Unity 시스템에 의해 호출되는 것을 알 수 있다.

이처럼 사전에 지정한 순서대로 여러 개의 이벤트 함수가 실행되며, 각 이벤트 함수가 실행 시퀀스에 어떻게 포함되는지에 대한 과정을 생명주기라고 말할 수 있다.

 

https://docs.unity3d.com/kr/2019.4/Manual/ExecutionOrder.html

 

이벤트 함수의 실행 순서 - Unity 매뉴얼

Unity 스크립트를 실행하면 사전에 지정한 순서대로 여러 개의 이벤트 함수가 실행됩니다. 이 페이지에서는 이러한 이벤트 함수를 소개하고 실행 시퀀스에 어떻게 포함되는지 설명합니다.

docs.unity3d.com

Unity 공식 문서를 확인하면 이러한 이벤트 함수들이 생명주기에 맞춰서 언제 호출되는지를 시각적으로도 확인할 수 있다.

 

이 이벤트 함수들은 모두 Monobehaviour 상속을 기준으로 하는데, Monobehaviour 클래스를 상속해야 생명주기 시스템에 등록되며 상속하지 않는 C# 클래스의 경우 new 키워드를 통해 객체 참조로 메모리에 직접 할당하는 과정이 필요하다.

 

본문에서는 각 단계에 맞춰 어떠한 이벤트 함수들이 있고, 해당 이벤트 함수들은 언제 호출되는지, 어떠한 특성을 갖고 있는지 등에 대해 알아보려고 한다.

 

1. 초기화 단계

초기화 단계는 스크립트가 로드될 때 가장 먼저 실행되는 단계이다.

Awake()

스크립트 인스턴스가 로딩될 때 단 한번 호출된다.

그 말은 스크립트가 비활성화 상태여도 게임 객체가 생성될 때, 예를 들어 Player 오브젝트가 Instantiate 될 때 Player.cs 컴포넌트는 비활성화 상태여도 Player.Awake는 호출된다는 뜻이다.

 

이 Awake 메서드에서는 주로 싱글톤(Singleton) 패턴 설정, 변수 초기화, 컴포넌트 참조(GetComponent<T>) 등을 수행한다.

이때 다른 객체 참조는 이 메서드에서 실행하는 것을 피해야 하는데, Unity 에서 기본적으로 스크립트 인스턴스의 초기화 순서는 보장하지 않는다. 따라서 해당 객체가 아직 초기화되지 않아 정확한 값이 할당되지 않을 수 있다는 것이 그 이유다.

 

OnEnable()

객체가 활성화(Active)될 때마다 호출된다.

오브젝트 풀링(Object Pooling) 기법을 사용하면 특정 오브젝트 인스턴스를 활성화/비활성화 하며 재사용하게 된다. 이때 해당 객체에 OnEnable 함수가 있다면 오브젝트가 활성화 될 때마다 호출된다.

따라서 이 OnEnable 함수는 이벤트 구독(Event Subscription), 델리게이트 연결, 객체 풀링 등에 사용되며, Awake() 함수 직후 실행된다.

 

Start()

스크립트를 만들면 기본적으로 만들어지는 함수 중 하나로, Update가 시작되기 직전 단 한번 호출된다.

이 메서드에서는 주로 다른 객체 참조, 플레이 전 필요한 최종 세팅이 이루어진다. Awake에서 다른 객체 참조는 피하라고 언급하였는데, 이 Start 가 실행되는 시점에는 다른 객체의 Awake(객체 내부 초기화)가 마친 시점이므로 안전한 참조가 이루어질 수 있다.

 

Awake와 다른 점으로는 스크립트가 비활성화 된 상태라면 호출되지 않는다는 점이 있다.

 

초기화 단계의 메서드 순서는 다음과 같다.

Unity 공식 문서 참조

 

2. 물리 및 로직 업데이트

프레임마다 반복 호출되는, 우리가 쉽게 아는 Update() 메서드 호출이 이루어지는 구간으로 주로 물리와 로직 구성이 이루어지는 구간이다.

해당 구간에는 상당히 많은 메서드가 포함이 되는데, 모든 메서드의 정확한 순서는 초반에 게시한 Unity 공식 문서 다이어그램을 확인하는 것이 도움이 될 것이다.

본문에서는 그 많은 메서드 중 우리가 집중적으로 사용하는 몇가지에 대해서만 알아보려고 한다.

 

FixedUpdate()

설정된 고정시간(기본0.02초)마다 호출되는 메서드로, Fixed 라는 이름처럼 고정적으로 호출되는 만큼 FPS(프레임률)와 무관하다는 특징이 있다.

이 고정적으로 호출된다는 점에서 사용자 환경이 얼마나 좋은지 혹은 나쁜지에 영향을 받지 않기 때문에 주로 물리 연산(RigidBody, 이동 등)에 활용한다. 또한 물리 엔진과 동기화되어 진행된다는 점에서 일관된 물리 시뮬레이션을 보장한다.

 

이 '물리 엔진과 동기화'된다는 것은 Unity 내부의 'NVIDIA PhysX' 라는 물리 연산 엔진을 말하는 것으로, 이 물리 엔진이 일정한 시간 간격으로 시뮬레이션을 진행하는데, 이 엔진과 동기화 한다는 것을 의미한다.

물리 엔진이 한 단계를 계산하기 직전에 이 FixedUpdate()를 호출하면서 그 과정이 이루어진다.

 

한가지 생각해볼 수 있는 점으로, 우리가 게임을 하다보면 종종 겪을 수 있는 '프레임 드랍' 현상이다.

말 그대로 프레임 내에 이루어져야 하는 연산이나 렌더링 등이 특정 이유에 의해 오랜 시간이 걸려 화면이 멈춰버리는 현상을 말하는데, 개발을 하다 프레임 드랍이 일어나는 것을 겪어본 사람이라면 Update() 같은 메서드들이 정상적으로 호출되지 않는다는 점을 알 수 있을 것이다.

 

FixedUpdate의 경우 다르게 동작하는데, 물리 엔진의 '추격(Catch-Up)' 메커니즘에 따라 돌아가게 된다.

예를 들어, 화면 렉으로 1초가 멈췄다고 가정해보자.

Update 메서드의 경우 1초 뒤, 화면 멈춤 현상이 풀린 뒤에 1번 호출되는데 이 경우 그 1초 사이에 이루어져야 했던 연산들은 모두 날아간다. 말 그대로 프레임이 날아가는 것이다.

반대로 FixedUpdate 메서드의 경우 Unity 에서 자체적으로 50번(기본 0.02초마다 1번이라고 가정)이 호출됐어야 했는데 이루어지지 않았다는 것을 판단한다. 하여 다음 프레임 시작 시, 화면 멈춤 현상이 풀린 뒤 정상적으로 실행되지 못한 50번의 FixedUpdate를 한꺼번에 몰아서 연속적으로 호출한다.

타이밍이 정상적으로 이루어지지 않을지언정 진행해야 하는 호출을 날려버리지는 않는다는 것이다.

 

실제로 게임을 하다보면 화면 멈춤 현상 후 일시적으로 N배속 된 것처럼 화면이 빠르게 움직인 후 다시 정상적인 프레임으로 돌아오는 것을 겪을 수 있다. 이러한 현상이 이 FixedUpdate의 Catch-Up 메커니즘에 의한 결과라고 인식하면 될 것이다.

 

Update()

Start 메서드와 마찬가지로 스크립트를 처음 만들면 기본적으로 만들어지는 메서드 중 하나로, 매 프레임마다 호출된다.

이 '매 프레임마다 호출' 이라는 점이 주의해야 하는게, 컴퓨터 사양에 따라 호출 간격에 차이가 있을 수 있다는 것이다.

어느 컴퓨터는 144fps, 또 어느 컴퓨터는 60fps 등 차이가 있을 수 있데 이 경우 각 사양에 따라 Update 메서드가 호출되는 빈도가 다르다는 것이다.

따라서 물리 연산을 여기서 처리하면 안된다. 프레임 드랍 시 물리 법칙이 무너져 게임 세상이 뒤엉키는 버그가 일어날 수 있다.

하여 Update 메서드에서는 주로 각 환경에 맞게 적용될 수 있는 입력 처리, 타이머 계산, 비물리적 이동 등을 구현한다.

 

LateUpdate

이름부터 쉽게 짐작할 수 있듯이, 늦게 호출되는 Update 메서드라고 할 수 있다.

모든 Update가 끝난 후 프레임의 마지막 단계에서 호출되며, 주로 카메라 추적 로직을 활용한다.

캐릭터의 이동 자체는 Update에서 키입력을 받아 이루어질텐데, 이동 후 카메라가 뒤늦게 따라가야 Jittering, 화면이 떨리는 현상 등이 없어지고 자연스러운 연출이 가능해진다.

 

Coroutine(코루틴)

코루틴은 조금 특수하게 동작하는데, yield return 후 어떻게 설정하느냐에 따라 호출 시기가 달라진다.

기본적으로 사용하는 yield return null, 혹은 yield return new WaitForSecond(N)의 경우 Update와 LateUpdate의 사이에 실행된다.

그와 반대로 yield return new WaitForFixedUpdate(N)은 FixedUpdate 이후에 실행된다.

 

이 Update 들의 사이에서 실행된다는 점에서 짐작할 수 있듯이 코루틴은 별도의 스레드로 실행되는 멀티 스레드 함수가 아니라는 것이다. 메인 스레드에서 실행되며, 생명 주기의 사이사이에서 진행된다.

 

 

이러한 업데이트 주기들은 다음과 같은 다이어그램에 따라 진행된다.

Unity 공식 문서 참조

설명하지 않은 다른 메서드들도 있으니, 필요한 경우 공식 문서를 참조하는 것을 추천한다.

 

 

3. 해제 및 종료

객체의 수명이 다하거나 게임이 종료되는 단계를 의미한다.

 

OnApplicationQuit()

게임이 종료되기 직전 모든 객체에서 호출된다.

하여 객체 별로 활용된 가변데이터들이 있을텐데, 이러한 데이터들을 저장하거나 네트워크 연결 해제 시에 활용한다.

여기서도 주의할 점으로 객체 참조 로직은 최소화해야 한다는 것이다.

 

OnDisable()

객체가 비활성화되거나 파괴될 때 호출된다.

이름에서부터 알 수 있듯이 OnEnable과 짝을 이루는 메서드로, OnEnable에서 이벤트 구독 연결을 담당했다면 이 OnDisable에서는 구독 해제에 주로 사용된다. 이벤트 시스템의 경우 구독이 진행되는 순간부터 메모리를 점유하게 되는데, 구독 해제까지 이루어져야 메모리 누수가 방지된다는 점에서 잊지 말고 활용해야 할 메서드가 될 것이다.

또한 Object Pooling 을 사용할 때 비활성화에 대한 처리를 여기서 담당할 텐데, 다시 객체가 활성화 될 것을 대비한 초기화 또한 이 메서드에서 이루어지는 것이 안전하다.

 

OnDestroy()

객체가 실제로 파괴될 때, 즉 Scene 내에서 완전히 지워질 때(Destroy() 호출의 경우) 마지막으로 실행되는 메서드이다.

C# 메모리 해제와 Instantiate로 만들었던 하위 객체들의 최종 정리를 담당하는데, Awake의 경우와 마찬가지로 기본적으로 Unity 시스템 상 Destroy의 순서를 보장할 수 없기 때문에 여기서 다른 싱글톤 객체를 호출하면 안된다는 점이 있다. 이미 파괴되었을 가능성이 높다.

 

 

이 세가지 메서드의 경우 다음과 같은 순서로 진행된다.

단순히 실행되는 순서라고 보기보다 좀 더 중요하고 위험할 수 있다는 것으로 인식해야 한다.

메모리 해제나 참조를 끊는 과정에 에러가 발생하는 대표적인 케이스가 주로 싱글톤과 관련이 깊다.

 

예를 들어 OnDestroy() 에서 다른 객체를 참조하여 파괴 시 필요한 메서드 호출 등을 하는 경우, 객체 파괴의 순서가 보장되지 않기 때문에 파괴된 객체에 접근 에러가 발생하거나 새로운 인스턴스를 만들어버리는 좀비 객체 현상이 발생할 수 있다.

 

따라서 각 메서드 별로 호출 시점과 메모리 해제 등의 로직을 나누는 과정을 주의깊게 살펴야 할 것이다.

또한 객체 참조를 끊는 동작은 반드시 직접 해제해야 하는 경우가 있는데, 외부 라이브러리나 네트워크 스택을 활용하는 경우가 된다. 이 경우 Unity 시스템에 기본적으로 내장된 기능들이 아니기 때문에 관리하지 않아, 개발 단계에서 명시적으로 닫아줘야 한다.

이러한 동작들은 OnApplicationQuit() 나 OnDestroy() 에서 시작하는 것이 좋을 것이다.

 

한가지 추가적으로 주의해야 하는 경우로 정적 이벤트(Static Event)가 있다.

static 으로 선언된 이벤트에 메서드 연결(+=) 하는 경우, OnDisable에서 반드시 (-=) 동작으로 구독을 끊어줘야 한다.

만약 이 구독 해제의 동작이 진행되지 않는다면 객체는 파괴되었는데 이벤트 리스트에는 유령 참조가 남아, 에러가 발생하게 된다.

관련글 더보기