2D Godot 2D 게임 기초
P2 움직임과 물리

8강_process와 _physics_process

렌더 프레임과 물리 프레임은 다른 시계다.

이 강의 목차 (6)
  1. 두 개의 시계
  2. 왜 굳이 나눴을까
  3. 어디에 무엇을 넣나
  4. 물리 틱 수를 바꾸기
  5. 떨림(jitter) 문제와 물리 보간
  6. 실습 — 두 시계를 눈으로 비교하기
이번 강의의 목표
  • 렌더 프레임과 물리 프레임이 왜 나뉘어 있는지 설명할 수 있다.
  • 어떤 코드를 어디에 넣어야 하는지 판단한다.
  • 물리 프레임이 렌더보다 느릴 때 생기는 떨림(jitter) 을 알고 대처한다.

두 개의 시계#

3강에서 잠깐 언급했던 걸 이제 제대로 봅니다.

_process(delta) _physics_process(delta)
언제 프레임을 그릴 때마다 고정된 간격마다
빈도 기기 성능·주사율에 따라 변함 기본 초당 60회 고정
delta 값 매번 다름 (0.008 ~ 0.05...) 항상 같음 (0.016666...)
용도 보기, 연출, UI 움직임, 물리, 충돌, 게임 규칙
카메라 셔터와 심장박동
  • _process = 카메라 셔터. 손이 빠르면 많이 찍고, 느리면 적게 찍는다.
  • _physics_process = 심장박동. 상황과 무관하게 일정하게 뛴다.

사진을 몇 장 찍든 심장은 1분에 60번 뜁니다. 반대로 심장이 아무리 뛰어도 사진은 찍을 때만 남습니다. 게임은 이 둘이 각자 돌아갑니다.

왜 굳이 나눴을까#

"그냥 하나로 하면 안 되나?" 이유가 세 가지 있습니다.

이유 1 — 계산 결과가 기기마다 달라지면 안 된다#

4강에서 delta를 곱해 속도는 맞췄습니다. 그런데 곱셈 하나로 안 되는 계산이 있습니다.

gdscript
# 중력을 적분하는 코드
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의 물리 단계는 이렇게 동작합니다.

text
렌더 프레임에서 0.05초가 흘렀다
  → 물리 프레임(1/60 = 0.0167초)을 3번 돌린다
  → 각 스텝마다 충돌 검사를 한다

한 렌더 프레임 안에서 물리는 여러 번 돌 수 있습니다. 이걸 고정 타임스텝(fixed timestep) 이라고 부릅니다.

그래도 뚫린다면 — CCD

물체가 아주 빠르면 한 물리 스텝(1/60초) 안에서도 벽을 넘어갈 수 있습니다. 그때 쓰는 것이 CCD(Continuous Collision Detection, 연속 충돌 검사) 입니다. "이 위치에서 저 위치까지 선을 그어 그 선이 뭔가를 지나갔는지" 검사합니다.

  • RigidBody2D → 인스펙터의 Continuous CDCast Ray
  • 직접 만든 총알 → RayCast2D / ShapeCast2D 로 이동 경로를 검사 (13강)

CCD는 비싸므로 필요한 물체에만 켭니다.

이유 3 — 물리 엔진의 계산 순서가 정해져 있다#

물리 엔진은 "모든 물체의 속도를 적분 → 충돌 검사 → 밀어내기 → 위치 확정"을 한 덩어리로 처리합니다. 이 중간에 아무 때나 위치를 대입하면 계산이 어긋납니다.

그래서 물리 바디의 위치·속도는 반드시 _physics_process 안에서만 건드립니다.

절대 하면 안 되는 것
gdscript
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번만 갱신됩니다. 같은 위치를 두 번 그리는 프레임이 생기고, 캐릭터가 미세하게 떠는 것처럼 보입니다.

물리 보간(Physics Interpolation)

해결책은 "두 물리 스텝 사이의 위치를 추정해서 그리는" 것입니다. 직전 물리 위치와 현재 물리 위치 사이를, 지금이 몇 % 지점인지에 따라 섞어 그립니다.

Godot 4.3부터 엔진이 지원합니다. 프로젝트 설정 → Physics → Common → Physics Interpolation 켜기.

직접 계산해야 할 때는 이 값을 씁니다.

gdscript
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강에서 다시 언급합니다.

실습 — 두 시계를 눈으로 비교하기#

gdscript
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

실행하면 이런 출력이 나옵니다.

text
1초 동안 → 렌더 60회 / 물리 60회

이제 3강의 Load Level 부하를 걸거나 Engine.max_fps = 15 로 낮춰보세요.

text
1초 동안 → 렌더 15회 / 물리 60회

렌더는 떨어져도 물리는 60을 유지합니다. 이게 두 시계가 따로 도는 증거입니다.

직접 바꿔보기
  1. Engine.max_fps 를 15 / 30 / 200 으로 바꿔가며 두 숫자의 관계를 보세요.
  2. 프로젝트 설정 → Physics Ticks Per Second 를 20으로 낮춰보세요. 캐릭터 움직임이 어떻게 보입니까? (뚝뚝 끊깁니다)
  3. 그 상태에서 Physics Interpolation 을 켜고 다시 보세요. 부드러워집니까?
  4. _process_physics_process 양쪽에 같은 이동 코드를 넣어보세요. 캐릭터가 2배 빨라집니다. 이게 "왜 속도가 이상하지"의 흔한 원인입니다.
  • 개발자 — 물리 바디의 position을 직접 대입해야 할 때는 _physics_process 안에서 하거나, RigidBody2D라면 _integrate_forces() 를 쓰세요.
  • 기획자 — "이 점프로 저기 닿아야 한다" 같은 정밀 스펙은 물리 틱에 의존합니다. 중간에 물리 틱을 바꾸면 밸런스가 통째로 흔들립니다. 초기에 정하고 안 건드리는 게 좋습니다.
  • 디자이너 — 애니메이션은 _process 쪽 세상입니다. 프레임이 떨어지면 애니메이션도 느려 보이지만 게임 규칙(판정)은 그대로입니다. 연출과 판정이 어긋나 보이는 이유가 이겁니다.
자주 나는 오류

"캐릭터가 벽에 붙으면 부들부들 떤다"_process에서 위치를 만지고 있거나, 서로 밀어내는 코드가 두 군데 있습니다.

"속도가 정확히 2배다" → 이동 코드가 _process_physics_process 양쪽에 있습니다.

"120Hz 모니터에서만 미세하게 떤다" → 물리 보간을 켜세요. Godot 4.3 미만이면 직접 보간하거나 물리 틱을 주사율에 맞춥니다.

여기까지 확인
  • _physics_process의 delta는 항상 고정값이다
  • 한 렌더 프레임에 물리가 여러 번 돌 수 있다
  • 게임 규칙은 물리 프레임, 보기는 렌더 프레임
  • 실습에서 두 숫자가 따로 노는 걸 확인했다

다음 강의 — 드디어 물리 바디를 씁니다. 벽에 막히고, 미끄러지고, 중력을 받는 진짜 캐릭터를 만듭니다.