Godot 4의 CharacterBody2D/3D — 완벽 가이드

1. 소개

Godot 4는 캐릭터 이동에 큰 변화를 도입했습니다. 기존의 KinematicBody2DKinematicBody3D 노드는 CharacterBody2DCharacterBody3D로 이름이 바뀌었고, move_and_slide() API는 완전히 재설계되었습니다. Godot 3에서 마이그레이션하든 새로 시작하든, 이 가이드는 알아야 할 모든 것을 다룹니다.

CharacterBody2DCharacterBody3D는 코드로 제어하는 캐릭터를 위해 설계된 물리 바디입니다. RigidBody와 달리 힘에 자동으로 반응하지 않으며, 이동을 전적으로 스크립트로 제어합니다. 그래서 플레이어 캐릭터, NPC, 그리고 정밀하고 결정론적인 이동이 필요한 모든 것에 이상적입니다.

2. Godot 3에서 달라진 점

Godot 3에서 Godot 4로 넘어가면서 캐릭터 이동과 관련해 가장 중요하게 바뀐 점은 다음과 같습니다:

핵심 포인트

Godot 3에서는 move_and_slide()가 결과 속도를 반환했습니다. Godot 4에서는 bool(충돌이 발생했는지 여부)을 반환하며, 속도는 velocity 속성에서 직접 갱신됩니다.

3. 기본 2D 플랫포머 컨트롤러

이것은 표준적인 횡스크롤 플랫포머 컨트롤러입니다. 캐릭터는 좌우로 이동할 수 있고, 지면에 있을 때 점프할 수 있습니다. 이것은 새 CharacterBody2D 스크립트를 만들 때 Godot이 생성하는 템플릿입니다.

GDScript — platformer_controller.gd
extends CharacterBody2D

const SPEED = 300.0
const JUMP_VELOCITY = -400.0

func _physics_process(delta: float) -> void:
    # Apply gravity when not on floor
    if not is_on_floor():
        velocity += get_gravity() * delta

    # Jump when on floor and jump pressed
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Horizontal movement
    var direction := Input.get_axis("ui_left", "ui_right")
    if direction:
        velocity.x = direction * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)

    move_and_slide()

한 줄씩 살펴보기

팁: 필수 노드 구조

CharacterBody2D에는 형상(예: RectangleShape2D 또는 CapsuleShape2D)이 할당된 CollisionShape2D 자식 노드가 최소 하나 이상 필요합니다. 이것이 없으면 move_and_slide()가 어떤 충돌도 감지하지 못합니다.

4. 기본 2D 탑다운 컨트롤러

탑다운 게임(RPG, 트윈스틱 슈터 등)에서는 중력 없이 네 방향으로 이동합니다. 코드는 플랫포머보다 간단합니다.

GDScript — topdown_controller.gd
extends CharacterBody2D

const SPEED = 200.0

func _physics_process(delta: float) -> void:
    var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    velocity = input_direction * SPEED
    move_and_slide()

작동 방식

가속과 마찰 추가하기

가속과 감속으로 더 부드러운 조작감을 원한다면:

GDScript — topdown_smooth.gd
extends CharacterBody2D

const SPEED = 200.0
const ACCELERATION = 1200.0
const FRICTION = 1000.0

func _physics_process(delta: float) -> void:
    var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")

    if input_direction != Vector2.ZERO:
        velocity = velocity.move_toward(input_direction * SPEED, ACCELERATION * delta)
    else:
        velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)

    move_and_slide()

5. 3D 3인칭 컨트롤러

3D 버전도 동일한 패턴을 따릅니다. 주요 차이점은 Vector3를 사용하고 transform.basis를 이용해 2D 입력을 3D 월드 공간 이동으로 변환한다는 점입니다.

GDScript — character_3d.gd
extends CharacterBody3D

const SPEED = 5.0
const JUMP_VELOCITY = 4.5

func _physics_process(delta: float) -> void:
    # Apply gravity
    if not is_on_floor():
        velocity += get_gravity() * delta

    # Jump
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Get input and convert to 3D direction
    var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

    if direction:
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)

    move_and_slide()

2D와의 주요 차이점

주의: get_gravity()는 Godot 4.4 이상이 필요합니다

get_gravity() 메서드는 Godot 4.4에서 추가되었습니다. 이전 버전에서는 2D의 경우 Vector2(0, ProjectSettings.get_setting("physics/2d/default_gravity")), 3D의 경우 Vector3(0, -ProjectSettings.get_setting("physics/3d/default_gravity"), 0)를 사용하세요.

6. 주요 속성 레퍼런스

CharacterBody2DCharacterBody3D는 모두 다음의 중요한 속성을 공유합니다. 인스펙터나 코드에서 조정하세요.

속성 타입 기본값 설명
velocity Vector2 / Vector3 Vector2.ZERO 캐릭터의 속도. move_and_slide()를 호출하기 전에 설정하세요. 호출 후에는 결과 속도로 갱신됩니다.
floor_snap_length float 1.0 move_and_slide_with_snap()을 대체합니다. 캐릭터를 바닥에 스냅하는 거리. 0으로 설정하면 비활성화됩니다. 경사와 계단에 유용합니다.
up_direction Vector2 / Vector3 Vector2.UP 어느 방향이 "위"인지 정의합니다. 이에 따라 무엇이 바닥, 벽, 천장으로 간주되는지 결정됩니다. 기본값은 2D에서 (0, -1), 3D에서 (0, 1, 0)입니다.
floor_stop_on_slope bool true true일 때, 정지 상태에서 캐릭터가 경사면을 미끄러져 내려가지 않습니다. 플랫포머에 필수적입니다.
floor_max_angle float 0.785 (45°) 걸어 다닐 수 있는 최대 경사 각도(라디안). 이보다 가파른 표면은 벽으로 취급됩니다. 60도로 설정하려면 deg_to_rad(60)을 사용하세요.
max_slides int 6 move_and_slide() 호출당 최대 충돌 반복 횟수. 값이 클수록 정확하지만 느립니다.
wall_min_slide_angle float 0.262 (15°) 벽 미끄러짐을 위한 최소 각도. 캐릭터가 거의 평행한 벽에 걸리는 것을 방지합니다.
platform_on_leave PlatformOnLeave ADD_VELOCITY 움직이는 플랫폼을 떠날 때의 동작. ADD_VELOCITY는 운동량을 유지하고, ADD_UPWARD_VELOCITY는 위쪽 성분만 추가하며, DO_NOTHING은 플랫폼 속도를 무시합니다.
slide_on_ceiling bool true true일 때, 천장에서 미끄러짐을 허용합니다. false일 때, 천장에 닿으면 수평 속도가 멈춥니다.

