Notice
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

월세 500 호기심 천국

6주차: 매 프레임 나보다 갓생 사는 Unity의 Life Cycle 본문

내 맘대로 쓰는 Unity 심층분석 析深層分

6주차: 매 프레임 나보다 갓생 사는 Unity의 Life Cycle

원펀치 쓰리 갓냥이 2024. 2. 4. 20:42
0주차: 인트로 - 새롭고 신기한 것을 좋아하거나, 모르는 것을 알고 싶어 하는 마음
1주차: Unity 엔진은 왜 개발 언어로 C#을 선택했을까?
2주차: 의외로 세상 잡다한 Transform에 대한 모든 것
3주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (1/2)
4주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (2/2)
5주차: 헐크 아저씨도 깜짝 놀란 Unity Render Pipeline이 작동하는 과정
6주차: 매 프레임 나보다 갓생 사는 Unity의 Life Cycle
7주차: 남들보다 더 갓생 사는 Coroutine의 충격적인 정체
8주차: 야물(YAML)딱진 그 녀석의 은밀한 속마음 - Unity Scene 파일 (.unity)
9주차: 내 과자 포장지 누가 버렸어? - meta data 파일 (.meta) 파헤치기
10주차: 철가방 채로 배달온 Unity 빌드 파일. 난 단무지 시킨 적 없는데, 왜 있지?
11주차: 마무리 - 후기

 


 

Unity에서 개발자가 작성하는 대부분의 C# 스크립트는 MonoBehaviour 클래스를 상속받는다.

 

MonoBehaviour 클래스는 에디터 상에서 스크립트가 게임 오브젝트와 잘 연동될 수 있도록 프레임워크를 제공해주며,

모든 스크립트가 공통된 생명 주기(Life Cycle)를 따라

일정에 맞게 딱딱 실행될 수 있도록 관리해준다.

 

이제 FixedUpdate로 이동할 시간입니다 공주님

 

위 그림에서 C# 스크립트를 공주로, MonoBehaviour를 시종으로 비유를 든 것은

C# 스크립트가 딱히 기품 있거나 고귀해서가 아니다.

하루 일정이 ㅈㄴ 빡세기 때문이다.

 

숨 한 번 크게 들이키고 스크롤을 내려보자.

Unity 공식 문서에서 하달 받은 오늘 한 프레임의 일정입니다 공주님

 

위 순서도의 대부분이 무려 '한 프레임' 이내에 일어나는 것들이다.

 

Unity의 Life Cycle을 하나하나 파헤치며

공주님과 함께하는 오늘의 일상 VLOG,

겟 레디 윗 미(Get Ready With Me)를 시작해보자

ㅁ...뭣

 

1. Initialization

 

하루의 시작이다.

씬에서 오브젝트가 처음 생성될 때 한 번씩 호출되는 함수들이다.

Awake

Initialization 단계에서 맨 처음으로 호출된다.

뒤이어 나올 Start와 비슷하지만 Start보다도 먼저 호출된다는 특징을 가지고 있다.

때문에 Start보다도 선행으로 실행돼야 할 코드들이 있을 경우 (Find 라던가)

주로 Awake 문에서 실행한다.

Awake에서 FInd를 사용하면 좋은 이유
1. Awake 함수 자체가 씬 내 모든 오브젝트의 초기화가 완료되고 호출됨
    = NullReference 우려 없음
2. 순서상 이후 단계(Start)에서 사용될 다른 컴포넌트들을 가져오기 좋음

OnEnable

씬에서 활성화돼있는 스크립트의 경우, Awake 이후 OnEnable을 호출한다.

처음 1회만 호출되는건 아니고, 스크립트가 활성화될 때마다 호출되기 때문에

그에 맞춰 새로이 활성화된 스크립트의 초기화 등 유동적인 용도로 활용할 수 있다.

Reset

에디터 모드에서만 호출되는 함수이다.

인스펙터 창에서 해당 스크립트를 Reset 했을 때 호출된다.

에디터 플레이 모드에서 스크립트를 조절하다가 초기값으로 복구하고 싶을 때 사용한다.

Start

