8강_process와 _physics_process
렌더 프레임과 물리 프레임은 다른 시계다.
- 렌더 프레임과 물리 프레임이 왜 나뉘어 있는지 설명할 수 있다.
- 어떤 코드를 어디에 넣어야 하는지 판단한다.
- 물리 프레임이 렌더보다 느릴 때 생기는 떨림(jitter) 을 알고 대처한다.
두 개의 시계#
3강에서 잠깐 언급했던 걸 이제 제대로 봅니다.
_process(delta) |
_physics_process(delta) |
|
|---|---|---|
| 언제 | 프레임을 그릴 때마다 | 고정된 간격마다 |
| 빈도 | 기기 성능·주사율에 따라 변함 | 기본 초당 60회 고정 |
| delta 값 | 매번 다름 (0.008 ~ 0.05...) | 항상 같음 (0.016666...) |
| 용도 | 보기, 연출, UI | 움직임, 물리, 충돌, 게임 규칙 |
_process= 카메라 셔터. 손이 빠르면 많이 찍고, 느리면 적게 찍는다._physics_process= 심장박동. 상황과 무관하게 일정하게 뛴다.
사진을 몇 장 찍든 심장은 1분에 60번 뜁니다. 반대로 심장이 아무리 뛰어도 사진은 찍을 때만 남습니다. 게임은 이 둘이 각자 돌아갑니다.
왜 굳이 나눴을까#
"그냥 하나로 하면 안 되나?" 이유가 세 가지 있습니다.
이유 1 — 계산 결과가 기기마다 달라지면 안 된다#
4강에서 delta를 곱해 속도는 맞췄습니다. 그런데 곱셈 하나로 안 되는 계산이 있습니다.
# 중력을 적분하는 코드
velocity.y += GRAVITY * delta
position.y += velocity.y * delta이건 수학적으로 근사값입니다. delta가 클수록(프레임이 낮을수록) 오차가 커집니다. 60 FPS에서 점프 최고 높이가 100픽셀이면, 20 FPS에서는 103픽셀쯤 됩니다.
혼자 하는 게임이면 티가 안 나지만, "이 점프로 저 발판에 닿아야 한다" 는 퍼즐이면 기기에 따라 클리어 여부가 갈립니다.
_physics_process는 delta가 항상 정확히 같으므로 이런 편차가 없습니다.
이유 2 — 빠른 물체가 벽을 뚫는다#
프레임이 뚝 떨어져 delta가 0.5초가 되면, 초속 1000픽셀인 총알은 한 프레임에 500픽셀을 갑니다. 그 사이에 벽이 있으면 그냥 통과합니다. 중간을 검사하지 않았으니까요.
Godot의 물리 단계는 이렇게 동작합니다.
렌더 프레임에서 0.05초가 흘렀다
→ 물리 프레임(1/60 = 0.0167초)을 3번 돌린다
→ 각 스텝마다 충돌 검사를 한다즉 한 렌더 프레임 안에서 물리는 여러 번 돌 수 있습니다. 이걸 고정 타임스텝(fixed timestep) 이라고 부릅니다.
그래도 뚫린다면 — CCD
물체가 아주 빠르면 한 물리 스텝(1/60초) 안에서도 벽을 넘어갈 수 있습니다. 그때 쓰는 것이 CCD(Continuous Collision Detection, 연속 충돌 검사) 입니다. "이 위치에서 저 위치까지 선을 그어 그 선이 뭔가를 지나갔는지" 검사합니다.
RigidBody2D→ 인스펙터의Continuous CD를Cast Ray로- 직접 만든 총알 →
RayCast2D/ShapeCast2D로 이동 경로를 검사 (13강)
CCD는 비싸므로 필요한 물체에만 켭니다.
이유 3 — 물리 엔진의 계산 순서가 정해져 있다#
물리 엔진은 "모든 물체의 속도를 적분 → 충돌 검사 → 밀어내기 → 위치 확정"을 한 덩어리로 처리합니다. 이 중간에 아무 때나 위치를 대입하면 계산이 어긋납니다.
그래서 물리 바디의 위치·속도는 반드시 _physics_process 안에서만 건드립니다.
func _process(delta):
# 물리 바디를 렌더 프레임에서 옮기면
# 충돌 판정이 어긋나고 떨림이 생긴다.
$Player.position += Vector2(5, 0)어디에 무엇을 넣나#
"이게 틀리면 게임 규칙이 틀리는가?"
→ 그렇다 = _physics_process
→ 아니다(그냥 보기 문제) = _process
| 코드 | 어디에 |
|---|---|
캐릭터 이동, move_and_slide() |
_physics_process |
| 중력, 점프 | _physics_process |
| 충돌 검사, 레이캐스트 | _physics_process |
| 적 AI 판단 | _physics_process |
| 쿨다운 감소, 체력 재생 | _physics_process |
| 카메라 따라가기 | _process (부드러워야 하므로) |
| 애니메이션 전환 | _process |
| UI 숫자 갱신 | _process |
| 파티클, 화면 흔들림 | _process |
| 색 깜빡임, 페이드 | _process |
물리 틱 수를 바꾸기#
프로젝트 설정 → Physics → Common → Physics Ticks Per Second (기본 60)
| 값 | 언제 |
|---|---|
| 30 | 모바일·웹에서 CPU를 아끼고 싶을 때. 판정이 거칠어짐 |
| 60 | 기본값. 대부분의 게임 |
| 120~144 | 격투·슈팅처럼 정밀한 판정이 필요할 때 |
"120으로 올리면 캐릭터가 2배 빨라지나요?" → 아닙니다.
delta가 절반이 되므로 결과는 같습니다. 대신 계산을 2배 자주 하므로 CPU를 2배 씁니다.
정밀도를 사는 대신 성능을 지불하는 설정입니다.
떨림(jitter) 문제와 물리 보간#
여기서 실무에서 진짜 자주 만나는 현상이 나옵니다.
상황 — 모니터는 144Hz인데 물리는 60Hz입니다. 1초에 144번 그리는데 위치는 60번만 갱신됩니다. 같은 위치를 두 번 그리는 프레임이 생기고, 캐릭터가 미세하게 떠는 것처럼 보입니다.
해결책은 "두 물리 스텝 사이의 위치를 추정해서 그리는" 것입니다. 직전 물리 위치와 현재 물리 위치 사이를, 지금이 몇 % 지점인지에 따라 섞어 그립니다.
Godot 4.3부터 엔진이 지원합니다.
프로젝트 설정 → Physics → Common → Physics Interpolation 켜기.
직접 계산해야 할 때는 이 값을 씁니다.
func _process(_delta: float) -> void:
# 0.0 ~ 1.0. 지금이 두 물리 스텝 사이 어디쯤인지.
var f := Engine.get_physics_interpolation_fraction()
$Sprite.global_position = _prev_pos.lerp(_curr_pos, f)왜 이 문제가 이제야 생겼나
예전에는 대부분 60Hz 모니터였고 물리도 60Hz라 딱 맞아떨어졌습니다. 요즘은 90/120/144/165Hz가 흔해지면서 어긋나는 조합이 많아졌습니다.
폴드 같은 최신 스마트폰도 120Hz입니다. 웹으로 빌드해서 폰에서 돌리면 PC에서는 안 보이던 떨림이 보일 수 있습니다. 26강에서 다시 언급합니다.
실습 — 두 시계를 눈으로 비교하기#
extends Node2D
var _p_frames := 0 # 렌더 프레임 수
var _f_frames := 0 # 물리 프레임 수
var _acc := 0.0
func _process(delta: float) -> void:
_p_frames += 1
_acc += delta
if _acc >= 1.0:
print("1초 동안 → 렌더 %d회 / 물리 %d회"
% [_p_frames, _f_frames])
_p_frames = 0
_f_frames = 0
_acc = 0.0
func _physics_process(_delta: float) -> void:
_f_frames += 1실행하면 이런 출력이 나옵니다.
1초 동안 → 렌더 60회 / 물리 60회이제 3강의 Load Level 부하를 걸거나 Engine.max_fps = 15 로 낮춰보세요.
1초 동안 → 렌더 15회 / 물리 60회렌더는 떨어져도 물리는 60을 유지합니다. 이게 두 시계가 따로 도는 증거입니다.
Engine.max_fps를 15 / 30 / 200 으로 바꿔가며 두 숫자의 관계를 보세요.프로젝트 설정 → Physics Ticks Per Second를 20으로 낮춰보세요. 캐릭터 움직임이 어떻게 보입니까? (뚝뚝 끊깁니다)- 그 상태에서 Physics Interpolation 을 켜고 다시 보세요. 부드러워집니까?
_process와_physics_process양쪽에 같은 이동 코드를 넣어보세요. 캐릭터가 2배 빨라집니다. 이게 "왜 속도가 이상하지"의 흔한 원인입니다.
- 개발자 — 물리 바디의
position을 직접 대입해야 할 때는_physics_process안에서 하거나,RigidBody2D라면_integrate_forces()를 쓰세요. - 기획자 — "이 점프로 저기 닿아야 한다" 같은 정밀 스펙은 물리 틱에 의존합니다. 중간에 물리 틱을 바꾸면 밸런스가 통째로 흔들립니다. 초기에 정하고 안 건드리는 게 좋습니다.
- 디자이너 — 애니메이션은
_process쪽 세상입니다. 프레임이 떨어지면 애니메이션도 느려 보이지만 게임 규칙(판정)은 그대로입니다. 연출과 판정이 어긋나 보이는 이유가 이겁니다.
"캐릭터가 벽에 붙으면 부들부들 떤다"
→ _process에서 위치를 만지고 있거나, 서로 밀어내는 코드가 두 군데 있습니다.
"속도가 정확히 2배다"
→ 이동 코드가 _process와 _physics_process 양쪽에 있습니다.
"120Hz 모니터에서만 미세하게 떤다" → 물리 보간을 켜세요. Godot 4.3 미만이면 직접 보간하거나 물리 틱을 주사율에 맞춥니다.
-
_physics_process의 delta는 항상 고정값이다 - 한 렌더 프레임에 물리가 여러 번 돌 수 있다
- 게임 규칙은 물리 프레임, 보기는 렌더 프레임
- 실습에서 두 숫자가 따로 노는 걸 확인했다
다음 강의 — 드디어 물리 바디를 씁니다. 벽에 막히고, 미끄러지고, 중력을 받는 진짜 캐릭터를 만듭니다.