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ũ KinematicBody2D và KinematicBody3D được đổi tên thành CharacterBody2D và CharacterBody3D, 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.
CharacterBody2D và CharacterBody3D 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:
-
KinematicBody2Dgiờ làCharacterBody2D -
KinematicBody3Dgiờ làCharacterBody3D -
velocitygiờ là một thuộc tính tích hợp sẵn — bạn không còn truyền nó vàomove_and_slide() -
move_and_slide()không nhận đối số — nó đọc trực tiếp từ thuộc tínhvelocity -
move_and_slide_with_snap()đã bị loại bỏ — hãy dùng thuộc tínhfloor_snap_lengththay thế -
is_on_floor(),is_on_wall(),is_on_ceiling()vẫn hoạt động y như cũ -
get_gravity()được bổ sung trong Godot 4.4 để đọc trọng lực mặc định của dự án dưới dạng Vector — rất tiện lợi
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.
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
-
extends CharacterBody2D— Script kế thừa từ CharacterBody2D, cho phép chúng ta truy cậpvelocity,move_and_slide(),is_on_floor(), v.v. -
SPEED = 300.0— Tốc độ di chuyển ngang tính bằng pixel mỗi giây. -
JUMP_VELOCITY = -400.0— Giá trị âm vì trong 2D trục Y hướng xuống dưới. Một giá trị âm sẽ đưa nhân vật lên trên. -
get_gravity() * delta— Áp dụng trọng lực của dự án mỗi khung hình.get_gravity()trả về mộtVector2hướng xuống dưới (ví dụVector2(0, 980)). Nhân vớideltagiúp nó độc lập với tốc độ khung hình. -
Input.get_axis("ui_left", "ui_right")— Trả về một float từ -1.0 đến 1.0 dựa trên phím hướng nào đang được nhấn. Hỗ trợ đầu vào analog. -
move_toward(velocity.x, 0, SPEED)— Giảm tốc mượt mà về không khi không có đầu vào nào được nhấn. Đối số thứ ba là mức thay đổi tối đa mỗi lần gọi. -
move_and_slide()— Di chuyển body bằngvelocityhiện tại, xử lý va chạm, trượt dọc theo bề mặt, và tự động cập nhậtvelocity.
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.
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
-
Input.get_vector()trả về mộtVector2đã chuẩn hóa từ bốn input action. Điều này xử lý chuyển động chéo một cách chính xác — độ dài của vector luôn là 1.0 hoặc 0.0, nên chuyển động chéo không nhanh hơn chuyển động theo trục ngang/dọc. -
Không áp dụng trọng lực vì đây là góc nhìn top-down. Nhân vật dừng lại ngay lập tức khi không có đầu vào vì chúng ta đặt
velocitytrực tiếp (không có quán tính).
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:
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.
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
-
Đơn vị: Trong 3D, 1 đơn vị = 1 mét (theo quy ước). Vậy
SPEED = 5.0có nghĩa là 5 mét mỗi giây — con số nhỏ hơn nhiều so với giá trị pixel trong 2D. -
Vận tốc nhảy là số dương: Trong 3D, trục Y hướng lên trên (khác với 2D), nên
JUMP_VELOCITY = 4.5là số dương. -
Biến đổi hướng:
transform.basis * Vector3(input_dir.x, 0, input_dir.y)chuyển đầu vào 2D thành một hướng 3D tương đối so với hướng nhìn của nhân vật. Thành phần Y của đầu vào ánh xạ tới trục Z (trước/sau trong 3D). -
Chỉ điều khiển X và Z: Chúng ta đặt
velocity.xvàvelocity.zriêng biệt, để dànhvelocity.ycho trọng lực và nhảy.
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ả CharacterBody2D và CharacterBody3D đề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.
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
get_collider()— Đối tượng đã bị va vào.get_normal()— Vector pháp tuyến của bề mặt va chạm. Hướng ra xa khỏi bề mặt.get_position()— Điểm trong không gian thế giới nơi va chạm xảy ra.get_travel()— Quãng đường body đã di chuyển trước khi va chạm.get_remainder()— Phần chuyển động còn lại chưa được áp dụng.get_collider_velocity()— Vận tốc của collider (hữu ích cho platform di chuyển).
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_with_snap(vel, snap, up) |
floor_snap_length = 4.0 |
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) |
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()
# 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()
Lỗi 2: Quên đặt velocity trước move_and_slide()
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
# This overwrites any jump velocity!
velocity.y = gravity * delta # should be +=, not =
# 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.