첫 번째 Update가 호출되기 직전, 스크립트가 활성화돼있을 경우 호출된다.

Update와 함께 Unity에서 C# Script를 생성하면 기본적으로 기재되어 있는 함수이며,

그만큼 Unity 초심자부터 고인물들까지 범용적으로 폭 넓게 사용한다.

 

2. Physics, Input Events, Game Logic

 

바쁜 Unity 공주의 본격적인 일과 시간이다.

매 프레임 각 부처에서 올린 공문과 국민들이 올린 탄원서,

왕이 내린 왕가의 규율과 지침을 담은 문서를 읽고

학교에서 받은 가정통신문과 알림장을 확인,

이번 달 우유급식비는 제때 냈는지 검토한다.

투니버스에서 틀어주는 신비아파트와 또봇을 한 편씩 보고

적국에서 보내온 자객들에 맞서기 위한 호신술과 살인 기술을 연마한다.

그리고 밤 9시가 되면 양치하고 발닦고 칼같이 잠에 든다.

 

이러한 루틴은 매일매일 매 프레임마다 반복된다.

다음은 그 루틴들을 좀 더 자세하게 풀어놓은 것이다.

FixedUpdate

별다른 상호작용을 하지 않아도 일정한 시간 간격으로 호출되는 함수이다.

여기서 일정한 시간 간격이란 Time.fixedDeltaTime(기본값: 0.02)이며,

프레임이 아닌 고정된 시간 간격에 의존하기 때문에

각종 플랫폼의 성능 차이에서 오는 frameRate에 영향받지 않을 수 있다.

때문에 일정한 시간 간격으로 힘을 받아야 하는 물리 연산(특히 RigidBody쪽)들에

주로 사용된다.

OnTrigger(Enter, Stay, Exit)

Collider끼리의 충돌을 체크하는 단계이다.

단, 물리 연산을 하지 않는 충돌도 포함이며,

어느 한쪽 Collider의 isTrigger가 켜져있어야 한다.

Enter, Stay, Exit?
처음 충돌(진입) 상태에 진입한 시점에 Enter,
충돌(진입) 상태를 유지하고 있는 시점에 Stay, (Stay의 호출 간격이 Update 호출 간격인과 맞지 않을 수 있음)
충돌(진입) 상태에서 벗어난 시점에 Exit을 호출한다.

 

OnCollision(Enter, Stay, Exit)

Collider끼리의 충돌을 체크하는 단계이다.

단, 물리 연산이 적용되는 충돌만을 말하며,

어느 한쪽 Collider도 isTrigger가 켜져있으면 안된다.

Enter, Stay, Exit?
처음 충돌(진입) 상태에 진입한 시점에 Enter,
충돌(진입) 상태를 유지하고 있는 시점에 Stay, (Stay의 호출 간격이 Update 호출 간격인과 맞지 않을 수 있음)
충돌(진입) 상태에서 벗어난 시점에 Exit을 호출한다.

yield WaitForFixedUpdate

뒤에서도 계속 나올 yield 시리즈의 함수(정확히는 코루틴)들은

코루틴 그 자체에 특별한 기능이 있다기보다는,

특정 시점이 오기 전까지 잠들어 있다가

생명 주기 상에서 어떠한 과정이 끝났다라는 '플래그'를 발견하면

다시 깨어나는 역할을 한다.

나 좀 깨워줘

 

그 중 yield WaitForFixedUpdate는 생명 주기 중 Physics 단계가 끝났을 때 깨어나며,

코루틴 내의 while 반복문 말미에 yield return new WaitForFixedUpdate();를 한 줄 적어넣어

마치 while 반복문이 FixedUpdate와 같은 주기로 돌게끔 설계할 수 있다. 

 

코루틴이 무엇인가?에 대해서는 바로 다음 주차인 '7주차: 코루틴'에서 살펴볼 예정이다.

OnMouse(Enter, Over, Exit, Down, Drag, Up)

플레이어의 마우스 클릭을 감지하는 단계이다.

각 함수들이 호출되는 시점은

 

해당 타겟 위에 마우스가 진입했을 경우 Enter,

