CharacterBody2D/3D trong Godot 4 — Hướng dẫn đầy đủ

1. Giới thiệu

Godot 4 đã đưa vào những thay đổi lớn đối với chuyển động của nhân vật. Các node cũ KinematicBody2DKinematicBody3D được đổi tên thành CharacterBody2DCharacterBody3D, còn API move_and_slide() đã được thiết kế lại hoàn toàn. Dù bạn đang chuyển đổi từ Godot 3 hay bắt đầu từ đầu, hướng dẫn này bao quát mọi điều bạn cần biết.

CharacterBody2DCharacterBody3D là các physics body được thiết kế cho những nhân vật điều khiển bằng mã. Khác với RigidBody, chúng không tự động phản ứng với các lực — bạn điều khiển toàn bộ chuyển động của chúng thông qua script. Điều này khiến chúng trở thành lựa chọn lý tưởng cho nhân vật người chơi, NPC và bất cứ thứ gì cần chuyển động chính xác, có tính tất định.

2. Những thay đổi so với Godot 3

Dưới đây là những thay đổi quan trọng nhất giữa Godot 3 và Godot 4 đối với chuyển động của nhân vật:

Điểm mấu chốt

Trong Godot 3, move_and_slide() trả về vận tốc kết quả. Trong Godot 4, nó trả về một bool (liệu có xảy ra va chạm hay không), và vận tốc được cập nhật ngay tại chỗ trên thuộc tính velocity.

3. Controller platformer 2D cơ bản

Đây là controller platformer cuộn cảnh ngang tiêu chuẩn. Nhân vật có thể di chuyển trái/phải và nhảy khi đang ở trên mặt đất. Đây chính là template mà Godot sinh ra khi bạn tạo một script CharacterBody2D mới.

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()

Giải thích từng dòng

Mẹo: Cấu trúc node bắt buộc

CharacterBody2D của bạn cần ít nhất một node con CollisionShape2D đã được gán một shape (ví dụ RectangleShape2D hoặc CapsuleShape2D). Nếu thiếu, move_and_slide() sẽ không phát hiện được va chạm nào.

4. Controller top-down 2D cơ bản

Với các game top-down (RPG, twin-stick shooter, v.v.), bạn di chuyển theo cả bốn hướng mà không có trọng lực. Mã đơn giản hơn so với platformer.

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()

Cách hoạt động

Thêm gia tốc và ma sát

Để có cảm giác mượt mà hơn với gia tốc và giảm tốc:

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. Controller góc nhìn thứ ba 3D

Phiên bản 3D tuân theo cùng một mô hình. Khác biệt chính là làm việc với Vector3 và dùng transform.basis để chuyển đầu vào 2D thành chuyển động trong không gian thế giới 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()

Khác biệt chính so với 2D

Cảnh báo: get_gravity() yêu cầu Godot 4.4+

Phương thức get_gravity() được bổ sung trong Godot 4.4. Với các phiên bản cũ hơn, hãy dùng Vector2(0, ProjectSettings.get_setting("physics/2d/default_gravity")) cho 2D, hoặc Vector3(0, -ProjectSettings.get_setting("physics/3d/default_gravity"), 0) cho 3D.

6. Tham chiếu các thuộc tính chính

Cả CharacterBody2DCharacterBody3D đều chia sẻ những thuộc tính quan trọng này. Hãy điều chỉnh chúng trong Inspector hoặc bằng mã.

Thuộc tính Kiểu Mặc định Mô tả
velocity Vector2 / Vector3 Vector2.ZERO Vận tốc của nhân vật. Đặt giá trị này trước khi gọi move_and_slide(). Sau khi gọi, nó được cập nhật với vận tốc kết quả.
floor_snap_length float 1.0 Thay thế cho move_and_slide_with_snap(). Khoảng cách để snap nhân vật xuống sàn. Đặt thành 0 để vô hiệu hóa. Hữu ích cho dốc và cầu thang.
up_direction Vector2 / Vector3 Vector2.UP Xác định hướng nào là "lên". Điều này quyết định thứ gì được tính là sàn, tường hay trần. Mặc định là (0, -1) trong 2D, (0, 1, 0) trong 3D.
floor_stop_on_slope bool true Khi bật true, nhân vật sẽ không trượt xuống dốc khi đứng yên. Thiết yếu cho platformer.
floor_max_angle float 0.785 (45°) Góc dốc tối đa có thể đi bộ, tính bằng radian. Bề mặt dốc hơn mức này được coi là tường. Dùng deg_to_rad(60) để đặt 60 độ.
max_slides int 6 Số lần lặp va chạm tối đa cho mỗi lần gọi move_and_slide(). Giá trị cao hơn thì chính xác hơn nhưng chậm hơn.
wall_min_slide_angle float 0.262 (15°) Góc tối thiểu để trượt trên tường. Ngăn nhân vật bị kẹt trên những bức tường gần như song song.
platform_on_leave PlatformOnLeave ADD_VELOCITY Hành vi khi rời khỏi một platform đang di chuyển. ADD_VELOCITY giữ lại động lượng, ADD_UPWARD_VELOCITY chỉ thêm thành phần hướng lên, DO_NOTHING bỏ qua vận tốc của platform.
slide_on_ceiling bool true Khi bật true, cho phép trượt trên trần. Khi false, dừng vận tốc ngang khi va vào trần.

