Godot 4 플랫포머에서 coyote time은 100ms(0.1초), jump buffer는 150ms(0.15초)가 권장 기본값입니다. 둘 다 _physics_process에서 float 타이머 변수를 delta만큼 감소시키는 방식으로 구현합니다 — coyote는 바닥을 떠난 직후 100ms 안의 점프 입력을 정상 점프로 처리하고, jump buffer는 착지 150ms 전에 누른 점프를 착지 순간 자동 발동시킵니다. 아래 표와 GDScript에 5요소 전체 수치를 정리합니다.
플랫포머의 "손에 감기는 조작감"은 5가지 역학의 조합입니다. 하나라도 빠지면 사용자는 "어색하다"고 느끼지만 무엇이 문제인지는 짚지 못합니다. Mario(1985)~Celeste(2018)까지 업계 표준으로 정착된 수치를 Godot 4 GDScript로 정리합니다.
| # | 요소 | 수치 기본값 | 효과 |
|---|---|---|---|
| 1 | Coyote time | 100ms (0.1s) | 플랫폼 엣지에서 떨어진 직후 이 시간 안에 점프 → 정상 점프 |
| 2 | Jump buffer | 150ms (0.15s) | 착지 직전 점프 입력 → 착지 순간 자동 발동 |
| 3 | Variable jump height | release 시 vy × 0.45 | 짧게 탭 = 낮은 점프, 홀드 = 높은 점프 |
| 4 | 비대칭 중력 | 하강 시 상승 중력 × 1.8 | 묵직한 착지감, apex에서 hang time |
| 5 | Screen shake on impact | 0.15s, intensity 6~12 | 피격·착지 임팩트 시 카메라 흔들림 |
extends CharacterBody2D
const SPEED := 200.0
const JUMP_VELOCITY := -400.0
const GRAVITY_UP := 900.0 # 상승 중력
const GRAVITY_DOWN := 1620.0 # 하강 중력 (상승 × 1.8)
const COYOTE_TIME := 0.10 # 100ms
const JUMP_BUFFER := 0.15 # 150ms
const VARIABLE_JUMP_MULT := 0.45
var coyote_timer := 0.0
var jump_buffer_timer := 0.0
var was_on_floor := false
func _physics_process(delta: float) -> void:
# 중력 (비대칭)
var grav = GRAVITY_UP if velocity.y < 0 else GRAVITY_DOWN
if not is_on_floor():
velocity.y += grav * delta
# Coyote time 갱신
if is_on_floor():
coyote_timer = COYOTE_TIME
else:
coyote_timer = maxf(0.0, coyote_timer - delta)
# Jump buffer 갱신
jump_buffer_timer = maxf(0.0, jump_buffer_timer - delta)
if Input.is_action_just_pressed("jump"):
jump_buffer_timer = JUMP_BUFFER
# 점프 발동 (coyote + buffer 조합)
if jump_buffer_timer > 0.0 and coyote_timer > 0.0:
velocity.y = JUMP_VELOCITY
jump_buffer_timer = 0.0
coyote_timer = 0.0
# Variable jump (키 놓으면 상승 속도 축약)
if Input.is_action_just_released("jump") and velocity.y < 0:
velocity.y *= VARIABLE_JUMP_MULT
# 수평 이동
var dir := Input.get_axis("move_left", "move_right")
velocity.x = dir * SPEED
move_and_slide()
extends Camera2D
var shake_intensity := 0.0
var shake_duration := 0.0
func shake(intensity: float, duration: float) -> void:
shake_intensity = intensity
shake_duration = duration
func _process(delta: float) -> void:
if shake_duration > 0.0:
shake_duration -= delta
offset = Vector2(
randf_range(-shake_intensity, shake_intensity),
randf_range(-shake_intensity, shake_intensity)
)
else:
offset = Vector2.ZERO
착지 시 $Camera.shake(8.0, 0.15), 피격 시 $Camera.shake(12.0, 0.15)로 호출합니다.