타겟 위에 마우스가 올라와 있을 경우 Over,

타겟에서 마우스가 벗어났을 경우 Exit,

 

타겟에서 마우스 버튼이 눌렸을 경우 Down,

타겟에서 마우스 클릭이 유지되고 있을 경우 Drag,

타겟에서 눌렸던 마우스 버튼이 다시 떼졌을 경우 Up

 

이다.

Update

생명 주기의 함수들 중 Start와 함께 가장 유명한 친구이다.

해당 스크립트가 켜져있는 경우 매 프레임마다 확정적으로 호출하지만,

프레임 당 1회이기 때문에 프레임 간격이 들쭉날쭉한 저성능의 플랫폼의 경우

Update 함수의 호출 빈도가 심각하게 낮아지거나 불균형해지는 모습을 볼 수 있다.

 

때문에 Update 호출 시마다 연산 결과를 누적해야 하는 경우 연산값에

프레임과 프레임 사이의 시간 간격인 Time.deltaTime을 곱해줘야 한다는 이야기를

어디선가 많이 들어봤을 것이다.

 

Time.deltaTime?

PC1과 PC2 모두 같은 시간(=5초)이 흘렀다고 했을 때,
둘 모두 매 Update마다 a += 1 연산을 했다면,

5초가 지난 후
PC1은 총 14 프레임이므로 a 값이 14,
PC2는 총 12 프레임이므로 a 값이 12가 될 것이다.

하지만 둘 모두 매 Update마다 a += 1 * Time.deltaTime 연산을 한다면,
(Time.deltaTime은 이전 프레임과 현재 프레임 사이의 시간 간격)
5초가 지난 후 a 값은 프레임 수와 무관하게 5가 될 것이다.

프레임 수와 무관하게 두 PC 모두 5초간의 Time.deltaTime 총합은 5이고,
이것이 프레임마다 분할되어 배분되었으므로.

yield null

yield WaitForFixedUpdate에서 언급했던 yield 시리즈이다.

yield null의 경우 모든 Update가 종료될 때까지 잠들어있다가 다시 깨어난다.

마찬가지로 While 반복문 말이에 yield return null;을 한 줄 넣어

Update와 같은 주기로 돌게끔 활용하는 방법이 있다.

yield WaitForSeconds

또 yield 시리즈.

숫자 하나를 인자로 받고 해당 숫자의 시간(초)만큼 활동을 멈춘 뒤,

시간이 지난 시점에서 Update문이 모두 종료되면 다시 깨어난다.

이제는 알람 맞추고 잠들어버린

yield WWW

또 또 yield 시리즈.

WWW 다운로드가 완료되기 전까지 잠들어 있는다.

코잇츠 또 잠들어버린 www

yield StartCoroutine

또 또 또 yield 시리즈

인자로 다른 코루틴을 받고, 해당 코루틴이 종료되기 전까지 잠들어 있는다.

해당 코루틴이 종료된 시점에서 다른 모든 Update가 종료될 때 다시 깨어난다.

쟤 끝나기 전까진 나 깨우지 말라고

LateUpdate

Game Logic 단계에서 가장 마지막으로 호출되는 함수이다.

온갖 종류의 Update문과 yield문의 연산이 종료되고

마지막으로 호출되는 찐막 Update이기 때문에

 

주로 여기에 Camera 관련 Logic을 넣어

더 매끄러운 화면을 보여준다.

이제 일할 시간이다

 

3. Scene, Gizmo, GUI Rendering

 

드디어 칼잠에 든 공주.

공주는 자신이 하루 겪었던 일을 토대로 꿈을 꾸기 시작한다.

머릿속 정리되어있지 않은 장면들이

감긴 눈 속에서 한 프레임씩 렌더링되고 그려지는 과정이다.

OnWillRenderObject

해당 오브젝트가 카메라에 보여지는 상황이고, UI가 아니라면

카메라 당 한 번씩 호출된다.

OnPreCull

씬이 카메라에 의해 컬링되기 직전에 호출된다.

컬링은 해당 오브젝트가 카메라에 표시될지, 표시되지 않을지 분별하는 작업이다.

