오르카의 아틀리에

저번 주에 소프트웨어 마에스트로에서 만난 사람들과 술자리를 가졌었다. 다들 오랜만에 보는 얼굴들이었는데 문득 1단계 때 옆자리였던 형을 보니 '유니티 생명주기'에 대해 알려주면서 트러뷸 슈팅을 도와주었던 기억이 났다. 생각도 다시 한번 정리해볼 겸 간단하게 포스팅해볼 생각이다.


유니티에서 생명주기?

유니티에서 자신이 제작한 스크립트의 대부분은 'Monobehaviour'라는 친구를 상속받아 만들어진다. 스크립트를 만들면 자동으로 상속받는 형태이니 유니티의 기본 클래스 형태라고 봐도 좋을 것이다. 이 'Monobehaviour'가 Scene에서 살아가는 일정한 흐름이 있다. 엔진에서 자동으로 호출해주는 함수들인데 이 패턴의 흐름을 모아 '생명 주기(Life cycle)'라고 한다. 일단 그림으로 먼저 알아보자.



대략적으로 위와 같은 Flow를 가진다. 첫 'Awake', 'OnEnable', 'Start'함수는 객체가 생성될때 1회 호출되는 함수라고 생각하면 편하다. 단, OnEnable은 스크립트에서 객체를 활성화/비활성화시킬 경우가 있는데 객체가 활성화 되었을 때 마다 호출되는 함수이다.


옆자리 형의 트러블슈팅을 도와줄때 바로 이 문제에서 발생되었다. 


??? : 집에서는 잘 되었는데 그대로 노트북에서 작업하니까 NullReferenceExeption을 던진다

는 이야기였다. 



코드를 살펴보니 문제는 즉, A라는 객체의 Awake에서 B라는 객체가 Awake될 때 동적 생성하는 멤버에 접근하려고 했는데 바로 여기서 문제가 발생한 것이다. 다른 객체에서 동적 생성한 멤버를 참조하고 싶다면 Awake가 아니라 Awake가 모두 실행되었음이 보장된 Start 쪽에서 처리하는 것이 바람직할 것이다. 


집에서는 B 객체의 동적 할당이 먼저 일어나서 무사히 넘어갔지만, 항상 그런 동작이 보장된 것은 아니다. 생명주기를 기억했다가 어느 타이밍에 내가 무엇을 했을 때 동작이 보장되는지 생각해 둘 필요가 있다. 여담으로 Update를 살펴보고 넘어가자. 


Update가 3개?

희한하게도 Update가 3가지 종류가 있다. 'FixedUpdate', 'Update', 'LastUpdate'다. 


1. 가장 기본적인것이 Update로 매 프레임 마다 실행된다. 일반적으로 유니티를 처음 시작했다면 이 부분에서 거의 모든 로직을 처리할 것이다. (아님 곰보) 


2. 그 뒤에 있는 LastUpdate가 바로 Update다음으로 실행된다. Update에서의 결과가 전부 반영된 뒤 작업하고 싶다면 LastUpdate에서 작업하는 것이 맞을 것이다. 


3. FixedUpdate는 고정된 프레임 주기로 호출되는 함수들이다. Update는 디바이스의 성능이나 게임의 최적화 상황에 따라 framerate가 변하게 되는데 이렇게 되면 프레임 사이 사이의 시간 간격이 다르게 적용된다. 이렇게 되면 물리적인 계산이 필요할 때 상당한 오차가 발생할 수 있다. 그래서 시간 간격이 고정된 FixedUpdate에서 처리해주는 것이 좋다.


이렇게 미리 생명 주기를 파악하고 내가 원하는 동작이 어느 동작이 보장되고나서 실행되는지 알고 있어야 삽질을 줄일 수 있을 것이다.