7. move_and_slide() 이후의 충돌 감지

move_and_slide()를 호출한 후, 발생한 충돌을 검사할 수 있습니다. 이는 특정 콜라이더 타입에 반응하거나, 착지 시 사운드 이펙트를 재생하거나, 벽 점프 메커니즘을 구현할 때 유용합니다.

GDScript — collision_detection.gd
func _physics_process(delta: float) -> void:
    # ... set velocity here ...
    move_and_slide()

    # Check all collisions from this frame
    for i in get_slide_collision_count():
        var collision := get_slide_collision(i)
        var collider := collision.get_collider()
        print("Collided with: ", collider.name)
        print("Normal: ", collision.get_normal())
        print("Position: ", collision.get_position())

    # Practical example: bounce off enemies
    for i in get_slide_collision_count():
        var collision := get_slide_collision(i)
        if collision.get_collider().is_in_group("enemies"):
            velocity = collision.get_normal() * 300
            break

유용한 KinematicCollision 메서드

8. 마이그레이션 치트 시트

Godot 3 코드를 Godot 4로 변환하기 위한 빠른 레퍼런스:

Godot 3 Godot 4
KinematicBody2D CharacterBody2D
KinematicBody3D CharacterBody3D
velocity = move_and_slide(velocity, Vector2.UP) velocity = ...
move_and_slide()
move_and_slide_with_snap(vel, snap, up) floor_snap_length = 4.0
move_and_slide()
var vel = move_and_slide(...)
velocity를 반환
move_and_slide()
velocity 속성이 직접 갱신됨
var gravity = ProjectSettings.get("physics/2d/default_gravity") get_gravity() (4.4+)
move_and_slide(..., up_direction, ...) up_direction = Vector2.UP (속성으로 설정)
move_and_slide(..., stop_on_slope, ...) floor_stop_on_slope = true (속성으로 설정)
Godot의 자동 변환기

Godot 3 프로젝트를 Godot 4에서 열면, 엔진이 프로젝트 변환을 제안합니다. 노드 이름을 바꾸고 스크립트 업데이트를 시도하지만, move_and_slide() 호출을 항상 올바르게 변환하지는 못합니다. 이동 코드를 직접 수정해야 할 가능성이 높습니다.

9. 흔한 실수

실수 1: move_and_slide()에 인자를 전달하기

잘못된 예
# ERROR: move_and_slide() takes no arguments in Godot 4
velocity = move_and_slide(velocity, Vector2.UP)
올바른 예
# Set velocity, then call move_and_slide() with no arguments
velocity.x = direction * SPEED
move_and_slide()

실수 2: move_and_slide() 전에 velocity 설정을 잊어버리기

잘못된 예 — velocity가 설정되지 않아 아무것도 움직이지 않음
func _physics_process(delta: float) -> void:
    move_and_slide()  # velocity is Vector2.ZERO — nothing happens

실수 3: _physics_process() 대신 _process() 사용하기

move_and_slide()는 고정된 속도(기본 60Hz)로 실행되는 _physics_process() 내에서 호출해야 합니다. _process()를 사용하면 물리가 렌더링 프레임 레이트에 묶여 동작이 일관되지 않게 됩니다.

실수 4: CollisionShape를 설정하지 않기

CollisionShape 자식이 없는 CharacterBody2D/CharacterBody3D는 모든 것을 통과해 지나갑니다. 충돌 형상이 없으면 Godot이 에디터에 경고 아이콘을 표시합니다.

실수 5: 매 프레임 velocity.y를 중력으로 덮어쓰기

잘못된 예 — velocity.y가 리셋되어 점프가 절대 작동하지 않음
# This overwrites any jump velocity!
velocity.y = gravity * delta  # should be +=, not =
올바른 예 — 중력을 누적
# Gravity accumulates over time
velocity += get_gravity() * delta

실수 6: 바닥에 있을 때 중력을 적용하기

중력은 항상 if not is_on_floor()로 감싸세요. 이 검사가 없으면 바닥에 있는 동안에도 중력이 계속 누적됩니다. 캐릭터가 가장자리에서 걸어 나가면, 자연스럽게 떨어지는 대신 극단적인 속도로 급강하합니다.

10. AI로 캐릭터 컨트롤러 설정하기

AI가 캐릭터 컨트롤러를 설정해 주길 원하시나요?

Godot MCP Pro를 사용하면 AI 어시스턴트가 CharacterBody 노드 생성, 물리 속성 구성, 충돌 형상 설정, 이동 스크립트 작성, 게임 실행, 이동 테스트까지 — 모두 자연어 프롬프트만으로 처리할 수 있습니다.

add_node setup_physics_body setup_collision set_physics_layers create_script simulate_key play_scene capture_frames
Godot MCP Pro 받기 — $15