OnBecameVisible/Invisible

컬링 작업을 거친 후 오브젝트가 카메라에 표시되는 것으로 판정됐을 때(Visible),

혹은 표시되지 않는 것으로 판정됐을 때(Invisible) 호출된다.

OnPreRender

씬이 화면에 본격적으로 렌더링되기 직전에 호출된다.

OnRenderObject

씬 내 모든 오브젝트들의 렌더링이 처리된 직후 호출된다.

OnPostRender

카메라의 모든 씬 렌더링이 종료된 후 호출된다.

OnRenderImage

씬의 모든 렌더링이 종료된 이후 포스트 프로세싱이 가능해진 시점에서 호출된다.

OnDrawGizmos

추가적으로 에디터 상의 화면에서 기즈모(격자)를 그려낼 필요가 있을 할 때 호출한다.

OnGUI

마지막으로 화면 상에서 UI에 해당하는 것들을 그릴 때 호출된다.

 

4. 기타

yield WaitForEndOfFrame

이름에서 알 수 있듯, 한 프레임이 종료될 때마다 호출된다.

특이한 점은 앞선 yield Wait 시리즈들과는 달리

화면 렌더링까지 전부 종료된 이후 화면에 나타내기 직전에 호출된다는 것이다.

OnApplicationPause

게임이 일시정지되었다는 사실을 감지한 이후 프레임의 OnApplicationPause 단계에서 호출된다.

정확히는 인자로 게임의 일시정지 여부를 bool 값으로 받고,

게임이 정상적으로 실행되고 있을 경우 false를,

무언가에 의해 멈췄을 경우 true를 반환한다.

이는 일시정지 기능을 자체적으로 제공하는 에디터에서는 물론,

빌드 버전에서도 게임 창이 다른 어플리케이션 화면들에 가려지는 등의 상황이 발생할 때 사용할 수 있다.

 

5. Decommissioning

바쁘게 살아온 일상,

한평생 나라를 위해 몸을 바쳐온 공주의 삶에도 끝이 찾아왔다.

매일매일 정해진 일과를 보내고 잠들기만을 반복했던 우리의 공주는

곧 영원히 잠에 들 준비를 한다.

OnApplicationQuit

에디터 기준으로 Playmode가 종료되었을 때,

빌드 버전으로 게임이 종료될 때 호출된다. 

OnDisable

해당 스크립트, 혹은 해당 오브젝트가 비활성화되었을 때 호출된다.

Initialization 단계의 OnEnable과 반대격이지만 순서상 더 이후에 호출된다고 생각하면 된다.

OnDestroy

활성화돼있던 스크립트, 혹은 오브젝트가 파괴될 때 호출된다.

 

 


 

이렇게 해서 Unity Life Cycle의 시작과 끝을 알아보았다.

이 수많은 과정들이 고작 한 프레임 단위로 사이클을 돌고 있다니...

그것도 한두개가 아닌 스크립트와 오브젝트들에서.

이것이 참으로 갓생이 아닐 수가 없다.

대단하다 유니티!

 

그럼 이번 주도 끝~

 


 

0주차: 인트로 - 새롭고 신기한 것을 좋아하거나, 모르는 것을 알고 싶어 하는 마음
1주차: Unity 엔진은 왜 개발 언어로 C#을 선택했을까?
2주차: 의외로 세상 잡다한 Transform에 대한 모든 것
3주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (1/2)
4주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (2/2)
5주차: 헐크 아저씨도 깜짝 놀란 Unity Render Pipeline이 작동하는 과정
6주차: 매 프레임 나보다 갓생 사는 Unity의 Life Cycle
7주차: 남들보다 더 갓생 사는 Coroutine의 충격적인 정체
8주차: 야물(YAML)딱진 그 녀석의 은밀한 속마음 - Unity Scene 파일 (.unity)
9주차: 내 과자 포장지 누가 버렸어? - meta data 파일 (.meta) 파헤치기
10주차: 철가방 채로 배달온 Unity 빌드 파일. 난 단무지 시킨 적 없는데, 왜 있지?
11주차: 마무리 - 후기