Update & Lifecycle
Update는 매 프레임, FixedUpdate는 고정 Physics 틱, LateUpdate는 모든 Update 이후에 실행됩니다. Time.deltaTime으로 FPS 독립 이동을 맞추고 물리·카메라·입력에 맞는 훅을 고릅니다. 실무에서 바로 쓰는 패턴을 익힙니다.
상상해 보세요...
Update는 FPS에 맞춰 가는 아날로그 시계——눈이 보는 것: 이동, 입력, AI. FixedUpdate는 일정한 박자의 디지털 시계(0.02초)——물리가 떨리지 않게. LateUpdate는 맨 마지막——Camera가 모두 움직인 뒤 따라가 한 프레임 끊김을 막습니다.
개념 자세히
Update()는 매 프레임——빈도는 FPS를 따릅니다
(30, 60, 120…). 이동/속도에 Time.deltaTime을 곱해 프레임률과 무관하게
결과를 맞춥니다.
FixedUpdate()는 Time.fixedDeltaTime
(기본 0.02초 = 초당 50회)으로 돕니다. FPS와 무관합니다.
Rigidbody.AddForce(), Rigidbody.velocity, Physics 쿼리에 쓰세요.
한 렌더 프레임에 FixedUpdate가 0·1·여러 번일 수 있습니다.
LateUpdate()는 그 프레임의 모든 스크립트 Update() 이후에 실행됩니다. Camera follow(플레이어가 이미 이동), IK, Update 이후 결과를 “읽어야” 하는 로직에 이상적입니다.
OnGUI()는 레거시입니다. Update vs 코루틴: 일회성·지연 로직은 Update에서 매 프레임 폴링보다 Coroutine을 선호하세요.
다이어그램: 한 프레임 안의 순서
FixedUpdate() × N
N = 이전 프레임에서 쌓인 timestep — 보통 0 또는 1, 렉 시 더 많을 수 있음
Update() — 모든 스크립트
Input, AI, 게임 로직, 애니메이션 트리거
LateUpdate() — 모든 스크립트
Camera follow, IK, Update 이후 로직
실습 단계
Update에서 항상 Time.deltaTime 곱하기
transform.position += speed * Time.deltaTime * direction — 초당 단위, FPS 독립.
물리 힘 → FixedUpdate
AddForce()와 velocity는 FixedUpdate. 입력은 Update에서 읽고 FixedUpdate에서 적용.
카메라 추종 → LateUpdate
카메라 스크립트는 LateUpdate——플레이어가 이미 움직여 한 프레임 지연이 없습니다.
쓰지 않는 Update 삭제
빈 Update도 오버헤드가 있습니다. 매 프레임이 필요 없으면 Event나 Coroutine을 쓰세요.
인터랙티브 시뮬레이터
Run을 눌러 프레임 루프를 단계별로 보세요. FPS와 Physics 주기를 조절해 보세요.
FixedUpdate
0
calls
Update
0
calls
LateUpdate
0
calls
Render
0
frames
코드 예제
기본Update·FixedUpdate·LateUpdate에 로직을 올바르게 나눕니다.
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float speed = 5f;
[SerializeField] private float jumpForce = 8f;
private Rigidbody _rb;
private Vector3 _moveDir;
private bool _jumpRequest;
void Awake() => _rb = GetComponent<Rigidbody>();
void Update()
{
// Input — every frame, store for FixedUpdate
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
_moveDir = new Vector3(h, 0, v).normalized;
if (Input.GetButtonDown("Jump")) _jumpRequest = true;
}
void FixedUpdate()
{
// Apply physics — do not use Time.deltaTime here
_rb.velocity = new Vector3(_moveDir.x * speed, _rb.velocity.y, _moveDir.z * speed);
if (_jumpRequest)
{
_rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
_jumpRequest = false;
}
}
}
public class CameraFollow : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private Vector3 offset = new Vector3(0, 5, -8);
void LateUpdate()
{
// After target.Update() — target pose is final
transform.position = Vector3.Lerp(transform.position,
target.position + offset, Time.deltaTime * 5f);
}
}코드 예제
고급매 프레임이 필요 없는 로직은 Update 대신 Coroutine을 선호하세요.
public class EnemyAI : MonoBehaviour
{
private Transform _player;
private float _attackCooldown = 2f;
void Start()
{
_player = GameObject.FindWithTag("Player").transform;
// AI tick every 0.2s instead of every frame → saves CPU
StartCoroutine(AIUpdateLoop());
StartCoroutine(AttackLoop());
}
// Run AI logic 5×/sec instead of 60×/sec
private IEnumerator AIUpdateLoop()
{
var wait = new WaitForSeconds(0.2f);
while (true)
{
UpdatePathfinding();
yield return wait;
}
}
private IEnumerator AttackLoop()
{
while (true)
{
yield return new WaitForSeconds(_attackCooldown);
if (Vector3.Distance(transform.position, _player.position) < 3f)
Attack();
}
}
}📌 빠른 정리
- ▸Update의 모든 이동에
* Time.deltaTime - ▸물리 힘/velocity → FixedUpdate (보통 deltaTime 불필요)
- ▸카메라 추종 → LateUpdate (플레이어 이동 대기)
- ▸드문 로직 → Coroutine + WaitForSeconds
⚠️ 흔한 실수
❌ FixedUpdate에서 Time.fixedDeltaTime 없이 transform 이동
속도가 물리 주기에 의존——불일치
✅ Time.fixedDeltaTime을 곱하거나 MovePosition()
❌ FixedUpdate 안에서 Input 읽기
빠른 프레임에서 GetKeyDown을 놓칠 수 있음
✅ Update에서 Input 읽고 플래그 저장, FixedUpdate에서 적용