7. Phát hiện va chạm sau move_and_slide()

Sau khi gọi move_and_slide(), bạn có thể kiểm tra các va chạm đã xảy ra. Điều này hữu ích để phản ứng với các loại collider cụ thể, phát hiệu ứng âm thanh khi tiếp đất, hoặc triển khai cơ chế nhảy tường.

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

Các phương thức KinematicCollision hữu ích

8. Bảng tra cứu chuyển đổi

Tham chiếu nhanh để chuyển mã Godot 3 sang 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(...)
trả về vận tốc
move_and_slide()
thuộc tính velocity được cập nhật tại chỗ
var gravity = ProjectSettings.get("physics/2d/default_gravity") get_gravity() (4.4+)
move_and_slide(..., up_direction, ...) up_direction = Vector2.UP (đặt làm thuộc tính)
move_and_slide(..., stop_on_slope, ...) floor_stop_on_slope = true (đặt làm thuộc tính)
Công cụ tự động chuyển đổi của Godot

Khi mở một dự án Godot 3 trong Godot 4, engine sẽ đề nghị chuyển đổi dự án của bạn. Nó đổi tên các node và cố gắng cập nhật script, nhưng không phải lúc nào cũng xử lý đúng các lời gọi move_and_slide(). Nhiều khả năng bạn sẽ phải tự sửa mã chuyển động của mình.

9. Những lỗi thường gặp

Lỗi 1: Truyền đối số vào move_and_slide()

Sai
# ERROR: move_and_slide() takes no arguments in Godot 4
velocity = move_and_slide(velocity, Vector2.UP)
Đúng
# Set velocity, then call move_and_slide() with no arguments
velocity.x = direction * SPEED
move_and_slide()

Lỗi 2: Quên đặt velocity trước move_and_slide()

Sai — velocity không bao giờ được đặt, nên không có gì di chuyển
func _physics_process(delta: float) -> void:
    move_and_slide()  # velocity is Vector2.ZERO — nothing happens

Lỗi 3: Dùng _process() thay vì _physics_process()

move_and_slide() phải được gọi trong _physics_process(), vốn chạy ở tốc độ cố định (mặc định 60 Hz). Dùng _process() sẽ gắn physics với tốc độ khung hình render, gây ra hành vi không nhất quán.

Lỗi 4: Không thiết lập CollisionShape

Một CharacterBody2D/CharacterBody3D không có node con CollisionShape sẽ đi xuyên qua mọi thứ. Godot hiển thị một biểu tượng cảnh báo trong editor nếu thiếu collision shape.

Lỗi 5: Ghi đè velocity.y bằng trọng lực mỗi khung hình

Sai — reset velocity.y, nên nhảy không bao giờ hoạt động
# This overwrites any jump velocity!
velocity.y = gravity * delta  # should be +=, not =
Đúng — tích lũy trọng lực
# Gravity accumulates over time
velocity += get_gravity() * delta

Lỗi 6: Áp dụng trọng lực khi đang ở trên sàn

Luôn bao trọng lực trong if not is_on_floor(). Nếu không có kiểm tra này, trọng lực sẽ tiếp tục tích lũy khi đang ở trên sàn. Khi nhân vật bước ra khỏi mép, nó sẽ lao xuống với tốc độ cực nhanh thay vì rơi một cách tự nhiên.

10. Thiết lập controller nhân vật với AI

Muốn AI thiết lập controller nhân vật cho bạn?

Godot MCP Pro cho phép các trợ lý AI tạo node CharacterBody, cấu hình thuộc tính physics, thiết lập collision shape, viết script chuyển động, khởi chạy game và kiểm thử chuyển động — tất cả từ những câu lệnh ngôn ngữ tự nhiên.

add_node setup_physics_body setup_collision set_physics_layers create_script simulate_key play_scene capture_frames
Nhận Godot MCP Pro — $15