유니티 게임 루프
유니티의 게임 루프를 이해하는 것은 게임 개발 과정에서 유니티의 실행 순서를 이해하는 데 도움이 될 수 있습니다.
예를 들어, OnEnable에서 OnDisable로 실행되는 순서를 이해하는 것 부터 시작해서, LateUpdate를 활용하는 방식도 제대로 이해할 수 있죠.
유니티의 게임 루프
아래는 유니티에서 제공하는 게임 루프의 이미지입니다.
첫 번째 Initialization
Awake() : 게임 오브젝트가 처음 로드될 때 한 번만 호출됩니다. 이 단계에서는 변수 초기화나 리소스 할당과 같은 작업이 이루어집니다.
게임 오브젝트가 활성화되기 전이라 할지라도, 스크립트가 처음 로딩될 때 실행됩니다.
OnEnable() : 오브젝트가 활성화될 때마다 호출됩니다. 이는 오브젝트가 처음 생성될 때와, 비활성화 상태에서 다시 활성화될 때 모두 발생합니다.
Start() : Start는 Awake 이후에, 스크립트의 첫 번째 업데이트 호출 전에, 게임 오브젝트가 활성화된 경우에 딱 한 번 호출됩니다.
Editor Reset
Editor의 Reset 단계가 유니티 게임 루프에 포함되어 있다는 것은 이상합니다. 실제로, Editor의 Reset은 게임 루프와 독립적으로 작동합니다.
1.
컴포넌트 추가 시: 스크립트 컴포넌트를 게임 오브젝트에 처음 추가할 때 Reset() 메소드가 호출됩니다. 이를 통해 개발자는 스크립트의 변수나 상태를 기본값으로 초기화할 수 있습니다.
2.
컨텍스트 메뉴를 통한 리셋: 유니티 에디터에서 사용자가 컴포넌트의 컨텍스트 메뉴(인스펙터 창에서 기어 아이콘 클릭)를 사용해 Reset옵션을 선택할 때 호출됩니다.
런타임에서 컴포넌트를 추가하는 경우도 마찬가지입니다. Editor Reset은 게임 루프와는 무관하게 에디터 상태에서만 작동합니다.
Physics FixedUpdate
FixedUpdate() 단계는 주로 물리 엔진과 관련된 업데이트를 처리하기 위해 사용됩니다. 이 단계는 일정한 시간 간격으로 호출되며, 게임의 프레임률에 영향을 받지 않습니다.
1.
물리 연산 처리: 유니티의 물리 엔진은 FixedUpdate()에서 업데이트됩니다. 여기서는 Rigidbody 컴포넌트를 사용하는 오브젝트의 위치와 회전을 업데이트하며, 물리적인 충돌 감지와 처리가 이루어집니다.
a.
Rigidbody에서 처리되는 물리 연산이 바로 이 곳에서 실행됩니다. 따라서 Rigidbody를 활용하여 게임을 만들 때는 Update보다는 FixedUpdate를 활용하는 것이 바람직합니다.
2.
정확한 타이밍의 이벤트 : 일정한 간격으로 반복해야 하는 작업들, 예를 들어 움직이는 플랫폼이나 타이머와 같은 기능들은 FixedUpdate()에서 처리하는 것이 좋습니다. 이는 FixedUpdate()가 일정한 시간 간격으로 호출되기 때문입니다.
"Catch-up" 메커니즘 : 프레임률이 매우 낮은 경우, 유니티는 놓친 FixedUpdate() 호출을 보충하기 위해 한 프레임 내에서 FixedUpdate()를 여러 번 호출할 수 있습니다.
Animator 동작 과정
유니티의 애니메이터 동작 과정은 Update Mode를 통해 조정할 수 있습니다.
Normal
•
동작 방식 : Normal 모드는 애니메이션을 각 프레임의 Update 호출 시에 업데이트합니다. 이는 가장 일반적인 애니메이션 업데이트 모드로, 대부분의 게임에서 사용됩니다.
Animate Physics
•
동작 방식: Animate Physics 모드에서는 애니메이션 업데이트가 FixedUpdate 호출과 동기화됩니다. 물리 시뮬레이션과 애니메이션 사이의 일관성을 유지합니다.
Unscaled Time
•
동작 방식 : Unscaled Time 모드는 게임의 시간 척도(Time.timeScale)의 영향을 받지 않고 애니메이션을 업데이트합니다. 즉, 게임이 일시정지되거나 시간 척도가 변경되어도 애니메이션은 계속 재생됩니다.
State machnie update(상태 머신 업데이트)
유니티의 애니메이션 시스템은 상태 머신 기반으로 작동하며, 이는 애니메이션 상태 간의 전환과 조건을 관리하는 데 사용됩니다. 상태 머신 업데이트 과정은 애니메이터와 연관되어 있으며, 애니메이션의 흐름과 상태 전환을 제어합니다.
StateMachine Update
•
애니메이션 상태 머신이 현재 상태를 업데이트합니다.
•
이 단계에서는 현재 활성화된 상태의 애니메이션을 평가하고, 필요에 따라 다른 상태로의 전환을 준비합니다.
1.
OnStateMachineEnter / OnStateMachineExit
•
특정 애니메이션 상태 머신에 진입하거나 그것을 벗어날 때 호출됩니다.
•
이 콜백 함수는 상태 머신의 전환점에서 특정 작업을 수행하는 데 사용됩니다.
public class MyStateMachineBehaviour : StateMachineBehaviour {
override public void OnStateMachineEnter(Animator animator, int stateMachinePathHash) {
Debug.Log("Entered StateMachine");
}
override public void OnStateMachineExit(Animator animator, int stateMachinePathHash) {
Debug.Log("Exited StateMachine");
}
}
C#
복사
2.
Process Graph
•
애니메이션 그래프를 처리하고, 각 상태 및 전환에 대한 로직을 실행합니다.
•
이 단계는 애니메이션의 흐름과 구조를 결정합니다.
◦
애니메이션 상태 평가
▪
각 애니메이션 상태(예: 걷기, 뛰기, 점프 등)의 애니메이션 클립이 평가됩니다.
▪
애니메이션 클립의 현재 재생 시점과 진행률이 계산되어 각 상태의 애니메이션을 결정합니다.
◦
상태 전환 로직 처리
▪
상태 머신 내의 각 상태 간 전환 조건이 평가됩니다.
▪
예를 들어, 캐릭터의 속도가 특정 수치 이상일 때 "걷기" 상태에서 "뛰기" 상태로 전환할 수 있는 조건을 체크합니다.
▪
상태 전환 조건이 충족되면, 상태 머신은 현재 상태에서 다음 상태로 전환합니다.
◦
블렌딩과 레이어 처리
▪
상태 간 전환 시 블렌딩(애니메이션의 부드러운 전환)이 적용됩니다.
▪
애니메이터 레이어를 사용하여 복잡한 애니메이션(예: 상체는 달리기, 하체는 점프)을 구현할 수 있으며, 각 레이어의 가중치와 블렌딩 방식을 결정합니다.
3.
Fire Animation Events
•
애니메이션 클립에 정의된 이벤트를 발생시킵니다.
•
이 이벤트들은 애니메이션의 특정 시점에서 추가적인 작업을 수행하도록 할 수 있습니다.
4.
StateMachineBehaviour callbacks
•
StateMachineBehaviour 스크립트의 콜백 함수들이 이 단계에서 호출됩니다
public class MyStateBehaviour : StateMachineBehaviour {
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
Debug.Log("State Entered");
}
}
C#
복사
OnStateMachineEnter와 차이점은, OnStateEnter는 특정 State에 들어갔을 때만 판단한다는 점입니다.
5.
OnAnimationMove
•
OnAnimationMove 콜백 함수는 애니메이터가 움직임을 처리한 후 호출됩니다.
•
이 함수를 사용하여 스크립트에서 애니메이터의 움직임을 재정의하거나 수정할 수 있습니다.
public class MyAnimationMove : MonoBehaviour {
void OnAnimatorMove() {
Animator animator = GetComponent<Animator>();
transform.position += animator.deltaPosition;
}
}
C#
복사
6.
Internal Physics Update
•
유니티의 내부 물리 엔진이 업데이트됩니다.
•
이 단계에서는 게임 오브젝트의 물리적 상호작용과 관련된 계산이 수행됩니다.
OnTriggerEnter와 OnCollisionEnter 등이 이 부분에 해당합니다!
단지, 실행 여부만 판단하는 것이며, 아직 실행되는 것은 아닙니다. 실제 실행은 마지막 단계에서 이루어집니다.
7.
ProcessAnimation
•
애니메이션 데이터가 실제 게임 오브젝트의 변환(Transform)에 적용됩니다.
•
이 단계는 애니메이터가 계산한 위치, 회전, 스케일 등의 변화를 게임 오브젝트에 적용하는 과정을 포함합니다.
이는 아직 완전한 형태의 최종 결과가 아닙니다. 애니메이션 데이터가 Transform에 반영되는 과정이 진행 중임을 의미합니다.
아래 과정 진행 전 Transform 상태 설정!
8.
OnAnimatorIK
•
OnAnimatorIK 콜백 함수가 호출됩니다.
•
이 함수는 Inverse Kinematics (IK) 시스템을 통해 더 정교한 애니메이션 제어를 가능하게 합니다.
9.
WriteTransform
•
애니메이션에 의한 최종 변환(위치, 회전 등)이 게임 오브젝트에 적용됩니다.
•
이 곳에서 최종 Transform이 설정됩니다.
10.
WriteProperties
•
애니메이션에 의해 변경된 다른 속성들(예: 머티리얼 속성)이 게임 오브젝트에 적용됩니다.
OnTrigger, OnCollision
OnCollision
OnCollisionEnter, OnCollisionStay, OnCollisionExit는 Rigidbody 컴포넌트를 가진 게임 오브젝트가 다른 Collider와 물리적으로 충돌했을 때 발생합니다.
OnTrigger
OnTriggerEnter, OnTriggerStay, OnTriggerExit는 Collider 컴포넌트 중 하나 또는 둘 다가 "Is Trigger" 옵션을 활성화했을 때 사용됩니다.(RigodBody 컴포넌트 옵션) 이 이벤트들은 물리적인 충돌 없이 물체 간의 상호작용을 감지할 때 사용됩니다.
Input Events
입력 이벤트 처리는 주로 Update() 함수 이전에 발생합니다. 이는 유니티 엔진이 각 프레임을 처리하기 전에 사용자의 입력을 수집하고 처리하기 위함입니다.
using UnityEngine;
public class PlayerController : MonoBehaviour
{
void Update()
{
// 키보드의 화살표 키 또는 WASD를 사용한 이동 입력 처리
// 이 부분의 입력 처리 Update 함수 이전에 발생합니다.
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
transform.Translate(movement * Time.deltaTime * 5f);
// 스페이스바를 누를 때 점프 처리
// 마찬가지로 이 부분의 입력 처리도 Update 이전에 발생합니다.
if (Input.GetKeyDown(KeyCode.Space))
{
// 점프 로직, 예를 들어 Rigidbody에 힘을 추가하는 코드 등
Debug.Log("Jump!");
}
}
}
C#
복사
Scene Rendering
이 부분은 렌더링에 관한 설명입니다. 여기서 주요하게 고려해야 할 것은 카메라가 대상인지, 아니면 오브젝트가 대상인지입니다.
1. OnPreCull
•
호출 시점 : 카메라에 의해 씬의 렌더링이 시작되기 직전에 호출됩니다.
•
목적: 이 단계에서는 렌더링 전에 필요한 준비 작업을 수행할 수 있습니다. 예를 들어, 특정 오브젝트를 렌더링에서 제외하거나, 카메라의 속성을 동적으로 변경할 수 있습니다.
using UnityEngine;
public class CameraPreCullListener : MonoBehaviour
{
void OnEnable()
{
Camera.onPreCull += MyPreCullAction;
}
void OnDisable()
{
Camera.onPreCull -= MyPreCullAction;
}
void MyPreCullAction(Camera cam)
{
// 여기에 카메라 렌더링 전 실행하고 싶은 코드를 작성합니다.
// 예: 특정 조건 하에 카메라의 속성 변경, 특정 오브젝트의 렌더링 설정 변경 등
Debug.Log("PreCull action triggered for camera: " + cam.name);
}
}
C#
복사
2. OnWillRenderObject
•
호출 시점: 오브젝트가 카메라에 의해 렌더링될 예정일 때 호출됩니다.
•
목적: 이 콜백은 개별 오브젝트가 렌더링되기 전에 호출되며, 오브젝트 레벨에서 렌더링 관련 로직을 처리할 수 있습니다.
이곳에서는 카메라 뷰에 표시되는 모든 오브젝트에 대해 호출됩니다.
void OnWillRenderObject() {
Debug.Log("Before the Object is rendered");
// 특정 오브젝트가 렌더링되기 전에 호출됩니다.
}
C#
복사
3. OnBecameVisible / OnBecameInvisible
•
호출 시점: 오브젝트가 카메라의 뷰에 들어오거나 나갈 때 호출됩니다.
•
목적: 이 콜백들은 오브젝트의 가시성이 변경될 때 사용됩니다. 예를 들어, 오브젝트가 화면에 보일 때 리소스를 할당하거나, 화면에서 사라질 때 리소스를 해제하는 등의 작업을 수행할 수 있습니다.
OnBecameVisible 카메라 뷰에 표시될 수 있더라도, 앞으 물체에 의해 가려진다면 호출되지 않습니다.
void OnBecameVisible() {
Debug.Log("Object became visible");
}
void OnBecameInvisible() {
Debug.Log("Object became invisible");
}
C#
복사
4. OnPreRender
•
호출 시점 : 카메라가 씬을 렌더링하기 직전에 호출됩니다.
•
목적: 이 단계에서는 렌더링 시작 전에 카메라 관련 설정을 변경하거나 다른 렌더링 준비 작업을 수행할 수 있습니다.
using UnityEngine;
public class CameraRenderEvents : MonoBehaviour
{
void OnEnable()
{
Camera.onPreRender += MyPreRender;
}
void OnDisable()
{
Camera.onPreRender -= MyPreRender;
}
void MyPreRender(Camera cam)
{
// 여기에 카메라의 렌더링이 시작되기 직전에 실행할 로직을 구현합니다.
Debug.Log("PreRender: " + cam.name);
}
}
C#
복사
5. OnRenderObject
•
출 시점 : 씬의 모든 렌더링 작업이 완료된 후에 호출됩니다.
•
목적: 이 단계는 주로 추가적인 커스텀 렌더링 작업을 수행하는 데 사용됩니다. 예를 들어, Gizmos 또는 커스텀 포스트 프로세싱 효과를 렌더링할 수 있습니다.
void OnRenderObject() {
Debug.Log("After all regular objects are rendered");
// 모든 렌더링이 완료된 후 수행할 작업
}
C#
복사
6. OnPostRender
•
호출 시점 : 카메라가 씬을 완전히 렌더링한 후에 호출됩니다.
•
목적: 이 단계에서는 렌더링된 씬에 대한 후처리 작업을 수행할 수 있습니다. 예를 들어, 렌더링 결과에 추가적인 그래픽 효과를 적용할 수 있습니다.
using UnityEngine;
public class CameraRenderEvents : MonoBehaviour
{
void OnEnable()
{
Camera.onPostRender += MyPostRender;
}
void OnDisable()
{
Camera.onPostRender -= MyPostRender;
}
void MyPostRender(Camera cam)
{
// 여기에 카메라의 렌더링이 완료된 후에 실행할 로직을 구현합니다.
Debug.Log("PostRender: " + cam.name);
}
}
C#
복사
7. OnRenderImage
•
호출 시점 : 카메라의 출력 이미지에 대한 후처리가 필요할 때 호출됩니다.
•
목적: 이 콜백은 카메라의 최종 이미지에 포스트 프로세싱 효과를 적용하는 데 사용됩니다. 예를 들어, 블러, 컬러 조정, 이미지 필터 등을 적용할 수 있습니다.
void OnRenderImage(RenderTexture src, RenderTexture dest) {
Graphics.Blit(src, dest); // 단순 예시, 실제로는 커스텀 포스트 프로세싱을 적용할 수 있습니다.
Debug.Log("OnRenderImage called");
}
C#
복사
Gizmo Rendering
유니티 에디터에서 사용되는 기즈모(Gizmo)를 렌더링하는 단계입니다.
using UnityEngine;
public class ExampleGizmo : MonoBehaviour
{
// Gizmo의 색상을 설정합니다.
void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
// 게임 오브젝트의 위치에 큐브 Gizmo를 그립니다.
Gizmos.DrawWireCube(transform.position, new Vector3(1, 1, 1));
}
// 선택된 상태에서만 Gizmo를 그립니다.
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
// 게임 오브젝트의 위치에 구 Gizmo를 그립니다.
Gizmos.DrawSphere(transform.position, 0.5f);
}
}
C#
복사
GUI Rendering
사용자 인터페이스를 렌더링하는 단계입니다. 버튼, 메뉴, 대화창 등의 UI 요소가 화면에 그려집니다.
Unity의 IMGUI (Immediate Mode GUI) 시스템을 사용하여 구현됩니다. 그러나 현대 Unity 프로젝트에서는 UGUI를 더 많이 사용합니다.
즉, 굳이 사용할 필요는 없습니다.
using UnityEngine;
public class ExampleGUI : MonoBehaviour
{
void OnGUI()
{
// 화면 좌측 상단에 FPS 값을 표시하는 GUI 박스를 생성합니다.
GUI.Box(new Rect(10, 10, 100, 50), "FPS: " + (1.0f / Time.deltaTime).ToString());
// 버튼이 클릭되면 로그를 출력합니다.
if (GUI.Button(new Rect(10, 70, 100, 30), "Click Me"))
{
Debug.Log("Button Clicked");
}
}
}
C#
복사
OnApplicationPause
OnApplicationPause 메소드는 애플리케이션이 백그라운드로 전환될 때 호출됩니다. 이는 사용자가 홈 버튼을 누르거나 다른 앱으로 전환하는 등의 이유로 애플리케이션이 화면에서 사라지게 될 때 발생합니다.
이 메소드는 주로 모바일 환경에서 많이 사용됩니다.
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
// 앱이 일시 정지되었을 때 할 작업
}
else
{
// 앱이 다시 활성화되었을 때 할 작업
}
}
C#
복사
OnApplicationQuit
OnApplicationQuit 메소드는 애플리케이션이 종료될 때 호출됩니다. 이 메소드는 애플리케이션을 정상적으로 종료하는 경우에 호출되므로, 사용자가 앱을 강제로 종료시키는 경우에는 호출되지 않을 수 있습니다.
이 메소드는 주로 데스크톱 프로그램에서 사용되지만, 강제 종료 시 호출되지 않는 이슈 때문에 사용하기가 까다로울 수 있습니다. 이러한 이유로 모바일에서는 사용하지 않습니다.
OnDisable, OnDestroy
OnDisable 메소드는 게임 오브젝트가 비활성화될 때 호출됩니다. 이는 오브젝트의 SetActive(false) 메소드가 호출되거나, 오브젝트를 포함하고 있는 부모 오브젝트가 비활성화될 때 발생할 수 있습니다. 또한, 오브젝트가 파괴되기 직전에도 OnDisable이 호출됩니다.
즉, OnDisable은 게임 오브젝트가 파괴되기 직전, OnDestroy가 호출되기 바로 전에도 발생합니다.
OnDestroy 메소드는 게임 오브젝트가 파괴될 때 호출됩니다. 이는 Destroy(gameObject) 메소드가 호출되어 오브젝트가 파괴될 때 발생합니다.