빠른 비교
private Vector2 _moveInput;
private void Update()
{
// 입력은 Update에서 캡처
_moveInput = new Vector2(
Input.GetAxisRaw("Horizontal"),
Input.GetAxisRaw("Vertical")
);
}
private void FixedUpdate()
{
// 물리 이동은 FixedUpdate에서 소비
Vector3 velocity = new Vector3(_moveInput.x, 0f, _moveInput.y) * moveSpeed;
_rb.MovePosition(_rb.position + velocity * Time.fixedDeltaTime);
}
private void LateUpdate()
{
// 카메라는 플레이어가 움직인 뒤
_cameraRig.position = target.position + offset;
}갈리는 기준
Update — 프레임 기준 로직
Update는 화면 프레임마다 한 번 호출됩니다. 기기 성능과 현재 부하에 따라 호출 간격이 달라지므로 Time.deltaTime으로 시간 보정을 해야 합니다.
입력 읽기, 상태 갱신, 타이머 누적, 애니메이션 파라미터 설정처럼 프레임 단위로 반응해야 하는 작업이 여기에 잘 맞습니다.
FixedUpdate — 물리 타임스텝 기준
FixedUpdate는 고정 타임스텝(기본 0.02초, 50Hz)으로 호출됩니다. 프레임 속도와 무관하게 일정한 간격으로 실행되기 때문에 물리 엔진과 동기화된 작업에 적합합니다.
중요한 점은 프레임마다 정확히 한 번 호출되지 않는다는 것입니다. 프레임이 느리면 물리 스텝을 따라잡기 위해 같은 프레임에서 여러 번 실행되고, 프레임이 매우 빠르면 건너뛰기도 합니다.
private void FixedUpdate()
{
// Rigidbody 이동 — 물리 경유 이동은 FixedUpdate에
_rb.MovePosition(_rb.position + _moveDir * speed * Time.fixedDeltaTime);
// 힘 적용도 FixedUpdate에
if (_jumpRequested)
{
_rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
_jumpRequested = false;
}
}입력을 FixedUpdate에서 직접 읽으면 안 되는 이유
버튼 입력(GetKeyDown, GetButtonDown)은 딱 한 프레임 동안만 true입니다. FixedUpdate가 그 프레임을 건너뛰면 입력을 놓치고, 여러 번 실행되는 프레임에서는 같은 입력을 중복 처리할 수 있습니다.
Update에서 읽어 플래그 변수에 저장하고, FixedUpdate에서 소비하는 패턴이 가장 안정적입니다.
// Update: 입력 캡처
private void Update()
{
if (Input.GetButtonDown("Jump")) _jumpRequested = true;
}
// FixedUpdate: 물리 반영 후 플래그 초기화
private void FixedUpdate()
{
if (_jumpRequested)
{
_rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
_jumpRequested = false;
}
}LateUpdate — 모든 Update 이후
LateUpdate는 같은 프레임의 모든 Update가 끝난 뒤 호출됩니다. 플레이어가 먼저 움직인 뒤 카메라가 따라오게 하거나, 여러 오브젝트 위치가 확정된 뒤 최종 보정을 적용할 때 씁니다.
선택 기준
| 메서드 | 호출 간격 | 추천 역할 | 시간 변수 |
|---|---|---|---|
Update | 매 프레임 (가변) | 입력, 로직, 타이머 | Time.deltaTime |
FixedUpdate | 고정 타임스텝 (기본 50Hz) | Rigidbody 이동, 힘 적용 | Time.fixedDeltaTime |
LateUpdate | 매 프레임, Update 이후 | 카메라, 후처리 보정 | Time.deltaTime |
| 상황 | 어디에 |
|---|---|
| 버튼 입력 읽기 | Update |
| 입력 기반 Rigidbody 이동 | Update에서 캡처 → FixedUpdate에서 적용 |
transform 직접 이동 (물리 미사용) | Update |
힘 적용, MovePosition | FixedUpdate |
| 플레이어 추적 카메라 | LateUpdate |
| 비교 | 더 잘 맞는 쪽 | 이유 |
|---|---|---|
| 입력 읽기 vs 물리 이동 | Update / FixedUpdate 분리 | 입력 유실과 중복 물리 적용을 막기 쉬움 |
transform 직접 이동 vs Rigidbody 이동 | 물리 미사용은 Update, 물리 사용은 FixedUpdate | 물리 시뮬레이션과 호출 타이밍이 다름 |
| 오브젝트 이동 vs 카메라 추적 | Update 또는 FixedUpdate 후 LateUpdate | 카메라가 한 프레임 늦지 않게 따라감 |
주의할 점
FixedUpdate는 "프레임마다 정확히 한 번" 도는 함수가 아닙니다. GetKeyDown처럼 단발 입력을 FixedUpdate에서 직접 읽으면 입력을 놓치거나 중복 처리가 생깁니다. 입력은 항상 Update에서 캡처하고, 물리 반영만 FixedUpdate로 넘기는 흐름을 기본값으로 두세요.
// ❌ Rigidbody 이동을 Update에서 처리
private void Update()
{
_rb.MovePosition(_rb.position + moveDir * speed * Time.deltaTime);
}
// → 프레임 속도에 따라 물리 반응이 흔들리고 충돌 일관성이 떨어질 수 있음
// ✅ 입력은 Update, 이동은 FixedUpdate
private void Update()
{
moveDir = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
}
private void FixedUpdate()
{
_rb.MovePosition(_rb.position + moveDir * speed * Time.fixedDeltaTime);
}참고 링크
3 sources